Showing preview only (982K chars total). Download the full file or copy to clipboard to get everything.
Repository: allenymt/PrivacySentry
Branch: main
Commit: c705b3f588ce
Files: 289
Total size: 902.5 KB
Directory structure:
gitextract_9uz9pdil/
├── .gitignore
├── CLAUDE.md
├── LICENSE
├── README.md
├── app/
│ ├── .gitignore
│ ├── build.gradle
│ ├── libs/
│ │ └── bcprov-jdk16-139.jar
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── com/
│ │ └── yl/
│ │ └── lib/
│ │ └── privacysentry/
│ │ └── ExampleInstrumentedTest.kt
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── yl/
│ │ │ └── lib/
│ │ │ └── privacysentry/
│ │ │ ├── APP.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── ReflexObjectUtil.java
│ │ │ ├── calendar/
│ │ │ │ ├── CalendarManager.kt
│ │ │ │ ├── CalenderActivity.kt
│ │ │ │ └── RRuleConstant.kt
│ │ │ ├── contact/
│ │ │ │ ├── ContactActivity.kt
│ │ │ │ └── ContactManager.kt
│ │ │ ├── location/
│ │ │ │ └── LocationTestActivity.java
│ │ │ ├── process/
│ │ │ │ ├── MultiProcessB.kt
│ │ │ │ └── MultiProcessC.kt
│ │ │ ├── telephony/
│ │ │ │ └── TelephonyTestActivity.kt
│ │ │ └── test/
│ │ │ ├── PrivacyMethod.kt
│ │ │ ├── PrivacyProxyCallJava.java
│ │ │ ├── PrivacyProxySelfTest.kt
│ │ │ ├── PrivacyTestMacAddress.java
│ │ │ ├── TestBCSeri.java
│ │ │ ├── TestFragmentActivity.kt
│ │ │ ├── TestInJava.java
│ │ │ ├── TestOaidGetter.java
│ │ │ ├── TestReflex.kt
│ │ │ ├── TestReflexJava.java
│ │ │ ├── TestService.kt
│ │ │ ├── constructor/
│ │ │ │ └── TestFileConstructor.java
│ │ │ └── ui/
│ │ │ └── main/
│ │ │ └── TestPermissionFragment.kt
│ │ └── res/
│ │ ├── drawable/
│ │ │ └── ic_launcher_background.xml
│ │ ├── drawable-v24/
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── layout/
│ │ │ ├── activity_calender.xml
│ │ │ ├── activity_contact.xml
│ │ │ ├── activity_location_test.xml
│ │ │ ├── activity_main.xml
│ │ │ ├── activity_telephony_test.xml
│ │ │ ├── activity_test_frament.xml
│ │ │ └── fragment_main.xml
│ │ ├── mipmap-anydpi-v26/
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── values/
│ │ │ ├── colors.xml
│ │ │ ├── strings.xml
│ │ │ └── themes.xml
│ │ └── values-night/
│ │ └── themes.xml
│ └── test/
│ └── java/
│ └── com/
│ └── yl/
│ └── lib/
│ └── privacysentry/
│ └── ExampleUnitTest.kt
├── build.gradle
├── config.gradle
├── demo_result.xls
├── docs/
│ ├── README.md
│ ├── architecture.html
│ └── architecture.md
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── hook-sentry/
│ ├── .gitignore
│ ├── build.gradle
│ ├── gradle.properties
│ ├── proguard-rules.pro
│ ├── src/
│ │ ├── androidTest/
│ │ │ └── java/
│ │ │ └── com/
│ │ │ └── yl/
│ │ │ └── lib/
│ │ │ └── hook_sentry/
│ │ │ └── ExampleInstrumentedTest.kt
│ │ ├── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java/
│ │ │ └── com/
│ │ │ └── yl/
│ │ │ └── lib/
│ │ │ └── sentry/
│ │ │ └── hook/
│ │ │ ├── PrivacySentry.kt
│ │ │ ├── PrivacySentryBuilder.kt
│ │ │ ├── cache/
│ │ │ │ ├── BasePrivacyCache.kt
│ │ │ │ ├── CachePrivacyManager.kt
│ │ │ │ ├── CacheUtils.kt
│ │ │ │ ├── DiskCache.kt
│ │ │ │ ├── MemoryCache.kt
│ │ │ │ ├── PrivacyCacheType.kt
│ │ │ │ ├── TimeLessDiskCache.kt
│ │ │ │ └── TimeLessMemoryCache.kt
│ │ │ ├── excel/
│ │ │ │ ├── ExcelBuildDataListener.kt
│ │ │ │ └── ExcelUtil.kt
│ │ │ ├── printer/
│ │ │ │ ├── BaseFilePrinter.kt
│ │ │ │ ├── BasePrinter.kt
│ │ │ │ ├── DefaultFilePrint.kt
│ │ │ │ ├── DefaultLogPrint.kt
│ │ │ │ ├── PrintCallBack.kt
│ │ │ │ └── PrivacyFunBean.kt
│ │ │ ├── util/
│ │ │ │ ├── MainProcessUtil.kt
│ │ │ │ ├── PrivacyClipBoardManager.kt
│ │ │ │ ├── PrivacyLog.kt
│ │ │ │ ├── PrivacyProxyUtil.kt
│ │ │ │ ├── PrivacyUtil.kt
│ │ │ │ └── ReflectUtils.kt
│ │ │ └── watcher/
│ │ │ ├── DelayTimeWatcher.kt
│ │ │ └── PrivacyDataManager.kt
│ │ └── test/
│ │ └── java/
│ │ └── com/
│ │ └── yl/
│ │ └── lib/
│ │ └── hook_sentry/
│ │ └── ExampleUnitTest.kt
│ └── ~/
│ └── .m2/
│ └── repository/
│ └── com/
│ └── yl/
│ └── lib/
│ └── privacy/
│ └── hook-sentry/
│ ├── 0.0.1-SNAPSHOT/
│ │ ├── maven-metadata-remote.xml
│ │ └── resolver-status.properties
│ ├── maven-metadata-remote.xml
│ └── resolver-status.properties
├── plugin-sentry/
│ ├── .gitignore
│ ├── build.gradle
│ ├── gradle.properties
│ └── src/
│ └── main/
│ ├── java/
│ │ └── com/
│ │ └── yl/
│ │ └── lib/
│ │ └── plugin/
│ │ └── sentry/
│ │ ├── PrivacySentryPlugin.kt
│ │ ├── PrivacyTransformTask.kt
│ │ ├── extension/
│ │ │ └── PrivacyExtension.kt
│ │ ├── transform/
│ │ │ ├── PrivacyTransformContext.kt
│ │ │ ├── PrivacyTransformInvocation.kt
│ │ │ ├── booster/
│ │ │ │ ├── asmtransform/
│ │ │ │ │ └── AbsClassTransformer.kt
│ │ │ │ ├── classtransform/
│ │ │ │ │ ├── collect/
│ │ │ │ │ │ ├── ClassProxyCollectTransform.kt
│ │ │ │ │ │ └── MethodProxyCollectTransform.kt
│ │ │ │ │ └── hook/
│ │ │ │ │ ├── BaseHookTransform.kt
│ │ │ │ │ ├── ClassProxyTransform.kt
│ │ │ │ │ ├── FieldProxyTransform.kt
│ │ │ │ │ ├── FlushHookDataTransform.kt
│ │ │ │ │ ├── MethodHookTransform.kt
│ │ │ │ │ └── ServiceHookTransform.kt
│ │ │ │ ├── processor/
│ │ │ │ │ ├── PrivacyAssetsProcessor.kt
│ │ │ │ │ └── PrivacyManifestProcessor.kt
│ │ │ │ ├── task/
│ │ │ │ │ └── PrivacyManifestTask.kt
│ │ │ │ └── transformer/
│ │ │ │ └── PrivacyBaseTransformer.kt
│ │ │ └── manager/
│ │ │ ├── HookFieldManager.kt
│ │ │ ├── HookMethodManager.kt
│ │ │ ├── HookedDataManger.kt
│ │ │ └── ReplaceClassManager.kt
│ │ └── util/
│ │ ├── PrivacyExt.kt
│ │ ├── PrivacyMoveAssetsUtil.kt
│ │ └── PrivacyPluginUtil.kt
│ └── resources/
│ └── META-INF/
│ └── gradle-plugins/
│ ├── com.allenymt.plugin.privacy.properties
│ └── privacy-sentry-plugin.properties
├── privacy
├── privacy-annotation/
│ ├── .gitignore
│ ├── build.gradle
│ ├── gradle.properties
│ ├── src/
│ │ └── main/
│ │ └── java/
│ │ └── com/
│ │ └── yl/
│ │ └── lib/
│ │ └── privacy_annotation/
│ │ ├── MethodInvokeOpcode.java
│ │ ├── PrivacyClassBlack.java
│ │ ├── PrivacyClassProxy.java
│ │ ├── PrivacyClassReplace.java
│ │ ├── PrivacyFieldProxy.java
│ │ └── PrivacyMethodProxy.java
│ └── ~/
│ └── .m2/
│ └── repository/
│ └── com/
│ └── yl/
│ └── lib/
│ └── privacy/
│ └── privacy-annotation/
│ ├── 0.0.1-SNAPSHOT/
│ │ ├── maven-metadata-remote.xml
│ │ └── resolver-status.properties
│ ├── 0.0.2-SNAPSHOT/
│ │ ├── maven-metadata-remote.xml
│ │ └── resolver-status.properties
│ ├── maven-metadata-remote.xml
│ ├── maven-metadata-remote.xml.sha1
│ └── resolver-status.properties
├── privacy-proxy/
│ ├── .gitignore
│ ├── README.md
│ ├── build.gradle
│ ├── gradle.properties
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ └── java/
│ └── com/
│ └── yl/
│ └── lib/
│ └── privacy_proxy/
│ ├── PrivacyPermissionProxy.kt
│ ├── PrivacyProxyCall.kt
│ ├── PrivacyProxyCallJava.java
│ ├── PrivacyProxyResolver.kt
│ ├── PrivacyReflectProxy.kt
│ ├── PrivacySensorProxy.kt
│ ├── PrivacyTelephonyProxy.kt
│ └── ProxyProxyField.java
├── privacy-replace/
│ ├── .gitignore
│ ├── README.md
│ ├── build.gradle
│ ├── consumer-rules.pro
│ ├── gradle.properties
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── com/
│ │ └── vdian/
│ │ └── android/
│ │ └── wdb/
│ │ └── privacy_replace/
│ │ └── ExampleInstrumentedTest.kt
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ └── java/
│ │ └── com/
│ │ └── yl/
│ │ └── lib/
│ │ └── privacy_replace/
│ │ ├── PrivacyFile.java
│ │ ├── PrivacyFileInputStream.java
│ │ └── PrivacyFileReader.java
│ └── test/
│ └── java/
│ └── com/
│ └── vdian/
│ └── android/
│ └── wdb/
│ └── privacy_replace/
│ └── ExampleUnitTest.kt
├── privacy-test/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ └── java/
│ └── com/
│ └── yl/
│ └── lib/
│ └── privacy_test/
│ ├── PrivacyProxySelfTest2.java
│ ├── TestMethod.kt
│ └── TestMethodInJava.java
├── privacy-ui/
│ └── build/
│ ├── generated/
│ │ └── source/
│ │ └── buildConfig/
│ │ └── debug/
│ │ └── com/
│ │ └── yl/
│ │ └── lib/
│ │ └── privacy_ui/
│ │ └── BuildConfig.java
│ ├── intermediates/
│ │ ├── aapt_friendly_merged_manifests/
│ │ │ └── debug/
│ │ │ └── aapt/
│ │ │ ├── AndroidManifest.xml
│ │ │ └── output-metadata.json
│ │ ├── aar_metadata/
│ │ │ └── debug/
│ │ │ └── aar-metadata.properties
│ │ ├── annotation_processor_list/
│ │ │ └── debug/
│ │ │ └── annotationProcessors.json
│ │ ├── compile_library_classes_jar/
│ │ │ └── debug/
│ │ │ └── classes.jar
│ │ ├── compile_r_class_jar/
│ │ │ └── debug/
│ │ │ └── R.jar
│ │ ├── compile_symbol_list/
│ │ │ └── debug/
│ │ │ └── R.txt
│ │ ├── compiled_local_resources/
│ │ │ └── debug/
│ │ │ └── out/
│ │ │ ├── layout_activity_permission_list.xml.flat
│ │ │ ├── layout_activity_real_time_privacy_item.xml.flat
│ │ │ ├── layout_activity_replace_list.xml.flat
│ │ │ ├── layout_permission_item_view.xml.flat
│ │ │ ├── layout_real_tile_item_view.xml.flat
│ │ │ └── layout_replace_item_view.xml.flat
│ │ ├── incremental/
│ │ │ ├── mergeDebugJniLibFolders/
│ │ │ │ └── merger.xml
│ │ │ ├── mergeDebugShaders/
│ │ │ │ └── merger.xml
│ │ │ ├── packageDebugAssets/
│ │ │ │ └── merger.xml
│ │ │ └── packageDebugResources/
│ │ │ ├── compile-file-map.properties
│ │ │ └── merger.xml
│ │ ├── library_java_res/
│ │ │ └── debug/
│ │ │ └── res.jar
│ │ ├── library_manifest/
│ │ │ └── debug/
│ │ │ └── AndroidManifest.xml
│ │ ├── local_only_symbol_list/
│ │ │ └── debug/
│ │ │ └── R-def.txt
│ │ ├── manifest_merge_blame_file/
│ │ │ └── debug/
│ │ │ └── manifest-merger-blame-debug-report.txt
│ │ ├── navigation_json/
│ │ │ └── debug/
│ │ │ └── navigation.json
│ │ ├── packaged_manifests/
│ │ │ └── debug/
│ │ │ └── output-metadata.json
│ │ ├── packaged_res/
│ │ │ └── debug/
│ │ │ └── layout/
│ │ │ ├── activity_permission_list.xml
│ │ │ ├── activity_real_time_privacy_item.xml
│ │ │ ├── activity_replace_list.xml
│ │ │ ├── permission_item_view.xml
│ │ │ ├── real_tile_item_view.xml
│ │ │ └── replace_item_view.xml
│ │ ├── runtime_library_classes_jar/
│ │ │ └── debug/
│ │ │ └── classes.jar
│ │ └── symbol_list_with_package_name/
│ │ └── debug/
│ │ └── package-aware-r.txt
│ ├── kotlin/
│ │ └── compileDebugKotlin/
│ │ └── caches-jvm/
│ │ ├── inputs/
│ │ │ ├── source-to-output.tab
│ │ │ ├── source-to-output.tab.keystream
│ │ │ ├── source-to-output.tab.keystream.len
│ │ │ ├── source-to-output.tab.len
│ │ │ ├── source-to-output.tab.values.at
│ │ │ ├── source-to-output.tab_i
│ │ │ └── source-to-output.tab_i.len
│ │ ├── jvm/
│ │ │ └── kotlin/
│ │ │ ├── class-fq-name-to-source.tab
│ │ │ ├── class-fq-name-to-source.tab.keystream
│ │ │ ├── class-fq-name-to-source.tab.keystream.len
│ │ │ ├── class-fq-name-to-source.tab.len
│ │ │ ├── class-fq-name-to-source.tab.values.at
│ │ │ ├── class-fq-name-to-source.tab_i
│ │ │ ├── class-fq-name-to-source.tab_i.len
│ │ │ ├── internal-name-to-source.tab
│ │ │ ├── internal-name-to-source.tab.keystream
│ │ │ ├── internal-name-to-source.tab.keystream.len
│ │ │ ├── internal-name-to-source.tab.len
│ │ │ ├── internal-name-to-source.tab.values.at
│ │ │ ├── internal-name-to-source.tab_i
│ │ │ ├── internal-name-to-source.tab_i.len
│ │ │ ├── proto.tab
│ │ │ ├── proto.tab.keystream
│ │ │ ├── proto.tab.keystream.len
│ │ │ ├── proto.tab.len
│ │ │ ├── proto.tab.values.at
│ │ │ ├── proto.tab_i
│ │ │ ├── proto.tab_i.len
│ │ │ ├── source-to-classes.tab
│ │ │ ├── source-to-classes.tab.keystream
│ │ │ ├── source-to-classes.tab.keystream.len
│ │ │ ├── source-to-classes.tab.len
│ │ │ ├── source-to-classes.tab.values.at
│ │ │ ├── source-to-classes.tab_i
│ │ │ ├── source-to-classes.tab_i.len
│ │ │ ├── subtypes.tab
│ │ │ ├── subtypes.tab.keystream
│ │ │ ├── subtypes.tab.keystream.len
│ │ │ ├── subtypes.tab.len
│ │ │ ├── subtypes.tab.values.at
│ │ │ ├── subtypes.tab_i
│ │ │ ├── subtypes.tab_i.len
│ │ │ ├── supertypes.tab
│ │ │ ├── supertypes.tab.keystream
│ │ │ ├── supertypes.tab.keystream.len
│ │ │ ├── supertypes.tab.len
│ │ │ ├── supertypes.tab.values.at
│ │ │ ├── supertypes.tab_i
│ │ │ └── supertypes.tab_i.len
│ │ └── lookups/
│ │ ├── counters.tab
│ │ ├── file-to-id.tab
│ │ ├── file-to-id.tab.keystream
│ │ ├── file-to-id.tab.keystream.len
│ │ ├── file-to-id.tab.len
│ │ ├── file-to-id.tab.values.at
│ │ ├── file-to-id.tab_i
│ │ ├── file-to-id.tab_i.len
│ │ ├── id-to-file.tab
│ │ ├── id-to-file.tab.keystream
│ │ ├── id-to-file.tab.keystream.len
│ │ ├── id-to-file.tab.len
│ │ ├── id-to-file.tab.values.at
│ │ ├── id-to-file.tab_i
│ │ ├── id-to-file.tab_i.len
│ │ ├── lookups.tab
│ │ ├── lookups.tab.keystream
│ │ ├── lookups.tab.keystream.len
│ │ ├── lookups.tab.len
│ │ ├── lookups.tab.values.at
│ │ ├── lookups.tab_i
│ │ └── lookups.tab_i.len
│ ├── outputs/
│ │ └── logs/
│ │ └── manifest-merger-debug-report.txt
│ └── tmp/
│ ├── compileDebugJavaWithJavac/
│ │ └── source-classes-mapping.txt
│ └── kotlin-classes/
│ └── debug/
│ └── META-INF/
│ └── privacy-ui_debug.kotlin_module
├── privacy_hook.json
├── publish.gradle
├── settings.gradle.kts
└── upload_local.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
/plugin-sentry/~/.m2/
.build_history/
/local
================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## 项目概述
PrivacySentry 是一个 Android 隐私合规检测工具,通过 ASM 字节码插桩技术在编译期和运行期拦截敏感 API 调用,帮助开发者规避应用市场上架时的隐私合规检测问题。
## 核心架构
### 三层架构设计
1. **plugin-sentry (Gradle 插件层)**
- 基于 AGP 8.0+ 和 Booster 框架的 Gradle 插件
- 负责在编译期进行字节码转换 (Transform)
- 两阶段 Transform:
- 第一阶段: 收集需要拦截的敏感函数 (MethodProxyCollectTransform, ClassProxyCollectTransform)
- 第二阶段: 替换敏感函数调用 (MethodHookTransform, FieldProxyTransform, ClassProxyTransform)
- 关键类: `PrivacySentryPlugin.kt`, `PrivacyTransformTask.kt`
2. **privacy-annotation (注解层)**
- 定义编译期注解用于声明需要拦截的方法和字段
- 核心注解:
- `@PrivacyMethodProxy`: 标记需要代理的方法
- `@PrivacyFieldProxy`: 标记需要代理的字段
- `@PrivacyClassProxy`: 标记包含代理方法的类
- `@PrivacyClassBlack`: 标记不需要拦截的黑名单类
3. **hook-sentry (运行时 Hook 层)**
- 提供运行时的 SDK 初始化和日志收集功能
- 支持三种缓存策略: 内存缓存 (MemoryCache)、时效性磁盘缓存 (TimeLessDiskCache)、永久磁盘缓存 (DiskCache)
- 日志输出到文件 (.xls 格式)
- 关键类: `PrivacySentry.kt`, `PrivacySentryBuilder.kt`
4. **privacy-proxy (预置代理实现)**
- 提供常用敏感 API 的拦截实现
- 通过注解声明拦截规则,由插件在编译期自动收集
- 关键类: `PrivacyProxyCall.kt`, `PrivacyTelephonyProxy.kt`, `PrivacySensorProxy.kt`
### 工作原理
1. **编译期**: 插件扫描所有 `@PrivacyMethodProxy` 注解,收集需要拦截的方法列表
2. **字节码转换**: 将目标方法调用替换为代理方法调用
3. **运行期**: 代理方法执行实际逻辑,并记录调用堆栈和时机到日志文件
## 常用命令
### 构建项目
```bash
./gradlew clean build
```
### 编译调试 (本地依赖模式)
修改 `config.gradle`:
```gradle
build = [
local_debug: true,
local_debug_dir : "${rootProject.projectDir}/local"
]
```
### 发布到本地 Maven
```bash
./gradlew publishToMavenLocal
```
### 运行测试应用
```bash
./gradlew :app:installDebug
```
## 项目模块说明
- **app**: 示例应用,演示如何集成和使用 PrivacySentry
- **plugin-sentry**: Gradle 插件,负责字节码转换
- **hook-sentry**: 运行时 SDK 核心库
- **privacy-annotation**: 注解定义模块
- **privacy-proxy**: 预置的敏感 API 拦截实现
- **privacy-replace**: 类替换功能 (已废弃,不再维护)
- **privacy-test**: 测试模块
## 版本兼容性
- **当前版本 (1.3.7_v820_beta4)**: 支持 AGP 8.0+
- **旧版本 (1.3.6)**: 支持 AGP 8.0 以下版本
- **最低 Android SDK**: minSdkVersion 19
- **编译 SDK**: compileSdkVersion 34
- **Kotlin**: 1.8.10
## 关键配置
### privacy 插件配置 (在 app/build.gradle 中)
```gradle
privacy {
// 黑名单包名,不会被字节码修改
blackList = []
// 插件功能总开关
enablePrivacy = true
// 是否 hook 反射方法
hookReflex = false
// 反射拦截配置 (如小米 OAID)
reflexMap = ["com.android.id.impl.IdProviderImpl":["getOAID","getAAID","getVAID"]]
// 已废弃的功能开关
hookConstructor = false
hookField = false
}
```
### SDK 初始化示例
```kotlin
val builder = PrivacySentryBuilder()
.configResultFileName("privacy_result")
.syncDebug(true) // 开启 logcat 输出
.enableFileResult(true) // 开启文件输出
.configWatchTime(30 * 60 * 1000) // 持续监控 30 分钟
PrivacySentry.Privacy.init(application, builder)
// 用户同意隐私协议后必须调用
PrivacySentry.Privacy.updatePrivacyShow()
```
## 添加自定义拦截
1. 创建包含 `@PrivacyClassProxy` 注解的类
2. 在静态方法上添加 `@PrivacyMethodProxy` 注解:
```kotlin
@PrivacyClassProxy
object CustomProxy {
@PrivacyMethodProxy(
originalClass = TargetClass::class,
originalMethod = "methodName",
originalOpcode = MethodInvokeOpcode.INVOKEVIRTUAL
)
@JvmStatic
fun proxyMethod(instance: TargetClass, param: String): ReturnType {
doFilePrinter("methodName", "方法说明")
// 自定义逻辑
return instance.methodName(param)
}
}
```
## 注意事项
1. **黑名单配置**: 如引入高德地图或 OpenInstall,需添加到黑名单避免 ASM 版本冲突:
```gradle
blackList = ["com.loc", "com.amap.api", "io.openinstall.sdk"]
```
2. **线上环境**: 务必关闭 `enableFileResult`,避免隐私数据泄露
3. **初始化时机**: 尽可能在 `Application.attachBaseContext()` 中第一个调用
4. **动态加载**: 热修复、插件化加载的代码无法被拦截
5. **支持的敏感 API**: 包括 IMEI、MAC、Android ID、位置信息、剪贴板、传感器、应用列表等,详见 README.md
## 调试技巧
- 查看生成的 hook 配置: `privacy_hook.json`
- 日志 TAG: `PrivacyOfficer`
- 输出文件路径: `/storage/emulated/0/Android/data/{packageName}/files/privacy/`
- 拉取文件: `adb pull /storage/emulated/0/Android/data/{packageName}/files/privacy/`
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2022 alleny
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# PrivacySentry
[](https://jitpack.io/#allenymt/PrivacySentry)
[](LICENSE)
[](https://android-arsenal.com/api?level=19)
> Android 隐私合规检测工具,可规避应用市场上架合规检测的大部分问题
## ✨ 核心特性
- ✅ **编译期字节码插桩**:基于 ASM + Booster 框架,零运行时性能损耗
- 🎯 **完整的拦截方案**:支持 60+ 敏感 API 拦截,覆盖工信部合规要求
- 📊 **自动化检测报告**:生成 Excel 格式的详细调用记录和统计分析
- 🔄 **智能缓存机制**:内存/磁盘多级缓存,优化性能
- 🌐 **多进程支持**:自动处理多进程场景,独立输出日志
- 🔧 **灵活可扩展**:支持自定义拦截规则,黑名单配置
## 📚 文档导航
- **[快速开始](#快速开始)** - 5 分钟集成使用
- **[插件配置详解](#插件配置详解)** - 完整配置说明
- **[SDK 初始化](#sdk-初始化)** - 运行时配置
- **[自定义拦截](#自定义拦截)** - 扩展拦截规则
- **[架构文档](./docs/architecture.md)** - 完整技术架构解析
- **[开发指南](./CLAUDE.md)** - Claude Code 开发指南
## 📱 社区支持
加作者个人微信,备注来意 PrivacySentry,进社区群
<img width="290" alt="image" src="https://github.com/allenymt/PrivacySentry/assets/8003195/76f2124e-f58d-4420-ac2d-8d33b1093907">
## 🚀 快速开始
### 版本要求
| 组件 | 版本要求 |
|------|---------|
| **AGP** | 8.0+ (推荐 8.2.0) |
| **Gradle** | 8.0+ |
| **Kotlin** | 1.8.10+ |
| **minSdk** | 19+ |
| **compileSdk** | 34+ |
> **注意**:AGP 8.0 以下版本请使用 `1.3.6` 版本
### Step 1: 添加插件依赖
在项目根目录的 `build.gradle` 中添加:
```gradle
buildscript {
repositories {
maven { url 'https://jitpack.io' }
mavenCentral()
google()
}
dependencies {
classpath 'com.github.allenymt.PrivacySentry:plugin-sentry:1.3.7_v820_beta4'
}
}
allprojects {
repositories {
maven { url 'https://jitpack.io' }
mavenCentral()
google()
}
}
```
### Step 2: 应用插件和依赖
在 app 模块的 `build.gradle` 中:
```gradle
// 应用插件
apply plugin: 'privacy-sentry-plugin'
dependencies {
def privacyVersion = "1.3.7_v820_beta4"
// 核心库(必须)
implementation "com.github.allenymt.PrivacySentry:hook-sentry:$privacyVersion"
implementation "com.github.allenymt.PrivacySentry:privacy-annotation:$privacyVersion"
// 预置拦截实现(强烈推荐)
implementation "com.github.allenymt.PrivacySentry:privacy-proxy:$privacyVersion"
// 类替换功能(已废弃,不推荐使用)
// implementation "com.github.allenymt.PrivacySentry:privacy-replace:$privacyVersion"
}
```
### Step 3: 插件配置
在 app 模块的 `build.gradle` 中添加 `privacy` 配置块:
```gradle
privacy {
// ========== 核心配置 ==========
/**
* 插件功能总开关
* 类型:Boolean
* 默认值:true
* 说明:控制 PrivacySentry 插件是否生效
* - true: 启用插件,执行字节码转换
* - false: 禁用插件,相当于未集成
*/
enablePrivacy = true
/**
* 黑名单配置
* 类型:Set<String>
* 默认值:null (空列表)
* 说明:指定不进行字节码修改的包名列表
* - 适用场景:
* 1. 使用了其他 ASM 字节码修改工具的三方库(如高德地图)
* 2. 已知会导致冲突的 SDK
* 3. 不需要监控的系统库或三方库
* - 注意:blackList 中的包不会被插件修改,也无法拦截其中的敏感 API
*/
blackList = []
// 常见黑名单配置示例:
// blackList = [
// "com.loc", // 高德地图(ASM 版本冲突)
// "com.amap.api", // 高德地图 API
// "io.openinstall.sdk", // OpenInstall SDK
// "com.google.android", // Google 服务(可选)
// "androidx" // AndroidX 库(可选)
// ]
/**
* 静态扫描结果文件名
* 类型:String
* 默认值:"privacy_hook.json"
* 说明:记录所有被代理的方法名和类名的文件名
* - 文件位置:项目根目录
* - 文件格式:JSON
* - 内容包含:
* 1. hookServiceList: 被 hook 的 Service 列表
* 2. replaceMethodMap: 被替换的方法映射表
* - 设置为 null 或空字符串则不生成文件
*/
replaceFileName = "privacy_hook.json"
// ========== 反射 Hook 配置(可选)==========
/**
* 反射方法 Hook 开关
* 类型:Boolean
* 默认值:false
* 说明:是否拦截通过反射调用的敏感方法
* - true: 拦截反射调用(需配合 reflexMap 使用)
* - false: 不拦截反射调用
* - 适用场景:
* 1. 三方 SDK 通过反射获取设备信息(如小米 OAID)
* 2. 极光推送、个推、穿山甲等 SDK 的设备标识获取
* - 性能影响:轻微(仅影响 LDC 指令的匹配)
*/
hookReflex = false
/**
* 反射拦截配置映射
* 类型:Map<String, List<String>>
* 默认值:null
* 说明:配置需要拦截的反射调用
* - Key: 类的全限定名
* - Value: 该类中需要拦截的方法名列表
* - 只有 hookReflex = true 时才生效
* - 匹配原理:检测字节码中的 LDC 指令加载的字符串常量
*/
reflexMap = [:]
// 反射拦截配置示例:
// reflexMap = [
// // 小米设备标识服务
// "com.android.id.impl.IdProviderImpl": [
// "getOAID", // 开放匿名设备标识符
// "getAAID", // 应用匿名设备标识符
// "getVAID" // 开发者匿名设备标识符
// ],
// // 自定义类的反射方法
// "com.example.utils.DeviceUtils": [
// "getDeviceId",
// "getIMEI"
// ]
// ]
// ========== 已废弃功能(不推荐使用,仅供参考)==========
/**
* 字段 Hook 开关(已废弃)
* 类型:Boolean
* 默认值:false
* 废弃原因:几乎没有业务场景,功能不稳定
* 说明:是否 hook 字段访问(如 Build.SERIAL)
* - 不推荐使用,请使用方法 hook 代替
*/
// hookField = false
/**
* 构造函数 Hook 开关(已废弃)
* 类型:Boolean
* 默认值:false
* 废弃原因:实现复杂,稳定性差,已停止维护
* 说明:是否 hook 构造函数(主要用于拦截 File 构造函数参数)
* - 相关模块:privacy-replace
* - 不推荐使用
*/
// hookConstructor = false
/**
* Manifest 处理开关(已废弃)
* 类型:Boolean
* 默认值:false
* 废弃原因:功能边界不清晰,已停止维护
* 说明:是否处理 AndroidManifest.xml 文件
* - 主要用于处理 Service 的 Priority 和 Export
* - 不推荐使用
*/
// enableProcessManifest = false
/**
* Service Priority 替换开关(已废弃)
* 类型:Boolean
* 默认值:false
* 废弃原因:针对 MIUI 自启动问题的特殊方案,已停止维护
* 说明:是否替换 Service 的 Priority 值
* - 部分 Service 设置 Priority = 1000 导致自启动
* - 开启后会将 Priority 替换为 replacePriority 的值
* - 不推荐使用
*/
// enableReplacePriority = false
/**
* Service Priority 替换值(已废弃)
* 类型:Int
* 默认值:0
* 说明:替换后的 Priority 值
* - 配合 enableReplacePriority 使用
* - 不推荐使用
*/
// replacePriority = 0
/**
* Service Export 关闭开关(已废弃)
* 类型:Boolean
* 默认值:false
* 废弃原因:可能影响厂商推送功能,已停止维护
* 说明:是否关闭 Service 的 Export 功能
* - 注意:部分厂商推送(小米、VIVO、华为)的 PushService 不能关闭
* - 配合 serviceExportPkgWhiteList 使用
* - 不推荐使用
*/
// enableCloseServiceExport = false
/**
* Service Export 白名单(已废弃)
* 类型:Set<String>
* 默认值:null
* 说明:允许保持 Export 的 Service 包名列表
* - 配合 enableCloseServiceExport 使用
* - 不推荐使用
*/
// serviceExportPkgWhiteList = []
/**
* Service StartCommand Hook 开关(已废弃)
* 类型:Boolean
* 默认值:false
* 废弃原因:针对 MIUI 自启动问题的特殊方案,已停止维护
* 说明:是否 hook Service 的 startCommand 方法
* - 不推荐使用
*/
// enableHookServiceStartCommand = false
}
```
**配置优先级说明**:
1. **必须配置**:`enablePrivacy`(总开关)
2. **强烈推荐**:`blackList`(避免冲突)
3. **按需配置**:`hookReflex` + `reflexMap`(反射拦截)
4. **可选配置**:`replaceFileName`(静态扫描文件)
5. **不推荐使用**:所有标记为 `@Deprecated` 的配置项
### Step 4: SDK 初始化
在 `Application` 中初始化 SDK:
**Kotlin 示例**:
```kotlin
class MyApplication : Application() {
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
// ⚠️ 重要:尽可能在 attachBaseContext 中第一个调用
// 这样可以确保捕获所有敏感 API 调用
initPrivacySentry()
}
override fun onCreate() {
super.onCreate()
// 展示隐私协议弹窗
showPrivacyDialog {
// 用户同意后,必须调用此方法
PrivacySentry.Privacy.updatePrivacyShow()
}
}
private fun initPrivacySentry() {
val builder = PrivacySentryBuilder()
// 自定义输出文件名
.configResultFileName("privacy_result")
// 开启 debug 模式(可在 logcat 查看日志)
.syncDebug(BuildConfig.DEBUG)
// 开启文件输出(⚠️ 线上版本请关闭)
.enableFileResult(BuildConfig.DEBUG)
// 监控时长(30 分钟)
.configWatchTime(30 * 60 * 1000)
// 文件输出完成回调
.configResultCallBack(object : PrivacyResultCallBack {
override fun onResultCallBack(filePath: String) {
Log.i("PrivacySentry", "结果文件:$filePath")
}
})
PrivacySentry.Privacy.init(this, builder)
}
}
```
**Java 示例**:
```java
public class MyApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// ⚠️ 重要:尽可能在 attachBaseContext 中第一个调用
initPrivacySentry();
}
@Override
public void onCreate() {
super.onCreate();
// 展示隐私协议弹窗
showPrivacyDialog(() -> {
// 用户同意后,必须调用此方法
PrivacySentry.Privacy.INSTANCE.updatePrivacyShow();
});
}
private void initPrivacySentry() {
PrivacySentryBuilder builder = new PrivacySentryBuilder()
.configResultFileName("privacy_result")
.syncDebug(BuildConfig.DEBUG)
.enableFileResult(BuildConfig.DEBUG)
.configWatchTime(30 * 60 * 1000)
.configResultCallBack(new PrivacyResultCallBack() {
@Override
public void onResultCallBack(@NonNull String filePath) {
Log.i("PrivacySentry", "结果文件:" + filePath);
}
});
PrivacySentry.Privacy.INSTANCE.init(this, builder);
}
}
```
### Step 5: 查看检测结果
#### 方式 1:Logcat 日志
```bash
# 实时查看日志
adb logcat | grep "PrivacyOfficer"
```
#### 方式 2:Excel 文件
```bash
# 拉取结果文件
adb pull /storage/emulated/0/Android/data/{your.package.name}/files/privacy/
# 文件名格式
# 主进程:privacy_result.xls
# 子进程:{进程名}_privacy_result.xls
```
**Excel 文件包含两个 Sheet**:
1. **Sheet 1 - 隐私合规明细**:按时间倒序记录所有敏感 API 调用
- 调用时间
- 方法别名
- 函数名
- 完整调用堆栈
2. **Sheet 2 - 调用次数统计**:按堆栈聚合统计调用次数
- 方法别名
- 函数名
- 调用堆栈
- 调用次数
## ⚙️ 插件配置详解
### 核心配置项完整说明
| 配置项 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| `enablePrivacy` | Boolean | `true` | **插件功能总开关**<br>- `true`: 启用插件,执行字节码转换<br>- `false`: 禁用插件 |
| `blackList` | Set\<String\> | `null` | **黑名单配置**<br>指定不进行字节码修改的包名列表<br>- 适用场景:ASM 冲突的三方库<br>- 注意:黑名单中的包无法拦截敏感 API |
| `replaceFileName` | String | `"privacy_hook.json"` | **静态扫描结果文件名**<br>记录所有被代理的方法和类<br>- 文件位置:项目根目录<br>- 文件格式:JSON<br>- 设置为 `null` 则不生成 |
| `hookReflex` | Boolean | `false` | **反射方法 Hook 开关**<br>是否拦截通过反射调用的敏感方法<br>- 需配合 `reflexMap` 使用<br>- 适用:三方 SDK 反射获取设备信息 |
| `reflexMap` | Map\<String, List\<String\>\> | `null` | **反射拦截配置**<br>- Key: 类的全限定名<br>- Value: 需要拦截的方法名列表<br>- 只有 `hookReflex=true` 时生效 |
### 已废弃配置项
| 配置项 | 类型 | 默认值 | 废弃原因 |
|--------|------|--------|---------|
| `hookField` | Boolean | `false` | 几乎没有业务场景,功能不稳定 |
| `hookConstructor` | Boolean | `false` | 实现复杂,稳定性差,已停止维护 |
| `enableProcessManifest` | Boolean | `false` | 功能边界不清晰,已停止维护 |
| `enableReplacePriority` | Boolean | `false` | 针对 MIUI 特殊问题,已停止维护 |
| `replacePriority` | Int | `0` | 配合 `enableReplacePriority` 使用 |
| `enableCloseServiceExport` | Boolean | `false` | 可能影响厂商推送,已停止维护 |
| `serviceExportPkgWhiteList` | Set\<String\> | `null` | 配合 `enableCloseServiceExport` 使用 |
| `enableHookServiceStartCommand` | Boolean | `false` | 针对 MIUI 特殊问题,已停止维护 |
> ⚠️ **重要提示**:所有标记为"已废弃"的配置项均不推荐使用,可能在未来版本中移除。
### 黑名单配置说明
#### 什么情况需要配置黑名单?
1. **ASM 版本冲突**
- 使用高德地图 SDK(ASM 9.1 vs 其他版本)
- 使用其他字节码修改工具的三方库
2. **已知冲突的 SDK**
- OpenInstall SDK
- 部分混淆工具
3. **不需要监控的库**
- 系统库(可选)
- 不涉及隐私的三方库
#### 黑名单工作原理
- 插件在 Transform 阶段会跳过黑名单中的包
- 黑名单采用**前缀匹配**规则
- 例如:`"com.loc"` 会匹配 `com.loc.*` 下的所有类
#### 配置示例
```gradle
privacy {
blackList = [
// 高德地图(ASM 版本冲突)
"com.loc",
"com.amap.api",
// OpenInstall SDK
"io.openinstall.sdk",
// Google 服务(可选)
"com.google.android",
// AndroidX 库(可选)
"androidx",
// 自定义不需要监控的包
"com.example.thirdparty"
]
}
```
#### 黑名单注意事项
- ✅ **推荐**:只添加确实冲突的包
- ❌ **不推荐**:盲目添加大量包到黑名单
- ⚠️ **影响**:黑名单中的包无法拦截敏感 API 调用
### 反射 Hook 配置详解
#### 使用场景
反射 Hook 用于拦截通过**反射方式**调用的敏感方法,常见场景:
1. **设备标识获取**
- 小米设备 OAID/AAID/VAID
- 华为设备标识
- OPPO/VIVO 设备标识
2. **三方 SDK**
- 极光推送(JPush)
- 个推(GeTui)
- 穿山甲广告 SDK
- 友盟统计
3. **自定义反射调用**
- 项目中通过反射获取的敏感信息
#### 工作原理
```kotlin
// 原始代码(反射调用)
Class.forName("com.android.id.impl.IdProviderImpl")
.getMethod("getOAID")
.invoke(obj)
// 字节码层面
LDC "com.android.id.impl.IdProviderImpl" // ← hookReflex 检测这里
LDC "getOAID" // ← reflexMap 匹配方法名
INVOKEVIRTUAL Method.invoke() // ← 替换为代理方法
```
#### 配置示例
**场景 1:小米设备标识**
```gradle
privacy {
hookReflex = true
reflexMap = [
"com.android.id.impl.IdProviderImpl": [
"getOAID", // 开放匿名设备标识符
"getAAID", // 应用匿名设备标识符
"getVAID" // 开发者匿名设备标识符
]
]
}
```
**场景 2:多个 SDK 配置**
```gradle
privacy {
hookReflex = true
reflexMap = [
// 小米设备标识
"com.android.id.impl.IdProviderImpl": [
"getOAID", "getAAID", "getVAID"
],
// 华为设备标识
"com.huawei.hms.ads.identifier.AdvertisingIdClient": [
"getAdvertisingIdInfo"
],
// 自定义工具类
"com.example.utils.DeviceUtils": [
"getDeviceId",
"getIMEI",
"getAndroidId"
]
]
}
```
**场景 3:极光推送/个推配置**
```gradle
privacy {
hookReflex = true
reflexMap = [
// 极光推送反射获取设备信息
"cn.jpush.android.api.JCoreInterface": [
"getDeviceId",
"getRegistrationID"
],
// 个推反射获取设备信息
"com.igexin.sdk.PushManager": [
"getClientid"
]
]
}
```
#### 反射 Hook 注意事项
- ✅ **精确匹配**:类名和方法名必须完全匹配
- ✅ **性能影响**:轻微(仅影响 LDC 指令匹配)
- ⚠️ **必须启用**:`hookReflex = true` 才生效
- ⚠️ **无法拦截**:动态生成的类名或方法名
### 静态扫描文件说明
#### replaceFileName 配置
```gradle
privacy {
// 生成静态扫描文件
replaceFileName = "privacy_hook.json"
// 不生成文件
// replaceFileName = null
}
```
#### 文件内容示例
**文件位置**:项目根目录 `/privacy_hook.json`
```json
{
"hookServiceList": [
"com.example.TestService",
"com.example.BackgroundService"
],
"replaceMethodMap": {
"android.app.ActivityManager.getRunningTasks": {
"count": 5,
"originMethodList": [
{
"originClassName": "com.example.MainActivity",
"originMethodName": "checkRunningTasks"
},
{
"originClassName": "com.example.utils.AppUtils",
"originMethodName": "getRunningApps"
}
]
},
"android.telephony.TelephonyManager.getDeviceId": {
"count": 2,
"originMethodList": [
{
"originClassName": "com.example.DeviceManager",
"originMethodName": "getIMEI"
}
]
}
}
}
```
#### 文件用途
1. **静态分析**:离线分析敏感 API 调用情况
2. **合规检查**:提供给安全团队审查
3. **调试参考**:确认插件是否正确拦截了目标方法
4. **版本对比**:对比不同版本的 API 调用变化
### 配置优先级建议
| 优先级 | 配置项 | 建议值 | 说明 |
|--------|--------|--------|------|
| ⭐⭐⭐ 必须 | `enablePrivacy` | `true` | 插件总开关 |
| ⭐⭐⭐ 强烈推荐 | `blackList` | 根据实际情况 | 避免 ASM 冲突 |
| ⭐⭐ 推荐 | `replaceFileName` | `"privacy_hook.json"` | 生成静态扫描文件 |
| ⭐ 按需配置 | `hookReflex` | `false` 或 `true` | 根据是否有反射调用 |
| ⭐ 按需配置 | `reflexMap` | `[:]` 或配置 | 配合 hookReflex 使用 |
| ❌ 不推荐 | 所有废弃配置 | - | 已停止维护 |
## 🔧 SDK 初始化
### PrivacySentryBuilder 配置项
| 方法 | 参数类型 | 默认值 | 说明 |
|------|---------|--------|------|
| `configResultFileName(String)` | String | 自动生成 | 自定义输出文件名 |
| `syncDebug(Boolean)` | Boolean | `false` | 开启 debug 日志 |
| `enableFileResult(Boolean)` | Boolean | `true` | 是否输出到文件 |
| `configWatchTime(Long)` | Long (毫秒) | 180000 (3分钟) | 监控时长 |
| `configResultCallBack(PrivacyResultCallBack)` | PrivacyResultCallBack | `null` | 文件输出完成回调 |
| `enableReadClipBoard(Boolean)` | Boolean | `true` | 是否允许读取剪贴板 |
### 最佳实践
#### 1. 初始化时机
```kotlin
class MyApplication : Application() {
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
// ✅ 推荐:第一个调用
PrivacySentry.Privacy.init(this, builder)
// ❌ 不推荐:在其他初始化之后
// MultiDex.install(this)
// PrivacySentry.Privacy.init(this, builder)
}
}
```
**原因**:attachBaseContext 之后才能通过反射获取 ActivityThread 的 application,如果初始化太晚,可能无法捕获早期的敏感 API 调用。
#### 2. Debug vs Release 配置
```kotlin
private fun initPrivacySentry() {
val builder = PrivacySentryBuilder()
.syncDebug(BuildConfig.DEBUG) // Debug 开启日志
.enableFileResult(BuildConfig.DEBUG) // Release 关闭文件输出
.configWatchTime(
if (BuildConfig.DEBUG) 30 * 60 * 1000 // Debug: 30分钟
else 3 * 60 * 1000 // Release: 3分钟(谨慎)
)
PrivacySentry.Privacy.init(this, builder)
}
```
#### 3. 隐私协议状态管理
```kotlin
override fun onCreate() {
super.onCreate()
// 检查是否已同意隐私协议
if (!hasAgreedPrivacyPolicy()) {
showPrivacyDialog {
// 用户同意后保存状态
savePrivacyAgreement()
// ⚠️ 必须调用,告知 SDK 用户已同意
PrivacySentry.Privacy.updatePrivacyShow()
}
} else {
// 已同意,直接告知 SDK
PrivacySentry.Privacy.updatePrivacyShow()
}
}
```
**重要提示**:
- ✅ 用户同意隐私协议后,**必须**调用 `updatePrivacyShow()`
- ✅ 调用前的敏感 API 会返回空数据并标记 `check!!!`
- ✅ 调用后的敏感 API 返回真实数据并记录日志
#### 4. 手动停止监控
```kotlin
// 手动停止监控和文件写入
PrivacySentry.Privacy.stop()
```
## 🎯 自定义拦截
### 创建自定义代理类
```kotlin
import androidx.annotation.Keep
import com.yl.lib.privacy_annotation.MethodInvokeOpcode
import com.yl.lib.privacy_annotation.PrivacyClassProxy
import com.yl.lib.privacy_annotation.PrivacyMethodProxy
import com.yl.lib.sentry.hook.PrivacySentry
import com.yl.lib.sentry.hook.util.PrivacyProxyUtil.Util.doFilePrinter
@Keep
@PrivacyClassProxy
object MyCustomProxy {
/**
* 示例 1:拦截实例方法
*/
@PrivacyMethodProxy(
originalClass = YourClass::class,
originalMethod = "getSensitiveData",
originalOpcode = MethodInvokeOpcode.INVOKEVIRTUAL
)
@JvmStatic
fun getSensitiveData(
instance: YourClass, // 第一个参数是实例对象
param1: String
): String {
// 记录日志
doFilePrinter("getSensitiveData", "获取敏感数据: $param1")
// 检查隐私协议状态
if (PrivacySentry.Privacy.inDangerousState()) {
return "" // 未同意返回空
}
// 调用原始方法
return instance.getSensitiveData(param1)
}
/**
* 示例 2:拦截静态方法
*/
@PrivacyMethodProxy(
originalClass = YourUtilClass::class,
originalMethod = "getDeviceId",
originalOpcode = MethodInvokeOpcode.INVOKESTATIC
)
@JvmStatic
fun getDeviceId(): String {
doFilePrinter("getDeviceId", "获取设备ID")
if (PrivacySentry.Privacy.inDangerousState()) {
return ""
}
return YourUtilClass.getDeviceId()
}
/**
* 示例 3:拦截接口方法
*/
@PrivacyMethodProxy(
originalClass = YourInterface::class,
originalMethod = "getData",
originalOpcode = MethodInvokeOpcode.INVOKEINTERFACE
)
@JvmStatic
fun getData(instance: YourInterface): String {
doFilePrinter("getData", "接口方法调用")
return instance.getData()
}
}
```
### Java 自定义代理示例
```java
import androidx.annotation.Keep;
import com.yl.lib.privacy_annotation.MethodInvokeOpcode;
import com.yl.lib.privacy_annotation.PrivacyClassProxy;
import com.yl.lib.privacy_annotation.PrivacyMethodProxy;
import com.yl.lib.sentry.hook.PrivacySentry;
import static com.yl.lib.sentry.hook.util.PrivacyProxyUtil.Util.doFilePrinter;
@Keep
@PrivacyClassProxy
public class MyCustomProxyJava {
@PrivacyMethodProxy(
originalClass = YourClass.class,
originalMethod = "getSensitiveData",
originalOpcode = MethodInvokeOpcode.INVOKEVIRTUAL
)
public static String getSensitiveData(YourClass instance, String param) {
doFilePrinter("getSensitiveData", "获取敏感数据: " + param, false);
if (PrivacySentry.Privacy.INSTANCE.inDangerousState()) {
return "";
}
return instance.getSensitiveData(param);
}
}
```
### 方法签名规则
#### 实例方法 (INVOKEVIRTUAL)
```kotlin
// 原始方法
class TargetClass {
fun method(param1: String, param2: Int): String
}
// 代理方法:第一个参数是实例对象
@JvmStatic
fun method(
instance: TargetClass, // ⬅️ 额外的第一个参数
param1: String,
param2: Int
): String
```
#### 静态方法 (INVOKESTATIC)
```kotlin
// 原始方法
object TargetClass {
fun method(param1: String): String
}
// 代理方法:参数完全相同
@JvmStatic
fun method(param1: String): String
```
#### 接口方法 (INVOKEINTERFACE)
```kotlin
// 原始接口
interface TargetInterface {
fun method(param: String): String
}
// 代理方法:第一个参数是接口实例
@JvmStatic
fun method(
instance: TargetInterface, // ⬅️ 接口实例
param: String
): String
```
## 📋 支持的敏感 API
### 设备标识
- ✅ IMEI / DeviceId (`TelephonyManager.getDeviceId()`)
- ✅ IMSI (`TelephonyManager.getSubscriberId()`)
- ✅ MEID (`TelephonyManager.getMeid()`)
- ✅ Android ID (`Settings.Secure.getAndroidId()`)
- ✅ Serial (`Build.getSerial()`, `Build.SERIAL`)
- ✅ MAC 地址 (`WifiInfo.getMacAddress()`)
- ✅ ICCID (`TelephonyManager.getSimSerialNumber()`)
### 网络信息
- ✅ WiFi 信息 (`WifiManager.getConnectionInfo()`)
- ✅ WiFi 扫描结果 (`WifiManager.getScanResults()`)
- ✅ IP 地址 (`NetworkInterface`, `WifiInfo.getIpAddress()`)
- ✅ DHCP 信息 (`WifiManager.getDhcpInfo()`)
### 位置信息
- ✅ GPS 定位 (`LocationManager.getLastKnownLocation()`)
- ✅ 基站信息 (`TelephonyManager.getAllCellInfo()`)
- ✅ 位置监听 (`LocationManager.requestLocationUpdates()`)
### 应用信息
- ✅ 已安装应用列表 (`PackageManager.getInstalledPackages()`)
- ✅ 运行中任务 (`ActivityManager.getRunningTasks()`)
- ✅ 运行中进程 (`ActivityManager.getRunningAppProcesses()`)
- ✅ 最近任务 (`ActivityManager.getRecentTasks()`)
### 联系人和日历
- ✅ 联系人查询 (`ContentResolver.query()`)
- ✅ 联系人插入 (`ContentResolver.insert()`)
- ✅ 日历事件 (Calendar Provider)
### 传感器
- ✅ 传感器列表 (`SensorManager.getSensorList()`)
- ✅ 传感器注册 (`SensorManager.registerListener()`)
### 其他
- ✅ 剪贴板 (`ClipboardManager.getPrimaryClip()`)
- ✅ 蓝牙 (`BluetoothAdapter.getAddress()`)
- ✅ 权限请求 (`requestPermissions()`)
- ✅ SIM 卡信息 (`TelephonyManager.getSimOperator()`)
> 完整列表请参考 [privacy-proxy](./privacy-proxy) 模块
## 🔍 检测结果说明
### 日志格式
```
[PrivacyOfficer] getDeviceId-线程名: main | 读取IMEI | com.example.MainActivity.onCreate(MainActivity.kt:42)
↑ 调用堆栈
```
### 危险状态标记
如果在日志中看到 `check!!!` 标记:
```
check!!! 还未展示隐私协议,Illegal print
```
**说明**:此时还未同意隐私协议,调用了敏感 API
**解决方法**:
1. 检查是否在隐私协议同意后调用了 `updatePrivacyShow()`
2. 优化代码,避免在隐私协议同意前调用敏感 API
## 🛠️ 常见问题
### Q1: 为什么某些 API 没有被拦截?
**可能原因**:
1. 该 API 未在 privacy-proxy 中实现
2. 包名在黑名单中
3. 使用动态加载的代码(热修复、插件化)
**解决方法**:
1. 查看 `privacy_hook.json` 确认是否包含该 API
2. 检查黑名单配置
3. 自定义拦截规则
### Q2: 编译失败或运行时崩溃
**可能原因**:
1. ASM 版本冲突(特别是高德地图)
2. AGP 版本不兼容
**解决方法**:
1. 添加冲突库到黑名单
2. 升级到 AGP 8.0+
3. 检查 Gradle 和 Kotlin 版本
### Q3: 如何在线上环境使用?
**建议配置**:
```kotlin
PrivacySentryBuilder()
.syncDebug(false) // 关闭 debug 日志
.enableFileResult(false) // 关闭文件输出
.configWatchTime(3 * 60 * 1000) // 缩短监控时间
```
**注意**:
- ❌ 线上版本**不要**开启 `enableFileResult`,避免隐私数据泄露
- ✅ 可以通过 `configResultCallBack` 上报统计数据
### Q4: 多进程如何处理?
SDK 自动支持多进程,会为不同进程生成独立的日志文件:
```
主进程:privacy_result.xls
子进程:com.example.service_privacy_result.xls
```
### Q5: 性能影响如何?
- **编译时间**:增加 < 2 秒
- **APK 体积**:增加 ~200KB
- **运行时性能**:零额外开销(字节码已修改)
- **内存占用**:缓存数据占用,可通过 watchTime 控制
## 📖 更新日志
### 1.3.7_v820_beta4 (2025-05-18)
- ✅ 支持 AGP 8.0+
- ❌ 不兼容 AGP 8.0 以下版本(请使用 1.3.6)
### 1.3.6 (2024-11-01)
- 修复 `T.(args..):T` 函数 hook 失败的问题
### 1.3.5 (2024-05-08)
- 修复读取小米系统 OAID 反射代理失败的问题
- 修复 SHA-256 digest error 问题
### 1.3.4 (2023-09-18)
- 修复内存缓存数据转换问题
- 修复 `getSimState` 闪退问题
### 1.3.3 (2023-08-22)
- 重构 plugin 部分,引入 Booster
- 适配 AGP 和 Gradle 高版本
- 支持 AGP 7.0+
> 完整更新日志请查看 [CHANGELOG](./CHANGELOG.md)
## 🤝 贡献指南
欢迎提交 Issue 和 Pull Request!
- **Bug 报告**:请详细描述问题和复现步骤
- **功能建议**:欢迎提出改进建议
- **Pull Request**:请先创建 Issue 讨论
## 📄 许可证
本项目采用 [MIT License](LICENSE)
## 💰 打赏支持
如果这个项目对你有帮助,欢迎打赏支持!
<img width="290" alt="image" src="https://github.com/user-attachments/assets/4d966c38-e1cb-44cd-bff3-09efed7b16a6">
<img width="290" alt="image" src="https://github.com/user-attachments/assets/1b7c628f-dc72-45b6-8ff6-161a1c90d463">
## 🌟 Star History
如果觉得有用,请给个 Star ⭭!
## 🔗 相关资源
- **架构文档**:[docs/architecture.md](./docs/architecture.md)
- **开发指南**:[CLAUDE.md](./CLAUDE.md)
- **示例项目**:[app](./app)
- **GitHub Issues**:[提交问题](https://github.com/allenymt/PrivacySentry/issues)
---
**注意事项**:
1. ⚠️ 线上版本请关闭 `enableFileResult`
2. ⚠️ 尽可能在 `attachBaseContext` 中第一个调用初始化
3. ⚠️ 用户同意隐私协议后必须调用 `updatePrivacyShow()`
4. ⚠️ 使用高德地图等三方库需配置黑名单
5. ⚠️ 动态加载的代码无法被拦截
---
> 如有问题,请提 [Issue](https://github.com/allenymt/PrivacySentry/issues)
>
> 兄弟们,走过路过请给个 Star ⭭~~~
================================================
FILE: app/.gitignore
================================================
/build
================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'privacy-sentry-plugin'
android {
compileSdkVersion 34
buildToolsVersion "34.0.0"
namespace "com.yl.lib.privacysentry"
defaultConfig {
applicationId "com.yl.lib.privacysentry"
minSdkVersion 19
targetSdkVersion 34
versionCode 1
versionName "1.0"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
signingConfigs {
release {
storeFile file("../privacy")
storePassword "123456"
keyAlias "privacy"
keyPassword "123456"
v2SigningEnabled true
}
}
buildTypes {
debug {
debuggable true
signingConfig signingConfigs.release //使用release签名
}
release {
debuggable true
jniDebuggable false
shrinkResources false
minifyEnabled true
signingConfig signingConfigs.release //使用release签
zipAlignEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
lintOptions {
abortOnError false
}
}
dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation "androidx.multidex:multidex:2.0.1"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
if (rootProject.ext.build.local_debug) {
implementation project(":hook-sentry")
implementation project(":privacy-proxy")
implementation project(":privacy-replace")
implementation project(":privacy-annotation")
} else {
def privacyVersion = rootProject.ext.publish_config['version']
implementation "com.github.allenymt.PrivacySentry:hook-sentry:$privacyVersion"
implementation "com.github.allenymt.PrivacySentry:privacy-annotation:$privacyVersion"
// 测试自定义拦截就注释掉
implementation "com.github.allenymt.PrivacySentry:privacy-proxy:$privacyVersion"
// 新增类替换,主要是为了hook构造函数的参数
implementation "com.github.allenymt.PrivacySentry:privacy-replace:$privacyVersion"
}
implementation(project(path: ':privacy-test')) {
exclude module: 'privacy-annotation'
}
implementation "androidx.exifinterface:exifinterface:1.3.6"
implementation "androidx.work:work-runtime-ktx:2.8.1"
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
// 测试SHA-256 digest error问题
// api "org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5"
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
privacy {
// 设置免hook的名单
blackList = []
// 开关PrivacySentry插件功能,核心功能开关,默认为true
enablePrivacy = true
// 开启hook反射的方法,默认为false,按需打开
hookReflex = false
// 配置反射拦截 反射获取小米系统的oaid、aaid、vaid,例如极光、个推、穿山甲等SDK都有获取
reflexMap = ["com.android.id.impl.IdProviderImpl":["getOAID","getAAID","getVAID"]]
///// *************后续不再维护,只保留hook方法的核心功能****************
// 默认为false,按需打开
hookConstructor = false
// 默认为false,按需打开
hookField = false
//*************以下是分割线,主要是对Service的自启动优化处理,默认为false,按需打开****************
// 处理Manifest文件,主要是处理Service的Priority , 关闭Service的Export
enableProcessManifest = false
// hook Service的部分代码,修复在MIUI上的自启动问题
// 部分Service把自己的Priority设置为1000,这里开启代理功能,可以代理成0
enableReplacePriority = false
replacePriority = 1
// 支持关闭Service的Export功能,默认为false,注意部分厂商通道之类的push(xiaomi、vivo、huawei等厂商的pushService),不能关闭
enableCloseServiceExport = false
// Export白名单Service
serviceExportPkgWhiteList = ["white"]
enableHookServiceStartCommand = false
}
project.afterEvaluate {
project.gradle.taskGraph.whenReady {
println("=======> print allTasks")
println(project.gradle.taskGraph.allTasks)
}
}
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# 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 *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: app/src/androidTest/java/com/yl/lib/privacysentry/ExampleInstrumentedTest.kt
================================================
package com.yl.lib.privacysentry
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.yl.lib.privacysentry", appContext.packageName)
}
}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="adnroid.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.BODY_SENSORS" />
<application
android:name=".APP"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.PrivacySentry">
<activity
android:name=".test.TestFragmentActivity"
android:exported="false" />
<activity
android:name=".telephony.TelephonyTestActivity"
android:exported="false" />
<activity
android:name=".location.LocationTestActivity"
android:exported="false" />
<activity
android:name=".calendar.CalenderActivity"
android:exported="true" />
<activity
android:name=".contact.ContactActivity"
android:exported="true" />
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".process.MultiProcessB"
android:exported="false"
android:process=":MultiProcessB" />
<service
android:name=".test.TestService"
android:enabled="true"
android:exported="true"
android:priority="10"/>
</application>
</manifest>
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/APP.kt
================================================
package com.yl.lib.privacysentry
import android.app.Application
import android.content.Context
import android.os.Build
import androidx.multidex.MultiDex
import com.yl.lib.privacysentry.test.PrivacyMethod
import com.yl.lib.sentry.hook.PrivacyResultCallBack
import com.yl.lib.sentry.hook.PrivacySentry
import com.yl.lib.sentry.hook.PrivacySentryBuilder
import com.yl.lib.sentry.hook.util.PrivacyLog
/**
* @author yulun
* @sinice 2021-11-19 10:20
*/
class APP : Application() {
override fun onCreate() {
super.onCreate()
var str = testJustSerial()
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
// 注意尽可能在attachBaseContext里第一个调用,因为attachBaseContext之后才能反射拿到ActivityThread的application
// 所以如果是在attachBaseContext中,且隐私合规SDK未初始化,不管是不是首次启动,都会认为是危险期,无法调用敏感api
PrivacyMethod.PrivacyMethod.getAndroidId(base!!)
MultiDex.install(this)
Thread { initPrivacyTransformComplete() }.start()
}
private fun initPrivacyTransform() {
PrivacySentry.Privacy.initTransform(this)
}
private fun initPrivacyTransformComplete() {
// 完整版配置
var builder = PrivacySentryBuilder()
// 自定义文件结果的输出名
.configResultFileName("demo_test")
//自定义检测时间,也支持主动停止检测 PrivacySentry.Privacy.stopWatch()
.configWatchTime(10 * 60 * 1000)
// 打开写入文件开关
.enableFileResult(true)
.syncDebug(true)
// 文件输出后的回调
.configResultCallBack(object : PrivacyResultCallBack {
override fun onResultCallBack(filePath: String) {
PrivacyLog.i("result file patch is $filePath")
}
})
// 添加默认结果输出,包含log输出和文件输出
PrivacySentry.Privacy.init(this, builder)
// 简易版配置
// PrivacySentry.Privacy.init(this)
}
fun testJustSerial() {
val serialNum = android.os.Build.SERIAL
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/MainActivity.kt
================================================
package com.yl.lib.privacysentry
import android.Manifest
import android.app.ActivityManager
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.View
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import com.yl.lib.privacy_test.PrivacyProxySelfTest2
import com.yl.lib.privacy_test.TestMethod
import com.yl.lib.privacy_test.TestMethodInJava
import com.yl.lib.privacysentry.calendar.CalenderActivity
import com.yl.lib.privacysentry.contact.ContactActivity
import com.yl.lib.privacysentry.location.LocationTestActivity
import com.yl.lib.privacysentry.process.MultiProcessB
import com.yl.lib.privacysentry.process.MultiProcessC
import com.yl.lib.privacysentry.telephony.TelephonyTestActivity
import com.yl.lib.privacysentry.test.*
import com.yl.lib.sentry.hook.PrivacySentry
import com.yl.lib.sentry.hook.util.MainProcessUtil
import com.yl.lib.sentry.hook.util.PrivacyClipBoardManager
import com.yl.lib.sentry.hook.util.PrivacyLog
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.btn_androidId).setOnClickListener {
var androidId = PrivacyMethod.PrivacyMethod.getAndroidId(this)
TestMethodInJava.getAndroidId(this)
TestMethodInJava.getAndroidIdSystem(this)
PrivacyLog.i("androidId is $androidId")
Thread{
TestInJava.testHttpUrlConnection()
}
val preferences = getSharedPreferences("my_preferences", MODE_PRIVATE)
val editor = preferences.edit()
editor.putString("key", "value")
editor.apply()
}
findViewById<Button>(R.id.btn_mac_address).setOnClickListener {
var macRaw = PrivacyMethod.PrivacyMethod.getMacRaw(this)
PrivacyLog.i("macRaw is $macRaw")
PrivacyTestMacAddress.getMacAddress()
PrivacyMethod.PrivacyMethod.getIp(this)
PrivacyMethod.PrivacyMethod.getScanResults(this)
TestMethodInJava.getAndroidId2(this)
}
findViewById<Button>(R.id.btn_installed_packages).setOnClickListener {
var privacySentryInstalled =
PrivacyMethod.PrivacyMethod.isInstalled(application, "com.yl.lib.privacysentry123")
PrivacyLog.i("privacySentryInstalled is $privacySentryInstalled")
var privacySentryInstalled2 =
PrivacyMethod.PrivacyMethod.isInstalled2(
application,
this,
"com.yl.lib.privacysentry123"
)
PrivacyLog.i("privacySentryInstalled2 is $privacySentryInstalled2")
PrivacyLog.i(
"privacySentryInstalled2 is ${
PrivacyMethod.PrivacyMethod.queryActivityInfo(
application,
this
)
}"
)
PrivacyLog.i(
"privacySentryInstalled3 is ${
PrivacyMethod.PrivacyMethod.isInstalled3(
application,
"com.xx.xx.xx"
)
}"
)
}
findViewById<Button>(R.id.btn_test_cms).setOnClickListener {
PrivacyMethod.PrivacyMethod.testHookCms(application)
PrivacyLog.i("testHookCms")
}
findViewById<Button>(R.id.btn_clipboard_readable).setOnClickListener {
var enableClipRead = PrivacyClipBoardManager.isReadClipboardEnable()
if (enableClipRead) {
PrivacyClipBoardManager.closeReadClipboard()
} else {
PrivacyClipBoardManager.openReadClipboard()
}
configClipReadText(it as Button)
}
configClipReadText(findViewById<Button>(R.id.btn_clipboard_readable))
findViewById<Button>(R.id.btn_test_ams_process).setOnClickListener {
PrivacyMethod.PrivacyMethod.testRunningProcess(application)
PrivacyMethod.PrivacyMethod.testRunningTask(application)
}
findViewById<Button>(R.id.btn_main_process).setOnClickListener {
var mainProcess = MainProcessUtil.MainProcessChecker.isMainProcess(this)
var currentProcessName = MainProcessUtil.MainProcessChecker.getProcessName(this)
PrivacyLog.i("mainProcess currentProcessName is $currentProcessName is $mainProcess")
PrivacyProxyCallJava.getRunningTasks(
getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager,
1
)
PrivacyProxySelfTest2.getRunningTasks456(
getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager,
1
)
}
findViewById<Button>(R.id.btn_force_finish).setOnClickListener {
PrivacySentry.Privacy.stop()
}
findViewById<Button>(R.id.btn_test_processB).setOnClickListener {
startService(Intent(this, MultiProcessB::class.java))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
startService(Intent(this, MultiProcessC::class.java))
}
}
findViewById<Button>(R.id.btn_test_lib_method).setOnClickListener {
TestMethod.PrivacyMethod.getDeviceId(applicationContext)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
TestMethod.PrivacyMethod.getDeviceId1(applicationContext)
}
TestMethod.PrivacyMethod.getICCID(applicationContext)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
TestMethod.PrivacyMethod.getIMEI(applicationContext)
}
TestMethod.PrivacyMethod.getIMSI(applicationContext)
TestMethod.PrivacyMethod.testHookCms(applicationContext)
TestMethod.PrivacyMethod.testRunningProcess(applicationContext)
TestMethod.PrivacyMethod.testRunningTask(applicationContext)
}
// 变量hook测试
findViewById<Button>(R.id.btn_test_sn).setOnClickListener {
var sn = PrivacyMethod.PrivacyMethod.getSerial()
var brand = android.os.Build.BRAND
PrivacyLog.i("Android SN is $sn brand is $brand")
}
findViewById<Button>(R.id.btn_to_calender).setOnClickListener {
startActivity(Intent(this, CalenderActivity::class.java))
}
findViewById<Button>(R.id.btn_to_telephony).setOnClickListener {
startActivity(Intent(this, TelephonyTestActivity::class.java))
}
findViewById<Button>(R.id.btn_to_contact).setOnClickListener {
startActivity(Intent(this, ContactActivity::class.java))
}
findViewById<Button>(R.id.btn_test_sensor).setOnClickListener {
PrivacyMethod.PrivacyMethod.testSensor(this)
}
findViewById<Button>(R.id.btn_thread_cache).setOnClickListener {
for (index in 1..20) {
Thread(Thread.currentThread().threadGroup, {
var result = PrivacyMethod.PrivacyMethod.getAndroidId(this@MainActivity)
PrivacyLog.e("btn_thread_cache result is $result")
PrivacyMethod.PrivacyMethod.getDeviceId(this@MainActivity)
PrivacyMethod.PrivacyMethod.getDeviceId1(this@MainActivity)
PrivacyMethod.PrivacyMethod.getICCID(this@MainActivity)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PrivacyMethod.PrivacyMethod.getIMEI(this@MainActivity)
}
PrivacyMethod.PrivacyMethod.getIMSI(this@MainActivity)
PrivacyMethod.PrivacyMethod.getMacRaw(this@MainActivity)
PrivacyMethod.PrivacyMethod.getMacV2()
PrivacyMethod.PrivacyMethod.getMeid(this@MainActivity)
PrivacyMethod.PrivacyMethod.getSerial()
}, "test_thread_$index", 0).start()
}
}
findViewById<Button>(R.id.btn_test_ExternalStorageDirectory).setOnClickListener {
PrivacyMethod.PrivacyMethod.getSdcardRoot(this)
}
findViewById<Button>(R.id.btn_test_get_all_sensor).setOnClickListener {
PrivacyMethod.PrivacyMethod.testGetSensorList(this)
}
findViewById<Button>(R.id.btn_to_location).setOnClickListener {
startActivity(Intent(this, LocationTestActivity::class.java))
}
findViewById<Button>(R.id.btn_to_fragment).setOnClickListener {
startActivity(Intent(this, TestFragmentActivity::class.java))
}
findViewById<Button>(R.id.btn_test_reflex).setOnClickListener {
var ap = packageManager
TestReflex().test(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
TestReflexJava().test(this)
TestReflexJava().reflex3(this, "123")
}
TestInJava.testReflexClipManager()
TestInJava.testReflexClipManagerOpen()
TestInJava.testReflexClipManagerClose()
}
AlertDialog.Builder(this).setMessage("确认隐私协议").setPositiveButton(
"确定"
) { dialog, which ->
PrivacySentry.Privacy.updatePrivacyShow()
PrivacySentry.Privacy.closeVisitorModel()
}.create().show()
//Android Q开始,READ_PHONE_STATE 不再有用,不用全局弹框
var permissions = arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_PHONE_STATE
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(permissions, 1000)
}
PrivacySentry.Privacy.initTransform(application)
}
fun testGetOaid(view: View) {
TestOaidGetter.getOaid(this);
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 1000) {
PrivacyLog.i("requestPermissions ${permissions[0]} success")
} else {
PrivacyLog.i("requestPermissions ${permissions[0]} fail")
}
}
private fun configClipReadText(btn: Button) {
var enableClipRead = PrivacyClipBoardManager.isReadClipboardEnable()
var btnText = ""
btnText = if (enableClipRead) {
"关闭读取剪切板"
} else {
"开启剪贴板"
}
btn.text = btnText
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/ReflexObjectUtil.java
================================================
package com.yl.lib.privacysentry;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Scanner;
/**
* @author yulun
* @since 2023-08-29 18:04
*/
public class ReflexObjectUtil {
public static void test(String className) {
try {
Class cl = Class.forName(className);
Class supercl = cl.getSuperclass();
String modifiers = Modifier.toString(cl.getModifiers());
if (modifiers.length() > 0) System.out.print(modifiers + " ");
System.out.print("class " + className);
if (supercl != null && supercl != Object.class) {
System.out.print("extends " + supercl.getName());
}
System.out.print("\n{\n");
printFields(cl);
System.out.println();
printConstructors(cl);
System.out.println();
printMethods(cl);
System.out.print("}\n");
} catch (Exception e) {
// TODO: handle exception
}
}
public static void printConstructors(Class cl) {
Constructor[] constructors = cl.getConstructors();
for (Constructor c : constructors) {
System.out.print(" ");
String name = c.getName();
String modifiers = Modifier.toString(c.getModifiers());
if (modifiers.length() > 0) System.out.print(modifiers + " ");
System.out.print(name + " (");
Class[] paramType = c.getParameterTypes();
for (int j = 0; j < paramType.length; j++) {
if (j > 0) System.out.print(", ");
System.out.print(paramType[j].getName());
}
System.out.print(")");
}
System.out.println();
}
public static void printMethods(Class cl) {
Method[] methods = cl.getDeclaredMethods();
for (Method m : methods) {
System.out.print(" ");
String name = m.getName();
String modifiers = Modifier.toString(m.getModifiers());
if (modifiers.length() > 0) System.out.print(modifiers + " ");
Class returnType = m.getReturnType();
System.out.print(returnType.getName() + " ");
System.out.print(name + " (");
Class[] paramType = m.getParameterTypes();
for (int j = 0; j < paramType.length; j++) {
if (j > 0) System.out.print(", ");
System.out.print(paramType[j].getName());
}
System.out.print(")");
System.out.println();
}
System.out.println();
}
public static void printFields(Class cl) {
Field[] fields = cl.getDeclaredFields();
for (Field f : fields) {
System.out.print(" ");
String name = f.getName();
Class type = f.getType();
String modifiers = Modifier.toString(f.getModifiers());
if (modifiers.length() > 0) System.out.print(modifiers + " ");
System.out.println(type.getName() + " " + name + ";");
}
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/calendar/CalendarManager.kt
================================================
package com.yl.lib.privacysentry.calendar
import android.content.ContentUris
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.database.Cursor
import android.graphics.Color
import android.net.Uri
import android.os.Build
import android.provider.CalendarContract
import android.widget.Toast
import java.text.SimpleDateFormat
import java.util.*
import kotlin.collections.ArrayList
/**
* @author yulun
* @since 2022-01-13 19:53
* 测试日历相关 代码从 https://github.com/kylechandev/CalendarProviderManager拷贝
*/
class CalendarManager {
object Manager {
private val builder = StringBuilder()
/*
TIP: 要向系统日历插入事件,前提系统中必须存在至少1个日历账户
*/
/*
TIP: 要向系统日历插入事件,前提系统中必须存在至少1个日历账户
*/
// ----------------------- 创建日历账户时账户名使用 ---------------------------
private var CALENDAR_NAME = "KyleC"
private var CALENDAR_ACCOUNT_NAME = "KYLE"
private var CALENDAR_DISPLAY_NAME = "KYLE的账户"
// ------------------------------- 日历账户 -----------------------------------
// ------------------------------- 日历账户 -----------------------------------
/**
* 获取日历账户ID(若没有则会自动创建一个)
*
* @return success: 日历账户ID failed : -1 permission deny : -2
*/
fun obtainCalendarAccountID(context: Context): Long {
val calID = checkCalendarAccount(context)
return if (calID >= 0) {
calID
} else {
createCalendarAccount(context)
}
}
/**
* 检查是否存在日历账户
*
* @return 存在:日历账户ID 不存在:-1
*/
private fun checkCalendarAccount(context: Context): Long {
context.getContentResolver().query(
CalendarContract.Calendars.CONTENT_URI,
null, null, null, null
).use({ cursor ->
// 不存在日历账户
if (null == cursor) {
return -1
}
val count: Int = cursor.getCount()
// 存在日历账户,获取第一个账户的ID
return if (count > 0) {
cursor.moveToFirst()
cursor.getInt(cursor.getColumnIndex(CalendarContract.Calendars._ID)).toLong()
} else {
-1
}
})
}
/**
* 创建一个新的日历账户
*
* @return success:ACCOUNT ID , create failed:-1 , permission deny:-2
*/
private fun createCalendarAccount(context: Context): Long {
// 系统日历表
var uri: Uri = CalendarContract.Calendars.CONTENT_URI
// 要创建的账户
val accountUri: Uri
// 开始组装账户数据
val account = ContentValues()
// 账户类型:本地
// 在添加账户时,如果账户类型不存在系统中,则可能该新增记录会被标记为脏数据而被删除
// 设置为ACCOUNT_TYPE_LOCAL可以保证在不存在账户类型时,该新增数据不会被删除
account.put(
CalendarContract.Calendars.ACCOUNT_TYPE,
CalendarContract.ACCOUNT_TYPE_LOCAL
)
// 日历在表中的名称
account.put(CalendarContract.Calendars.NAME, CALENDAR_NAME)
// 日历账户的名称
account.put(CalendarContract.Calendars.ACCOUNT_NAME, CALENDAR_ACCOUNT_NAME)
// 账户显示的名称
account.put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, CALENDAR_DISPLAY_NAME)
// 日历的颜色
account.put(CalendarContract.Calendars.CALENDAR_COLOR, Color.parseColor("#515bd4"))
// 用户对此日历的获取使用权限等级
account.put(
CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL,
CalendarContract.Calendars.CAL_ACCESS_OWNER
)
// 设置此日历可见
account.put(CalendarContract.Calendars.VISIBLE, 1)
// 日历时区
account.put(
CalendarContract.Calendars.CALENDAR_TIME_ZONE,
TimeZone.getDefault().getID()
)
// 可以修改日历时区
account.put(CalendarContract.Calendars.CAN_MODIFY_TIME_ZONE, 1)
// 同步此日历到设备上
account.put(CalendarContract.Calendars.SYNC_EVENTS, 1)
// 拥有者的账户
account.put(CalendarContract.Calendars.OWNER_ACCOUNT, CALENDAR_ACCOUNT_NAME)
// 可以响应事件
account.put(CalendarContract.Calendars.CAN_ORGANIZER_RESPOND, 1)
// 单个事件设置的最大的提醒数
account.put(CalendarContract.Calendars.MAX_REMINDERS, 8)
// 设置允许提醒的方式
account.put(CalendarContract.Calendars.ALLOWED_REMINDERS, "0,1,2,3,4")
// 设置日历支持的可用性类型
account.put(CalendarContract.Calendars.ALLOWED_AVAILABILITY, "0,1,2")
// 设置日历允许的出席者类型
account.put(CalendarContract.Calendars.ALLOWED_ATTENDEE_TYPES, "0,1,2")
/*
TIP: 修改或添加ACCOUNT_NAME只能由SYNC_ADAPTER调用
对uri设置CalendarContract.CALLER_IS_SYNCADAPTER为true,即标记当前操作为SYNC_ADAPTER操作
在设置CalendarContract.CALLER_IS_SYNCADAPTER为true时,必须带上参数ACCOUNT_NAME和ACCOUNT_TYPE(任意)
*/uri = uri.buildUpon()
.appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
.appendQueryParameter(
CalendarContract.Calendars.ACCOUNT_NAME,
CALENDAR_ACCOUNT_NAME
)
.appendQueryParameter(
CalendarContract.Calendars.ACCOUNT_TYPE,
CalendarContract.Calendars.CALENDAR_LOCATION
)
.build()
accountUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// 检查日历权限
if (PackageManager.PERMISSION_GRANTED == context.checkSelfPermission(
"android.permission.WRITE_CALENDAR"
)
) {
context.getContentResolver().insert(uri, account)!!
} else {
return -2
}
} else {
context.getContentResolver().insert(uri, account)!!
}
return if (accountUri == null) -1 else ContentUris.parseId(accountUri)
}
/**
* 删除创建的日历账户
*
* @return -2: permission deny 0: No designated account 1: delete success
*/
fun deleteCalendarAccountByName(context: Context): Int {
val deleteCount: Int
val uri: Uri = CalendarContract.Calendars.CONTENT_URI
val selection = ("((" + CalendarContract.Calendars.ACCOUNT_NAME + " = ?) AND ("
+ CalendarContract.Calendars.ACCOUNT_TYPE + " = ?))")
val selectionArgs = arrayOf(CALENDAR_ACCOUNT_NAME, CalendarContract.ACCOUNT_TYPE_LOCAL)
deleteCount = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (PackageManager.PERMISSION_GRANTED == context.checkSelfPermission(
"android.permission.WRITE_CALENDAR"
)
) {
context.getContentResolver().delete(uri, selection, selectionArgs)
} else {
return -2
}
} else {
context.getContentResolver().delete(uri, selection, selectionArgs)
}
return deleteCount
}
// ------------------------------- 添加日历事件 -----------------------------------
// ------------------------------- 添加日历事件 -----------------------------------
/**
* 添加日历事件
*
* @param calendarEvent 日历事件(详细参数说明请参看[CalendarEvent]构造方法)
* @return 0: success -1: failed -2: permission deny
*/
fun addCalendarEvent(context: Context, calendarEvent: CalendarEvent): Int {
/*
TIP: 插入一个新事件的规则:
1. 必须包含CALENDAR_ID和DTSTART字段
2. 必须包含EVENT_TIMEZONE字段,使用TimeZone.getDefault().getID()方法获取默认时区
3. 对于非重复发生的事件,必须包含DTEND字段
4. 对重复发生的事件,必须包含一个附加了RRULE或RDATE字段的DURATION字段
*/
// 获取日历账户ID,也就是要将事件插入到的账户
val calID = obtainCalendarAccountID(context)
// 系统日历事件表
val uri1: Uri = CalendarContract.Events.CONTENT_URI
// 创建的日历事件
val eventUri: Uri
// 系统日历事件提醒表
val uri2: Uri = CalendarContract.Reminders.CONTENT_URI
// 创建的日历事件提醒
val reminderUri: Uri
// 开始组装事件数据
val event = ContentValues()
// 事件要插入到的日历账户
event.put(CalendarContract.Events.CALENDAR_ID, calID)
setupEvent(calendarEvent, event)
eventUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// 判断权限
if (PackageManager.PERMISSION_GRANTED == context.checkSelfPermission(
"android.permission.WRITE_CALENDAR"
)
) {
context.getContentResolver().insert(uri1, event)!!
} else {
return -2
}
} else {
context.getContentResolver().insert(uri1, event)!!
}
if (null == eventUri) {
return -1
}
if (-2 != calendarEvent.getAdvanceTime()) {
// 获取事件ID
val eventID = ContentUris.parseId(eventUri)
// 开始组装事件提醒数据
val reminders = ContentValues()
// 此提醒所对应的事件ID
reminders.put(CalendarContract.Reminders.EVENT_ID, eventID)
// 设置提醒提前的时间(0:准时 -1:使用系统默认)
reminders.put(CalendarContract.Reminders.MINUTES, calendarEvent.getAdvanceTime())
// 设置事件提醒方式为通知警报
reminders.put(
CalendarContract.Reminders.METHOD,
CalendarContract.Reminders.METHOD_ALERT
)
reminderUri = context.getContentResolver().insert(uri2, reminders)!!
if (null == reminderUri) {
return -1
}
}
return 0
}
// ------------------------------- 更新日历事件 -----------------------------------
// ------------------------------- 更新日历事件 -----------------------------------
/**
* 更新指定ID的日历事件
*
* @param newCalendarEvent 更新的日历事件
* @return -2: permission deny else success
*/
fun updateCalendarEvent(
context: Context,
eventID: Long,
newCalendarEvent: CalendarEvent
): Int {
val updatedCount1: Int
val uri1: Uri = CalendarContract.Events.CONTENT_URI
val uri2: Uri = CalendarContract.Reminders.CONTENT_URI
val event = ContentValues()
setupEvent(newCalendarEvent, event)
// 更新匹配条件
val selection1 = "(" + CalendarContract.Events._ID + " = ?)"
val selectionArgs1 = arrayOf(eventID.toString())
updatedCount1 = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (PackageManager.PERMISSION_GRANTED == context.checkSelfPermission(
"android.permission.WRITE_CALENDAR"
)
) {
context.getContentResolver().update(uri1, event, selection1, selectionArgs1)
} else {
return -2
}
} else {
context.getContentResolver().update(uri1, event, selection1, selectionArgs1)
}
val reminders = ContentValues()
reminders.put(CalendarContract.Reminders.MINUTES, newCalendarEvent.getAdvanceTime())
reminders.put(
CalendarContract.Reminders.METHOD,
CalendarContract.Reminders.METHOD_ALERT
)
// 更新匹配条件
val selection2 = "(" + CalendarContract.Reminders.EVENT_ID + " = ?)"
val selectionArgs2 = arrayOf(eventID.toString())
val updatedCount2: Int =
context.getContentResolver().update(uri2, reminders, selection2, selectionArgs2)
return (updatedCount1 + updatedCount2) / 2
}
/**
* 更新指定ID事件的开始时间
*
* @return If successfully returns 1
*/
fun updateCalendarEventbeginTime(context: Context, eventID: Long, newBeginTime: Long): Int {
val uri: Uri = CalendarContract.Events.CONTENT_URI
// 新的数据
val event = ContentValues()
event.put(CalendarContract.Events.DTSTART, newBeginTime)
// 匹配条件
val selection = "(" + CalendarContract.Events._ID + " = ?)"
val selectionArgs = arrayOf(eventID.toString())
return context.getContentResolver().update(uri, event, selection, selectionArgs)
}
/**
* 更新指定ID事件的结束时间
*
* @return If successfully returns 1
*/
fun updateCalendarEventEndTime(context: Context, eventID: Long, newEndTime: Long): Int {
val uri: Uri = CalendarContract.Events.CONTENT_URI
// 新的数据
val event = ContentValues()
event.put(CalendarContract.Events.DTEND, newEndTime)
// 匹配条件
val selection = "(" + CalendarContract.Events._ID + " = ?)"
val selectionArgs = arrayOf(eventID.toString())
return context.getContentResolver().update(uri, event, selection, selectionArgs)
}
/**
* 更新指定ID事件的起始时间
*
* @return If successfully returns 1
*/
fun updateCalendarEventTime(
context: Context, eventID: Long, newBeginTime: Long,
newEndTime: Long
): Int {
val uri: Uri = CalendarContract.Events.CONTENT_URI
// 新的数据
val event = ContentValues()
event.put(CalendarContract.Events.DTSTART, newBeginTime)
event.put(CalendarContract.Events.DTEND, newEndTime)
// 匹配条件
val selection = "(" + CalendarContract.Events._ID + " = ?)"
val selectionArgs = arrayOf(eventID.toString())
return context.getContentResolver().update(uri, event, selection, selectionArgs)
}
/**
* 更新指定ID事件的标题
*
* @return If successfully returns 1
*/
fun updateCalendarEventTitle(context: Context, eventID: Long, newTitle: String?): Int {
val uri: Uri = CalendarContract.Events.CONTENT_URI
// 新的数据
val event = ContentValues()
event.put(CalendarContract.Events.TITLE, newTitle)
// 匹配条件
val selection = "(" + CalendarContract.Events._ID + " = ?)"
val selectionArgs = arrayOf(eventID.toString())
return context.getContentResolver().update(uri, event, selection, selectionArgs)
}
/**
* 更新指定ID事件的描述
*
* @return If successfully returns 1
*/
fun updateCalendarEventDes(context: Context, eventID: Long, newEventDes: String?): Int {
val uri: Uri = CalendarContract.Events.CONTENT_URI
// 新的数据
val event = ContentValues()
event.put(CalendarContract.Events.DESCRIPTION, newEventDes)
// 匹配条件
val selection = "(" + CalendarContract.Events._ID + " = ?)"
val selectionArgs = arrayOf(eventID.toString())
return context.getContentResolver().update(uri, event, selection, selectionArgs)
}
/**
* 更新指定ID事件的地点
*
* @return If successfully returns 1
*/
fun updateCalendarEventLocation(
context: Context,
eventID: Long,
newEventLocation: String?
): Int {
val uri: Uri = CalendarContract.Events.CONTENT_URI
// 新的数据
val event = ContentValues()
event.put(CalendarContract.Events.EVENT_LOCATION, newEventLocation)
// 匹配条件
val selection = "(" + CalendarContract.Events._ID + " = ?)"
val selectionArgs = arrayOf(eventID.toString())
return context.getContentResolver().update(uri, event, selection, selectionArgs)
}
/**
* 更新指定ID事件的标题和描述
*
* @return If successfully returns 1
*/
fun updateCalendarEventTitAndDes(
context: Context, eventID: Long, newEventTitle: String?,
newEventDes: String?
): Int {
val uri: Uri = CalendarContract.Events.CONTENT_URI
// 新的数据
val event = ContentValues()
event.put(CalendarContract.Events.TITLE, newEventTitle)
event.put(CalendarContract.Events.DESCRIPTION, newEventDes)
// 匹配条件
val selection = "(" + CalendarContract.Events._ID + " = ?)"
val selectionArgs = arrayOf(eventID.toString())
return context.getContentResolver().update(uri, event, selection, selectionArgs)
}
/**
* 更新指定ID事件的常用信息(标题、描述、地点)
*
* @return If successfully returns 1
*/
fun updateCalendarEventCommonInfo(
context: Context, eventID: Long, newEventTitle: String?,
newEventDes: String?, newEventLocation: String?
): Int {
val uri: Uri = CalendarContract.Events.CONTENT_URI
// 新的数据
val event = ContentValues()
event.put(CalendarContract.Events.TITLE, newEventTitle)
event.put(CalendarContract.Events.DESCRIPTION, newEventDes)
event.put(CalendarContract.Events.EVENT_LOCATION, newEventLocation)
// 匹配条件
val selection = "(" + CalendarContract.Events._ID + " = ?)"
val selectionArgs = arrayOf(eventID.toString())
return context.getContentResolver().update(uri, event, selection, selectionArgs)
}
/**
* 更新指定ID事件的提醒方式
*
* @return If successfully returns 1
*/
private fun updateCalendarEventReminder(
context: Context,
eventID: Long,
newAdvanceTime: Long
): Int {
val uri: Uri = CalendarContract.Reminders.CONTENT_URI
val reminders = ContentValues()
reminders.put(CalendarContract.Reminders.MINUTES, newAdvanceTime)
// 更新匹配条件
val selection2 = "(" + CalendarContract.Reminders.EVENT_ID + " = ?)"
val selectionArgs2 = arrayOf(eventID.toString())
return context.getContentResolver().update(uri, reminders, selection2, selectionArgs2)
}
/**
* 更新指定ID事件的提醒重复规则
*
* @return If successfully returns 1
*/
private fun updateCalendarEventRRule(
context: Context,
eventID: Long,
newRRule: String
): Int {
val uri: Uri = CalendarContract.Events.CONTENT_URI
// 新的数据
val event = ContentValues()
event.put(CalendarContract.Events.RRULE, newRRule)
// 匹配条件
val selection = "(" + CalendarContract.Events._ID + " = ?)"
val selectionArgs = arrayOf(eventID.toString())
return context.getContentResolver().update(uri, event, selection, selectionArgs)
}
// ------------------------------- 删除日历事件 -----------------------------------
// ------------------------------- 删除日历事件 -----------------------------------
/**
* 删除日历事件
*
* @param eventID 事件ID
* @return -2: permission deny else success
*/
fun deleteCalendarEvent(context: Context, eventID: Long): Int {
val deletedCount1: Int
val uri1: Uri = CalendarContract.Events.CONTENT_URI
val uri2: Uri = CalendarContract.Reminders.CONTENT_URI
// 删除匹配条件
val selection = "(" + CalendarContract.Events._ID + " = ?)"
val selectionArgs = arrayOf(eventID.toString())
deletedCount1 = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (PackageManager.PERMISSION_GRANTED == context.checkSelfPermission(
"android.permission.WRITE_CALENDAR"
)
) {
context.getContentResolver().delete(uri1, selection, selectionArgs)
} else {
return -2
}
} else {
context.getContentResolver().delete(uri1, selection, selectionArgs)
}
// 删除匹配条件
val selection2 = "(" + CalendarContract.Reminders.EVENT_ID + " = ?)"
val selectionArgs2 = arrayOf(eventID.toString())
val deletedCount2: Int =
context.getContentResolver().delete(uri2, selection2, selectionArgs2)
return (deletedCount1 + deletedCount2) / 2
}
// ------------------------------- 查询日历事件 -----------------------------------
// ------------------------------- 查询日历事件 -----------------------------------
/**
* 查询指定日历账户下的所有事件
*
* @return If failed return null else return List<CalendarEvent>
</CalendarEvent> */
fun queryAccountEvent(context: Context, calID: Long): List<CalendarEvent>? {
val EVENT_PROJECTION = arrayOf(
CalendarContract.Events.CALENDAR_ID, // 在表中的列索引0
CalendarContract.Events.TITLE, // 在表中的列索引1
CalendarContract.Events.DESCRIPTION, // 在表中的列索引2
CalendarContract.Events.EVENT_LOCATION, // 在表中的列索引3
CalendarContract.Events.DISPLAY_COLOR, // 在表中的列索引4
CalendarContract.Events.STATUS, // 在表中的列索引5
CalendarContract.Events.DTSTART, // 在表中的列索引6
CalendarContract.Events.DTEND, // 在表中的列索引7
CalendarContract.Events.DURATION, // 在表中的列索引8
CalendarContract.Events.EVENT_TIMEZONE, // 在表中的列索引9
CalendarContract.Events.EVENT_END_TIMEZONE, // 在表中的列索引10
CalendarContract.Events.ALL_DAY, // 在表中的列索引11
CalendarContract.Events.ACCESS_LEVEL, // 在表中的列索引12
CalendarContract.Events.AVAILABILITY, // 在表中的列索引13
CalendarContract.Events.HAS_ALARM, // 在表中的列索引14
CalendarContract.Events.RRULE, // 在表中的列索引15
CalendarContract.Events.RDATE, // 在表中的列索引16
CalendarContract.Events.HAS_ATTENDEE_DATA, // 在表中的列索引17
CalendarContract.Events.LAST_DATE, // 在表中的列索引18
CalendarContract.Events.ORGANIZER, // 在表中的列索引19
CalendarContract.Events.IS_ORGANIZER, // 在表中的列索引20
CalendarContract.Events._ID // 在表中的列索引21
)
// 事件匹配
val uri: Uri = CalendarContract.Events.CONTENT_URI
val uri2: Uri = CalendarContract.Reminders.CONTENT_URI
val selection = "(" + CalendarContract.Events.CALENDAR_ID + " = ?)"
val selectionArgs = arrayOf(calID.toString())
val cursor: Cursor
cursor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (PackageManager.PERMISSION_GRANTED == context.checkSelfPermission(
"android.permission.READ_CALENDAR"
)
) {
context.getContentResolver().query(
uri, EVENT_PROJECTION, selection,
selectionArgs, null
)!!
} else {
return null
}
} else {
context.getContentResolver().query(
uri, EVENT_PROJECTION, selection,
selectionArgs, null
)!!
}
if (null == cursor) {
return null
}
// 查询结果
val result: MutableList<CalendarEvent> = ArrayList()
// 开始查询数据
if (cursor.moveToFirst()) {
do {
val calendarEvent = CalendarEvent()
result.add(calendarEvent)
calendarEvent.setId(
cursor.getLong(
cursor.getColumnIndex(
CalendarContract.Events._ID
)
)
)
calendarEvent.setCalID(
cursor.getLong(
cursor.getColumnIndex(
CalendarContract.Events.CALENDAR_ID
)
)
)
calendarEvent.setTitle(
cursor.getString(
cursor.getColumnIndex(
CalendarContract.Events.TITLE
)
)
)
calendarEvent.setDescription(
cursor.getString(
cursor.getColumnIndex(
CalendarContract.Events.DESCRIPTION
)
)
)
calendarEvent.setEventLocation(
cursor.getString(
cursor.getColumnIndex(
CalendarContract.Events.EVENT_LOCATION
)
)
)
calendarEvent.setDisplayColor(
cursor.getInt(
cursor.getColumnIndex(
CalendarContract.Events.DISPLAY_COLOR
)
)
)
calendarEvent.setStatus(
cursor.getInt(
cursor.getColumnIndex(
CalendarContract.Events.STATUS
)
)
)
calendarEvent.setStart(
cursor.getLong(
cursor.getColumnIndex(
CalendarContract.Events.DTSTART
)
)
)
calendarEvent.setEnd(
cursor.getLong(
cursor.getColumnIndex(
CalendarContract.Events.DTEND
)
)
)
calendarEvent.setDuration(
cursor.getString(
cursor.getColumnIndex(
CalendarContract.Events.DURATION
)
)
)
calendarEvent.setEventTimeZone(
cursor.getString(
cursor.getColumnIndex(
CalendarContract.Events.EVENT_TIMEZONE
)
)
)
calendarEvent.setEventEndTimeZone(
cursor.getString(
cursor.getColumnIndex(
CalendarContract.Events.EVENT_END_TIMEZONE
)
)
)
calendarEvent.setAllDay(
cursor.getInt(
cursor.getColumnIndex(
CalendarContract.Events.ALL_DAY
)
)
)
calendarEvent.setAccessLevel(
cursor.getInt(
cursor.getColumnIndex(
CalendarContract.Events.ACCESS_LEVEL
)
)
)
calendarEvent.setAvailability(
cursor.getInt(
cursor.getColumnIndex(
CalendarContract.Events.AVAILABILITY
)
)
)
calendarEvent.setHasAlarm(
cursor.getInt(
cursor.getColumnIndex(
CalendarContract.Events.HAS_ALARM
)
)
)
calendarEvent.setRRule(
cursor.getString(
cursor.getColumnIndex(
CalendarContract.Events.RRULE
)
)
)
calendarEvent.setRDate(
cursor.getString(
cursor.getColumnIndex(
CalendarContract.Events.RDATE
)
)
)
calendarEvent.setHasAttendeeData(
cursor.getInt(
cursor.getColumnIndex(
CalendarContract.Events.HAS_ATTENDEE_DATA
)
)
)
calendarEvent.setLastDate(
cursor.getInt(
cursor.getColumnIndex(
CalendarContract.Events.LAST_DATE
)
)
)
calendarEvent.setOrganizer(
cursor.getString(
cursor.getColumnIndex(
CalendarContract.Events.ORGANIZER
)
)
)
calendarEvent.setIsOrganizer(
cursor.getString(
cursor.getColumnIndex(
CalendarContract.Events.IS_ORGANIZER
)
)
)
// ----------------------- 开始查询事件提醒 ------------------------------
val REMINDER_PROJECTION = arrayOf(
CalendarContract.Reminders._ID, // 在表中的列索引0
CalendarContract.Reminders.EVENT_ID, // 在表中的列索引1
CalendarContract.Reminders.MINUTES, // 在表中的列索引2
CalendarContract.Reminders.METHOD
)
val selection2 = "(" + CalendarContract.Reminders.EVENT_ID + " = ?)"
val selectionArgs2 =
arrayOf<String>(java.lang.String.valueOf(calendarEvent.getId()))
context.getContentResolver().query(
uri2, REMINDER_PROJECTION,
selection2, selectionArgs2, null
).use { reminderCursor ->
if (null != reminderCursor) {
if (reminderCursor.moveToFirst()) {
val reminders: MutableList<EventReminders> = ArrayList()
do {
val reminders1: EventReminders =
EventReminders()
reminders.add(reminders1)
reminders1.reminderId =
reminderCursor.getLong(
reminderCursor.getColumnIndex(CalendarContract.Reminders._ID)
)
reminders1.reminderEventID =
reminderCursor.getLong(
reminderCursor.getColumnIndex(CalendarContract.Reminders.EVENT_ID)
)
reminders1.reminderMinute =
reminderCursor.getInt(
reminderCursor.getColumnIndex(CalendarContract.Reminders.MINUTES)
)
reminders1.reminderMethod =
reminderCursor.getInt(
reminderCursor.getColumnIndex(CalendarContract.Reminders.METHOD)
)
} while (reminderCursor.moveToNext())
calendarEvent.setReminders(reminders)
}
}
}
} while (cursor.moveToNext())
cursor.close()
}
return result
}
/**
* 判断日历账户中是否已经存在此事件
*
* @param begin 事件开始时间
* @param end 事件结束时间
* @param title 事件标题
*/
fun isEventAlreadyExist(context: Context, begin: Long, end: Long, title: String?): Boolean {
val projection = arrayOf(
CalendarContract.Instances.BEGIN,
CalendarContract.Instances.END,
CalendarContract.Instances.TITLE
)
val cursor: Cursor? = CalendarContract.Instances.query(
context.getContentResolver(), projection, begin, end, title
)
return (null != cursor && cursor.moveToFirst()
&& cursor.getString(
cursor.getColumnIndex(CalendarContract.Instances.TITLE)
).equals(title))
}
// ------------------------------- 日历事件相关 -----------------------------------
// ------------------------------- 日历事件相关 -----------------------------------
/**
* 组装日历事件
*/
private fun setupEvent(calendarEvent: CalendarEvent, event: ContentValues) {
// 事件开始时间
event.put(CalendarContract.Events.DTSTART, calendarEvent.getStart())
// 事件结束时间
event.put(CalendarContract.Events.DTEND, calendarEvent.getEnd())
// 事件标题
event.put(CalendarContract.Events.TITLE, calendarEvent.getTitle())
// 事件描述(对应手机系统日历备注栏)
event.put(CalendarContract.Events.DESCRIPTION, calendarEvent.getDescription())
// 事件地点
event.put(CalendarContract.Events.EVENT_LOCATION, calendarEvent.getEventLocation())
// 事件时区
event.put(CalendarContract.Events.EVENT_TIMEZONE, TimeZone.getDefault().getID())
// 定义事件的显示,默认即可
event.put(CalendarContract.Events.ACCESS_LEVEL, CalendarContract.Events.ACCESS_DEFAULT)
// 事件的状态
event.put(CalendarContract.Events.STATUS, 0)
// 设置事件提醒警报可用
event.put(CalendarContract.Events.HAS_ALARM, 1)
// 设置事件忙
event.put(
CalendarContract.Events.AVAILABILITY,
CalendarContract.Events.AVAILABILITY_BUSY
)
if (null != calendarEvent.getRRule()) {
// 设置事件重复规则
event.put(
CalendarContract.Events.RRULE,
getFullRRuleForRRule(
calendarEvent.getRRule()!!,
calendarEvent.getStart(), calendarEvent.getEnd()
)
)
}
}
/**
* 获取完整的重复规则(包含终止时间)
*
* @param rRule 重复规则
* @param beginTime 开始时间
* @param endTime 结束时间
*/
private fun getFullRRuleForRRule(rRule: String, beginTime: Long, endTime: Long): String? {
builder.delete(0, builder.length)
return when (rRule) {
RRuleConstant.REPEAT_WEEKLY_BY_MO, RRuleConstant.REPEAT_WEEKLY_BY_TU, RRuleConstant.REPEAT_WEEKLY_BY_WE, RRuleConstant.REPEAT_WEEKLY_BY_TH, RRuleConstant.REPEAT_WEEKLY_BY_FR, RRuleConstant.REPEAT_WEEKLY_BY_SA, RRuleConstant.REPEAT_WEEKLY_BY_SU -> builder.append(
rRule
).append(Util.Util.getFinalRRuleMode(endTime)).toString()
RRuleConstant.REPEAT_CYCLE_WEEKLY -> builder.append(rRule)
.append(Util.Util.getWeekForDate(beginTime)).append("; UNTIL = ")
.append(Util.Util.getFinalRRuleMode(endTime)).toString()
RRuleConstant.REPEAT_CYCLE_MONTHLY -> builder.append(rRule)
.append(Util.Util.getDayOfMonth(beginTime))
.append("; UNTIL = ").append(Util.Util.getFinalRRuleMode(endTime)).toString()
else -> rRule
}
}
// ------------------------------- 通过Intent启动系统日历 -----------------------------------
/*
日历的Intent对象:
动作 描述 附加功能
ACTION_VIEW 打开指定时间的日历 无
ACTION_VIEW 查看由EVENT_ID指定的事件 开始时间,结束时间
ACTION_EDIT 编辑由EVENT_ID指定的事件 开始时间,结束时间
ACTION_INSERT 创建一个事件 所有
Intent对象的附加功能:
Events.TITLE 事件标题
CalendarContract.EXTRA_EVENT_BEGIN_TIME 开始时间
CalendarContract.EXTRA_EVENT_END_TIME 结束时间
CalendarContract.EXTRA_EVENT_ALL_DAY 是否全天
Events.EVENT_LOCATION 事件地点
Events.DESCRIPTION 事件描述
Intent.EXTRA_EMALL 受邀者电子邮件,用逗号分隔
Events.RRULE 事件重复规则
Events.ACCESS_LEVEL 事件私有还是公有
Events.AVAILABILITY 预定事件是在忙时计数还是闲时计数
*/
// ------------------------------- 通过Intent启动系统日历 -----------------------------------
/*
日历的Intent对象:
动作 描述 附加功能
ACTION_VIEW 打开指定时间的日历 无
ACTION_VIEW 查看由EVENT_ID指定的事件 开始时间,结束时间
ACTION_EDIT 编辑由EVENT_ID指定的事件 开始时间,结束时间
ACTION_INSERT 创建一个事件 所有
Intent对象的附加功能:
Events.TITLE 事件标题
CalendarContract.EXTRA_EVENT_BEGIN_TIME 开始时间
CalendarContract.EXTRA_EVENT_END_TIME 结束时间
CalendarContract.EXTRA_EVENT_ALL_DAY 是否全天
Events.EVENT_LOCATION 事件地点
Events.DESCRIPTION 事件描述
Intent.EXTRA_EMALL 受邀者电子邮件,用逗号分隔
Events.RRULE 事件重复规则
Events.ACCESS_LEVEL 事件私有还是公有
Events.AVAILABILITY 预定事件是在忙时计数还是闲时计数
*/
/**
* 通过Intent启动系统日历新建事件界面插入新的事件
*
*
* TIP: 这将不再需要声明读写日历数据的权限
*
* @param beginTime 事件开始时间
* @param endTime 事件结束时间
* @param title 事件标题
* @param des 事件描述
* @param location 事件地点
* @param isAllDay 事件是否全天
*/
fun startCalendarForIntentToInsert(
context: Context, beginTime: Long, endTime: Long,
title: String?, des: String?, location: String?,
isAllDay: Boolean
) {
checkCalendarAccount(context)
// FIXME: 2019/3/6 VIVO手机无法打开界面,找不到对应的Activity com.bbk.calendar
val intent = Intent(Intent.ACTION_INSERT)
.setData(CalendarContract.Events.CONTENT_URI)
.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime)
.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime)
.putExtra(CalendarContract.Events.ALL_DAY, isAllDay)
.putExtra(CalendarContract.Events.TITLE, title)
.putExtra(CalendarContract.Events.DESCRIPTION, des)
.putExtra(CalendarContract.Events.EVENT_LOCATION, location)
if (null != intent.resolveActivity(context.getPackageManager())) {
context.startActivity(intent)
}
}
/**
* 通过Intent启动系统日历来编辑指定ID的事件
*
*
*
* @param eventID 要编辑的事件ID
*/
fun startCalendarForIntentToEdit(context: Context, eventID: Long) {
checkCalendarAccount(context)
val uri: Uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventID)
val intent = Intent(Intent.ACTION_EDIT).setData(uri)
if (null != intent.resolveActivity(context.getPackageManager())) {
context.startActivity(intent)
}
}
/**
* 通过Intent启动系统日历来查看指定ID的事件
*
* @param eventID 要查看的事件ID
*/
fun startCalendarForIntentToView(context: Context, eventID: Long) {
checkCalendarAccount(context)
val uri: Uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventID)
val intent = Intent(Intent.ACTION_VIEW).setData(uri)
if (null != intent.resolveActivity(context.getPackageManager())) {
context.startActivity(intent)
}
}
// ----------------------------- 日历账户名相关设置 -----------------------------------
// ----------------------------- 日历账户名相关设置 -----------------------------------
fun getCalendarName(): String? {
return CALENDAR_NAME
}
fun setCalendarName(calendarName: String) {
CALENDAR_NAME = calendarName
}
fun getCalendarAccountName(): String? {
return CALENDAR_ACCOUNT_NAME
}
fun setCalendarAccountName(calendarAccountName: String) {
CALENDAR_ACCOUNT_NAME = calendarAccountName
}
fun getCalendarDisplayName(): String? {
return CALENDAR_DISPLAY_NAME
}
fun setCalendarDisplayName(calendarDisplayName: String) {
CALENDAR_DISPLAY_NAME = calendarDisplayName
}
}
}
class CalendarEvent {
// ----------------------- 事件属性 -----------------------
// ----------------------- 事件属性 -----------------------
/**
* 事件在表中的ID
*/
private var id: Long = 0
/**
* 事件所属日历账户的ID
*/
private var calID: Long = 0
private var title: String? = null
private var description: String? = null
private var eventLocation: String? = null
private var displayColor = 0
private var status = 0
private var start: Long = 0
private var end: Long = 0
private var duration: String? = null
private var eventTimeZone: String? = null
private var eventEndTimeZone: String? = null
private var allDay = 0
private var accessLevel = 0
private var availability = 0
private var hasAlarm = 0
private var rRule: String? = null
private var rDate: String? = null
private var hasAttendeeData = 0
private var lastDate = 0
private var organizer: String? = null
private var isOrganizer: String? = null
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
/**
* 注:此属性不属于CalendarEvent
* 这里只是为了方便构造方法提供事件提醒时间
*/
private var advanceTime = 0
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// ----------------------- 事件提醒属性 -----------------------
private var reminders: List<EventReminders?>? = null
constructor() {}
/**
* 用于方便添加完整日历事件提供一个构造方法
*
* @param title 事件标题
* @param description 事件描述
* @param eventLocation 事件地点
* @param start 事件开始时间
* @param end 事件结束时间 If is not a repeat event, this param is must need else null
* @param advanceTime 事件提醒时间[AdvanceTime]
* (If you don't need to remind the incoming parameters -2)
* @param rRule 事件重复规则 [RRuleConstant] `null` if dose not need
*/
constructor(
title: String?, description: String?, eventLocation: String?,
start: Long, end: Long, advanceTime: Int, rRule: String?
) {
this.title = title
this.description = description
this.eventLocation = eventLocation
this.start = start
this.end = end
this.advanceTime = advanceTime
this.rRule = rRule
}
fun getAdvanceTime(): Int {
return advanceTime
}
fun setAdvanceTime(advanceTime: Int) {
this.advanceTime = advanceTime
}
fun getId(): Long {
return id
}
fun setId(id: Long) {
this.id = id
}
fun getCalID(): Long {
return calID
}
fun setCalID(calID: Long) {
this.calID = calID
}
fun getTitle(): String? {
return title
}
fun setTitle(title: String?) {
this.title = title
}
fun getDescription(): String? {
return description
}
fun setDescription(description: String?) {
this.description = description
}
fun getEventLocation(): String? {
return eventLocation
}
fun setEventLocation(eventLocation: String?) {
this.eventLocation = eventLocation
}
fun getDisplayColor(): Int {
return displayColor
}
fun setDisplayColor(displayColor: Int) {
this.displayColor = displayColor
}
fun getStatus(): Int {
return status
}
fun setStatus(status: Int) {
this.status = status
}
fun getStart(): Long {
return start
}
fun setStart(start: Long) {
this.start = start
}
fun getEnd(): Long {
return end
}
fun setEnd(end: Long) {
this.end = end
}
fun getDuration(): String? {
return duration
}
fun setDuration(duration: String?) {
this.duration = duration
}
fun getEventTimeZone(): String? {
return eventTimeZone
}
fun setEventTimeZone(eventTimeZone: String?) {
this.eventTimeZone = eventTimeZone
}
fun getEventEndTimeZone(): String? {
return eventEndTimeZone
}
fun setEventEndTimeZone(eventEndTimeZone: String?) {
this.eventEndTimeZone = eventEndTimeZone
}
fun getAllDay(): Int {
return allDay
}
fun setAllDay(allDay: Int) {
this.allDay = allDay
}
fun getAccessLevel(): Int {
return accessLevel
}
fun setAccessLevel(accessLevel: Int) {
this.accessLevel = accessLevel
}
fun getAvailability(): Int {
return availability
}
fun setAvailability(availability: Int) {
this.availability = availability
}
fun getHasAlarm(): Int {
return hasAlarm
}
fun setHasAlarm(hasAlarm: Int) {
this.hasAlarm = hasAlarm
}
fun getRRule(): String? {
return rRule
}
fun setRRule(rRule: String?) {
this.rRule = rRule
}
fun getRDate(): String? {
return rDate
}
fun setRDate(rDate: String?) {
this.rDate = rDate
}
fun getHasAttendeeData(): Int {
return hasAttendeeData
}
fun setHasAttendeeData(hasAttendeeData: Int) {
this.hasAttendeeData = hasAttendeeData
}
fun getLastDate(): Int {
return lastDate
}
fun setLastDate(lastDate: Int) {
this.lastDate = lastDate
}
fun getOrganizer(): String? {
return organizer
}
fun setOrganizer(organizer: String?) {
this.organizer = organizer
}
fun getIsOrganizer(): String? {
return isOrganizer
}
fun setIsOrganizer(isOrganizer: String?) {
this.isOrganizer = isOrganizer
}
fun getReminders(): List<EventReminders?>? {
return reminders
}
fun setReminders(reminders: List<EventReminders?>?) {
this.reminders = reminders
}
override fun hashCode(): Int {
return (id * 37 + calID).toInt()
}
}
/**
* 事件提醒
*/
class EventReminders {
// ----------------------- 事件提醒属性 -----------------------
var reminderId: Long = 0
var reminderEventID: Long = 0
var reminderMinute = 0
var reminderMethod = 0
}
class Util {
object Util {
/**
* 获取日历事件结束日期
*
* @param time time in ms
*/
private fun getEndDate(time: Long): String {
val date = Date(time)
val format = SimpleDateFormat("yyyyMMdd", Locale.getDefault())
return format.format(date)
}
/**
* 获取最终日历事件重复规则
*
* @param time time in ms
* "T235959" [#51][com.kyle.calendarprovider.calendar.RRuleConstant]
*/
fun getFinalRRuleMode(time: Long): String? {
return getEndDate(time) + "T235959Z"
}
/**
* 格式化星期
*/
private fun formatWeek(week: Int): String? {
return when (week) {
0 -> "SU"
1 -> "MO"
2 -> "TU"
3 -> "WE"
4 -> "TH"
5 -> "FR"
6 -> "SA"
else -> null
}
}
/**
* 获取重复周
*
* @param time time in ms
*/
fun getWeekForDate(time: Long): String? {
val date = Date(time)
val calendar = Calendar.getInstance()
calendar.time = date
var week = calendar[Calendar.DAY_OF_WEEK] - 1
if (week < 0) {
week = 0
}
return formatWeek(week)
}
/**
* 获取指定时间段在一个月中的哪一天
*
* @param time time in ms
*/
fun getDayOfMonth(time: Long): Int {
val date = Date(time)
val calendar = Calendar.getInstance()
calendar.time = date
return calendar[Calendar.DAY_OF_MONTH]
}
/**
* check null
*/
fun checkContextNull(context: Context?) {
requireNotNull(context) { "context can not be null" }
}
}
}
// todo 加入测试
class CalendarTest {
object Test {
fun insert(context: Context) {
val calendarEvent = CalendarEvent(
"马上吃饭",
"吃好吃的",
"南信院二食堂",
System.currentTimeMillis(),
System.currentTimeMillis() + 60000,
0, null
)
// 添加事件
val result: Int = CalendarManager.Manager.addCalendarEvent(context, calendarEvent)
if (result == 0) {
Toast.makeText(context, "插入成功", Toast.LENGTH_SHORT).show()
} else if (result == -1) {
Toast.makeText(context, "插入失败", Toast.LENGTH_SHORT).show()
} else if (result == -2) {
Toast.makeText(context, "没有权限", Toast.LENGTH_SHORT).show()
}
}
fun delete(context: Context) {
// 删除事件
// 删除事件
val calID2: Long = CalendarManager.Manager.obtainCalendarAccountID(context)
val events2: List<CalendarEvent>? =
CalendarManager.Manager.queryAccountEvent(context, calID2)
if (null != events2) {
if (events2.isEmpty()) {
Toast.makeText(context, "没有事件可以删除", Toast.LENGTH_SHORT).show()
} else {
val eventID = events2[0].getId()
val result2: Int = CalendarManager.Manager.deleteCalendarEvent(context, eventID)
if (result2 == -2) {
Toast.makeText(context, "没有权限", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "删除成功", Toast.LENGTH_SHORT).show()
}
}
} else {
Toast.makeText(context, "查询失败", Toast.LENGTH_SHORT).show()
}
}
fun update(context: Context) {
// 更新事件
// 更新事件
val calID: Long = CalendarManager.Manager.obtainCalendarAccountID(context)
val events: List<CalendarEvent>? =
CalendarManager.Manager.queryAccountEvent(context, calID)
if (null != events) {
if (events.isEmpty()) {
Toast.makeText(context, "没有事件可以更新", Toast.LENGTH_SHORT).show()
} else {
val eventID = events[0].getId()
val result3: Int = CalendarManager.Manager.updateCalendarEventTitle(
context, eventID, "改吃晚饭的房间第三方监督司法"
)
if (result3 == 1) {
Toast.makeText(context, "更新成功", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "更新失败", Toast.LENGTH_SHORT).show()
}
}
} else {
Toast.makeText(context, "查询失败", Toast.LENGTH_SHORT).show()
}
}
fun query(context: Context) {
// 查询事件
// 查询事件
val calID4: Long = CalendarManager.Manager.obtainCalendarAccountID(context)
val events4: List<CalendarEvent>? =
CalendarManager.Manager.queryAccountEvent(context, calID4)
val stringBuilder4 = java.lang.StringBuilder()
if (null != events4) {
for (event in events4) {
stringBuilder4.append(events4.toString()).append("\n")
}
Toast.makeText(context, "查询成功", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "查询失败", Toast.LENGTH_SHORT).show()
}
}
fun edit(context: Context) {
// 启动系统日历进行编辑事件
CalendarManager.Manager.startCalendarForIntentToInsert(
context, System.currentTimeMillis(),
System.currentTimeMillis() + 60000, "哈", "哈哈哈哈", "蒂埃纳",
false
);
}
fun edit2(context: Context) {
// 启动系统日历进行编辑事件
if (CalendarManager.Manager.isEventAlreadyExist(
context, 1552986006309L,
155298606609L, "马上吃饭"
)
) {
Toast.makeText(context, "存在", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "不存在", Toast.LENGTH_SHORT).show();
}
}
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/calendar/CalenderActivity.kt
================================================
package com.yl.lib.privacysentry.calendar
import android.Manifest
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.yl.lib.privacysentry.R
import com.yl.lib.sentry.hook.util.PrivacyLog
class CalenderActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_calender)
findViewById<Button>(R.id.btn_add_calendar).setOnClickListener {
CalendarTest.Test.insert(this)
}
findViewById<Button>(R.id.btn_del_calendar).setOnClickListener {
CalendarTest.Test.delete(this)
}
findViewById<Button>(R.id.btn_query_calendar).setOnClickListener {
CalendarTest.Test.query(this)
}
findViewById<Button>(R.id.btn_edit_calendar).setOnClickListener {
CalendarTest.Test.edit(this)
}
findViewById<Button>(R.id.btn_edit2_calendar).setOnClickListener {
CalendarTest.Test.edit2(this)
}
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_CALENDAR
) !== PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this, arrayOf<String>(
Manifest.permission.WRITE_CALENDAR,
Manifest.permission.READ_CALENDAR
), 1000
)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 1000) {
PrivacyLog.i("requestPermissions CALENDAR success")
} else {
PrivacyLog.i("requestPermissions CALENDAR fail")
}
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/calendar/RRuleConstant.kt
================================================
package com.yl.lib.privacysentry.calendar
/**
* @author yulun
* @sinice 2022-01-13 19:57
*/
class RRuleConstant {
companion object {
/**
* 每天重复 - 永远
*/
val REPEAT_CYCLE_DAILY_FOREVER = "FREQ=DAILY;INTERVAL=1"
/**
* 每周某天重复
*/
val REPEAT_CYCLE_WEEKLY = "FREQ=WEEKLY;INTERVAL=1;WKST=SU;BYDAY="
/**
* 每月某天重复
*/
val REPEAT_CYCLE_MONTHLY = "FREQ=WEEKLY;INTERVAL=2;WKST=SU;BYMONTHDAY ="
/**
* 每周重复 - 周一
*/
val REPEAT_WEEKLY_BY_MO = "FREQ=WEEKLY;INTERVAL=1;WKST=MO;BYDAY=MO;UNTIL="
/**
* 每周重复 - 周二
*/
val REPEAT_WEEKLY_BY_TU = "FREQ=WEEKLY;INTERVAL=1;WKST=MO;BYDAY=TU;UNTIL="
/**
* 每周重复 - 周三
*/
val REPEAT_WEEKLY_BY_WE = "FREQ=WEEKLY;INTERVAL=1;WKST=MO;BYDAY=WE;UNTIL="
/**
* 每周重复 - 周四
*/
val REPEAT_WEEKLY_BY_TH = "FREQ=WEEKLY;INTERVAL=1;WKST=MO;BYDAY=TH;UNTIL="
/**
* 每周重复 - 周五
*/
val REPEAT_WEEKLY_BY_FR = "FREQ=WEEKLY;INTERVAL=1;WKST=MO;BYDAY=FR;UNTIL="
/**
* 每周重复 - 周六
*/
val REPEAT_WEEKLY_BY_SA = "FREQ=WEEKLY;INTERVAL=1;WKST=MO;BYDAY=SA;UNTIL="
/**
* 每周重复 - 周日
*/
val REPEAT_WEEKLY_BY_SU = "FREQ=WEEKLY;INTERVAL=1;WKST=MO;BYDAY=SU;UNTIL="
/**
* 每年第一天和最后一天 - 永远
*/
val REPEAT_YEARLY_FIRST_AND_LAST_FOREVER = "FREQ=YEARLY;BYYEARDAY=1,-1"
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/contact/ContactActivity.kt
================================================
package com.yl.lib.privacysentry.contact
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.yl.lib.privacysentry.R
import com.yl.lib.privacysentry.calendar.CalendarTest
import com.yl.lib.privacysentry.test.TestReflex
import com.yl.lib.sentry.hook.util.PrivacyLog
class ContactActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_contact)
findViewById<Button>(R.id.btn_get_all_contacts).setOnClickListener {
ContactManager.Manager.testGetAllContact(this)
}
findViewById<Button>(R.id.btn_add_contact1).setOnClickListener {
ContactManager.Manager.testInsert(this)
}
findViewById<Button>(R.id.btn_add_contact2).setOnClickListener {
ContactManager.Manager.testSave(this)
}
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_CONTACTS
) !== PackageManager.PERMISSION_GRANTED
) {
var permissions = arrayOf<String>(
Manifest.permission.WRITE_CONTACTS,
Manifest.permission.READ_CONTACTS,
Manifest.permission.READ_CALL_LOG
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
super.requestPermissions(
arrayOf<String>(
Manifest.permission.WRITE_CONTACTS,
Manifest.permission.READ_CONTACTS,
Manifest.permission.READ_CALL_LOG
), 1000
)
}
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 1000) {
PrivacyLog.i("requestPermissions CONTACTS success")
} else {
PrivacyLog.i("requestPermissions CONTACTS fail")
}
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/contact/ContactManager.kt
================================================
package com.yl.lib.privacysentry.contact
import android.Manifest
import android.content.*
import android.net.Uri
import android.provider.ContactsContract
import com.yl.lib.sentry.hook.util.PrivacyLog
import java.lang.StringBuilder
import android.provider.CallLog
import android.content.pm.PackageManager
import android.database.Cursor
import androidx.core.app.ActivityCompat
import java.text.SimpleDateFormat
import java.util.*
/**
* @author yulun
* @since 2022-01-17 14:42
*/
class ContactManager {
object Manager{
/**
* 获取通讯录中所有联系人的简单信息
* @throws Throwable
*/
fun testGetAllContact(context: Context) {
//获取联系人信息的Uri
val uri: Uri = ContactsContract.Contacts.CONTENT_URI
//获取ContentResolver
val contentResolver: ContentResolver = context.getContentResolver()
//查询数据,返回Cursor
val cursor = contentResolver.query(uri, null, null, null, null)
while (cursor!!.moveToNext()) {
val sb = StringBuilder()
//获取联系人的ID
val contactId =
cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID))
//获取联系人的姓名
val name =
cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))
//构造联系人信息
sb.append("contactId=").append(contactId).append(",Name=").append(name)
//查询电话类型的数据操作
val phones = contentResolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + contactId,
null, null
)
while (phones!!.moveToNext()) {
val phoneNumber = phones.getString(
phones.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.NUMBER
)
)
//添加Phone的信息
sb.append(",Phone=").append(phoneNumber)
}
phones.close()
//查询Email类型的数据操作
val emails = contentResolver.query(
ContactsContract.CommonDataKinds.Email.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = " + contactId,
null, null
)
while (emails!!.moveToNext()) {
val emailAddress = emails.getString(
emails.getColumnIndex(
ContactsContract.CommonDataKinds.Email.DATA
)
)
//添加Email的信息
sb.append(",Email=").append(emailAddress)
}
emails.close()
PrivacyLog.i("读取联系人 ${sb.toString()}")
}
cursor.close()
}
/**添加联系人的第一种方法:
* 首先向RawContacts.CONTENT_URI执行一个空值插入,目的是获取系统返回的rawContactId
* 这时后面插入data表的依据,只有执行空值插入,才能使插入的联系人在通讯录里面可见
*/
fun testInsert(context: Context) {
val values = ContentValues()
//首先向RawContacts.CONTENT_URI执行一个空值插入,目的是获取系统返回的rawContactId
val rawContactUri: Uri =
context.getContentResolver().insert(ContactsContract.RawContacts.CONTENT_URI, values)!!
//获取id
val rawContactId = ContentUris.parseId(rawContactUri)
//往data表入姓名数据
values.clear()
values.put(ContactsContract.Contacts.Data.RAW_CONTACT_ID, rawContactId) //添加id
values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) //添加内容类型(MIMETYPE)
values.put(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, "凯风自南") //添加名字,添加到first name位置
context.getContentResolver().insert(ContactsContract.Data.CONTENT_URI, values)
//往data表入电话数据
values.clear()
values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId)
values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
values.put(ContactsContract.CommonDataKinds.Phone.NUMBER, "13921009789")
values.put(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE)
context.getContentResolver().insert(ContactsContract.Data.CONTENT_URI, values)
//往data表入Email数据
values.clear()
values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId)
values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
values.put(ContactsContract.CommonDataKinds.Email.DATA, "kesenhoo@gmail.com")
values.put(ContactsContract.CommonDataKinds.Email.TYPE, ContactsContract.CommonDataKinds.Email.TYPE_WORK)
context.getContentResolver().insert(ContactsContract.Data.CONTENT_URI, values)
}
/**添加联系人的第二种方法:
* 批量添加联系人
* @throws Throwable
*/
fun testSave(context: Context) {
//官方文档位置:reference\android\provider\ContactsContract.RawContacts.html
//建立一个ArrayList存放批量的参数
val ops = ArrayList<ContentProviderOperation>()
val rawContactInsertIndex: Int = ops.size
ops.add(
ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
.build()
)
//官方文档位置:reference\android\provider\ContactsContract.Data.html
//withValueBackReference后退引用前面联系人的id
ops.add(
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, "小明")
.build()
)
ops.add(
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(
ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, "13671323809")
.withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE)
.withValue(ContactsContract.CommonDataKinds.Phone.LABEL, "手机号")
.build()
)
ops.add(
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Email.DATA, "kesen@gmail.com")
.withValue(ContactsContract.CommonDataKinds.Email.TYPE, ContactsContract.CommonDataKinds.Email.TYPE_WORK)
.build()
)
val results: Array<ContentProviderResult> = context.getContentResolver()
.applyBatch(ContactsContract.AUTHORITY, ops)
for (result in results) {
PrivacyLog.i("增加联系人 sb.toString()")
}
}
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/location/LocationTestActivity.java
================================================
package com.yl.lib.privacysentry.location;
import android.Manifest;
import android.content.pm.PackageManager;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import com.yl.lib.privacysentry.R;
import java.io.IOException;
import java.util.List;
/**
* @author yulun
* @since 2022-06-13 15:05
*/
public class LocationTestActivity extends AppCompatActivity {
private Handler mHandler = new Handler(Looper.myLooper());
private TextView textView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_location_test);
textView = findViewById(R.id.location_text);
// 判断当前是否拥有使用GPS的权限
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// 申请权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(
new String[]{
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION,
},
100);
}
} else {
}
findViewById(R.id.location).setOnClickListener(v -> {
getLocation();
});
findViewById(R.id.last_location).setOnClickListener(v -> {
LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
Location l = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
printLocation(l, LocationManager.NETWORK_PROVIDER + "last");
});
}
private void doNextLocation() {
textView.setText("location is null ,doNextLocation");
mHandler.postDelayed(locationRunnable, 500);
}
private void getLocation() {
// 获取当前位置管理器
LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
// 启动位置请求
// LocationManager.GPS_PROVIDER GPS定位
// LocationManager.NETWORK_PROVIDER 网络定位
// LocationManager.PASSIVE_PROVIDER 被动接受定位信息
try {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1000, new LocationListener() {
@Override
public void onLocationChanged(@NonNull Location location) {
printLocation(location, LocationManager.GPS_PROVIDER);
}
});
mHandler.postDelayed(locationRunnable, 200);
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1000, 1000, new LocationListener() {
@Override
public void onLocationChanged(@NonNull Location location) {
printLocation(location, LocationManager.NETWORK_PROVIDER);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
private void printLocation(Location location, String provider) {
if (location == null) {
return;
}
double lat = location.getLatitude();
double lng = location.getLongitude();
List<Address> list = null;
Geocoder gd = new Geocoder(this);
try {
list = gd.getFromLocation(lat, lng, 2);
} catch (IOException e) {
e.printStackTrace();
}
// 城市转换
String cityHome = "provider is " + provider + "x y is" + location.getLatitude() + "__" + location.getLongitude();
if (list != null && list.size() > 0) {
for (int i = 0; i < list.size(); i++) {
Address address = list.get(i);
cityHome = cityHome + "__" + address.getLocality();
}
}
textView.setText(cityHome);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
private Runnable locationRunnable = new Runnable() {
@Override
public void run() {
if (ActivityCompat.checkSelfPermission(LocationTestActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(LocationTestActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
Location lGps = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (lGps != null) {
printLocation(lGps, LocationManager.GPS_PROVIDER);
}
Location lNet = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
if (lNet != null) {
printLocation(lNet, LocationManager.NETWORK_PROVIDER);
}
if (lGps == null && lNet == null) {
doNextLocation();
}
}
};
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/process/MultiProcessB.kt
================================================
package com.yl.lib.privacysentry.process
import android.app.Application
import android.app.IntentService
import android.content.Intent
import android.os.Build
import com.yl.lib.privacysentry.test.PrivacyMethod
import com.yl.lib.sentry.hook.util.MainProcessUtil
import com.yl.lib.sentry.hook.util.PrivacyLog
/**
* @author yulun
* @sinice 2021-07-06 15:47
*/
class MultiProcessB : IntentService("MultiProcessB") {
override fun onHandleIntent(intent: Intent?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
PrivacyLog.i("mutliProcss " + Application.getProcessName())
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PrivacyMethod.PrivacyMethod.getIMEI(context = this)
}
PrivacyMethod.PrivacyMethod.isInstalled(
context = this,
pkgName = MainProcessUtil.MainProcessChecker.getProcessName(this)
)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return super.onStartCommand(intent, flags, startId)
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/process/MultiProcessC.kt
================================================
package com.yl.lib.privacysentry.process
import android.app.Application
import android.app.IntentService
import android.app.job.JobParameters
import android.app.job.JobService
import android.content.Intent
import android.os.Build
import androidx.annotation.RequiresApi
import com.yl.lib.privacysentry.test.PrivacyMethod
import com.yl.lib.sentry.hook.util.MainProcessUtil
import com.yl.lib.sentry.hook.util.PrivacyLog
/**
* @author yulun
* @sinice 2021-07-06 15:47
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class MultiProcessC : JobService() {
// override fun onHandleIntent(intent: Intent?) {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// PrivacyLog.i("mutliProcss " + Application.getProcessName())
// }
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// PrivacyMethod.PrivacyMethod.getIMEI(context = this)
// }
// PrivacyMethod.PrivacyMethod.isInstalled(
// context = this,
// pkgName = MainProcessUtil.MainProcessChecker.getProcessName(this)
// )
// }
override fun onStartJob(params: JobParameters?): Boolean {
return true
}
override fun onStopJob(params: JobParameters?): Boolean {
return true
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return super.onStartCommand(intent, flags, startId)
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/telephony/TelephonyTestActivity.kt
================================================
package com.yl.lib.privacysentry.telephony
import android.Manifest
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import com.yl.lib.privacysentry.R
import com.yl.lib.privacysentry.test.PrivacyMethod
import com.yl.lib.sentry.hook.util.PrivacyLog
class TelephonyTestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_telephony_test)
findViewById<Button>(R.id.btn_deviceId).setOnClickListener {
var deviceId = PrivacyMethod.PrivacyMethod.getDeviceId(this)
PrivacyLog.i("deviceId is $deviceId")
var deviceId1 = PrivacyMethod.PrivacyMethod.getDeviceId1(this)
PrivacyLog.i("deviceId is $deviceId1")
PrivacyLog.i("deviceId is ${PrivacyMethod.PrivacyMethod.getMeid(this)}")
}
findViewById<Button>(R.id.btn_iccid).setOnClickListener {
var iccid = PrivacyMethod.PrivacyMethod.getICCID(this)
PrivacyLog.i("iccid is $iccid")
}
findViewById<Button>(R.id.btn_imei).setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
var imei = PrivacyMethod.PrivacyMethod.getIMEI(this)
PrivacyLog.i("imei is $imei")
}
}
findViewById<Button>(R.id.btn_sim_operator).setOnClickListener {
var simOperator = PrivacyMethod.PrivacyMethod.getSimOperator(this)
PrivacyLog.i("simOperator is $simOperator")
var networkOperator = PrivacyMethod.PrivacyMethod.getNetworkOperator(this)
PrivacyLog.i("networkOperator is $networkOperator")
}
findViewById<Button>(R.id.btn_sim_state).setOnClickListener {
var simOperator = PrivacyMethod.PrivacyMethod.getSimState(this)
PrivacyLog.i("simOperator is $simOperator")
}
//Android Q开始,READ_PHONE_STATE 不再有用,不用全局弹框
var permissions = arrayOf(
Manifest.permission.READ_PHONE_STATE
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(permissions, 1000)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 1000) {
PrivacyLog.i("requestPermissions ${permissions[0]} success")
} else {
PrivacyLog.i("requestPermissions ${permissions[0]} fail")
}
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/test/PrivacyMethod.kt
================================================
package com.yl.lib.privacysentry.test
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.app.ActivityManager
import android.app.Application
import android.bluetooth.BluetoothAdapter
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventCallback
import android.hardware.SensorManager
import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
import android.os.Build
import android.os.Environment
import android.provider.Settings
import android.telephony.TelephonyManager
import android.telephony.TelephonyManager.SIM_STATE_UNKNOWN
import android.text.TextUtils
import androidx.annotation.NonNull
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat.getSystemService
import com.yl.lib.privacysentry.MainActivity
import com.yl.lib.sentry.hook.util.PrivacyLog
import java.io.File
import java.net.NetworkInterface
import java.net.SocketException
import java.util.*
/**
* @author yulun
* @sinice 2021-11-16 15:08
*/
class PrivacyMethod {
object PrivacyMethod {
/**TMS START================================**/
/**
* test for device id
*/
fun getDeviceId(context: Context?): String {
if (context == null) {
return ""
}
// if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
// return ""
// }
var imei = ""
// 在某些平板上可能会抛出异常
try {
val mTelephonyMgr = context
.getSystemService(AppCompatActivity.TELEPHONY_SERVICE) as TelephonyManager
imei = mTelephonyMgr.getDeviceId()
} catch (e: Throwable) {
e.printStackTrace()
}
return imei ?: ""
}
fun getDeviceId1(context: Context?): String {
if (context == null) {
return ""
}
// if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
// return ""
// }
var imei = ""
// 在某些平板上可能会抛出异常
try {
if (checkPermissions(
context,
Manifest.permission.READ_PHONE_STATE
)
) {
val mTelephonyMgr = context
.getSystemService(AppCompatActivity.TELEPHONY_SERVICE) as TelephonyManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
imei = mTelephonyMgr.getDeviceId(1)
}
}
} catch (e: Throwable) {
// e.printStackTrace()
}
return imei ?: ""
}
fun getMeid(context: Context?): String {
if (context == null) {
return ""
}
// if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
// return ""
// }
var imei = ""
// 在某些平板上可能会抛出异常
try {
val mTelephonyMgr = context
.getSystemService(AppCompatActivity.TELEPHONY_SERVICE) as TelephonyManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
imei = mTelephonyMgr.getMeid()
}
} catch (e: Throwable) {
e.printStackTrace()
}
return imei ?: ""
}
/**
* imei
*/
@RequiresApi(Build.VERSION_CODES.O)
fun getIMEI(context: Context?): String {
if (context == null) {
return ""
}
// if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
// return ""
// }
var imei = ""
// 在某些平板上可能会抛出异常
try {
if (checkPermissions(
context,
Manifest.permission.READ_PHONE_STATE
)
) {
val mTelephonyMgr = context
.getSystemService(AppCompatActivity.TELEPHONY_SERVICE) as TelephonyManager
imei = mTelephonyMgr.imei
}
} catch (e: Throwable) {
e.printStackTrace()
}
return imei ?: ""
}
/**
* 获得imsi
* @return
*/
@SuppressLint("HardwareIds")
fun getIMSI(context: Context?): String? {
if (context == null) {
return ""
}
// if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
// return ""
// }
var imsi = ""
try {
if (checkPermissions(
context,
Manifest.permission.READ_PHONE_STATE
)
) {
val mTelephonyMgr = context
.getSystemService(AppCompatActivity.TELEPHONY_SERVICE) as TelephonyManager
?: return ""
imsi =
mTelephonyMgr.subscriberId
}
} catch (e: Throwable) {
e.printStackTrace()
}
return imsi ?: ""
}
fun getIp(context: Context) {
var ip1 = TestInJava.getHostIp()
PrivacyLog.i("ip1 is $ip1")
Thread {
var ip2 = TestInJava.getIpAddress(context)
PrivacyLog.i(" ip2 is $ip2")
}.start()
}
// 获取sim卡操作码
fun getSimOperator(context: Context?): String? {
if (context == null) {
return ""
}
var simOperator = ""
try {
val mTelephonyMgr = context
.getSystemService(AppCompatActivity.TELEPHONY_SERVICE) as TelephonyManager
simOperator =
mTelephonyMgr.simOperator
} catch (e: Throwable) {
e.printStackTrace()
}
return simOperator
}
fun getSimState(context: Context?): Int {
if (context == null) {
return SIM_STATE_UNKNOWN
}
var simState = SIM_STATE_UNKNOWN
// try {
val mTelephonyMgr = context
.getSystemService(AppCompatActivity.TELEPHONY_SERVICE) as TelephonyManager
simState =
mTelephonyMgr.simState
// } catch (e: Throwable) {
// e.printStackTrace()
// }
return simState
}
fun getNetworkOperator(context: Context?): String? {
if (context == null) {
return ""
}
var networkOperator = ""
try {
val mTelephonyMgr = context
.getSystemService(AppCompatActivity.TELEPHONY_SERVICE) as TelephonyManager
networkOperator =
mTelephonyMgr.networkOperator
} catch (e: Throwable) {
e.printStackTrace()
}
return networkOperator
}
/**
* 获取sim卡唯一标示
*
* @param context
* @return
*/
@SuppressLint("HardwareIds")
fun getICCID(context: Context?): String? {
if (context == null) {
return ""
}
var iccid = ""
try {
val mTelephonyMgr = context
.getSystemService(AppCompatActivity.TELEPHONY_SERVICE) as TelephonyManager
?: return ""
iccid =
mTelephonyMgr.simSerialNumber
} catch (e: Throwable) {
e.printStackTrace()
}
return iccid ?: ""
}
/**TMS END================================**/
val MAC_DEFAULT = "00:00:00:00:00:00"
val MAC_SYSTEM = "02:00:00:00:00:00"
/**
* wifiInfo.macAddress
* networkInterface.hardwareAddress
* BluetoothAdapter.address
*/
fun getMacRaw(context: Context?): String? {
var mac: String? = MAC_DEFAULT
if (context == null) {
return mac
}
// 蓝牙
try {
BluetoothAdapter.getDefaultAdapter().address
} catch (e: Exception) {
e.printStackTrace()
}
try {
val wifiManager =
context.applicationContext.getSystemService(AppCompatActivity.WIFI_SERVICE) as WifiManager
wifiManager.isWifiEnabled
if (wifiManager != null) {
val wifiInfo = wifiManager.connectionInfo
if (wifiInfo != null) {
val result = wifiInfo.macAddress
if (result != null && result.length > 0) {
mac = result
mac = mac.replace("\u0000".toRegex(), "")
mac = mac.replace("null".toRegex(), "")
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
//7.0以上获取不到,获得的都是02:00:00:00:00:00
getMacV2()
return if (TextUtils.isEmpty(mac)) MAC_DEFAULT else mac
}
fun getMacV2(): String? {
try {
val networkInterfaces: Enumeration<*> = NetworkInterface.getNetworkInterfaces()
while (networkInterfaces.hasMoreElements()) {
val networkInterface = networkInterfaces.nextElement() as NetworkInterface
val hardwareAddress = networkInterface.hardwareAddress
if (hardwareAddress != null && hardwareAddress.size != 0) {
val stringBuffer = java.lang.StringBuilder()
val var5 = hardwareAddress.size
for (var6 in 0 until var5) {
val hardwareAddres = hardwareAddress[var6]
stringBuffer.append(String.format("%02X:", hardwareAddres))
}
if (stringBuffer.length > 0) {
stringBuffer.deleteCharAt(stringBuffer.length - 1)
}
return stringBuffer.toString()
}
}
} catch (e: SocketException) {
e.printStackTrace()
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
return ""
}
private fun checkPermissions(context: Context, permission: String): Boolean {
// val localPackageManager = context.packageManager ?: return false
// return localPackageManager.checkPermission(
// permission,
// context.packageName
// ) == PackageManager.PERMISSION_GRANTED
return true
}
/**PMS START================================**/
/**
* 判断指定app应用是否已安装
*
* @param context 上下文
* @param pkgName app 包名
* @return 指定app应用是否已安装
*/
fun isInstalled(@NonNull context: Context, pkgName: String): Boolean {
if (TextUtils.isEmpty(pkgName)) {
return false
}
// 获取所有已安装程序的包信息
val packages = getInstalledPackages(context)
for (i in packages.indices) {
// 循环判断是否存在指定包名
if (packages[i].packageName == pkgName) {
return true
}
}
return false
}
fun isInstalled2(
context: Application,
@NonNull activity: Activity,
pkgName: String
): Boolean {
if (TextUtils.isEmpty(pkgName)) {
return false
}
// 获取所有已安装程序的包信息
val packageManager = context.packageManager
return (packageManager.queryIntentActivities(
Intent(
activity,
MainActivity::javaClass.javaClass
), 0
)).isNotEmpty()
}
fun isInstalled3(
context: Application,
pkgName: String
): Boolean {
if (TextUtils.isEmpty(pkgName)) {
return false
}
// 获取所有已安装程序的包信息
val packageManager = context.packageManager
try {
var info = packageManager.getPackageInfo(
pkgName, 0
)
return info != null
} catch (e: Exception) {
e.printStackTrace()
}
return false
}
fun queryActivityInfo(
context: Application,
@NonNull activity: Activity
): Boolean {
// 获取所有已安装程序的包信息
val packageManager = context.packageManager
return (packageManager.queryIntentActivityOptions(
null, null,
Intent(
activity,
MainActivity::javaClass.javaClass
), 0
)).isNotEmpty()
}
/**
* 获取手机中所有安装的app应用
*
* @param context 上下文
* @return 所有安装的app应用信息
*/
private fun getInstalledPackages(@NonNull context: Context): List<PackageInfo> {
val packageManager = context.packageManager
return packageManager.getInstalledPackages(0)
}
/**PMS END================================**/
/**CMS START================================**/
fun testHookCms(@NonNull context: Context) {
//获取剪切板服务
val cm: ClipboardManager? =
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?
cm?.hasPrimaryClip()
//设置剪切板内容
cm?.setPrimaryClip(ClipData.newPlainText("data", "yl_vd"))
val cd: ClipData? = cm?.primaryClip
cm?.text = ("yl_vd123")
//获取剪切板数据对象
cm?.text
cm?.primaryClipDescription
val clipStr = cd?.getItemAt(0)?.text.toString()
// PrivacyLog.i("testHookCms cms data is :$clipStr")
}
/**CMS END================================**/
fun testRunningProcess(@NonNull context: Context) {
val manager = context
.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val runningAppProcesses = manager
.runningAppProcesses
}
fun testRunningTask(@NonNull context: Context) {
val manager = context
.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val runningAppProcesses = manager
.getRunningTasks(100)
}
fun getAndroidId(context: Context): String? {
return "" + Settings.Secure.getString(context.contentResolver, "android_id")
}
//读取 Android SN(Serial)
fun getSerial(): String {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
android.os.Build.getSerial()
} catch (e: Exception) {
e.printStackTrace()
return ""
}
} else {
android.os.Build.SERIAL
}
}
fun testSensor(context: Context) {
var sensorManager: SensorManager? = null
var callback: SensorEventCallback? = null
// 摇一摇注册
// 摇一摇注册
sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
var sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
// 获得重力传感器
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
// 注册
var lastUpdateTime: Long = 0
var lastX = 0f
var lastY = 0f
var lastZ = 0f
if (sensor != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
callback = object : SensorEventCallback() {
override fun onSensorChanged(event: SensorEvent) {
// 现在检测时间
val currentUpdateTime = System.currentTimeMillis()
// 两次检测的时间间隔
val timeInterval: Long = currentUpdateTime - lastUpdateTime
// 判断是否达到了检测时间间隔
if (timeInterval < 70) return
// 现在的时间变成last时间
lastUpdateTime = currentUpdateTime
// 获得x,y,z坐标
val x = event.values[0]
val y = event.values[1]
val z = event.values[2]
// 获得x,y,z的变化值
val deltaX: Float = x - lastX
val deltaY: Float = y - lastY
val deltaZ: Float = z - lastZ
// 将现在的坐标变成last坐标
lastX = x
lastY = y
lastZ = z
// PrivacyLog.i(" 摇了摇 $deltaX,$deltaY,$deltaZ")
val speed =
Math.sqrt((deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ).toDouble()) / timeInterval * 10000
// 达到速度阀值,发出提示
if (speed >= 3000) {
PrivacyLog.i(" 摇了摇 😂😂")
sensorManager.unregisterListener(this)
}
}
}
sensorManager.registerListener(
callback,
sensor,
SensorManager.SENSOR_DELAY_GAME
)
}
}
}
fun testGetSensorList(context: Context) {
var sensorManager: SensorManager? =
context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
// 获取传感器列表
var sensor: List<Sensor>? = sensorManager?.getSensorList(Sensor.TYPE_ACCELEROMETER)
PrivacyLog.i("sensor size is :${sensor?.size}")
}
/**
* 返回SD卡根路径
*
* @return
*/
fun getSdcardRoot(context: Context): String? {
var path: String? = null
if (isSdcardReady()
&& hasExternalStoragePermission(context)
) {
val sdDir = Environment.getExternalStorageDirectory()
path = sdDir.absolutePath
}
var newPath = getPath(context)
return path
}
fun isSdcardReady(): Boolean {
return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
}
fun getPath(context: Context): String? {
var dir: File? = null
val state = Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
dir = if (state) {
if (Build.VERSION.SDK_INT >= 29) {
//Android10之后
context.getExternalFilesDir(null)
} else {
Environment.getExternalStorageDirectory()
}
} else {
Environment.getRootDirectory()
}
return dir.toString()
}
/**
* 是否有写扩展存储的权限
*
* @param context
* @return
*/
fun hasExternalStoragePermission(context: Context): Boolean {
val perm =
context.checkCallingOrSelfPermission("android.permission.WRITE_EXTERNAL_STORAGE")
return perm == PackageManager.PERMISSION_GRANTED
}
fun getScanResults(context: Context){
val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager?
val scanResults: List<ScanResult> = wifiManager!!.scanResults
for (scanResult in scanResults) {
val ssid: String = scanResult.SSID // Wi-Fi 名称
val bssid: String = scanResult.BSSID // Wi-Fi MAC 地址
val level: Int = scanResult.level // 信号强度
val frequency: Int = scanResult.frequency // 频率
val capabilities: String = scanResult.capabilities // 加密类型
// 处理扫描结果
PrivacyLog.i("getScanResults ssid is :$ssid,bssid is :$bssid,level is :$level,frequency is :$frequency,capabilities is :$capabilities")
}
}
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/test/PrivacyProxyCallJava.java
================================================
package com.yl.lib.privacysentry.test;
import android.app.ActivityManager;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import com.yl.lib.privacy_annotation.MethodInvokeOpcode;
import com.yl.lib.privacy_annotation.PrivacyClassProxy;
import com.yl.lib.privacy_annotation.PrivacyMethodProxy;
import com.yl.lib.sentry.hook.PrivacySentry;
import com.yl.lib.sentry.hook.printer.BasePrinter;
import com.yl.lib.sentry.hook.util.PrivacyProxyUtil;
import com.yl.lib.sentry.hook.util.PrivacyUtil;
import java.util.ArrayList;
import java.util.List;
import kotlin.jvm.JvmStatic;
/**
* @author yulun
* @sinice 2022-01-05 19:31
*/
@PrivacyClassProxy
public class PrivacyProxyCallJava {
public static List<ActivityManager.RunningTaskInfo> getRunningTasks(
ActivityManager manager,
int maxNum
) {
doFilePrinter("getRunningTasks", "获取当前运行中的任务", "");
return manager.getRunningTasks(maxNum);
}
@PrivacyMethodProxy(
originalClass = SharedPreferences.Editor.class,
originalMethod = "putString",
originalOpcode = MethodInvokeOpcode.INVOKEINTERFACE
)
public static SharedPreferences.Editor putString(SharedPreferences.Editor editor, String var1,String var2) {
return editor.putString(var1, var2);
}
private static void doFilePrinter(
String funName,
String methodDocumentDesc,
String args
) {
for (BasePrinter p :
PrivacySentry.Privacy.INSTANCE.getBuilder().getPrinterList()) {
p.filePrint(
funName,
methodDocumentDesc + (args.isEmpty() ? "" : "--参数: " + args),
PrivacyUtil.Util.INSTANCE.getStackTrace()
);
}
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/test/PrivacyProxySelfTest.kt
================================================
package com.yl.lib.privacysentry.test
import android.app.ActivityManager
import android.content.SharedPreferences
import androidx.annotation.Keep
import com.yl.lib.privacy_annotation.MethodInvokeOpcode
import com.yl.lib.privacy_annotation.PrivacyClassProxy
import com.yl.lib.privacy_annotation.PrivacyMethodProxy
import com.yl.lib.sentry.hook.util.PrivacyLog
import java.net.HttpURLConnection
/**
* @author yulun
* @since 2022-06-15 20:01
*/
@Keep
class PrivacyProxySelfTest {
// kotlin里实际解析的是这个PrivacyProxyCall$Proxy 内部类
@PrivacyClassProxy
@Keep
object Proxy {
// 这个方法的注册放在了PrivacyProxyCall2中,提供了一个java注册的例子
@PrivacyMethodProxy(
originalClass = ActivityManager::class,
originalMethod = "getRunningTasks",
originalOpcode = MethodInvokeOpcode.INVOKEVIRTUAL
)
@JvmStatic
fun getRunningTasks123(
manager: ActivityManager,
maxNum: Int
): List<ActivityManager.RunningTaskInfo?>? {
return manager.getRunningTasks(maxNum)
}
@PrivacyMethodProxy(
originalClass = HttpURLConnection::class,
originalMethod = "connect",
originalOpcode = MethodInvokeOpcode.INVOKEVIRTUAL
)
@JvmStatic
fun connect(httpURLConnection: HttpURLConnection) {
PrivacyLog.i("HttpURLConnection connect")
httpURLConnection.connect()
}
@PrivacyMethodProxy(
originalClass = SharedPreferences.Editor::class,
originalMethod = "apply",
originalOpcode = MethodInvokeOpcode.INVOKEINTERFACE
)
@JvmStatic
fun apply(
editor: SharedPreferences.Editor
): Unit {
editor.apply()
}
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/test/PrivacyTestMacAddress.java
================================================
package com.yl.lib.privacysentry.test;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
/**
* @author yulun
* @since 2022-11-18 10:03
*/
public class PrivacyTestMacAddress {
public static void getMacAddress() {
FileInputStream fis_name = null;
FileInputStream fis = null;
String mac = "";
//interfaceName可以直接填写 eth0
String path = "sys/class/net/eth0/address";
try {
fis_name = new FileInputStream(path);
byte[] buffer_name = new byte[1024 * 8];
int byteCount_name = fis_name.read(buffer_name);
if (byteCount_name > 0) {
mac = new String(buffer_name, 0, byteCount_name, "utf-8");
}
if (mac.length() == 0) {
path = "sys/class/net/eth0/wlan0";
fis = new FileInputStream(path);
byte[] buffer = new byte[1024 * 8];
int byteCount = fis.read(buffer);
if (byteCount > 0) {
mac = new String(buffer, 0, byteCount, "utf-8");
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fis_name != null) {
try {
fis_name.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void testNewFile() {
String path = "sys/class/net/eth0/address";
new File(path);
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/test/TestBCSeri.java
================================================
package com.yl.lib.privacysentry.test;
import android.os.Build;
/**
* @author yulun
* @since 2022-02-25 11:37
*/
public class TestBCSeri {
public TestBCSeri() {
}
public void testAAAA() {
String seri = Build.SERIAL;
}
public boolean test1() {
return false;
}
public Boolean test2() {
return false;
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/test/TestFragmentActivity.kt
================================================
package com.yl.lib.privacysentry.test
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.yl.lib.privacysentry.R
import com.yl.lib.privacysentry.test.ui.main.TestPermissionFragment
class TestFragmentActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test_frament)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, TestPermissionFragment.newInstance())
.commitNow()
}
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/test/TestInJava.java
================================================
package com.yl.lib.privacysentry.test;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.text.format.Formatter;
import androidx.annotation.RequiresApi;
import com.yl.lib.privacy_annotation.MethodInvokeOpcode;
import com.yl.lib.privacy_annotation.PrivacyMethodProxy;
import com.yl.lib.sentry.hook.PrivacySentry;
import com.yl.lib.sentry.hook.PrivacySentryBuilder;
import com.yl.lib.sentry.hook.util.PrivacyLog;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.URL;
import java.net.URLConnection;
import java.util.Enumeration;
/**
* @author yulun
* @sinice 2021-12-26 13:30
*/
public class TestInJava {
// 测试hook http url connection
// 思考: 如何解决还没有暴露的合规问题?比如某天规则增加了?线上如何做?如何减少调整?你有没有比较好的解决方案?
// 由于是基于编译期,那是否可以通过 替换新的产物来解决,通过patch的方式,因为本质上是替换方法的调用
// 动态替换拦不了的
public static void testHttpUrlConnection() {
URL url = null;
try {
url = new URL("https://www.baidu.com");
URLConnection rulConnection = url.openConnection();// 此处的urlConnection对象实际上是根据URL的
HttpURLConnection httpUrlConnection = (HttpURLConnection) rulConnection;
// 设定请求的方法为"POST",默认是GET
httpUrlConnection.setRequestMethod("POST");
// 设置是否向httpUrlConnection输出,因为这个是post请求,参数要放在
// http正文内,因此需要设为true, 默认情况下是false;
httpUrlConnection.setDoOutput(true);
// 设置是否从httpUrlConnection读入,默认情况下是true;
httpUrlConnection.setDoInput(true);
// Post 请求不能使用缓存
httpUrlConnection.setUseCaches(false);
// 设定传送的内容类型是可序列化的java对象
// (如果不设此项,在传送序列化对象时,当WEB服务默认的不是这种类型时可能抛java.io.EOFException)
httpUrlConnection.setRequestProperty("Content-type", "application/x-java-serialized-object");
// 连接,从上述url.openConnection()至此的配置必须要在connect之前完成,
httpUrlConnection.connect();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void testInitJava() {
// 完整版配置
PrivacySentryBuilder builder = new PrivacySentryBuilder()
// 自定义文件结果的输出名
.configResultFileName("demo_test")
//自定义检测时间,也支持主动停止检测 PrivacySentry.Privacy.stopWatch()
.configWatchTime(10 * 60 * 1000);
// 添加默认结果输出,包含log输出和文件输出
PrivacySentry.Privacy.INSTANCE.init(null, builder);
}
// 获取有限网IP
public static String getHostIp() {
try {
for (Enumeration<NetworkInterface> en = NetworkInterface
.getNetworkInterfaces(); en.hasMoreElements(); ) {
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = intf
.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress()
&& inetAddress instanceof Inet4Address) {
return inetAddress.getHostAddress();
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
return "0.0.0.0";
}
@SuppressLint("MissingPermission")
public static String getIpAddress(Context context) {
if (context == null) {
return "";
}
ConnectivityManager conManager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
try {
NetworkInfo info = conManager.getActiveNetworkInfo();
if (info != null && info.isConnected()) {
// 3/4g网络
if (info.getType() == ConnectivityManager.TYPE_MOBILE) {
return getHostIp();
} else if (info.getType() == ConnectivityManager.TYPE_WIFI) {
getIPByWifiInfo(context);
return getOutNetIP(); // 外网地址
} else if (info.getType() == ConnectivityManager.TYPE_ETHERNET) {
// 以太网有限网络
return getHostIp();
}
}
} catch (Exception e) {
return "";
}
return "";
}
/**
* 获取外网ip地址(非本地局域网地址)的方法
*/
public static String getOutNetIP() {
String ipAddress = "";
try {
String address = "https://ip.taobao.com/service/getIpInfo2.php?ip=myip";
URL url = new URL(address);
HttpURLConnection connection = (HttpURLConnection) url
.openConnection();
connection.setUseCaches(false);
connection.setRequestMethod("GET");
connection.setRequestProperty("user-agent",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.7 Safari/537.36"); //设置浏览器ua 保证不出现503
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
InputStream in = connection.getInputStream();
// 将流转化为字符串
BufferedReader reader = new BufferedReader(
new InputStreamReader(in));
String tmpString;
StringBuilder retJSON = new StringBuilder();
while ((tmpString = reader.readLine()) != null) {
retJSON.append(tmpString + "\n");
}
JSONObject jsonObject = new JSONObject(retJSON.toString());
String code = jsonObject.getString("code");
PrivacyLog.Log.e("提示:" + retJSON.toString());
if (code.equals("0")) {
JSONObject data = jsonObject.getJSONObject("data");
ipAddress = data.getString("ip")/* + "(" + data.getString("country")
+ data.getString("area") + "区"
+ data.getString("region") + data.getString("city")
+ data.getString("isp") + ")"*/;
PrivacyLog.Log.e("您的IP地址是:" + ipAddress);
} else {
PrivacyLog.Log.e("IP接口异常,无法获取IP地址!");
}
} else {
PrivacyLog.Log.e("网络连接异常,无法获取IP地址!");
}
} catch (Exception e) {
PrivacyLog.Log.e("获取IP地址时出现异常,异常信息是:" + e.toString());
}
return ipAddress;
}
public static void getIPByWifiInfo(Context context){
WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = wifiMgr.getConnectionInfo();
int ipAddress = wifiInfo.getIpAddress();
String ip = Formatter.formatIpAddress(ipAddress);
}
public static void testReflexClipManager() {
try {
Class<?> companionClass = Class.forName("com.yl.lib.sentry.hook.util.PrivacyClipBoardManager$Companion");
Class<?> privacyClipBoardManagerClass = Class.forName("com.yl.lib.sentry.hook.util.PrivacyClipBoardManager");
Field companion = privacyClipBoardManagerClass.getField("Companion");
Method m1 = companionClass.getDeclaredMethod("isReadClipboardEnable", (Class[]) null);
boolean open = (Boolean) m1.invoke(companion.get(null), (Object[]) null);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void testReflexClipManagerOpen() {
try {
Class<?> companionClass = Class.forName("com.yl.lib.sentry.hook.util.PrivacyClipBoardManager$Companion");
Class<?> privacyClipBoardManagerClass = Class.forName("com.yl.lib.sentry.hook.util.PrivacyClipBoardManager");
Field companion = privacyClipBoardManagerClass.getField("Companion");
Method m1 = companionClass.getDeclaredMethod("openReadClipboard", (Class[]) null);
m1.invoke(companion.get(null), (Object[]) null);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void testReflexClipManagerClose() {
try {
Class<?> companionClass = Class.forName("com.yl.lib.sentry.hook.util.PrivacyClipBoardManager$Companion");
Class<?> privacyClipBoardManagerClass = Class.forName("com.yl.lib.sentry.hook.util.PrivacyClipBoardManager");
Field companion = privacyClipBoardManagerClass.getField("Companion");
Method m1 = companionClass.getDeclaredMethod("closeReadClipboard", (Class[]) null);
m1.invoke(companion.get(null), (Object[]) null);
} catch (Exception e) {
e.printStackTrace();
}
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/test/TestOaidGetter.java
================================================
package com.yl.lib.privacysentry.test;
import android.content.Context;
import android.os.Build;
import org.json.JSONObject;
import java.lang.reflect.Method;
/**
* @author yulun
* @since 2024-04-25 15:43
*/
public class TestOaidGetter {
public static String getOaid(Context var0) {
try {
String var1 = "com.android.id.impl.IdProviderImpl";
Class var2 = a(var0, var1);
Object var3 = var2.newInstance();
String var4 = "getOAID";
Method var5 = var2.getMethod(var4, Context.class);
String var6 = "getAAID";
Method var7 = var2.getMethod(var6, Context.class);
String var8 = "getVAID";
Method var9 = var2.getMethod(var8, Context.class);
Object var10 = var9.invoke(var3, var0);
Object var11 = var7.invoke(var3, var0);
Object var12 = var5.invoke(var3, var0);
JSONObject var13 = new JSONObject();
var13.put("joad", var12);
var13.put("jvad", var11);
var13.put("jaad", var10);
return var13.toString();
} catch (Throwable var14) {
return "";
}
}
private static Class<?> a(Context var0, String var1) throws ClassNotFoundException {
if (var1 != null && var1.trim().length() != 0) {
boolean var2 = var0 != null;
if (var2 && Build.VERSION.SDK_INT >= 29) {
try {
return var0.getClassLoader().loadClass(var1);
} catch (ClassNotFoundException var5) {
}
}
try {
return Class.forName(var1);
} catch (ClassNotFoundException var4) {
throw new ClassNotFoundException("loadClass fail ", var4);
}
} else {
throw new ClassNotFoundException("class is empty");
}
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/test/TestReflex.kt
================================================
package com.yl.lib.privacysentry.test
import android.content.Context
import android.content.Context.TELEPHONY_SERVICE
import android.telephony.TelephonyManager
import com.yl.lib.sentry.hook.util.ReflectUtils
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method
/**
* @author yulun
* @since 2022-10-20 10:21
*/
class TestReflex {
fun test(var1: Context) {
val var2 = var1.applicationContext.getSystemService(TELEPHONY_SERVICE) as TelephonyManager
try {
val ctm = Class.forName("android.telephony.TelephonyManager")
val method = ctm.getDeclaredMethod("getDeviceId", Int::class.javaPrimitiveType)
method.invoke(var2, 2)
} catch (e: ClassNotFoundException) {
e.printStackTrace()
} catch (e: NoSuchMethodException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
} catch (e: InvocationTargetException) {
e.printStackTrace()
} catch (e: SecurityException){
e.printStackTrace()
}
}
fun test2(obj: Any,
name: String,
types: Array<Class<*>>,
args: Array<Any?>){
try {
val method: Method? =
getMethod(obj.javaClass.superclass, name, types)
if (null != method) {
method.isAccessible = true
method.invoke(obj, *args)
}
} catch (t: Throwable) {
t.printStackTrace()
}
}
private fun getMethod(klass: Class<*>, name: String, types: Array<Class<*>>): Method? {
return try {
klass.getDeclaredMethod(name, *types)
} catch (e: NoSuchMethodException) {
val parent = klass.superclass ?: return null
getMethod(parent, name, types)
}
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/test/TestReflexJava.java
================================================
package com.yl.lib.privacysentry.test;
import static android.content.Context.INPUT_METHOD_SERVICE;
import static android.content.Context.TELEPHONY_SERVICE;
import android.content.Context;
import android.inputmethodservice.InputMethodService;
import android.os.Build;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.RequiresApi;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author yulun
* @since 2022-10-20 10:27
*/
public class TestReflexJava {
public String c(Context var0, String var1) {
String var2 = null;
try {
TelephonyManager var3 = (TelephonyManager) var0.getSystemService(TELEPHONY_SERVICE);
Method var4 = TelephonyManager.class.getMethod("getSubscriberId");
var2 = (String) var4.invoke(var3);
} catch (Throwable var5) {
}
if (TextUtils.isEmpty(var2)) {
var2 = var1;
}
return var2;
}
public String reflex1(Context var0, String var1) {
String var2 = null;
try {
TelephonyManager var3 = (TelephonyManager) var0.getSystemService(TELEPHONY_SERVICE);
Method var4 = TelephonyManager.class.getMethod("getSubscriberId");
reflex2(var4, var3);
} catch (Throwable var5) {
}
return var2;
}
public void reflex2(Method var4, Object var3) {
try {
String var2 = (String) var4.invoke(var3);
} catch (Throwable var5) {
}
}
public String reflex3(Context var0, String var1) {
String var2 = null;
try {
InputMethodManager var3 = (InputMethodManager) var0.getSystemService(INPUT_METHOD_SERVICE);
Method var4 = InputMethodManager.class.getMethod("dasdsad");
var4.invoke(var3 );
} catch (Throwable var5) {
}
return var2;
}
@RequiresApi(api = Build.VERSION_CODES.M)
public void test(Context var1) {
TelephonyManager var2 = (TelephonyManager) var1.getApplicationContext().getSystemService(TELEPHONY_SERVICE);
try {
Class ctm = Class.forName("android.telephony.TelephonyManager");
Method method = ctm.getDeclaredMethod("getSubscriberId");
method.invoke(var2);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/test/TestService.kt
================================================
package com.yl.lib.privacysentry.test
/**
* @author yulun
* @since 2025-05-14 10:07
*/
import android.app.Service
import android.content.Intent
import android.os.IBinder
class TestService : Service() {
override fun onBind(intent: Intent): IBinder {
TODO("Return the communication channel to the service.")
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/test/constructor/TestFileConstructor.java
================================================
package com.yl.lib.privacysentry.test.constructor;
import android.os.Build;
import android.system.Os;
import androidx.annotation.NonNull;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* @author yulun
* @since 2022-11-22 16:36
*/
public class TestFileConstructor {
public TestFileConstructor(@NonNull File file) throws IOException {
if (file == null) {
throw new NullPointerException("file cannot be null");
}
file.getAbsolutePath();
}
public TestFileConstructor(@NonNull String filename) throws IOException {
if (filename == null) {
throw new NullPointerException("filename cannot be null");
}
new File(filename).getAbsolutePath();
}
public TestFileConstructor(@NonNull FileDescriptor fileDescriptor) throws IOException {
if (fileDescriptor == null) {
throw new NullPointerException("fileDescriptor cannot be null");
}
boolean isFdDuped = false;
try {
// asm 7.1的时候,这个写法,会导致数组越界,升级到9.1好了,浪费了一天时间 坑爹
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
fileDescriptor = Os.dup(fileDescriptor);
}
isFdDuped = true;
} catch (Exception e) {
throw new IOException("Failed to duplicate file descriptor", e);
}
FileInputStream in = null;
try {
in = new FileInputStream(fileDescriptor);
} finally {
if (isFdDuped) {
in.close();
}
}
}
public TestFileConstructor(@NonNull InputStream inputStream) throws IOException {
if (inputStream == null) {
throw new NullPointerException("inputStream cannot be null");
}
}
}
================================================
FILE: app/src/main/java/com/yl/lib/privacysentry/test/ui/main/TestPermissionFragment.kt
================================================
package com.yl.lib.privacysentry.test.ui.main
import android.Manifest
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import com.yl.lib.privacysentry.R
import com.yl.lib.sentry.hook.util.PrivacyLog
import com.yl.lib.sentry.hook.util.PrivacyUtil
class TestPermissionFragment : Fragment() {
companion object {
fun newInstance() = TestPermissionFragment()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val requestMultiplePermissions = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
Thread.currentThread().stackTrace
permissions.entries.forEach {
PrivacyLog.i("TestFragmentFragment ${PrivacyUtil.Util.getStackTrace()} \n ${it.key} = ${it.value}")
}
}
requestMultiplePermissions.launch(
arrayOf(
Manifest.permission.CAMERA
)
)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.fragment_main, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
}
override fun onStart() {
super.onStart()
requestPermissions(arrayOf(
Manifest.permission.BODY_SENSORS
),10000)
}
}
================================================
FILE: app/src/main/res/drawable/ic_launcher_background.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>
================================================
FILE: app/src/main/res/drawable-v24/ic_launcher_foreground.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
================================================
FILE: app/src/main/res/layout/activity_calender.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".calendar.CalenderActivity">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_add_calendar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="新增一个日历事件" />
<Button
android:id="@+id/btn_query_calendar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查询一个日历事件" />
<Button
android:id="@+id/btn_del_calendar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="删除一个日历事件" />
<Button
android:id="@+id/btn_edit_calendar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="编辑一个日历事件" />
<Button
android:id="@+id/btn_edit2_calendar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="编辑一个日历事件2" />
</LinearLayout>
</ScrollView>
</LinearLayout>
================================================
FILE: app/src/main/res/layout/activity_contact.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".contact.ContactActivity">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
gitextract_9uz9pdil/ ├── .gitignore ├── CLAUDE.md ├── LICENSE ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── libs/ │ │ └── bcprov-jdk16-139.jar │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── yl/ │ │ └── lib/ │ │ └── privacysentry/ │ │ └── ExampleInstrumentedTest.kt │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── yl/ │ │ │ └── lib/ │ │ │ └── privacysentry/ │ │ │ ├── APP.kt │ │ │ ├── MainActivity.kt │ │ │ ├── ReflexObjectUtil.java │ │ │ ├── calendar/ │ │ │ │ ├── CalendarManager.kt │ │ │ │ ├── CalenderActivity.kt │ │ │ │ └── RRuleConstant.kt │ │ │ ├── contact/ │ │ │ │ ├── ContactActivity.kt │ │ │ │ └── ContactManager.kt │ │ │ ├── location/ │ │ │ │ └── LocationTestActivity.java │ │ │ ├── process/ │ │ │ │ ├── MultiProcessB.kt │ │ │ │ └── MultiProcessC.kt │ │ │ ├── telephony/ │ │ │ │ └── TelephonyTestActivity.kt │ │ │ └── test/ │ │ │ ├── PrivacyMethod.kt │ │ │ ├── PrivacyProxyCallJava.java │ │ │ ├── PrivacyProxySelfTest.kt │ │ │ ├── PrivacyTestMacAddress.java │ │ │ ├── TestBCSeri.java │ │ │ ├── TestFragmentActivity.kt │ │ │ ├── TestInJava.java │ │ │ ├── TestOaidGetter.java │ │ │ ├── TestReflex.kt │ │ │ ├── TestReflexJava.java │ │ │ ├── TestService.kt │ │ │ ├── constructor/ │ │ │ │ └── TestFileConstructor.java │ │ │ └── ui/ │ │ │ └── main/ │ │ │ └── TestPermissionFragment.kt │ │ └── res/ │ │ ├── drawable/ │ │ │ └── ic_launcher_background.xml │ │ ├── drawable-v24/ │ │ │ └── ic_launcher_foreground.xml │ │ ├── layout/ │ │ │ ├── activity_calender.xml │ │ │ ├── activity_contact.xml │ │ │ ├── activity_location_test.xml │ │ │ ├── activity_main.xml │ │ │ ├── activity_telephony_test.xml │ │ │ ├── activity_test_frament.xml │ │ │ └── fragment_main.xml │ │ ├── mipmap-anydpi-v26/ │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── themes.xml │ │ └── values-night/ │ │ └── themes.xml │ └── test/ │ └── java/ │ └── com/ │ └── yl/ │ └── lib/ │ └── privacysentry/ │ └── ExampleUnitTest.kt ├── build.gradle ├── config.gradle ├── demo_result.xls ├── docs/ │ ├── README.md │ ├── architecture.html │ └── architecture.md ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── hook-sentry/ │ ├── .gitignore │ ├── build.gradle │ ├── gradle.properties │ ├── proguard-rules.pro │ ├── src/ │ │ ├── androidTest/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── yl/ │ │ │ └── lib/ │ │ │ └── hook_sentry/ │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main/ │ │ │ ├── AndroidManifest.xml │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── yl/ │ │ │ └── lib/ │ │ │ └── sentry/ │ │ │ └── hook/ │ │ │ ├── PrivacySentry.kt │ │ │ ├── PrivacySentryBuilder.kt │ │ │ ├── cache/ │ │ │ │ ├── BasePrivacyCache.kt │ │ │ │ ├── CachePrivacyManager.kt │ │ │ │ ├── CacheUtils.kt │ │ │ │ ├── DiskCache.kt │ │ │ │ ├── MemoryCache.kt │ │ │ │ ├── PrivacyCacheType.kt │ │ │ │ ├── TimeLessDiskCache.kt │ │ │ │ └── TimeLessMemoryCache.kt │ │ │ ├── excel/ │ │ │ │ ├── ExcelBuildDataListener.kt │ │ │ │ └── ExcelUtil.kt │ │ │ ├── printer/ │ │ │ │ ├── BaseFilePrinter.kt │ │ │ │ ├── BasePrinter.kt │ │ │ │ ├── DefaultFilePrint.kt │ │ │ │ ├── DefaultLogPrint.kt │ │ │ │ ├── PrintCallBack.kt │ │ │ │ └── PrivacyFunBean.kt │ │ │ ├── util/ │ │ │ │ ├── MainProcessUtil.kt │ │ │ │ ├── PrivacyClipBoardManager.kt │ │ │ │ ├── PrivacyLog.kt │ │ │ │ ├── PrivacyProxyUtil.kt │ │ │ │ ├── PrivacyUtil.kt │ │ │ │ └── ReflectUtils.kt │ │ │ └── watcher/ │ │ │ ├── DelayTimeWatcher.kt │ │ │ └── PrivacyDataManager.kt │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── yl/ │ │ └── lib/ │ │ └── hook_sentry/ │ │ └── ExampleUnitTest.kt │ └── ~/ │ └── .m2/ │ └── repository/ │ └── com/ │ └── yl/ │ └── lib/ │ └── privacy/ │ └── hook-sentry/ │ ├── 0.0.1-SNAPSHOT/ │ │ ├── maven-metadata-remote.xml │ │ └── resolver-status.properties │ ├── maven-metadata-remote.xml │ └── resolver-status.properties ├── plugin-sentry/ │ ├── .gitignore │ ├── build.gradle │ ├── gradle.properties │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── yl/ │ │ └── lib/ │ │ └── plugin/ │ │ └── sentry/ │ │ ├── PrivacySentryPlugin.kt │ │ ├── PrivacyTransformTask.kt │ │ ├── extension/ │ │ │ └── PrivacyExtension.kt │ │ ├── transform/ │ │ │ ├── PrivacyTransformContext.kt │ │ │ ├── PrivacyTransformInvocation.kt │ │ │ ├── booster/ │ │ │ │ ├── asmtransform/ │ │ │ │ │ └── AbsClassTransformer.kt │ │ │ │ ├── classtransform/ │ │ │ │ │ ├── collect/ │ │ │ │ │ │ ├── ClassProxyCollectTransform.kt │ │ │ │ │ │ └── MethodProxyCollectTransform.kt │ │ │ │ │ └── hook/ │ │ │ │ │ ├── BaseHookTransform.kt │ │ │ │ │ ├── ClassProxyTransform.kt │ │ │ │ │ ├── FieldProxyTransform.kt │ │ │ │ │ ├── FlushHookDataTransform.kt │ │ │ │ │ ├── MethodHookTransform.kt │ │ │ │ │ └── ServiceHookTransform.kt │ │ │ │ ├── processor/ │ │ │ │ │ ├── PrivacyAssetsProcessor.kt │ │ │ │ │ └── PrivacyManifestProcessor.kt │ │ │ │ ├── task/ │ │ │ │ │ └── PrivacyManifestTask.kt │ │ │ │ └── transformer/ │ │ │ │ └── PrivacyBaseTransformer.kt │ │ │ └── manager/ │ │ │ ├── HookFieldManager.kt │ │ │ ├── HookMethodManager.kt │ │ │ ├── HookedDataManger.kt │ │ │ └── ReplaceClassManager.kt │ │ └── util/ │ │ ├── PrivacyExt.kt │ │ ├── PrivacyMoveAssetsUtil.kt │ │ └── PrivacyPluginUtil.kt │ └── resources/ │ └── META-INF/ │ └── gradle-plugins/ │ ├── com.allenymt.plugin.privacy.properties │ └── privacy-sentry-plugin.properties ├── privacy ├── privacy-annotation/ │ ├── .gitignore │ ├── build.gradle │ ├── gradle.properties │ ├── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── yl/ │ │ └── lib/ │ │ └── privacy_annotation/ │ │ ├── MethodInvokeOpcode.java │ │ ├── PrivacyClassBlack.java │ │ ├── PrivacyClassProxy.java │ │ ├── PrivacyClassReplace.java │ │ ├── PrivacyFieldProxy.java │ │ └── PrivacyMethodProxy.java │ └── ~/ │ └── .m2/ │ └── repository/ │ └── com/ │ └── yl/ │ └── lib/ │ └── privacy/ │ └── privacy-annotation/ │ ├── 0.0.1-SNAPSHOT/ │ │ ├── maven-metadata-remote.xml │ │ └── resolver-status.properties │ ├── 0.0.2-SNAPSHOT/ │ │ ├── maven-metadata-remote.xml │ │ └── resolver-status.properties │ ├── maven-metadata-remote.xml │ ├── maven-metadata-remote.xml.sha1 │ └── resolver-status.properties ├── privacy-proxy/ │ ├── .gitignore │ ├── README.md │ ├── build.gradle │ ├── gradle.properties │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── com/ │ └── yl/ │ └── lib/ │ └── privacy_proxy/ │ ├── PrivacyPermissionProxy.kt │ ├── PrivacyProxyCall.kt │ ├── PrivacyProxyCallJava.java │ ├── PrivacyProxyResolver.kt │ ├── PrivacyReflectProxy.kt │ ├── PrivacySensorProxy.kt │ ├── PrivacyTelephonyProxy.kt │ └── ProxyProxyField.java ├── privacy-replace/ │ ├── .gitignore │ ├── README.md │ ├── build.gradle │ ├── consumer-rules.pro │ ├── gradle.properties │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── vdian/ │ │ └── android/ │ │ └── wdb/ │ │ └── privacy_replace/ │ │ └── ExampleInstrumentedTest.kt │ ├── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── yl/ │ │ └── lib/ │ │ └── privacy_replace/ │ │ ├── PrivacyFile.java │ │ ├── PrivacyFileInputStream.java │ │ └── PrivacyFileReader.java │ └── test/ │ └── java/ │ └── com/ │ └── vdian/ │ └── android/ │ └── wdb/ │ └── privacy_replace/ │ └── ExampleUnitTest.kt ├── privacy-test/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── com/ │ └── yl/ │ └── lib/ │ └── privacy_test/ │ ├── PrivacyProxySelfTest2.java │ ├── TestMethod.kt │ └── TestMethodInJava.java ├── privacy-ui/ │ └── build/ │ ├── generated/ │ │ └── source/ │ │ └── buildConfig/ │ │ └── debug/ │ │ └── com/ │ │ └── yl/ │ │ └── lib/ │ │ └── privacy_ui/ │ │ └── BuildConfig.java │ ├── intermediates/ │ │ ├── aapt_friendly_merged_manifests/ │ │ │ └── debug/ │ │ │ └── aapt/ │ │ │ ├── AndroidManifest.xml │ │ │ └── output-metadata.json │ │ ├── aar_metadata/ │ │ │ └── debug/ │ │ │ └── aar-metadata.properties │ │ ├── annotation_processor_list/ │ │ │ └── debug/ │ │ │ └── annotationProcessors.json │ │ ├── compile_library_classes_jar/ │ │ │ └── debug/ │ │ │ └── classes.jar │ │ ├── compile_r_class_jar/ │ │ │ └── debug/ │ │ │ └── R.jar │ │ ├── compile_symbol_list/ │ │ │ └── debug/ │ │ │ └── R.txt │ │ ├── compiled_local_resources/ │ │ │ └── debug/ │ │ │ └── out/ │ │ │ ├── layout_activity_permission_list.xml.flat │ │ │ ├── layout_activity_real_time_privacy_item.xml.flat │ │ │ ├── layout_activity_replace_list.xml.flat │ │ │ ├── layout_permission_item_view.xml.flat │ │ │ ├── layout_real_tile_item_view.xml.flat │ │ │ └── layout_replace_item_view.xml.flat │ │ ├── incremental/ │ │ │ ├── mergeDebugJniLibFolders/ │ │ │ │ └── merger.xml │ │ │ ├── mergeDebugShaders/ │ │ │ │ └── merger.xml │ │ │ ├── packageDebugAssets/ │ │ │ │ └── merger.xml │ │ │ └── packageDebugResources/ │ │ │ ├── compile-file-map.properties │ │ │ └── merger.xml │ │ ├── library_java_res/ │ │ │ └── debug/ │ │ │ └── res.jar │ │ ├── library_manifest/ │ │ │ └── debug/ │ │ │ └── AndroidManifest.xml │ │ ├── local_only_symbol_list/ │ │ │ └── debug/ │ │ │ └── R-def.txt │ │ ├── manifest_merge_blame_file/ │ │ │ └── debug/ │ │ │ └── manifest-merger-blame-debug-report.txt │ │ ├── navigation_json/ │ │ │ └── debug/ │ │ │ └── navigation.json │ │ ├── packaged_manifests/ │ │ │ └── debug/ │ │ │ └── output-metadata.json │ │ ├── packaged_res/ │ │ │ └── debug/ │ │ │ └── layout/ │ │ │ ├── activity_permission_list.xml │ │ │ ├── activity_real_time_privacy_item.xml │ │ │ ├── activity_replace_list.xml │ │ │ ├── permission_item_view.xml │ │ │ ├── real_tile_item_view.xml │ │ │ └── replace_item_view.xml │ │ ├── runtime_library_classes_jar/ │ │ │ └── debug/ │ │ │ └── classes.jar │ │ └── symbol_list_with_package_name/ │ │ └── debug/ │ │ └── package-aware-r.txt │ ├── kotlin/ │ │ └── compileDebugKotlin/ │ │ └── caches-jvm/ │ │ ├── inputs/ │ │ │ ├── source-to-output.tab │ │ │ ├── source-to-output.tab.keystream │ │ │ ├── source-to-output.tab.keystream.len │ │ │ ├── source-to-output.tab.len │ │ │ ├── source-to-output.tab.values.at │ │ │ ├── source-to-output.tab_i │ │ │ └── source-to-output.tab_i.len │ │ ├── jvm/ │ │ │ └── kotlin/ │ │ │ ├── class-fq-name-to-source.tab │ │ │ ├── class-fq-name-to-source.tab.keystream │ │ │ ├── class-fq-name-to-source.tab.keystream.len │ │ │ ├── class-fq-name-to-source.tab.len │ │ │ ├── class-fq-name-to-source.tab.values.at │ │ │ ├── class-fq-name-to-source.tab_i │ │ │ ├── class-fq-name-to-source.tab_i.len │ │ │ ├── internal-name-to-source.tab │ │ │ ├── internal-name-to-source.tab.keystream │ │ │ ├── internal-name-to-source.tab.keystream.len │ │ │ ├── internal-name-to-source.tab.len │ │ │ ├── internal-name-to-source.tab.values.at │ │ │ ├── internal-name-to-source.tab_i │ │ │ ├── internal-name-to-source.tab_i.len │ │ │ ├── proto.tab │ │ │ ├── proto.tab.keystream │ │ │ ├── proto.tab.keystream.len │ │ │ ├── proto.tab.len │ │ │ ├── proto.tab.values.at │ │ │ ├── proto.tab_i │ │ │ ├── proto.tab_i.len │ │ │ ├── source-to-classes.tab │ │ │ ├── source-to-classes.tab.keystream │ │ │ ├── source-to-classes.tab.keystream.len │ │ │ ├── source-to-classes.tab.len │ │ │ ├── source-to-classes.tab.values.at │ │ │ ├── source-to-classes.tab_i │ │ │ ├── source-to-classes.tab_i.len │ │ │ ├── subtypes.tab │ │ │ ├── subtypes.tab.keystream │ │ │ ├── subtypes.tab.keystream.len │ │ │ ├── subtypes.tab.len │ │ │ ├── subtypes.tab.values.at │ │ │ ├── subtypes.tab_i │ │ │ ├── subtypes.tab_i.len │ │ │ ├── supertypes.tab │ │ │ ├── supertypes.tab.keystream │ │ │ ├── supertypes.tab.keystream.len │ │ │ ├── supertypes.tab.len │ │ │ ├── supertypes.tab.values.at │ │ │ ├── supertypes.tab_i │ │ │ └── supertypes.tab_i.len │ │ └── lookups/ │ │ ├── counters.tab │ │ ├── file-to-id.tab │ │ ├── file-to-id.tab.keystream │ │ ├── file-to-id.tab.keystream.len │ │ ├── file-to-id.tab.len │ │ ├── file-to-id.tab.values.at │ │ ├── file-to-id.tab_i │ │ ├── file-to-id.tab_i.len │ │ ├── id-to-file.tab │ │ ├── id-to-file.tab.keystream │ │ ├── id-to-file.tab.keystream.len │ │ ├── id-to-file.tab.len │ │ ├── id-to-file.tab.values.at │ │ ├── id-to-file.tab_i │ │ ├── id-to-file.tab_i.len │ │ ├── lookups.tab │ │ ├── lookups.tab.keystream │ │ ├── lookups.tab.keystream.len │ │ ├── lookups.tab.len │ │ ├── lookups.tab.values.at │ │ ├── lookups.tab_i │ │ └── lookups.tab_i.len │ ├── outputs/ │ │ └── logs/ │ │ └── manifest-merger-debug-report.txt │ └── tmp/ │ ├── compileDebugJavaWithJavac/ │ │ └── source-classes-mapping.txt │ └── kotlin-classes/ │ └── debug/ │ └── META-INF/ │ └── privacy-ui_debug.kotlin_module ├── privacy_hook.json ├── publish.gradle ├── settings.gradle.kts └── upload_local.sh
SYMBOL INDEX (83 symbols across 18 files)
FILE: app/src/main/java/com/yl/lib/privacysentry/ReflexObjectUtil.java
class ReflexObjectUtil (line 13) | public class ReflexObjectUtil {
method test (line 15) | public static void test(String className) {
method printConstructors (line 40) | public static void printConstructors(Class cl) {
method printMethods (line 58) | public static void printMethods(Class cl) {
method printFields (line 79) | public static void printFields(Class cl) {
FILE: app/src/main/java/com/yl/lib/privacysentry/location/LocationTestActivity.java
class LocationTestActivity (line 30) | public class LocationTestActivity extends AppCompatActivity {
method onCreate (line 35) | @Override
method doNextLocation (line 68) | private void doNextLocation() {
method getLocation (line 73) | private void getLocation() {
method printLocation (line 110) | private void printLocation(Location location, String provider) {
method onRequestPermissionsResult (line 138) | @Override
method run (line 144) | @Override
FILE: app/src/main/java/com/yl/lib/privacysentry/test/PrivacyProxyCallJava.java
class PrivacyProxyCallJava (line 25) | @PrivacyClassProxy
method getRunningTasks (line 28) | public static List<ActivityManager.RunningTaskInfo> getRunningTasks(
method putString (line 36) | @PrivacyMethodProxy(
method doFilePrinter (line 45) | private static void doFilePrinter(
FILE: app/src/main/java/com/yl/lib/privacysentry/test/PrivacyTestMacAddress.java
class PrivacyTestMacAddress (line 11) | public class PrivacyTestMacAddress {
method getMacAddress (line 12) | public static void getMacAddress() {
method testNewFile (line 59) | public static void testNewFile() {
FILE: app/src/main/java/com/yl/lib/privacysentry/test/TestBCSeri.java
class TestBCSeri (line 9) | public class TestBCSeri {
method TestBCSeri (line 10) | public TestBCSeri() {
method testAAAA (line 14) | public void testAAAA() {
method test1 (line 18) | public boolean test1() {
method test2 (line 22) | public Boolean test2() {
FILE: app/src/main/java/com/yl/lib/privacysentry/test/TestInJava.java
class TestInJava (line 40) | public class TestInJava {
method testHttpUrlConnection (line 46) | public static void testHttpUrlConnection() {
method testInitJava (line 74) | public static void testInitJava() {
method getHostIp (line 86) | public static String getHostIp() {
method getIpAddress (line 106) | @SuppressLint("MissingPermission")
method getOutNetIP (line 136) | public static String getOutNetIP() {
method getIPByWifiInfo (line 179) | public static void getIPByWifiInfo(Context context){
method testReflexClipManager (line 186) | public static void testReflexClipManager() {
method testReflexClipManagerOpen (line 198) | public static void testReflexClipManagerOpen() {
method testReflexClipManagerClose (line 210) | public static void testReflexClipManagerClose() {
FILE: app/src/main/java/com/yl/lib/privacysentry/test/TestOaidGetter.java
class TestOaidGetter (line 14) | public class TestOaidGetter {
method getOaid (line 15) | public static String getOaid(Context var0) {
method a (line 39) | private static Class<?> a(Context var0, String var1) throws ClassNotFo...
FILE: app/src/main/java/com/yl/lib/privacysentry/test/TestReflexJava.java
class TestReflexJava (line 22) | public class TestReflexJava {
method c (line 23) | public String c(Context var0, String var1) {
method reflex1 (line 40) | public String reflex1(Context var0, String var1) {
method reflex2 (line 51) | public void reflex2(Method var4, Object var3) {
method reflex3 (line 59) | public String reflex3(Context var0, String var1) {
method test (line 70) | @RequiresApi(api = Build.VERSION_CODES.M)
FILE: app/src/main/java/com/yl/lib/privacysentry/test/constructor/TestFileConstructor.java
class TestFileConstructor (line 18) | public class TestFileConstructor {
method TestFileConstructor (line 19) | public TestFileConstructor(@NonNull File file) throws IOException {
method TestFileConstructor (line 26) | public TestFileConstructor(@NonNull String filename) throws IOException {
method TestFileConstructor (line 33) | public TestFileConstructor(@NonNull FileDescriptor fileDescriptor) thr...
method TestFileConstructor (line 58) | public TestFileConstructor(@NonNull InputStream inputStream) throws IO...
FILE: privacy-annotation/src/main/java/com/yl/lib/privacy_annotation/MethodInvokeOpcode.java
class MethodInvokeOpcode (line 8) | public class MethodInvokeOpcode {
FILE: privacy-proxy/src/main/java/com/yl/lib/privacy_proxy/PrivacyProxyCallJava.java
class PrivacyProxyCallJava (line 29) | @PrivacyClassProxy
method hasPrimaryClip (line 32) | @PrivacyMethodProxy(
method isWifiEnabled (line 52) | @PrivacyMethodProxy(
method getIpAddress (line 68) | @PrivacyMethodProxy(
class PrivacyProxyCallJavaWifiEnabled (line 78) | @PrivacyClassBlack
method PrivacyProxyCallJavaWifiEnabled (line 82) | PrivacyProxyCallJavaWifiEnabled(WifiManager wifiManager) {
method invoke (line 87) | public Boolean invoke() {
FILE: privacy-proxy/src/main/java/com/yl/lib/privacy_proxy/ProxyProxyField.java
class ProxyProxyField (line 15) | @Keep
FILE: privacy-replace/src/main/java/com/yl/lib/privacy_replace/PrivacyFile.java
class PrivacyFile (line 19) | @PrivacyClassReplace(originClass = File.class)
method PrivacyFile (line 22) | public PrivacyFile(@NonNull String pathname) {
method PrivacyFile (line 27) | public PrivacyFile(@Nullable String parent, @NonNull String child) {
method PrivacyFile (line 32) | public PrivacyFile(@Nullable File parent, @NonNull String child) {
method PrivacyFile (line 37) | public PrivacyFile(@NonNull URI uri) {
method record (line 42) | private void record(String path) {
FILE: privacy-replace/src/main/java/com/yl/lib/privacy_replace/PrivacyFileInputStream.java
class PrivacyFileInputStream (line 17) | @PrivacyClassReplace(originClass = FileInputStream.class)
method PrivacyFileInputStream (line 19) | public PrivacyFileInputStream(String name) throws FileNotFoundException {
method PrivacyFileInputStream (line 24) | public PrivacyFileInputStream(File file) throws FileNotFoundException {
method PrivacyFileInputStream (line 29) | public PrivacyFileInputStream(FileDescriptor fdObj) {
method record (line 34) | private void record(String path) {
FILE: privacy-replace/src/main/java/com/yl/lib/privacy_replace/PrivacyFileReader.java
class PrivacyFileReader (line 16) | @PrivacyClassReplace(originClass = FileReader.class)
method PrivacyFileReader (line 18) | public PrivacyFileReader(String fileName) throws FileNotFoundException {
method PrivacyFileReader (line 23) | public PrivacyFileReader(File file) throws FileNotFoundException {
method PrivacyFileReader (line 28) | public PrivacyFileReader(FileDescriptor fd) {
method record (line 33) | private void record(String path) {
FILE: privacy-test/src/main/java/com/yl/lib/privacy_test/PrivacyProxySelfTest2.java
class PrivacyProxySelfTest2 (line 19) | @PrivacyClassProxy
method getRunningTasks456 (line 24) | @PrivacyMethodProxy(
FILE: privacy-test/src/main/java/com/yl/lib/privacy_test/TestMethodInJava.java
class TestMethodInJava (line 12) | public class TestMethodInJava {
method getAndroidId (line 13) | public static String getAndroidId(Context context) {
method getAndroidId2 (line 18) | public static String getAndroidId2(Context context) {
method getAndroidIdSystem (line 23) | public static String getAndroidIdSystem(Context context) {
method getSubscriberId (line 28) | public static void getSubscriberId(Context context) {
method testRunningProcess (line 42) | public static void testRunningProcess(Context context) {
method testRunningTask (line 48) | public static void testRunningTask(Context context) {
FILE: privacy-ui/build/generated/source/buildConfig/debug/com/yl/lib/privacy_ui/BuildConfig.java
class BuildConfig (line 6) | public final class BuildConfig {
Condensed preview — 289 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (976K chars).
[
{
"path": ".gitignore",
"chars": 155,
"preview": "*.iml\n.gradle\n/local.properties\n/.idea/\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.cxx\nlocal.properties\n/plugin-se"
},
{
"path": "CLAUDE.md",
"chars": 3935,
"preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## "
},
{
"path": "LICENSE",
"chars": 1063,
"preview": "MIT License\n\nCopyright (c) 2022 alleny\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
},
{
"path": "README.md",
"chars": 24640,
"preview": "# PrivacySentry\n\n[](https://jitpack.io/#allenymt/PrivacySentry)\n[![L"
},
{
"path": "app/.gitignore",
"chars": 6,
"preview": "/build"
},
{
"path": "app/build.gradle",
"chars": 4713,
"preview": "apply plugin: 'com.android.application'\napply plugin: 'kotlin-android'\napply plugin: 'privacy-sentry-plugin'\n\nandroid {\n"
},
{
"path": "app/proguard-rules.pro",
"chars": 750,
"preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
},
{
"path": "app/src/androidTest/java/com/yl/lib/privacysentry/ExampleInstrumentedTest.kt",
"chars": 675,
"preview": "package com.yl.lib.privacysentry\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx.test.ext.jun"
},
{
"path": "app/src/main/AndroidManifest.xml",
"chars": 3008,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <uses-"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/APP.kt",
"chars": 1969,
"preview": "package com.yl.lib.privacysentry\n\nimport android.app.Application\nimport android.content.Context\nimport android.os.Build\n"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/MainActivity.kt",
"chars": 10951,
"preview": "package com.yl.lib.privacysentry\n\nimport android.Manifest\nimport android.app.ActivityManager\nimport android.app.AlertDia"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/ReflexObjectUtil.java",
"chars": 3171,
"preview": "package com.yl.lib.privacysentry;\n\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Field;\nimport java.lan"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/calendar/CalendarManager.kt",
"chars": 55714,
"preview": "package com.yl.lib.privacysentry.calendar\n\nimport android.content.ContentUris\nimport android.content.ContentValues\nimpor"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/calendar/CalenderActivity.kt",
"chars": 2070,
"preview": "package com.yl.lib.privacysentry.calendar\n\nimport android.Manifest\nimport android.os.Bundle\nimport android.widget.Button"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/calendar/RRuleConstant.kt",
"chars": 1535,
"preview": "package com.yl.lib.privacysentry.calendar\n\n/**\n * @author yulun\n * @sinice 2022-01-13 19:57\n */\nclass RRuleConstant {\n\n "
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/contact/ContactActivity.kt",
"chars": 2338,
"preview": "package com.yl.lib.privacysentry.contact\n\nimport android.Manifest\nimport android.content.pm.PackageManager\nimport androi"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/contact/ContactManager.kt",
"chars": 8022,
"preview": "package com.yl.lib.privacysentry.contact\n\nimport android.Manifest\nimport android.content.*\nimport android.net.Uri\nimport"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/location/LocationTestActivity.java",
"chars": 7205,
"preview": "package com.yl.lib.privacysentry.location;\n\nimport android.Manifest;\nimport android.content.pm.PackageManager;\nimport an"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/process/MultiProcessB.kt",
"chars": 1070,
"preview": "package com.yl.lib.privacysentry.process\n\nimport android.app.Application\nimport android.app.IntentService\nimport android"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/process/MultiProcessC.kt",
"chars": 1410,
"preview": "package com.yl.lib.privacysentry.process\n\nimport android.app.Application\nimport android.app.IntentService\nimport android"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/telephony/TelephonyTestActivity.kt",
"chars": 2713,
"preview": "package com.yl.lib.privacysentry.telephony\n\nimport android.Manifest\nimport android.os.Build\nimport androidx.appcompat.ap"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/test/PrivacyMethod.kt",
"chars": 21395,
"preview": "package com.yl.lib.privacysentry.test\n\nimport android.Manifest\nimport android.annotation.SuppressLint\nimport android.app"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/test/PrivacyProxyCallJava.java",
"chars": 1854,
"preview": "package com.yl.lib.privacysentry.test;\n\nimport android.app.ActivityManager;\nimport android.content.SharedPreferences;\nim"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/test/PrivacyProxySelfTest.kt",
"chars": 1794,
"preview": "package com.yl.lib.privacysentry.test\n\nimport android.app.ActivityManager\nimport android.content.SharedPreferences\nimpor"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/test/PrivacyTestMacAddress.java",
"chars": 1765,
"preview": "package com.yl.lib.privacysentry.test;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/test/TestBCSeri.java",
"chars": 368,
"preview": "package com.yl.lib.privacysentry.test;\n\nimport android.os.Build;\n\n/**\n * @author yulun\n * @since 2022-02-25 11:37\n */\npu"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/test/TestFragmentActivity.kt",
"chars": 643,
"preview": "package com.yl.lib.privacysentry.test\n\nimport androidx.appcompat.app.AppCompatActivity\nimport android.os.Bundle\nimport c"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/test/TestInJava.java",
"chars": 9081,
"preview": "package com.yl.lib.privacysentry.test;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport andr"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/test/TestOaidGetter.java",
"chars": 1909,
"preview": "package com.yl.lib.privacysentry.test;\n\nimport android.content.Context;\nimport android.os.Build;\n\nimport org.json.JSONOb"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/test/TestReflex.kt",
"chars": 1887,
"preview": "package com.yl.lib.privacysentry.test\n\nimport android.content.Context\nimport android.content.Context.TELEPHONY_SERVICE\ni"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/test/TestReflexJava.java",
"chars": 2570,
"preview": "package com.yl.lib.privacysentry.test;\n\nimport static android.content.Context.INPUT_METHOD_SERVICE;\nimport static androi"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/test/TestService.kt",
"chars": 332,
"preview": "package com.yl.lib.privacysentry.test\n\n/**\n * @author yulun\n * @since 2025-05-14 10:07\n */\nimport android.app.Service\nim"
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/test/constructor/TestFileConstructor.java",
"chars": 1876,
"preview": "package com.yl.lib.privacysentry.test.constructor;\n\nimport android.os.Build;\nimport android.system.Os;\n\nimport androidx."
},
{
"path": "app/src/main/java/com/yl/lib/privacysentry/test/ui/main/TestPermissionFragment.kt",
"chars": 1675,
"preview": "package com.yl.lib.privacysentry.test.ui.main\n\nimport android.Manifest\nimport android.os.Bundle\nimport android.view.Layo"
},
{
"path": "app/src/main/res/drawable/ic_launcher_background.xml",
"chars": 5606,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:wi"
},
{
"path": "app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
"chars": 1702,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:aapt=\"http://schemas.android.com/aapt\"\n "
},
{
"path": "app/src/main/res/layout/activity_calender.xml",
"chars": 1763,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layout/activity_contact.xml",
"chars": 1315,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layout/activity_location_test.xml",
"chars": 1102,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layout/activity_main.xml",
"chars": 5666,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layout/activity_telephony_test.xml",
"chars": 2094,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layout/activity_test_frament.xml",
"chars": 324,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns"
},
{
"path": "app/src/main/res/layout/fragment_main.xml",
"chars": 823,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
},
{
"path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
"chars": 272,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
"chars": 272,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "app/src/main/res/values/colors.xml",
"chars": 378,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"purple_200\">#FFBB86FC</color>\n <color name=\"purpl"
},
{
"path": "app/src/main/res/values/strings.xml",
"chars": 75,
"preview": "<resources>\n <string name=\"app_name\">PrivacySentry</string>\n</resources>"
},
{
"path": "app/src/main/res/values/themes.xml",
"chars": 835,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n <!-- Base application theme. -->\n <style name=\"Theme.P"
},
{
"path": "app/src/main/res/values-night/themes.xml",
"chars": 835,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n <!-- Base application theme. -->\n <style name=\"Theme.P"
},
{
"path": "app/src/test/java/com/yl/lib/privacysentry/ExampleUnitTest.kt",
"chars": 348,
"preview": "package com.yl.lib.privacysentry\n\nimport org.junit.Test\n\nimport org.junit.Assert.*\n\n/**\n * Example local unit test, whic"
},
{
"path": "build.gradle",
"chars": 1054,
"preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nbuildscript {\n ap"
},
{
"path": "config.gradle",
"chars": 195,
"preview": "ext {\n publish_config = [\n version : '1.3.7_v820_beta4'\n ]\n build = [\n local_debug: false"
},
{
"path": "docs/README.md",
"chars": 1132,
"preview": "# PrivacySentry 文档中心\n\n欢迎访问 PrivacySentry 项目文档!\n\n## 📚 文档列表\n\n### 架构文档\n\n- **[架构深度分析 (Markdown)](./architecture.md)** - 完整的技"
},
{
"path": "docs/architecture.html",
"chars": 35944,
"preview": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-wi"
},
{
"path": "docs/architecture.md",
"chars": 31007,
"preview": "# PrivacySentry 架构深度分析\n\n> Android 隐私合规检测工具的完整技术解析\n\n## 目录\n\n- [执行摘要](#执行摘要)\n- [第一部分:架构总体设计](#第一部分架构总体设计)\n- [第二部分:注解层详细分析]("
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 230,
"preview": "#Mon Nov 15 17:09:33 CST 2021\ndistributionBase=GRADLE_USER_HOME\ndistributionUrl=https\\://services.gradle.org/distributio"
},
{
"path": "gradle.properties",
"chars": 1184,
"preview": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will ov"
},
{
"path": "gradlew",
"chars": 5766,
"preview": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0"
},
{
"path": "gradlew.bat",
"chars": 2763,
"preview": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (th"
},
{
"path": "hook-sentry/.gitignore",
"chars": 6,
"preview": "/build"
},
{
"path": "hook-sentry/build.gradle",
"chars": 1489,
"preview": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\n\nandroid {\n compileSdkVersion 30\n buildToolsVer"
},
{
"path": "hook-sentry/gradle.properties",
"chars": 23,
"preview": "ARTIFACT_ID=hook-sentry"
},
{
"path": "hook-sentry/proguard-rules.pro",
"chars": 750,
"preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
},
{
"path": "hook-sentry/src/androidTest/java/com/yl/lib/hook_sentry/ExampleInstrumentedTest.kt",
"chars": 671,
"preview": "package com.yl.lib.hook_sentry\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx.test.ext.junit"
},
{
"path": "hook-sentry/src/main/AndroidManifest.xml",
"chars": 120,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n</manifest>"
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/PrivacySentry.kt",
"chars": 6895,
"preview": "package com.yl.lib.sentry.hook\n\nimport android.app.Application\nimport android.content.Context\nimport android.os.Handler\n"
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/PrivacySentryBuilder.kt",
"chars": 3427,
"preview": "package com.yl.lib.sentry.hook\n\nimport com.yl.lib.sentry.hook.printer.BasePrinter\nimport com.yl.lib.sentry.hook.printer."
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/cache/BasePrivacyCache.kt",
"chars": 389,
"preview": "package com.yl.lib.sentry.hook.cache\n\n/**\n * @author yulun\n * @since 2022-10-17 11:32\n */\nabstract class BasePrivacyCach"
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/cache/CachePrivacyManager.kt",
"chars": 6571,
"preview": "package com.yl.lib.sentry.hook.cache\n\nimport android.location.Location\nimport android.text.TextUtils\nimport com.yl.lib.s"
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/cache/CacheUtils.kt",
"chars": 2991,
"preview": "package com.yl.lib.sentry.hook.cache\n\nimport android.content.Context.MODE_PRIVATE\nimport android.content.SharedPreferenc"
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/cache/DiskCache.kt",
"chars": 952,
"preview": "package com.yl.lib.sentry.hook.cache\n\nimport java.util.concurrent.ConcurrentHashMap\n\n/**\n * @author yulun\n * @since 2022"
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/cache/MemoryCache.kt",
"chars": 648,
"preview": "package com.yl.lib.sentry.hook.cache\n\nimport java.util.concurrent.ConcurrentHashMap\n\n/**\n * @author yulun\n * @since 2022"
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/cache/PrivacyCacheType.kt",
"chars": 339,
"preview": "package com.yl.lib.sentry.hook.cache\n\n/**\n * @author yulun\n * @since 2022-10-14 17:55\n */\nenum class PrivacyCacheType {\n"
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/cache/TimeLessDiskCache.kt",
"chars": 1547,
"preview": "package com.yl.lib.sentry.hook.cache\n\nimport android.text.TextUtils\nimport java.util.concurrent.ConcurrentHashMap\n\n/**\n "
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/cache/TimeLessMemoryCache.kt",
"chars": 1326,
"preview": "package com.yl.lib.sentry.hook.cache\n\nimport android.text.TextUtils\nimport java.util.concurrent.ConcurrentHashMap\n\n/**\n "
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/excel/ExcelBuildDataListener.kt",
"chars": 262,
"preview": "package com.yl.lib.sentry.hook.excel\n\nimport com.yl.lib.sentry.hook.printer.PrivacyFunBean\n\n/**\n * @author yulun\n * @sin"
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/excel/ExcelUtil.kt",
"chars": 7049,
"preview": "package com.yl.lib.sentry.hook.excel\n\nimport com.yl.lib.sentry.hook.printer.PrivacyFunBean\nimport com.yl.lib.sentry.hook"
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/printer/BaseFilePrinter.kt",
"chars": 782,
"preview": "package com.yl.lib.sentry.hook.printer\n\nimport com.yl.lib.sentry.hook.util.PrivacyLog\n\n/**\n * @author yulun\n * @sinice 2"
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/printer/BasePrinter.kt",
"chars": 246,
"preview": "package com.yl.lib.sentry.hook.printer\n\n/**\n * @author yulun\n * @sinice 2021-09-24 15:43\n */\nopen class BasePrinter {\n\n "
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/printer/DefaultFilePrint.kt",
"chars": 5216,
"preview": "package com.yl.lib.sentry.hook.printer\n\nimport com.yl.lib.sentry.hook.PrivacySentry\nimport com.yl.lib.sentry.hook.excel."
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/printer/DefaultLogPrint.kt",
"chars": 392,
"preview": "package com.yl.lib.sentry.hook.printer\n\nimport com.yl.lib.sentry.hook.util.PrivacyLog\n\n\n/**\n * @author yulun\n * @sinice "
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/printer/PrintCallBack.kt",
"chars": 176,
"preview": "package com.yl.lib.sentry.hook.printer\n\n/**\n * @author yulun\n * @sinice 2021-11-22 14:20\n */\ninterface PrintCallBack {\n "
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/printer/PrivacyFunBean.kt",
"chars": 1555,
"preview": "package com.yl.lib.sentry.hook.printer\n\nimport com.yl.lib.sentry.hook.util.PrivacyUtil\n\n/**\n * @author yulun\n * @sinice "
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/util/MainProcessUtil.kt",
"chars": 3879,
"preview": "package com.yl.lib.sentry.hook.util\n\nimport android.app.Application\nimport android.content.Context\nimport android.os.Bui"
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/util/PrivacyClipBoardManager.kt",
"chars": 1594,
"preview": "package com.yl.lib.sentry.hook.util\n\nimport androidx.annotation.Keep\nimport com.yl.lib.sentry.hook.PrivacySentry\nimport "
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/util/PrivacyLog.kt",
"chars": 820,
"preview": "package com.yl.lib.sentry.hook.util\n\nimport com.yl.lib.sentry.hook.PrivacySentry\n\n\n/**\n * @author yulun\n * @sinice 2021-"
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/util/PrivacyProxyUtil.kt",
"chars": 1738,
"preview": "package com.yl.lib.sentry.hook.util\n\nimport android.content.pm.PackageManager\nimport com.yl.lib.sentry.hook.PrivacySentr"
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/util/PrivacyUtil.kt",
"chars": 5776,
"preview": "package com.yl.lib.sentry.hook.util\n\nimport android.app.Application\nimport android.location.Location\nimport android.loca"
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/util/ReflectUtils.kt",
"chars": 1611,
"preview": "package com.yl.lib.sentry.hook.util\n\nimport java.lang.reflect.Method\n\n/**\n * @author yulun\n * @since 2022-11-10 15:06\n *"
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/watcher/DelayTimeWatcher.kt",
"chars": 1456,
"preview": "package com.yl.lib.sentry.hook.watcher\n\nimport android.os.CountDownTimer\nimport android.os.Handler\nimport android.os.Loo"
},
{
"path": "hook-sentry/src/main/java/com/yl/lib/sentry/hook/watcher/PrivacyDataManager.kt",
"chars": 1544,
"preview": "package com.yl.lib.sentry.hook.watcher\n\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.lifecycle.MutableLiveDa"
},
{
"path": "hook-sentry/src/test/java/com/yl/lib/hook_sentry/ExampleUnitTest.kt",
"chars": 1953,
"preview": "package com.yl.lib.hook_sentry\n\nimport com.yl.lib.sentry.hook.cache.DiskCache\nimport com.yl.lib.sentry.hook.cache.Memory"
},
{
"path": "hook-sentry/~/.m2/repository/com/yl/lib/privacy/hook-sentry/0.0.1-SNAPSHOT/maven-metadata-remote.xml",
"chars": 779,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<metadata modelVersion=\"1.1.0\">\n <groupId>com.yl.lib.privacy</groupId>\n <artifa"
},
{
"path": "hook-sentry/~/.m2/repository/com/yl/lib/privacy/hook-sentry/0.0.1-SNAPSHOT/resolver-status.properties",
"chars": 178,
"preview": "#NOTE: This is an internal implementation file, its format can be changed without prior notice.\n#Thu Jan 06 17:27:04 CST"
},
{
"path": "hook-sentry/~/.m2/repository/com/yl/lib/privacy/hook-sentry/maven-metadata-remote.xml",
"chars": 289,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<metadata>\n <groupId>com.yl.lib.privacy</groupId>\n <artifactId>hook-sentry</art"
},
{
"path": "hook-sentry/~/.m2/repository/com/yl/lib/privacy/hook-sentry/resolver-status.properties",
"chars": 178,
"preview": "#NOTE: This is an internal implementation file, its format can be changed without prior notice.\n#Thu Jan 06 17:27:04 CST"
},
{
"path": "plugin-sentry/.gitignore",
"chars": 25,
"preview": "/build\n/plugins\n/~/.m2\n/~"
},
{
"path": "plugin-sentry/build.gradle",
"chars": 1422,
"preview": "apply plugin: 'kotlin'\napply plugin: 'kotlin-kapt'\napply plugin: 'java'\napply plugin: 'groovy'\n//apply plugin: 'maven-pu"
},
{
"path": "plugin-sentry/gradle.properties",
"chars": 25,
"preview": "ARTIFACT_ID=plugin-sentry"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/PrivacySentryPlugin.kt",
"chars": 5819,
"preview": "package com.yl.lib.plugin.sentry\n\nimport com.android.build.api.artifact.ScopedArtifact\nimport com.android.build.api.vari"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/PrivacyTransformTask.kt",
"chars": 8986,
"preview": "package com.yl.lib.plugin.sentry\n\nimport com.android.build.api.variant.Variant\nimport com.didiglobal.booster.kotlinx.NCP"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/extension/PrivacyExtension.kt",
"chars": 1250,
"preview": "package com.yl.lib.plugin.sentry.extension\n\n/**\n * @author yulun\n * @sinice 2021-12-13 17:28\n */\nopen class PrivacyExten"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/transform/PrivacyTransformContext.kt",
"chars": 296,
"preview": "package com.yl.lib.plugin.sentry.transform\n\nimport com.yl.lib.plugin.sentry.extension.PrivacyExtension\nimport org.gradle"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/transform/PrivacyTransformInvocation.kt",
"chars": 843,
"preview": "package com.yl.lib.plugin.sentry.transform\n\nimport com.didiglobal.booster.transform.*\nimport com.yl.lib.plugin.sentry.ex"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/transform/booster/asmtransform/AbsClassTransformer.kt",
"chars": 2223,
"preview": "package com.yl.lib.plugin.sentry.transform.booster.asmtransform\n\nimport com.didiglobal.booster.transform.TransformContex"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/transform/booster/classtransform/collect/ClassProxyCollectTransform.kt",
"chars": 1959,
"preview": "package com.yl.lib.plugin.sentry.transform.booster.classtransform.collect\n\nimport com.didiglobal.booster.transform.Trans"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/transform/booster/classtransform/collect/MethodProxyCollectTransform.kt",
"chars": 4410,
"preview": "package com.yl.lib.plugin.sentry.transform.booster.classtransform.collect\n\nimport com.didiglobal.booster.transform.Trans"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/transform/booster/classtransform/hook/BaseHookTransform.kt",
"chars": 1563,
"preview": "package com.yl.lib.plugin.sentry.transform.booster.classtransform.hook\n\nimport com.didiglobal.booster.transform.Transfor"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/transform/booster/classtransform/hook/ClassProxyTransform.kt",
"chars": 1935,
"preview": "package com.yl.lib.plugin.sentry.transform.booster.classtransform.hook\n\nimport com.didiglobal.booster.transform.Transfor"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/transform/booster/classtransform/hook/FieldProxyTransform.kt",
"chars": 1892,
"preview": "package com.yl.lib.plugin.sentry.transform.booster.classtransform.hook\n\nimport com.didiglobal.booster.transform.Transfor"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/transform/booster/classtransform/hook/FlushHookDataTransform.kt",
"chars": 608,
"preview": "package com.yl.lib.plugin.sentry.transform.booster.classtransform.hook\n\nimport com.didiglobal.booster.transform.Transfor"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/transform/booster/classtransform/hook/MethodHookTransform.kt",
"chars": 3360,
"preview": "package com.yl.lib.plugin.sentry.transform.booster.classtransform.hook\n\nimport com.didiglobal.booster.transform.Transfor"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/transform/booster/classtransform/hook/ServiceHookTransform.kt",
"chars": 2810,
"preview": "package com.yl.lib.plugin.sentry.transform.booster.classtransform.hook\n\nimport com.didiglobal.booster.transform.Transfor"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/transform/booster/processor/PrivacyAssetsProcessor.kt",
"chars": 1803,
"preview": "package com.yl.lib.plugin.sentry.transform.booster.processor\n\nimport com.android.build.gradle.api.ApplicationVariant\nimp"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/transform/booster/processor/PrivacyManifestProcessor.kt",
"chars": 1925,
"preview": "package com.yl.lib.plugin.sentry.transform.booster.processor\n\nimport com.android.build.api.variant.Variant\nimport com.di"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/transform/booster/task/PrivacyManifestTask.kt",
"chars": 6711,
"preview": "package com.yl.lib.plugin.sentry.transform.booster.task\n\nimport com.android.SdkConstants\nimport com.android.build.api.ar"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/transform/booster/transformer/PrivacyBaseTransformer.kt",
"chars": 2843,
"preview": "package com.yl.lib.plugin.sentry.transform.booster.transformer\n\nimport com.didiglobal.booster.annotations.Priority\nimpor"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/transform/manager/HookFieldManager.kt",
"chars": 5890,
"preview": "package com.yl.lib.plugin.sentry.transform.manager\n\n/**\n * @author yulun\n * @since 2022-08-30 11:10\n */\nopen class HookF"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/transform/manager/HookMethodManager.kt",
"chars": 7110,
"preview": "package com.yl.lib.plugin.sentry.transform.manager\n\n/**\n * @author yulun\n * @sinice 2021-12-14 11:39\n * 汇总所有的hook方法配置\n *"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/transform/manager/HookedDataManger.kt",
"chars": 5256,
"preview": "package com.yl.lib.plugin.sentry.transform.manager\n\nimport com.alibaba.fastjson.JSON\nimport com.alibaba.fastjson.annotat"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/transform/manager/ReplaceClassManager.kt",
"chars": 4246,
"preview": "package com.yl.lib.plugin.sentry.transform.manager\n\n/**\n * @author yulun\n * @since 2022-11-18 15:09\n * 类代理,暂时主要用于构造函数代理\n"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/util/PrivacyExt.kt",
"chars": 5614,
"preview": "package com.yl.lib.plugin.sentry.util\n\nimport com.didiglobal.booster.kotlinx.NCPU\nimport com.didiglobal.booster.kotlinx."
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/util/PrivacyMoveAssetsUtil.kt",
"chars": 1617,
"preview": "package com.yl.lib.plugin.sentry.util\n\nimport com.yl.lib.plugin.sentry.transform.manager.HookedDataManger\nimport org.gra"
},
{
"path": "plugin-sentry/src/main/java/com/yl/lib/plugin/sentry/util/PrivacyPluginUtil.kt",
"chars": 2467,
"preview": "package com.yl.lib.plugin.sentry.util\n\nimport org.gradle.api.logging.Logger\nimport kotlin.math.log\n\n/**\n * @author yulun"
},
{
"path": "plugin-sentry/src/main/resources/META-INF/gradle-plugins/com.allenymt.plugin.privacy.properties",
"chars": 65,
"preview": "implementation-class=com.yl.lib.plugin.sentry.PrivacySentryPlugin"
},
{
"path": "plugin-sentry/src/main/resources/META-INF/gradle-plugins/privacy-sentry-plugin.properties",
"chars": 65,
"preview": "implementation-class=com.yl.lib.plugin.sentry.PrivacySentryPlugin"
},
{
"path": "privacy-annotation/.gitignore",
"chars": 6,
"preview": "/build"
},
{
"path": "privacy-annotation/build.gradle",
"chars": 237,
"preview": "apply plugin: 'java-library'\napply plugin: 'kotlin'\napply from: '../publish.gradle' // Changed from 'maven' to 'maven-p"
},
{
"path": "privacy-annotation/gradle.properties",
"chars": 30,
"preview": "ARTIFACT_ID=privacy-annotation"
},
{
"path": "privacy-annotation/src/main/java/com/yl/lib/privacy_annotation/MethodInvokeOpcode.java",
"chars": 781,
"preview": "package com.yl.lib.privacy_annotation;\n\n/**\n * @author yulun\n * @sinice 2021-12-31 11:12\n * 猜测是编译顺序的关系,当插件里引用这个类时,插件里是ko"
},
{
"path": "privacy-annotation/src/main/java/com/yl/lib/privacy_annotation/PrivacyClassBlack.java",
"chars": 369,
"preview": "package com.yl.lib.privacy_annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\n"
},
{
"path": "privacy-annotation/src/main/java/com/yl/lib/privacy_annotation/PrivacyClassProxy.java",
"chars": 373,
"preview": "package com.yl.lib.privacy_annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\n"
},
{
"path": "privacy-annotation/src/main/java/com/yl/lib/privacy_annotation/PrivacyClassReplace.java",
"chars": 389,
"preview": "package com.yl.lib.privacy_annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\n"
},
{
"path": "privacy-annotation/src/main/java/com/yl/lib/privacy_annotation/PrivacyFieldProxy.java",
"chars": 451,
"preview": "package com.yl.lib.privacy_annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\n"
},
{
"path": "privacy-annotation/src/main/java/com/yl/lib/privacy_annotation/PrivacyMethodProxy.java",
"chars": 711,
"preview": "package com.yl.lib.privacy_annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\n"
},
{
"path": "privacy-annotation/~/.m2/repository/com/yl/lib/privacy/privacy-annotation/0.0.1-SNAPSHOT/maven-metadata-remote.xml",
"chars": 786,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<metadata modelVersion=\"1.1.0\">\n <groupId>com.yl.lib.privacy</groupId>\n <artifa"
},
{
"path": "privacy-annotation/~/.m2/repository/com/yl/lib/privacy/privacy-annotation/0.0.1-SNAPSHOT/resolver-status.properties",
"chars": 178,
"preview": "#NOTE: This is an internal implementation file, its format can be changed without prior notice.\n#Fri Dec 31 11:47:02 CST"
},
{
"path": "privacy-annotation/~/.m2/repository/com/yl/lib/privacy/privacy-annotation/0.0.2-SNAPSHOT/maven-metadata-remote.xml",
"chars": 786,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<metadata modelVersion=\"1.1.0\">\n <groupId>com.yl.lib.privacy</groupId>\n <artifa"
},
{
"path": "privacy-annotation/~/.m2/repository/com/yl/lib/privacy/privacy-annotation/0.0.2-SNAPSHOT/resolver-status.properties",
"chars": 178,
"preview": "#NOTE: This is an internal implementation file, its format can be changed without prior notice.\n#Tue Jan 04 15:45:53 CST"
},
{
"path": "privacy-annotation/~/.m2/repository/com/yl/lib/privacy/privacy-annotation/maven-metadata-remote.xml",
"chars": 336,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<metadata>\n <groupId>com.yl.lib.privacy</groupId>\n <artifactId>privacy-annotati"
},
{
"path": "privacy-annotation/~/.m2/repository/com/yl/lib/privacy/privacy-annotation/maven-metadata-remote.xml.sha1",
"chars": 40,
"preview": "b11b5206278051fa303dd8d74cb4cf7dff7bbf6e"
},
{
"path": "privacy-annotation/~/.m2/repository/com/yl/lib/privacy/privacy-annotation/resolver-status.properties",
"chars": 178,
"preview": "#NOTE: This is an internal implementation file, its format can be changed without prior notice.\n#Tue Jan 04 15:45:53 CST"
},
{
"path": "privacy-proxy/.gitignore",
"chars": 6,
"preview": "/build"
},
{
"path": "privacy-proxy/README.md",
"chars": 97,
"preview": "# PrivacySentry\n 如果不想自建代理类,可以直接依赖这个库,同时也可以作为参考\n\n### 组件化项目建议把类拷贝出去自己维护; 插件化项目比较麻烦,还是封装一个aar比较好\n"
},
{
"path": "privacy-proxy/build.gradle",
"chars": 1113,
"preview": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply from: '../publish.gradle' // Changed from 'mav"
},
{
"path": "privacy-proxy/gradle.properties",
"chars": 25,
"preview": "ARTIFACT_ID=privacy-proxy"
},
{
"path": "privacy-proxy/src/main/AndroidManifest.xml",
"chars": 275,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <uses-"
},
{
"path": "privacy-proxy/src/main/java/com/yl/lib/privacy_proxy/PrivacyPermissionProxy.kt",
"chars": 3045,
"preview": "package com.yl.lib.privacy_proxy\n\nimport android.app.Activity\nimport android.os.Build\nimport androidx.annotation.Keep\nim"
},
{
"path": "privacy-proxy/src/main/java/com/yl/lib/privacy_proxy/PrivacyProxyCall.kt",
"chars": 30806,
"preview": "package com.yl.lib.privacy_proxy\n\nimport android.annotation.SuppressLint\nimport android.app.ActivityManager\nimport andro"
},
{
"path": "privacy-proxy/src/main/java/com/yl/lib/privacy_proxy/PrivacyProxyCallJava.java",
"chars": 3591,
"preview": "package com.yl.lib.privacy_proxy;\n\nimport android.content.ClipboardManager;\nimport android.net.wifi.WifiInfo;\nimport and"
},
{
"path": "privacy-proxy/src/main/java/com/yl/lib/privacy_proxy/PrivacyProxyResolver.kt",
"chars": 8145,
"preview": "package com.yl.lib.privacy_proxy\n\nimport android.annotation.SuppressLint\nimport android.content.ContentResolver\nimport a"
},
{
"path": "privacy-proxy/src/main/java/com/yl/lib/privacy_proxy/PrivacyReflectProxy.kt",
"chars": 4784,
"preview": "package com.yl.lib.privacy_proxy\n\nimport android.bluetooth.BluetoothAdapter\nimport android.net.wifi.WifiInfo\nimport andr"
},
{
"path": "privacy-proxy/src/main/java/com/yl/lib/privacy_proxy/PrivacySensorProxy.kt",
"chars": 8011,
"preview": "package com.yl.lib.privacy_proxy\n\nimport android.hardware.Sensor\nimport android.hardware.SensorEventListener\nimport andr"
},
{
"path": "privacy-proxy/src/main/java/com/yl/lib/privacy_proxy/PrivacyTelephonyProxy.kt",
"chars": 14769,
"preview": "package com.yl.lib.privacy_proxy\n\nimport android.Manifest\nimport android.annotation.SuppressLint\nimport android.os.Build"
},
{
"path": "privacy-proxy/src/main/java/com/yl/lib/privacy_proxy/ProxyProxyField.java",
"chars": 822,
"preview": "package com.yl.lib.privacy_proxy;\n\nimport android.os.Build;\n\nimport androidx.annotation.Keep;\n\nimport com.yl.lib.privacy"
},
{
"path": "privacy-replace/.gitignore",
"chars": 6,
"preview": "/build"
},
{
"path": "privacy-replace/README.md",
"chars": 35,
"preview": "# PrivacySentry\n 替换类,主要是想代理构造方法\n"
},
{
"path": "privacy-replace/build.gradle",
"chars": 1108,
"preview": "plugins {\n id 'com.android.library'\n id 'kotlin-android'\n id 'maven-publish'\n}\napply from: '../publish.gradle' "
},
{
"path": "privacy-replace/consumer-rules.pro",
"chars": 0,
"preview": ""
},
{
"path": "privacy-replace/gradle.properties",
"chars": 27,
"preview": "ARTIFACT_ID=privacy-replace"
},
{
"path": "privacy-replace/proguard-rules.pro",
"chars": 750,
"preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
},
{
"path": "privacy-replace/src/androidTest/java/com/vdian/android/wdb/privacy_replace/ExampleInstrumentedTest.kt",
"chars": 706,
"preview": "package com.vdian.android.wdb.privacy_replace\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx"
},
{
"path": "privacy-replace/src/main/AndroidManifest.xml",
"chars": 121,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n</manifest"
},
{
"path": "privacy-replace/src/main/java/com/yl/lib/privacy_replace/PrivacyFile.java",
"chars": 1193,
"preview": "package com.yl.lib.privacy_replace;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport co"
},
{
"path": "privacy-replace/src/main/java/com/yl/lib/privacy_replace/PrivacyFileInputStream.java",
"chars": 1084,
"preview": "package com.yl.lib.privacy_replace;\n\nimport com.yl.lib.privacy_annotation.PrivacyClassReplace;\nimport com.yl.lib.sentry."
},
{
"path": "privacy-replace/src/main/java/com/yl/lib/privacy_replace/PrivacyFileReader.java",
"chars": 999,
"preview": "package com.yl.lib.privacy_replace;\n\nimport com.yl.lib.privacy_annotation.PrivacyClassReplace;\nimport com.yl.lib.sentry."
},
{
"path": "privacy-replace/src/test/java/com/vdian/android/wdb/privacy_replace/ExampleUnitTest.kt",
"chars": 361,
"preview": "package com.vdian.android.wdb.privacy_replace\n\nimport org.junit.Test\n\nimport org.junit.Assert.*\n\n/**\n * Example local un"
},
{
"path": "privacy-test/.gitignore",
"chars": 6,
"preview": "/build"
},
{
"path": "privacy-test/build.gradle",
"chars": 854,
"preview": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\n\nandroid {\n compileSdkVersion 30\n buildToolsVer"
},
{
"path": "privacy-test/proguard-rules.pro",
"chars": 750,
"preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
},
{
"path": "privacy-test/src/main/AndroidManifest.xml",
"chars": 206,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <uses-p"
},
{
"path": "privacy-test/src/main/java/com/yl/lib/privacy_test/PrivacyProxySelfTest2.java",
"chars": 965,
"preview": "package com.yl.lib.privacy_test;\n\nimport android.app.ActivityManager;\n\nimport androidx.annotation.Keep;\n\nimport com.yl.l"
},
{
"path": "privacy-test/src/main/java/com/yl/lib/privacy_test/TestMethod.kt",
"chars": 10147,
"preview": "package com.yl.lib.privacy_test\n\nimport android.Manifest\nimport android.annotation.SuppressLint\nimport android.app.Activ"
},
{
"path": "privacy-test/src/main/java/com/yl/lib/privacy_test/TestMethodInJava.java",
"chars": 1717,
"preview": "package com.yl.lib.privacy_test;\n\nimport android.app.ActivityManager;\nimport android.content.Context;\nimport android.pro"
},
{
"path": "privacy-ui/build/generated/source/buildConfig/debug/com/yl/lib/privacy_ui/BuildConfig.java",
"chars": 318,
"preview": "/**\n * Automatically generated file. DO NOT MODIFY\n */\npackage com.yl.lib.privacy_ui;\n\npublic final class BuildConfig {\n"
},
{
"path": "privacy-ui/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/AndroidManifest.xml",
"chars": 881,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package="
},
{
"path": "privacy-ui/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/output-metadata.json",
"chars": 303,
"preview": "{\n \"version\": 2,\n \"artifactType\": {\n \"type\": \"AAPT_FRIENDLY_MERGED_MANIFESTS\",\n \"kind\": \"Directory\"\n },\n \"appl"
},
{
"path": "privacy-ui/build/intermediates/aar_metadata/debug/aar-metadata.properties",
"chars": 44,
"preview": "aarFormatVersion=1.0\naarMetadataVersion=1.0\n"
},
{
"path": "privacy-ui/build/intermediates/annotation_processor_list/debug/annotationProcessors.json",
"chars": 2,
"preview": "{}"
},
{
"path": "privacy-ui/build/intermediates/compile_symbol_list/debug/R.txt",
"chars": 214158,
"preview": "int anim abc_fade_in 0x0\nint anim abc_fade_out 0x0\nint anim abc_grow_fade_in_from_bottom 0x0\nint anim abc_popup_enter 0x"
},
{
"path": "privacy-ui/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml",
"chars": 478,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merger version=\"3\"><dataSet config=\"main\" ignore_pattern=\"!.svn:!.git:!.ds_store"
},
{
"path": "privacy-ui/build/intermediates/incremental/mergeDebugShaders/merger.xml",
"chars": 478,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merger version=\"3\"><dataSet config=\"main\" ignore_pattern=\"!.svn:!.git:!.ds_store"
},
{
"path": "privacy-ui/build/intermediates/incremental/packageDebugAssets/merger.xml",
"chars": 581,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merger version=\"3\"><dataSet config=\"main\" ignore_pattern=\"!.svn:!.git:!.ds_store"
},
{
"path": "privacy-ui/build/intermediates/incremental/packageDebugResources/compile-file-map.properties",
"chars": 1290,
"preview": "#Wed May 08 10:30:24 CST 2024\n/Users/yulun/GitHub/PrivacySentry/privacy-ui/src/main/res/layout/permission_item_view.xml="
},
{
"path": "privacy-ui/build/intermediates/incremental/packageDebugResources/merger.xml",
"chars": 2702,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merger version=\"3\"><dataSet aapt-namespace=\"http://schemas.android.com/apk/res-a"
},
{
"path": "privacy-ui/build/intermediates/library_manifest/debug/AndroidManifest.xml",
"chars": 881,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package="
},
{
"path": "privacy-ui/build/intermediates/local_only_symbol_list/debug/R-def.txt",
"chars": 369,
"preview": "R_DEF: Internal format may change without notice\nlocal\nid content\nid function_name_tv\nid origin_count_tv\nid origin_metho"
},
{
"path": "privacy-ui/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt",
"chars": 2137,
"preview": "1<?xml version=\"1.0\" encoding=\"utf-8\"?>\n2<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n3 packa"
},
{
"path": "privacy-ui/build/intermediates/navigation_json/debug/navigation.json",
"chars": 2,
"preview": "[]"
},
{
"path": "privacy-ui/build/intermediates/packaged_manifests/debug/output-metadata.json",
"chars": 320,
"preview": "{\n \"version\": 2,\n \"artifactType\": {\n \"type\": \"PACKAGED_MANIFESTS\",\n \"kind\": \"Directory\"\n },\n \"applicationId\": "
},
{
"path": "privacy-ui/build/intermediates/packaged_res/debug/layout/activity_permission_list.xml",
"chars": 805,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "privacy-ui/build/intermediates/packaged_res/debug/layout/activity_real_time_privacy_item.xml",
"chars": 810,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "privacy-ui/build/intermediates/packaged_res/debug/layout/activity_replace_list.xml",
"chars": 800,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "privacy-ui/build/intermediates/packaged_res/debug/layout/permission_item_view.xml",
"chars": 899,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n andr"
},
{
"path": "privacy-ui/build/intermediates/packaged_res/debug/layout/real_tile_item_view.xml",
"chars": 893,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n andr"
},
{
"path": "privacy-ui/build/intermediates/packaged_res/debug/layout/replace_item_view.xml",
"chars": 899,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n andr"
},
{
"path": "privacy-ui/build/intermediates/symbol_list_with_package_name/debug/package-aware-r.txt",
"chars": 134860,
"preview": "com.yl.lib.privacy_ui\nanim abc_fade_in\nanim abc_fade_out\nanim abc_grow_fade_in_from_bottom\nanim abc_popup_enter\nanim abc"
},
{
"path": "privacy-ui/build/kotlin/compileDebugKotlin/caches-jvm/lookups/counters.tab",
"chars": 4,
"preview": "11\n0"
},
{
"path": "privacy-ui/build/outputs/logs/manifest-merger-debug-report.txt",
"chars": 4230,
"preview": "-- Merging decision tree log ---\nmanifest\nADDED from /Users/yulun/GitHub/PrivacySentry/privacy-ui/src/main/AndroidManife"
},
{
"path": "privacy-ui/build/tmp/compileDebugJavaWithJavac/source-classes-mapping.txt",
"chars": 74,
"preview": "com/yl/lib/privacy_ui/BuildConfig.java\n com.yl.lib.privacy_ui.BuildConfig\n"
}
]
// ... and 89 more files (download for full content)
About this extraction
This page contains the full source code of the allenymt/PrivacySentry GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 289 files (902.5 KB), approximately 240.5k tokens, and a symbol index with 83 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.