├── .github ├── dependabot.yml └── workflows │ ├── android-ci.yml │ └── dependency-submission.yml ├── .gitignore ├── CHANGELOG.md ├── GOOGLE_PLAY_PRIVACY_POLICY.md ├── JOTTINGS.md ├── LICENSE ├── README.md ├── SECURITY.md ├── app ├── build.gradle.kts ├── proguard-firebase-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── net │ │ └── imknown │ │ └── android │ │ └── forefrontinfo │ │ └── ExampleInstrumentedTest.kt │ ├── debug │ ├── AndroidManifest.xml │ ├── java │ │ └── net │ │ │ └── imknown │ │ │ └── android │ │ │ └── forefrontinfo │ │ │ └── MyDebugApplication.kt │ └── res │ │ └── values │ │ └── strings.xml │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── lld.json │ ├── ic_launcher-playstore.png │ └── java │ │ └── net │ │ └── imknown │ │ └── android │ │ └── forefrontinfo │ │ ├── base │ │ ├── MyApplication.kt │ │ ├── mvvm │ │ │ ├── BaseFragment.kt │ │ │ ├── BaseViewModel.kt │ │ │ ├── Event.kt │ │ │ ├── SharedPreferenceChangeEventLiveData.kt │ │ │ ├── ToastExt.kt │ │ │ └── ViewBindingExt.kt │ │ ├── res │ │ │ ├── resources.properties │ │ │ ├── values-fr-rFR │ │ │ │ └── strings.xml │ │ │ ├── values-zh-rCN │ │ │ │ └── strings.xml │ │ │ ├── values-zh-rTW │ │ │ │ └── strings.xml │ │ │ └── values │ │ │ │ └── strings.xml │ │ ├── resBackup │ │ │ └── xml │ │ │ │ ├── backup_rules.xml │ │ │ │ └── data_extraction_rules.xml │ │ ├── resLauncher │ │ │ ├── drawable │ │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── values-night │ │ │ │ └── colors.xml │ │ │ └── values │ │ │ │ └── colors.xml │ │ └── resTheme │ │ │ ├── values-night │ │ │ ├── colors.xml │ │ │ └── themes.xml │ │ │ ├── values-v23 │ │ │ └── colors.xml │ │ │ ├── values-v27 │ │ │ └── colors.xml │ │ │ └── values │ │ │ ├── colors.xml │ │ │ └── themes.xml │ │ └── ui │ │ ├── MainActivity.kt │ │ ├── MainViewModel.kt │ │ ├── base │ │ ├── AndroidVersionExt.kt │ │ ├── JsonIo.kt │ │ └── list │ │ │ ├── BaseListFragment.kt │ │ │ ├── BaseListViewModel.kt │ │ │ ├── BasePureListViewModel.kt │ │ │ ├── MyAdapter.kt │ │ │ ├── MyItemDecoration.kt │ │ │ ├── MyModel.kt │ │ │ ├── MyViewHolder.kt │ │ │ └── res │ │ │ ├── layout │ │ │ ├── base_list_fragment.xml │ │ │ └── my_view_holder.xml │ │ │ └── values │ │ │ ├── dimens.xml │ │ │ └── styles.xml │ │ ├── home │ │ ├── GatewayApi.kt │ │ ├── HomeFragment.kt │ │ ├── HomeViewModel.kt │ │ ├── model │ │ │ ├── BaseInfo.kt │ │ │ ├── Lld.kt │ │ │ └── Subtitle.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── ic_home_24dp.xml │ │ │ ├── values-fr-rFR │ │ │ └── strings.xml │ │ │ ├── values-night │ │ │ └── colors.xml │ │ │ ├── values-zh-rCN │ │ │ └── strings.xml │ │ │ ├── values-zh-rTW │ │ │ └── strings.xml │ │ │ └── values │ │ │ ├── attrs.xml │ │ │ ├── colors.xml │ │ │ └── strings.xml │ │ ├── others │ │ ├── OthersFragment.kt │ │ ├── OthersViewModel.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── ic_others_24dp.xml │ │ │ ├── values-fr-rFR │ │ │ └── strings.xml │ │ │ ├── values-zh-rCN │ │ │ └── strings.xml │ │ │ ├── values-zh-rTW │ │ │ └── strings.xml │ │ │ └── values │ │ │ └── strings.xml │ │ ├── prop │ │ ├── PropFragment.kt │ │ ├── PropViewModel.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── ic_prop_24dp.xml │ │ │ ├── values-fr-rFR │ │ │ └── strings.xml │ │ │ ├── values-zh-rCN │ │ │ └── strings.xml │ │ │ ├── values-zh-rTW │ │ │ └── strings.xml │ │ │ └── values │ │ │ └── strings.xml │ │ ├── res │ │ ├── anim │ │ │ └── drop_scale.xml │ │ ├── layout │ │ │ └── main_activity.xml │ │ ├── menu │ │ │ └── bottom_nav_menu.xml │ │ ├── values-fr-rFR │ │ │ └── strings.xml │ │ ├── values-zh-rCN │ │ │ └── strings.xml │ │ ├── values-zh-rTW │ │ │ └── strings.xml │ │ └── values │ │ │ └── strings.xml │ │ └── settings │ │ ├── SettingsFragment.kt │ │ ├── SettingsViewModel.kt │ │ └── res │ │ ├── drawable │ │ └── ic_settings_24dp.xml │ │ ├── layout │ │ └── preference_widget_material_switch.xml │ │ ├── values-fr-rFR │ │ └── strings.xml │ │ ├── values-zh-rCN │ │ └── strings.xml │ │ ├── values-zh-rTW │ │ └── strings.xml │ │ ├── values │ │ ├── arrays.xml │ │ └── strings.xml │ │ └── xml │ │ └── preferences.xml │ └── test │ └── java │ └── net │ └── imknown │ └── android │ └── forefrontinfo │ └── ExampleUnitTest.kt ├── base ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── net │ │ └── imknown │ │ └── android │ │ └── forefrontinfo │ │ └── base │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── net │ │ └── imknown │ │ └── android │ │ └── forefrontinfo │ │ └── base │ │ ├── extension │ │ └── DateTimeExt.kt │ │ ├── property │ │ ├── IProperty.kt │ │ ├── PropertyManager.kt │ │ └── impl │ │ │ ├── DefaultProperty.kt │ │ │ └── ShizukuProperty.kt │ │ └── shell │ │ ├── IShell.kt │ │ ├── ShellManager.kt │ │ ├── ShellResult.kt │ │ └── impl │ │ ├── DefaultShell.kt │ │ └── LibSuShell.kt │ └── test │ └── java │ └── net │ └── imknown │ └── android │ └── forefrontinfo │ └── base │ └── ExampleUnitTest.kt ├── binderDetector ├── build.gradle.kts └── src │ └── main │ ├── cpp │ ├── BinderDetector.cpp │ ├── BinderDetector.h │ └── CMakeLists.txt │ └── kotlin │ └── net │ └── imknown │ └── android │ └── forefrontinfo │ └── binderdetector │ └── BinderDetector.kt ├── build.gradle.kts ├── buildScriptConfig.gradle ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── java │ ├── Ext.kt │ ├── Misc.kt │ └── Versions.kt ├── gradle.properties ├── gradle ├── toml │ ├── android.toml │ ├── build.toml │ ├── google.toml │ ├── kotlin.toml │ └── thirdParty.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── keys └── debug.keystore ├── metadata └── en-US │ ├── changelogs │ └── 62.txt │ ├── full_description.txt │ ├── images │ ├── icon.png │ └── phoneScreenshots │ │ ├── 1.png │ │ └── 2.png │ └── short_description.txt └── settings.gradle.kts /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gradle" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/android-ci.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ "develop" ] 6 | pull_request: 7 | branches: [ "develop" ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | # region [JDK] 19 | - name: Set up JDK 20 | uses: actions/setup-java@v4 21 | with: 22 | java-version: '21' 23 | distribution: 'temurin' 24 | cache: gradle 25 | # endregion [JDK] 26 | 27 | # region [Android SDK] 28 | # https://github.com/android-actions/setup-android 29 | - name: Setup Android SDK 30 | uses: android-actions/setup-android@v3 31 | with: 32 | packages: '' 33 | # https://developer.android.com/tools/sdkmanager#install 34 | # https://github.com/imknown/AndroidLowLevelDetector/blob/develop/gradle/toml/build.toml 35 | - name: Install Android SDK Cmake 36 | run: sdkmanager --install "cmake;4.0.2" 37 | - name: Install Android SDK NDK 38 | # https://github.com/android/ndk/releases 39 | run: sdkmanager --install "ndk;29.0.13113456" 40 | # endregion [Android SDK] 41 | 42 | # region [Gradle] 43 | - name: Grant execute permission for gradlew 44 | run: chmod +x gradlew 45 | 46 | - name: Build with Gradle 47 | run: ./gradlew assembleFossDebug 48 | # endregion [Gradle] -------------------------------------------------------------------------------- /.github/workflows/dependency-submission.yml: -------------------------------------------------------------------------------- 1 | name: Dependency Submission 2 | 3 | on: 4 | push: 5 | branches: [ "develop" ] 6 | paths: [ "**.kts", "**.dcl", "**.toml", "**.properties" ] 7 | pull_request: 8 | branches: [ "develop" ] 9 | paths: [ "**.kts", "**.dcl", "**.toml", "**.properties" ] 10 | workflow_dispatch: 11 | 12 | permissions: 13 | contents: write 14 | 15 | jobs: 16 | # https://github.com/gradle/actions/blob/main/docs/dependency-submission.md 17 | # https://github.com/actions/gradle-build-tools-actions/blob/main/docs/dependency-submission.md 18 | dependency-submission: 19 | 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - name: Set up Gradle 26 | uses: gradle/actions/setup-gradle@v4 27 | 28 | # region [Android SDK] 29 | # https://github.com/android-actions/setup-android 30 | - name: Setup Android SDK 31 | uses: android-actions/setup-android@v3 32 | with: 33 | packages: '' 34 | # https://developer.android.com/tools/sdkmanager#install 35 | # https://github.com/imknown/AndroidLowLevelDetector/blob/develop/gradle/toml/build.toml 36 | - name: Install Android SDK Cmake 37 | run: sdkmanager --install "cmake;4.0.2" 38 | - name: Install Android SDK NDK 39 | # https://github.com/android/ndk/releases 40 | run: sdkmanager --install "ndk;29.0.13113456" 41 | # endregion [Android SDK] 42 | 43 | - name: Run dependency submission 44 | uses: gradle/actions/dependency-submission@v4 45 | with: 46 | gradle-project-path: "." 47 | gradle-build-module: ":app" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ Idea project files 2 | *.iml 3 | /.idea 4 | 5 | # Gradle files 6 | .gradle 7 | /local.properties 8 | 9 | # Generated files 10 | build 11 | .kotlin 12 | 13 | # Android Studio 14 | /captures 15 | 16 | # C/C++ 17 | .cxx 18 | 19 | # Release keystore 20 | /keys/release.jks 21 | 22 | # Firebase 23 | google-services.json 24 | 25 | # macOS 26 | .DS_Store -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | https://github.com/imknown/AndroidLowLevelDetector/releases -------------------------------------------------------------------------------- /GOOGLE_PLAY_PRIVACY_POLICY.md: -------------------------------------------------------------------------------- 1 | # LowLevelDetector privacy policy 2 | 3 | ## Data collections 4 | 5 | For me: 6 | - No user personal privacy data will be collected on user's phone 7 | 8 | For 3rd-parties: 9 | > - https://developers.google.com/assistant/console/firebase-services 10 | > - https://play.google.com/intl/en_us/about/privacy-security-deception 11 | > - https://firebase.google.com/terms 12 | > - https://firebase.google.com/support/privacy 13 | 14 | - Google Play will collect some data for statistics if your device is running Google Play Service, 15 | and Google Firebase will also collect some data for statistics if this app crashes or ANR. 16 | - These data are not requested by me forwardly; 17 | - I can see the backend report on their Consoles; 18 | - I will not share these data to any other people. 19 | 20 | ## Permissions 21 | 22 | - android.permission.INTERNET 23 | - This permission is used to fetch data from remote server; 24 | - This permission is only used when user turns on the switch in settings manually; 25 | - Please notice that some 3rd-parties dependencies, such as Firebase, 26 | still use this permission silently in the background. 27 | 28 | - android.permission.QUERY_ALL_PACKAGES 29 | - This permission is used to fetch target api, package name, etc. of apps; 30 | - This permission is only used to retrieve prebuilt ROM system apps, 31 | and no user installed apps will be retrieved. 32 | 33 | - Used by `Play services`: 34 | - `Play services measurement`: 35 | - android.permission.ACCESS_NETWORK_STATE 36 | - android.permission.INTERNET 37 | - android.permission.WAKE_LOCK 38 | - com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE 39 | - `Play services measurement api`: 40 | - android.permission.ACCESS_ADSERVICES_AD_ID 41 | - android.permission.ACCESS_ADSERVICES_ATTRIBUTION 42 | - android.permission.ACCESS_NETWORK_STAT 43 | - android.permission.INTERNET 44 | - android.permission.WAKE_LOCK 45 | - com.google.android.gms.permission.AD_ID 46 | - `Play services ads identifier`: 47 | - com.google.android.gms.permission.AD_ID 48 | 49 | - Used by `androidx.core`: 50 | - net.imknown.android.forefrontinfo.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION 51 | 52 | ## Security 53 | 54 | - You can review all the code here, so I promise there are no things below inside: 55 | - trojan 56 | - backdoor 57 | - virus 58 | - any other bad things 59 | -------------------------------------------------------------------------------- /JOTTINGS.md: -------------------------------------------------------------------------------- 1 | # JOTTINGS 2 | Minds 3 | 4 | ## Features todo 5 | 6 | - JetPack 7 | - UseCase 8 | - Navigation 9 | - Hilt 10 | - Startup 11 | - Compose 12 | - Transitions 13 | - detect dm-verity (version) 14 | - detect FBE/FDE 15 | - detect HAL 16 | - Android version market share 17 | - OpenGL ES/Vulkan/GPU Driver version 18 | - icu (https://developer.android.com/guide/topics/resources/internationalization) 19 | - unicode 20 | - custom commands, result filters, scroll top, scrollbar (V2.0 ?) 21 | - settings 22 | - root mode 23 | - show commands 24 | - developer mode 25 | - network cache logic 26 | - select json server 27 | - documents 28 | - development references 29 | - user-readable explanation why mine not supported 30 | - copy result 31 | - acknowledge 32 | - etc. 33 | 34 | ## external 35 | 36 | https://android.googlesource.com/platform/external/ 37 | 38 | ## avb 39 | 40 | https://source.android.com/security/verifiedboot/avb 41 | https://source.android.com/security/verifiedboot 42 | https://android.googlesource.com/platform/external/avb/+/master/README.md 43 | https://android.googlesource.com/platform/external/avb/+/master/avbtool 44 | https://android.googlesource.com/platform/external/avb/+/master/libavb/avb_version.h 45 | 46 | ``` sh 47 | adb shell "grep dm- /proc/mounts" 48 | 49 | adb shell getprop ro.boot.avb_version # avbtool version 50 | adb shell getprop ro.boot.veritymode 51 | adb shell getprop ro.boot.verifiedbootstate 52 | adb shell getprop ro.boot.flash.locked 53 | 54 | adb shell getprop ro.boot.secureboot # MIUI only? 55 | adb shell getprop ro.secure # Secure boot 56 | adb shell getprop ro.debuggable 57 | # * eng builds: ro.secure=0 and ro.debuggable=1 58 | # * userdebug builds: ro.secure=1 and ro.debuggable=1 59 | # * user builds: ro.secure=1 and ro.debuggable=0 60 | 61 | adb shell getprop ro.oem_unlock_supported 62 | 63 | adb shell getprop ro.boot.vbmeta.avb_version # required (lib)avb version 64 | adb shell getprop ro.boot.vbmeta.device_state 65 | ``` 66 | 67 | ## busybox 68 | 69 | busybox (https://busybox.net/downloads/binaries/) 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidLowLevelDetector 2 | 3 | [![Android CI](https://github.com/imknown/AndroidLowLevelDetector/actions/workflows/android-ci.yml/badge.svg)](https://github.com/imknown/AndroidLowLevelDetector/actions/workflows/android-ci.yml) 4 | [![Dependabot Updates](https://github.com/imknown/AndroidLowLevelDetector/actions/workflows/dependabot/dependabot-updates/badge.svg)](https://github.com/imknown/AndroidLowLevelDetector/actions/workflows/dependabot/dependabot-updates) 5 | [![Dependency Submission](https://github.com/imknown/AndroidLowLevelDetector/actions/workflows/dependency-submission.yml/badge.svg)](https://github.com/imknown/AndroidLowLevelDetector/actions/workflows/dependency-submission.yml) 6 | 7 | Detect Treble, GSI, Mainline, APEX, system-as-root(SAR), A/B, etc. . 8 | Some source codes refer to [Magisk][Magisk], [OpenGApps][OpenGApps], [TrebleInfo][TrebleInfo], [TrebleCheck][TrebleCheck], etc. . 9 | 10 | [Magisk]:https://github.com/topjohnwu/Magisk 11 | [OpenGApps]:https://github.com/opengapps/opengapps 12 | [TrebleInfo]:https://github.com/penn5/TrebleCheck 13 | [TrebleCheck]:https://github.com/kevintresuelo/treble 14 | 15 | ## Source 16 | 1. https://github.com/imknown/AndroidLowLevelDetector 17 | 1. https://gitee.com/imknown/AndroidLowLevelDetector (Mirror) 18 | 19 | ## Download 20 | 1. https://play.google.com/store/apps/details?id=net.imknown.android.forefrontinfo 21 | 1. https://github.com/imknown/AndroidLowLevelDetector/releases 22 | 1. https://gitee.com/imknown/AndroidLowLevelDetector/releases (Mirror) 23 | 24 | ## Features 25 |
26 | Click me 27 | 28 | - Detect Android version 29 | - Detect Android Build Id version 30 | - Detect Android security patch level 31 | - Detect Vendor security patch level 32 | - Detect Project Mainline module version (Google Play system update) 33 | - Detect Linux kernel 34 | - Detect A/B or A-Only 35 | - Detect Dynamic Partitions 36 | - Detect Dynamic System Update(DSU) 37 | - Detect Project Treble 38 | - Detect GSI compatibility 39 | - Detect Binder bitness 40 | - Detect Process/VM architecture 41 | - Detect Vendor NDK 42 | - Detect System-as-root 43 | - Detect (flattened) APEX 44 | - Detect Toybox 45 | - Detect WebView implement 46 | - Detect outdatedTargetSdkVersion apk 47 | - Dark mode supported 48 | - Online/offline mode (fetching data from remote server or local) 49 | - MultiWindow/FreeForm/Foldable/Landscape supported 50 | - Etc. 51 | 52 |
53 | 54 | ## Contribute 55 | Just use `Pull Request`. 56 | Translations are also welcome. 57 | 58 | ## Build 59 | ### Flavor 60 | - Firebase 61 | Choose `Firebase` in `Build Variants`, then follow Firebase Official Guide. 62 | 63 | - FOSS 64 | Choose `Foss` in `Build Variants`. 65 | 66 | ### Release 67 | Provide the whole following properties in file `$rootDir/local.properties`: 68 | 69 | ``` ini 70 | storeFile= 71 | storePassword= 72 | keyAlias= 73 | keyPassword= 74 | ``` 75 | 76 | The location of `storeFile` can be `../keys/release.jks`. 77 | It has been already ignored in file `$rootDir/.gitingore` by default. 78 | So you can put your own private certificate or signing key there safely. 79 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ### Reporting 4 | We team and community take security bugs seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions. 5 | 6 | To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/imknown/AndroidLowLevelDetector/security/advisories) tab. 7 | 8 | We will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. 9 | 10 | Report security bugs in third-party modules to the person or team maintaining the module. 11 | 12 | Please also pay attention to [release notes](https://github.com/imknown/AndroidLowLevelDetector/blob/develop/CHANGELOG.md). 13 | 14 | ## More About Security 15 | 1. https://docs.github.com/en/code-security/getting-started/adding-a-security-policy-to-your-repository 16 | 1. https://docs.github.com/en/code-security/security-advisories 17 | -------------------------------------------------------------------------------- /app/proguard-firebase-rules.pro: -------------------------------------------------------------------------------- 1 | # https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports-new-sdk?platform=android 2 | 3 | # To preserve the information Crashlytics requires for producing readable crash reports, add the following lines to your Proguard or Dexguard config file: 4 | -keepattributes SourceFile,LineNumberTable # Keep file names and line numbers. 5 | -keep public class * extends java.lang.Exception # Optional: Keep custom exceptions. 6 | 7 | # If you want a faster, obfuscated build with ProGuard, exclude Crashlytics by adding the following lines to your ProGuard config file: 8 | # -keep class com.google.firebase.crashlytics.** { *; } 9 | # -dontwarn com.google.firebase.crashlytics.** -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/net/imknown/android/forefrontinfo/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("net.imknown.android.forefrontinfo", appContext.packageName) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | -------------------------------------------------------------------------------- /app/src/debug/java/net/imknown/android/forefrontinfo/MyDebugApplication.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo 2 | 3 | import android.os.StrictMode 4 | import com.topjohnwu.superuser.Shell 5 | import kotlinx.coroutines.DEBUG_PROPERTY_NAME 6 | import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_ON 7 | import net.imknown.android.forefrontinfo.base.MyApplication 8 | 9 | class MyDebugApplication : MyApplication() { 10 | override fun onCreate() { 11 | super.onCreate() 12 | 13 | StrictMode.enableDefaults() 14 | 15 | Shell.enableVerboseLogging = true 16 | 17 | System.setProperty(DEBUG_PROPERTY_NAME, DEBUG_PROPERTY_VALUE_ON) 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/debug/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | LLD Debug 4 | LLD Leaks 5 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/assets/lld.json: -------------------------------------------------------------------------------- 1 | { 2 | "scheme": 1, 3 | "version": "2025-06-05 20:00 +0800", 4 | "android": { 5 | "securityPatchLevel": "2025-05-05", 6 | "googlePlaySystemUpdates": "2025-05-01", 7 | "build": { 8 | "version": "2025-05-06", 9 | "details": [ 10 | { 11 | "id": "BD4A.250505.003", 12 | "revision": "r31" 13 | }, 14 | { 15 | "id": "BP1A.250505.005", 16 | "revision": "r32" 17 | }, 18 | { 19 | "id": "BP1A.250505.005.A1", 20 | "revision": "r33" 21 | }, 22 | { 23 | "id": "BP1A.250505.005.B1", 24 | "revision": "r34" 25 | }, 26 | { 27 | "id": "BP1A.250505.005.C1", 28 | "revision": "r35" 29 | }, 30 | { 31 | "id": "BP1A.250505.005.D1", 32 | "revision": "r36" 33 | } 34 | ] 35 | }, 36 | "stable": { 37 | "name": "VanillaIceCream", 38 | "api": "35", 39 | "version": "15, VanillaIceCream" 40 | }, 41 | "stablePreview": { 42 | "name": "Baklava", 43 | "api": "36", 44 | "version": "16 QPR1 Beta 1.1, Baklava" 45 | }, 46 | "support": { 47 | "name": "Tiramisu", 48 | "api": "33", 49 | "version": "13, Tiramisu" 50 | }, 51 | "preview": { 52 | "name": "Baklava", 53 | "api": "36", 54 | "version": "16 QPR1 Beta 1.1, Baklava", 55 | "phase": "" 56 | }, 57 | "internal": { 58 | "name": "C", 59 | "api": "37", 60 | "version": "17 Internal, C" 61 | } 62 | }, 63 | "linux": { 64 | "google": { 65 | "versions": [ 66 | "6.12.32", 67 | "6.6.93", 68 | "6.1.141", 69 | "5.15.185", 70 | "5.10.238", 71 | "5.4.294" 72 | ] 73 | }, 74 | "mainline": { 75 | "version": "6.15" 76 | } 77 | }, 78 | "toybox": { 79 | "stable": { 80 | "version": "0.8.11" 81 | }, 82 | "support": { 83 | "version": "0.8.6" 84 | }, 85 | "mainline": { 86 | "version": "0.8.12" 87 | }, 88 | "master": { 89 | "version": "0.8.12" 90 | } 91 | }, 92 | "webView": { 93 | "stable": { 94 | "version": "137.0.7151.72" 95 | }, 96 | "beta": { 97 | "version": "138.0.7204.14" 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imknown/AndroidLowLevelDetector/9b9ffa12d7324fd953cb7eaf34d1b9d66d1e432e/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/MyApplication.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.base 2 | 3 | import android.app.Application 4 | import android.content.BroadcastReceiver 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.content.IntentFilter 8 | import android.content.SharedPreferences 9 | import android.os.Environment 10 | import androidx.annotation.StringRes 11 | import androidx.appcompat.app.AppCompatDelegate 12 | import androidx.lifecycle.LiveData 13 | import androidx.lifecycle.MutableLiveData 14 | import androidx.preference.PreferenceManager 15 | import com.google.android.material.color.DynamicColors 16 | import com.topjohnwu.superuser.Shell 17 | import net.imknown.android.forefrontinfo.BuildConfig 18 | import net.imknown.android.forefrontinfo.R 19 | import net.imknown.android.forefrontinfo.base.mvvm.Event 20 | import net.imknown.android.forefrontinfo.base.property.PropertyManager 21 | import net.imknown.android.forefrontinfo.base.property.impl.DefaultProperty 22 | import net.imknown.android.forefrontinfo.base.shell.ShellManager 23 | import net.imknown.android.forefrontinfo.base.shell.impl.LibSuShell 24 | import java.io.File 25 | 26 | open class MyApplication : Application() { 27 | 28 | companion object { 29 | lateinit var instance: MyApplication 30 | 31 | val homeLanguageEvent: LiveData> by lazy { instance._homeLanguageEvent } 32 | val othersLanguageEvent: LiveData> by lazy { instance._othersLanguageEvent } 33 | val propLanguageEvent: LiveData> by lazy { instance._propLanguageEvent } 34 | 35 | val sharedPreferences: SharedPreferences by lazy { 36 | PreferenceManager.getDefaultSharedPreferences(instance) 37 | } 38 | 39 | fun getDownloadDir() = getFileDir(Environment.DIRECTORY_DOWNLOADS) 40 | 41 | private fun getFileDir(type: String): File { 42 | val externalFilesDir = instance.getExternalFilesDir(type) 43 | return if (externalFilesDir != null && externalFilesDir.exists()) { 44 | externalFilesDir 45 | } else { 46 | instance.filesDir.resolve(type) 47 | }.apply { 48 | mkdirs() 49 | } 50 | } 51 | 52 | fun getMyString(@StringRes resId: Int) = 53 | instance.getString(resId) 54 | 55 | fun getMyString(@StringRes resId: Int, vararg formatArgs: Any?) = 56 | instance.getString(resId, *formatArgs) 57 | 58 | fun setMyTheme(themesValue: String) { 59 | @AppCompatDelegate.NightMode val mode = when (themesValue) { 60 | getMyString(R.string.interface_themes_follow_system_value) -> { 61 | AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM 62 | } 63 | getMyString(R.string.interface_themes_power_saver_value) -> { 64 | AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY 65 | } 66 | getMyString(R.string.interface_themes_always_light_value) -> { 67 | AppCompatDelegate.MODE_NIGHT_NO 68 | } 69 | getMyString(R.string.interface_themes_always_dark_value) -> { 70 | AppCompatDelegate.MODE_NIGHT_YES 71 | } 72 | else -> { 73 | AppCompatDelegate.MODE_NIGHT_YES 74 | } 75 | } 76 | 77 | AppCompatDelegate.setDefaultNightMode(mode) 78 | } 79 | } 80 | 81 | private val _homeLanguageEvent by lazy { MutableLiveData>() } 82 | private val _othersLanguageEvent by lazy { MutableLiveData>() } 83 | private val _propLanguageEvent by lazy { MutableLiveData>() } 84 | 85 | override fun onCreate() { 86 | super.onCreate() 87 | 88 | instance = this@MyApplication 89 | 90 | initTheme() 91 | 92 | initShellAndProperty() 93 | 94 | initLanguage() 95 | } 96 | 97 | private fun initTheme() { 98 | DynamicColors.applyToActivitiesIfAvailable(this) 99 | 100 | val themesValue = sharedPreferences.getString( 101 | getMyString(R.string.interface_themes_key), 102 | getMyString(R.string.interface_themes_follow_system_value) 103 | )!! 104 | setMyTheme(themesValue) 105 | } 106 | 107 | private fun initShellAndProperty() { 108 | Shell.enableVerboseLogging = BuildConfig.DEBUG 109 | Shell.enableLegacyStderrRedirection = true 110 | Shell.setDefaultBuilder( 111 | Shell.Builder.create() 112 | .setFlags(Shell.FLAG_NON_ROOT_SHELL) 113 | // .setInitializers(Shell.Initializer::class.java) 114 | ) 115 | 116 | ShellManager.instance = ShellManager(LibSuShell) 117 | 118 | PropertyManager.instance = PropertyManager(DefaultProperty) 119 | } 120 | 121 | private fun initLanguage() { 122 | val receiver = object : BroadcastReceiver() { 123 | override fun onReceive(context: Context, intent: Intent) { 124 | _homeLanguageEvent.value = Event(Unit) 125 | _othersLanguageEvent.value = Event(Unit) 126 | _propLanguageEvent.value = Event(Unit) 127 | } 128 | } 129 | 130 | registerReceiver(receiver, IntentFilter(Intent.ACTION_LOCALE_CHANGED)) 131 | } 132 | } -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/mvvm/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.base.mvvm 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.viewbinding.ViewBinding 9 | 10 | abstract class BaseFragment : Fragment() { 11 | private var _binding: T? = null 12 | protected val binding by lazy { _binding!! } 13 | 14 | protected abstract val inflate: (LayoutInflater, ViewGroup?, Boolean) -> T 15 | 16 | override fun onCreateView( 17 | inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? 18 | ): View? { 19 | _binding = viewBinding(inflate, container) 20 | return _binding?.root 21 | } 22 | 23 | override fun onDestroyView() { 24 | super.onDestroyView() 25 | 26 | _binding = null 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/mvvm/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.base.mvvm 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.launch 9 | import kotlinx.coroutines.withContext 10 | import net.imknown.android.forefrontinfo.R 11 | import net.imknown.android.forefrontinfo.base.MyApplication 12 | 13 | abstract class BaseViewModel : ViewModel() { 14 | private val _changeScrollBarModeEvent by lazy { MutableLiveData>() } 15 | val changeScrollBarModeEvent: LiveData> by lazy { _changeScrollBarModeEvent } 16 | 17 | fun setScrollBarMode( 18 | scrollBarMode: String 19 | ) = viewModelScope.launch(Dispatchers.Default) { 20 | when (scrollBarMode) { 21 | MyApplication.getMyString(R.string.interface_no_scroll_bar_value) -> { 22 | withContext(Dispatchers.Main) { 23 | _changeScrollBarModeEvent.value = Event(false) 24 | } 25 | } 26 | MyApplication.getMyString(R.string.interface_normal_scroll_bar_value) -> { 27 | withContext(Dispatchers.Main) { 28 | _changeScrollBarModeEvent.value = Event(true) 29 | } 30 | } 31 | MyApplication.getMyString(R.string.interface_fast_scroll_bar_value) -> { 32 | withContext(Dispatchers.Main) { 33 | _changeScrollBarModeEvent.value = Event(false) 34 | } 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/mvvm/Event.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.imknown.android.forefrontinfo.base.mvvm 18 | 19 | import androidx.lifecycle.Observer 20 | 21 | /** 22 | * Used as a wrapper for data that is exposed via a LiveData that represents an event. 23 | * 24 | * Copied from: 25 | * https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150 26 | * https://github.com/android/architecture-samples/blob/master/app/src/main/java/com/example/android/architecture/blueprints/todoapp/Event.kt 27 | */ 28 | open class Event(private val content: T) { 29 | 30 | @Suppress("MemberVisibilityCanBePrivate") 31 | var hasBeenHandled = false 32 | private set // Allow external read but not write 33 | 34 | /** 35 | * Returns the content and prevents its use again. 36 | */ 37 | fun getContentIfNotHandled(): T? { 38 | return if (hasBeenHandled) { 39 | null 40 | } else { 41 | hasBeenHandled = true 42 | content 43 | } 44 | } 45 | 46 | /** 47 | * Returns the content, even if it's already been handled. 48 | */ 49 | fun peekContent(): T = content 50 | } 51 | 52 | /** 53 | * An [Observer] for [Event]s, simplifying the pattern of checking if the [Event]'s content has 54 | * already been handled. 55 | * 56 | * [onEventUnhandledContent] is *only* called if the [Event]'s contents has not been handled. 57 | */ 58 | class EventObserver(private val onEventUnhandledContent: (T) -> Unit) : Observer> { 59 | override fun onChanged(value: Event) { 60 | value.getContentIfNotHandled()?.let { 61 | onEventUnhandledContent(it) 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/mvvm/SharedPreferenceChangeEventLiveData.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.base.mvvm 2 | 3 | import android.content.SharedPreferences 4 | import androidx.lifecycle.LiveData 5 | import kotlinx.coroutines.CoroutineScope 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.launch 8 | import kotlinx.coroutines.withContext 9 | 10 | /** 11 | * Copied from: 12 | * https://gist.github.com/rharter/1df1cd72ce4e9d1801bd2d49f2a96810 13 | */ 14 | abstract class SharedPreferenceChangeEventLiveData( 15 | private val scope: CoroutineScope, 16 | protected val sharedPrefs: SharedPreferences, 17 | private val key: String, 18 | private val defValue: T 19 | ) : LiveData>() { 20 | 21 | private val preferenceChangeListener = 22 | SharedPreferences.OnSharedPreferenceChangeListener { _, key -> 23 | scope.launch(Dispatchers.IO) { 24 | if (key == this@SharedPreferenceChangeEventLiveData.key) { 25 | val event = getValueFromPreferences(key, defValue) 26 | 27 | withContext(Dispatchers.Main) { 28 | value = event 29 | } 30 | } 31 | } 32 | } 33 | 34 | abstract suspend fun getValueFromPreferences(key: String, defValue: T): Event 35 | 36 | override fun onActive() { 37 | super.onActive() 38 | // value = getValueFromPreferences(key, defValue) 39 | sharedPrefs.registerOnSharedPreferenceChangeListener(preferenceChangeListener) 40 | } 41 | 42 | override fun onInactive() { 43 | super.onInactive() 44 | sharedPrefs.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener) 45 | } 46 | } 47 | 48 | fun SharedPreferences.stringEventLiveData(scope: CoroutineScope, key: String, defValue: String) = 49 | object : SharedPreferenceChangeEventLiveData(scope, this, key, defValue) { 50 | override suspend fun getValueFromPreferences(key: String, defValue: String?) = 51 | Event(sharedPrefs.getString(key, defValue)) 52 | } 53 | 54 | fun SharedPreferences.booleanEventLiveData(scope: CoroutineScope, key: String, defValue: Boolean) = 55 | object : SharedPreferenceChangeEventLiveData(scope, this, key, defValue) { 56 | override suspend fun getValueFromPreferences(key: String, defValue: Boolean) = 57 | Event(sharedPrefs.getBoolean(key, defValue)) 58 | } -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/mvvm/ToastExt.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.base.mvvm 2 | 3 | import android.content.Context 4 | import android.widget.Toast 5 | import androidx.annotation.StringRes 6 | import androidx.annotation.UiContext 7 | 8 | fun @receiver:UiContext Context.toast(@StringRes resId: Int) = 9 | Toast.makeText(this, resId, Toast.LENGTH_LONG).show() 10 | 11 | fun @receiver:UiContext Context.toast(text: String) = 12 | Toast.makeText(this, text, Toast.LENGTH_LONG).show() -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/mvvm/ViewBindingExt.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.base.mvvm 2 | 3 | import android.app.Activity 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.core.view.WindowInsetsCompat 8 | import androidx.fragment.app.Fragment 9 | import androidx.viewbinding.ViewBinding 10 | 11 | val windowInsetsCompatTypes = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() 12 | 13 | inline fun Activity.viewBinding( 14 | crossinline inflate: (LayoutInflater) -> T 15 | ) = lazy { inflate(layoutInflater) } 16 | 17 | inline fun Fragment.viewBinding( 18 | crossinline inflate: (LayoutInflater, ViewGroup?, Boolean) -> T, 19 | container: ViewGroup? 20 | ) = inflate(layoutInflater, container, false) 21 | 22 | inline fun ViewGroup.viewBinding( 23 | crossinline inflate: (LayoutInflater, ViewGroup?, Boolean) -> T 24 | ) = inflate(LayoutInflater.from(context), this, false) 25 | 26 | inline fun View.viewBinding( 27 | crossinline inflate: (LayoutInflater, ViewGroup?, Boolean) -> T 28 | ) = lazy { inflate(LayoutInflater.from(context), null, false) } 29 | 30 | inline fun View.viewBinding( 31 | crossinline bind: (View) -> T 32 | ) = lazy { bind(this) } -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/res/resources.properties: -------------------------------------------------------------------------------- 1 | unqualifiedResLocale=en-US -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/res/values-fr-rFR/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | LowLevelDetector 4 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/res/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 底层探测器 4 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 底層檢測器 4 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | LowLevelDetector 4 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/resBackup/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/resBackup/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 13 | 14 | 17 | 20 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imknown/AndroidLowLevelDetector/9b9ffa12d7324fd953cb7eaf34d1b9d66d1e432e/app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imknown/AndroidLowLevelDetector/9b9ffa12d7324fd953cb7eaf34d1b9d66d1e432e/app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imknown/AndroidLowLevelDetector/9b9ffa12d7324fd953cb7eaf34d1b9d66d1e432e/app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imknown/AndroidLowLevelDetector/9b9ffa12d7324fd953cb7eaf34d1b9d66d1e432e/app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imknown/AndroidLowLevelDetector/9b9ffa12d7324fd953cb7eaf34d1b9d66d1e432e/app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imknown/AndroidLowLevelDetector/9b9ffa12d7324fd953cb7eaf34d1b9d66d1e432e/app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imknown/AndroidLowLevelDetector/9b9ffa12d7324fd953cb7eaf34d1b9d66d1e432e/app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imknown/AndroidLowLevelDetector/9b9ffa12d7324fd953cb7eaf34d1b9d66d1e432e/app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imknown/AndroidLowLevelDetector/9b9ffa12d7324fd953cb7eaf34d1b9d66d1e432e/app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imknown/AndroidLowLevelDetector/9b9ffa12d7324fd953cb7eaf34d1b9d66d1e432e/app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #069868 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/resLauncher/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #069868 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/resTheme/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/resTheme/values-v23/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @android:color/transparent 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/resTheme/values-v27/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @android:color/transparent 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/base/resTheme/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 63 | 64 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.ui 2 | 3 | import android.os.Bundle 4 | import android.widget.TextView 5 | import androidx.activity.viewModels 6 | import androidx.annotation.IdRes 7 | import androidx.appcompat.app.AppCompatActivity 8 | import androidx.appcompat.widget.Toolbar 9 | import androidx.core.view.ViewCompat 10 | import androidx.core.view.WindowCompat 11 | import androidx.core.view.updatePadding 12 | import androidx.fragment.app.Fragment 13 | import androidx.fragment.app.FragmentManager 14 | import androidx.fragment.app.FragmentTransaction 15 | import androidx.fragment.app.commitNow 16 | import androidx.lifecycle.lifecycleScope 17 | import kotlinx.coroutines.Dispatchers 18 | import kotlinx.coroutines.launch 19 | import net.imknown.android.forefrontinfo.R 20 | import net.imknown.android.forefrontinfo.base.mvvm.viewBinding 21 | import net.imknown.android.forefrontinfo.base.mvvm.windowInsetsCompatTypes 22 | import net.imknown.android.forefrontinfo.databinding.MainActivityBinding 23 | import net.imknown.android.forefrontinfo.ui.home.HomeFragment 24 | import net.imknown.android.forefrontinfo.ui.others.OthersFragment 25 | import net.imknown.android.forefrontinfo.ui.prop.PropFragment 26 | import net.imknown.android.forefrontinfo.ui.settings.SettingsFragment 27 | 28 | class MainActivity : AppCompatActivity() { 29 | 30 | internal val binding by viewBinding(MainActivityBinding::inflate) 31 | 32 | private val mainViewModel by viewModels() 33 | 34 | override fun onCreate(savedInstanceState: Bundle?) { 35 | super.onCreate(savedInstanceState) 36 | 37 | setContentView(binding.root) 38 | 39 | initWindowInsets() 40 | 41 | initViews() 42 | 43 | if (savedInstanceState == null) { 44 | supportFragmentManager.switch(R.id.navigation_home, true) 45 | } 46 | 47 | // mainViewModel.dealWithShizuku() 48 | } 49 | 50 | override fun onDestroy() { 51 | super.onDestroy() 52 | 53 | // mainViewModel.removeRequestPermissionResultListener() 54 | } 55 | 56 | private fun initWindowInsets() { 57 | // https://developer.android.com/design/ui/mobile/guides/foundations/system-bars#button_modes 58 | // https://developer.android.com/develop/ui/views/layout/edge-to-edge#create-transparent 59 | // https://developer.android.com/develop/ui/views/layout/edge-to-edge-manually#change-color 60 | // https://developer.android.com/develop/ui/compose/layouts/system-bars#create-transparent 61 | // enableEdgeToEdge() 62 | WindowCompat.setDecorFitsSystemWindows(window, false) 63 | 64 | // https://developer.android.com/develop/ui/views/layout/edge-to-edge#material-components 65 | // https://developer.android.com/develop/ui/compose/layouts/insets#material3-components 66 | ViewCompat.setOnApplyWindowInsetsListener(binding.appBar) { appBar, windowInsetsCompat -> 67 | val insets = windowInsetsCompat.getInsets(windowInsetsCompatTypes) 68 | appBar.updatePadding( 69 | left = insets.left, 70 | right = insets.right, 71 | top = insets.top 72 | ) 73 | 74 | windowInsetsCompat 75 | } 76 | 77 | // https://developer.android.com/develop/ui/views/layout/edge-to-edge#material-components 78 | // https://developer.android.com/develop/ui/compose/layouts/insets#material3-components 79 | ViewCompat.setOnApplyWindowInsetsListener(binding.bottomNavigationView) { bnView, windowInsetsCompat -> 80 | val insets = windowInsetsCompat.getInsets(windowInsetsCompatTypes) 81 | bnView.updatePadding( 82 | left = insets.left, 83 | right = insets.right, 84 | bottom = insets.bottom 85 | ) 86 | 87 | windowInsetsCompat 88 | } 89 | } 90 | 91 | private fun initViews() { 92 | initSubtitle() 93 | 94 | binding.bottomNavigationView.setOnItemSelectedListener { 95 | supportFragmentManager.switch(it.itemId) 96 | 97 | true 98 | } 99 | } 100 | 101 | private fun initSubtitle() { 102 | setSupportActionBar(binding.toolbar) 103 | 104 | lifecycleScope.launch(Dispatchers.IO) { 105 | val subtitleTextView = Toolbar::class.java 106 | .getDeclaredField("mSubtitleTextView") 107 | .apply { isAccessible = true } 108 | .get(binding.toolbar) as TextView 109 | subtitleTextView.isVerticalScrollBarEnabled = false 110 | } 111 | } 112 | 113 | private fun FragmentManager.switch(@IdRes selectedId: Int, isFirst: Boolean = false) { 114 | val lastId = mainViewModel.lastId 115 | if (selectedId == lastId && !isFirst) { 116 | return 117 | } 118 | 119 | commitNow(true) { 120 | setCustomAnimations(R.anim.drop_scale, FragmentTransaction.TRANSIT_NONE) 121 | 122 | val selectedFragment = findFragmentByTag(selectedId.toString()) 123 | ?: createFragment(selectedId) 124 | show(selectedFragment) 125 | 126 | findFragmentByTag(lastId.toString())?.let { 127 | hide(it) 128 | } 129 | 130 | setReorderingAllowed(true) 131 | } 132 | 133 | mainViewModel.setSavedStateLastId(selectedId) 134 | } 135 | 136 | private fun FragmentTransaction.createFragment(@IdRes id: Int): Fragment = when (id) { 137 | R.id.navigation_home -> HomeFragment.newInstance() 138 | R.id.navigation_others -> OthersFragment.newInstance() 139 | R.id.navigation_prop -> PropFragment.newInstance() 140 | R.id.navigation_settings -> SettingsFragment.newInstance() 141 | else -> throw Exception() 142 | }.apply { 143 | add(R.id.container, this, id.toString()) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.ui 2 | 3 | import androidx.annotation.IdRes 4 | import androidx.lifecycle.SavedStateHandle 5 | import androidx.lifecycle.ViewModel 6 | import net.imknown.android.forefrontinfo.R 7 | 8 | class MainViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { 9 | 10 | companion object { 11 | private const val SAVED_STATE_HANDLE_KEY_LAST_ID = "SAVED_STATE_HANDLE_KEY_LAST_ID" 12 | } 13 | 14 | @IdRes 15 | var lastId = getSavedStateLastId() 16 | 17 | fun setSavedStateLastId(@IdRes id: Int) { 18 | lastId = id 19 | savedStateHandle[SAVED_STATE_HANDLE_KEY_LAST_ID] = id 20 | } 21 | 22 | @IdRes 23 | private fun getSavedStateLastId() = savedStateHandle[SAVED_STATE_HANDLE_KEY_LAST_ID] 24 | ?: R.id.navigation_home 25 | 26 | // private val requestPermissionResultListener = 27 | // Shizuku.OnRequestPermissionResultListener { _, grantResult -> 28 | // removeRequestPermissionResultListener() 29 | // 30 | // if (grantResult == PackageManager.PERMISSION_GRANTED) { 31 | // PropertyManager.instance = PropertyManager(ShizukuProperty) 32 | // } 33 | // } 34 | // 35 | // fun dealWithShizuku() { 36 | // if (Shizuku.isPreV11() || !Shizuku.pingBinder()) { 37 | // return 38 | // } 39 | // 40 | // if (Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) { 41 | // PropertyManager.instance = PropertyManager(ShizukuProperty) 42 | // } else if (Shizuku.shouldShowRequestPermissionRationale()) { 43 | // // Doing nothing is OK 44 | // } else { 45 | // val shizukuFirstAskingKey = 46 | // MyApplication.getMyString(R.string.function_shizuku_first_asking_key) 47 | // val isShizukuFirstAsking = 48 | // MyApplication.sharedPreferences.getBoolean(shizukuFirstAskingKey, true) 49 | // if (!isShizukuFirstAsking) { 50 | // return 51 | // } 52 | // 53 | // MyApplication.sharedPreferences.edit { 54 | // putBoolean(shizukuFirstAskingKey, false) 55 | // } 56 | // 57 | // addRequestPermissionResultListener() 58 | // 59 | // Shizuku.requestPermission(0) 60 | // } 61 | // } 62 | // 63 | // private fun addRequestPermissionResultListener() { 64 | // Shizuku.addRequestPermissionResultListener(requestPermissionResultListener) 65 | // } 66 | // 67 | // fun removeRequestPermissionResultListener() { 68 | // Shizuku.removeRequestPermissionResultListener(requestPermissionResultListener) 69 | // } 70 | } -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/base/AndroidVersionExt.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.ui.base 2 | 3 | import android.app.ActivityManager 4 | import android.content.Context 5 | import android.os.Build 6 | import androidx.core.content.ContextCompat 7 | import net.imknown.android.forefrontinfo.ui.home.model.Lld 8 | 9 | private const val CODENAME_RELEASE = "REL" 10 | 11 | fun isAtLeastStableAndroid6() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M 12 | fun isAtLeastStableAndroid7() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N 13 | fun isAtLeastStableAndroid8() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O 14 | fun isAtLeastStableAndroid8P1() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 15 | fun isAtLeastStableAndroid9() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P 16 | fun isAtLeastStableAndroid10() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q 17 | fun isAtLeastStableAndroid11() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R 18 | fun isAtLeastStableAndroid12() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S 19 | fun isAtLeastStableAndroid13() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU 20 | // fun isAtLeastStableAndroid14() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE 21 | // fun isAtLeastStableAndroid15() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM 22 | 23 | fun isStableAndroid() = Build.VERSION.CODENAME == CODENAME_RELEASE 24 | fun isPreviewAndroid() = !isStableAndroid() 25 | 26 | fun isLatestStableAndroid(lld: Lld) = isStableAndroid() 27 | && Build.VERSION.SDK_INT >= lld.android.stable.api.toInt() 28 | 29 | fun isLatestPreviewAndroid(lld: Lld) = isPreviewAndroid() 30 | && getAndroidVersionName() >= lld.android.preview.name[0].toString() 31 | 32 | fun isSupportedByUpstreamAndroid(lld: Lld) = isStableAndroid() 33 | && Build.VERSION.SDK_INT >= lld.android.support.api.toInt() 34 | 35 | /** For Android 11+, use `Build.VERSION.RELEASE_OR_CODENAME` (`ro.build.version.release_or_codename`) */ 36 | fun getAndroidVersionName(): String = if (isStableAndroid()) { 37 | Build.VERSION.RELEASE 38 | } else { 39 | if (isAtLeastStableAndroid13()) { 40 | "${Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY}, " 41 | } else { 42 | "" 43 | } + Build.VERSION.CODENAME 44 | } 45 | 46 | fun Context.isGoEdition() = isAtLeastStableAndroid8P1() && isLowRamDevice() 47 | 48 | private fun Context.isLowRamDevice() = ContextCompat.getSystemService( 49 | this, ActivityManager::class.java 50 | )?.isLowRamDevice == true 51 | 52 | fun getAndroidApiLevel() = if (isStableAndroid()) { 53 | Build.VERSION.SDK_INT 54 | } else { 55 | Build.VERSION_CODES.CUR_DEVELOPMENT 56 | } 57 | 58 | //fun Context.getAndroidApiLevelDynamic() = packageManager.getApplicationInfo( 59 | // "android", 0 60 | //).targetSdkVersion -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/base/JsonIo.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.ui.base 2 | 3 | import android.content.res.AssetManager 4 | import android.util.Log 5 | import kotlinx.serialization.json.Json 6 | import net.imknown.android.forefrontinfo.R 7 | import net.imknown.android.forefrontinfo.base.MyApplication 8 | import net.imknown.android.forefrontinfo.ui.home.model.Lld 9 | import java.io.BufferedReader 10 | import java.io.File 11 | import java.io.FileWriter 12 | 13 | object JsonIo { 14 | const val LLD_JSON_NAME = "lld.json" 15 | 16 | val savedLldJsonFile by lazy { 17 | File(MyApplication.getDownloadDir(), LLD_JSON_NAME) 18 | } 19 | 20 | private fun deleteDirtyDirectory() { 21 | if (!savedLldJsonFile.deleteRecursively()) { 22 | Log.e(javaClass.simpleName, "Delete dirty directory failed.") 23 | } else { 24 | Log.i(javaClass.simpleName, "Dirty directory deleted.") 25 | } 26 | } 27 | 28 | fun copyJsonIfNeeded() { 29 | var shouldCopy = false 30 | 31 | if (savedLldJsonFile.exists()) { 32 | if (savedLldJsonFile.isDirectory) { 33 | deleteDirtyDirectory() 34 | shouldCopy = true 35 | } else { 36 | val savedLldVersion = try { 37 | savedLldJsonFile.fromJson().version 38 | } catch (e: Exception) { 39 | val message = 40 | MyApplication.getMyString(R.string.lld_json_parse_failed, e.message) 41 | Log.e(javaClass.simpleName, message, e) 42 | null 43 | } 44 | 45 | if (savedLldVersion == null) { 46 | shouldCopy = true 47 | } else { 48 | val assetLldVersion = getAssetLldVersion(MyApplication.instance.assets) 49 | if (assetLldVersion != null && savedLldVersion < assetLldVersion) { 50 | shouldCopy = true 51 | } 52 | } 53 | } 54 | } else { 55 | shouldCopy = true 56 | } 57 | 58 | if (!shouldCopy) { 59 | return 60 | } 61 | 62 | copyAssetsFileToContextFilesDir( 63 | MyApplication.instance.assets, 64 | savedLldJsonFile, 65 | LLD_JSON_NAME 66 | ) 67 | } 68 | 69 | /** 70 | * https://discuss.kotlinlang.org/t/copy-file-from-res/7068/10 71 | */ 72 | private fun copyAssetsFileToContextFilesDir( 73 | assets: AssetManager, 74 | savedFile: File, 75 | assetName: String 76 | ) { 77 | assets.open(assetName).use { inStream -> 78 | savedFile.parentFile?.mkdirs() 79 | 80 | savedFile.outputStream().use { outStream -> 81 | outStream.let { 82 | inStream.copyTo(it) 83 | } 84 | } 85 | } 86 | } 87 | 88 | fun saveLldJsonFile(lldString: String) { 89 | if (savedLldJsonFile.exists() && savedLldJsonFile.isDirectory) { 90 | deleteDirtyDirectory() 91 | } 92 | 93 | FileWriter(savedLldJsonFile).use { 94 | it.write(lldString) 95 | } 96 | } 97 | 98 | fun getAssetLld(assets: AssetManager): Lld? = 99 | try { 100 | assets.open(LLD_JSON_NAME) 101 | .bufferedReader() 102 | .use(BufferedReader::readText) 103 | .fromJson() 104 | } catch (e: Exception) { 105 | e.printStackTrace() 106 | null 107 | } 108 | 109 | fun getAssetLldVersion(assets: AssetManager): String? = getAssetLld(assets)?.version 110 | } 111 | 112 | val json by lazy { Json { ignoreUnknownKeys = true } } 113 | 114 | @Throws 115 | inline fun File.fromJson(): T = 116 | readText().fromJson() 117 | 118 | @Throws 119 | inline fun String.fromJson(): T = 120 | json.decodeFromString(this) -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/base/list/BaseListFragment.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.ui.base.list 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.core.view.ViewCompat 8 | import androidx.core.view.doOnLayout 9 | import androidx.core.view.updatePadding 10 | import androidx.lifecycle.LiveData 11 | import com.google.android.material.color.MaterialColors 12 | import net.imknown.android.forefrontinfo.R 13 | import net.imknown.android.forefrontinfo.base.mvvm.BaseFragment 14 | import net.imknown.android.forefrontinfo.base.mvvm.Event 15 | import net.imknown.android.forefrontinfo.base.mvvm.EventObserver 16 | import net.imknown.android.forefrontinfo.base.mvvm.toast 17 | import net.imknown.android.forefrontinfo.base.mvvm.windowInsetsCompatTypes 18 | import net.imknown.android.forefrontinfo.databinding.BaseListFragmentBinding 19 | import net.imknown.android.forefrontinfo.ui.MainActivity 20 | import com.google.android.material.R as materialR 21 | 22 | abstract class BaseListFragment : BaseFragment() { 23 | 24 | override val inflate: (LayoutInflater, ViewGroup?, Boolean) -> BaseListFragmentBinding = 25 | BaseListFragmentBinding::inflate 26 | 27 | protected val myAdapter by lazy { MyAdapter() } 28 | 29 | protected abstract val listViewModel: BaseListViewModel 30 | 31 | protected fun observeLanguageEvent(event: LiveData>) { 32 | event.observe(viewLifecycleOwner, EventObserver { 33 | binding.swipeRefreshLayout.isRefreshing = true 34 | 35 | listViewModel.collectModels() 36 | }) 37 | } 38 | 39 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 40 | // super.onViewCreated(view, savedInstanceState) 41 | 42 | initWindowInsets() 43 | 44 | initViews(savedInstanceState) 45 | 46 | listViewModel.changeScrollBarModeEvent.observe(viewLifecycleOwner, EventObserver { 47 | binding.recyclerView.isVerticalScrollBarEnabled = it 48 | }) 49 | 50 | listViewModel.models.observe(viewLifecycleOwner) { 51 | listViewModel.showModels(myAdapter.myModels, it) 52 | } 53 | 54 | listViewModel.showModelsEvent.observe(viewLifecycleOwner, EventObserver { 55 | myAdapter.notifyDataSetChanged() 56 | 57 | binding.swipeRefreshLayout.isRefreshing = false 58 | }) 59 | 60 | listViewModel.showErrorEvent.observe(viewLifecycleOwner, EventObserver { 61 | context?.toast(it) 62 | 63 | binding.swipeRefreshLayout.isRefreshing = false 64 | }) 65 | 66 | listViewModel.scrollBarMode.observe(viewLifecycleOwner, EventObserver { 67 | it?.let { scrollBarMode -> 68 | listViewModel.setScrollBarMode(scrollBarMode) 69 | } 70 | }) 71 | 72 | listViewModel.init(savedInstanceState) 73 | } 74 | 75 | private fun initWindowInsets() { 76 | ViewCompat.setOnApplyWindowInsetsListener(binding.recyclerView) { rv, windowInsetsCompat -> 77 | val insets = windowInsetsCompat.getInsets(windowInsetsCompatTypes) 78 | (activity as? MainActivity)?.binding?.bottomNavigationView?.doOnLayout { bnv -> 79 | rv.updatePadding( 80 | left = insets.left, 81 | right = insets.right, 82 | bottom = bnv.height 83 | ) 84 | } 85 | 86 | windowInsetsCompat 87 | } 88 | } 89 | 90 | private fun initViews(savedInstanceState: Bundle?) { 91 | val color = MaterialColors.getColor(binding.root, materialR.attr.colorOnPrimaryContainer) 92 | binding.swipeRefreshLayout.setColorSchemeColors(color) 93 | val backgroundColor = 94 | MaterialColors.getColor(binding.root, materialR.attr.colorPrimaryContainer) 95 | binding.swipeRefreshLayout.setProgressBackgroundColorSchemeColor(backgroundColor) 96 | 97 | if (listViewModel.hasNoData(savedInstanceState)) { 98 | // When activity is recreated, data is filled by memory. 99 | // It is fast. No progress indicator needed indeed. 100 | binding.swipeRefreshLayout.isRefreshing = true 101 | } 102 | 103 | binding.swipeRefreshLayout.setOnRefreshListener { 104 | listViewModel.collectModels() 105 | } 106 | 107 | binding.recyclerView.apply { 108 | setHasFixedSize(true) 109 | 110 | addItemDecoration( 111 | MyItemDecoration( 112 | resources.getDimensionPixelSize(R.dimen.item_divider_space_horizontal), 113 | resources.getDimensionPixelSize(R.dimen.item_divider_space_vertical) 114 | ) 115 | ) 116 | 117 | adapter = myAdapter 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/base/list/BaseListViewModel.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.ui.base.list 2 | 3 | import android.os.Bundle 4 | import androidx.annotation.StringRes 5 | import androidx.lifecycle.LiveData 6 | import androidx.lifecycle.MutableLiveData 7 | import androidx.lifecycle.viewModelScope 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.Job 10 | import kotlinx.coroutines.launch 11 | import kotlinx.coroutines.withContext 12 | import net.imknown.android.forefrontinfo.BuildConfig 13 | import net.imknown.android.forefrontinfo.R 14 | import net.imknown.android.forefrontinfo.base.MyApplication 15 | import net.imknown.android.forefrontinfo.base.mvvm.BaseViewModel 16 | import net.imknown.android.forefrontinfo.base.mvvm.Event 17 | import net.imknown.android.forefrontinfo.base.mvvm.stringEventLiveData 18 | import net.imknown.android.forefrontinfo.base.property.PropertyManager 19 | import net.imknown.android.forefrontinfo.base.shell.ShellManager 20 | import net.imknown.android.forefrontinfo.base.shell.ShellResult 21 | 22 | abstract class BaseListViewModel : BaseViewModel() { 23 | private val _models by lazy { MutableLiveData>() } 24 | val models: LiveData> by lazy { _models } 25 | 26 | private val _showModelsEvent by lazy { MutableLiveData>() } 27 | val showModelsEvent: LiveData> by lazy { _showModelsEvent } 28 | 29 | private val _showErrorEvent by lazy { MutableLiveData>() } 30 | val showErrorEvent: LiveData> by lazy { _showErrorEvent } 31 | 32 | private val _scrollBarMode by lazy { 33 | MyApplication.sharedPreferences.stringEventLiveData( 34 | viewModelScope, 35 | MyApplication.getMyString(R.string.interface_scroll_bar_key), 36 | MyApplication.getMyString(R.string.interface_no_scroll_bar_value) 37 | ) 38 | } 39 | val scrollBarMode: LiveData> by lazy { _scrollBarMode } 40 | 41 | abstract fun collectModels(): Job 42 | 43 | fun init(savedInstanceState: Bundle?) { 44 | viewModelScope.launch(Dispatchers.IO) { 45 | val scrollBarMode = MyApplication.sharedPreferences.getString( 46 | MyApplication.getMyString(R.string.interface_scroll_bar_key), 47 | MyApplication.getMyString(R.string.interface_no_scroll_bar_value) 48 | )!! 49 | setScrollBarMode(scrollBarMode) 50 | 51 | // When activity is recreated, use LiveData to restore the data 52 | if (hasNoData(savedInstanceState)) { 53 | collectModels() 54 | } 55 | } 56 | } 57 | 58 | fun hasNoData(savedInstanceState: Bundle?) = 59 | (savedInstanceState == null || _showModelsEvent.value == null) && _showErrorEvent.value == null 60 | 61 | protected suspend fun setModels(tempModels: ArrayList) = 62 | withContext(Dispatchers.Main) { 63 | _models.value = tempModels 64 | } 65 | 66 | fun showModels( 67 | myModels: ArrayList, 68 | newModels: ArrayList 69 | ) = viewModelScope.launch(Dispatchers.Default) { 70 | if (newModels.isEmpty()) { 71 | return@launch 72 | } 73 | 74 | myModels.clear() 75 | myModels.addAll(newModels) 76 | 77 | withContext(Dispatchers.Main) { 78 | _showModelsEvent.value = Event(Unit) 79 | } 80 | } 81 | 82 | protected fun showError(@StringRes messageId: Int, cause: Throwable) = 83 | viewModelScope.launch(Dispatchers.Default) { 84 | withContext(Dispatchers.Main) { 85 | _showErrorEvent.value = Event(MyApplication.getMyString(messageId, cause.message)) 86 | } 87 | 88 | if (BuildConfig.DEBUG) { 89 | cause.printStackTrace() 90 | } 91 | } 92 | 93 | protected fun getStringProperty(key: String, condition: Boolean = true): String = 94 | if (condition) { 95 | PropertyManager.instance.getString( 96 | key, MyApplication.getMyString(R.string.build_not_filled) 97 | ) 98 | } else { 99 | MyApplication.getMyString(R.string.result_not_supported) 100 | } 101 | 102 | protected fun getBooleanProperty(key: String, condition: Boolean = true) = 103 | if (condition) { 104 | PropertyManager.instance.getBoolean(key, false) 105 | } else { 106 | false 107 | } 108 | 109 | protected fun sh(cmd: String, condition: Boolean = true): ShellResult { 110 | return if (condition) { 111 | ShellManager.instance.execute(cmd) 112 | } else { 113 | ShellResult() 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/base/list/BasePureListViewModel.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.ui.base.list 2 | 3 | import net.imknown.android.forefrontinfo.R 4 | import net.imknown.android.forefrontinfo.base.MyApplication 5 | 6 | abstract class BasePureListViewModel : BaseListViewModel() { 7 | protected fun add(tempModels: ArrayList, title: String, detail: String?) { 8 | val translatedDetail = if (detail.isNullOrEmpty()) { 9 | MyApplication.getMyString(R.string.build_not_filled) 10 | } else { 11 | detail.toString() 12 | } 13 | 14 | tempModels.add(MyModel(title, translatedDetail)) 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/base/list/MyAdapter.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.ui.base.list 2 | 3 | import android.view.ViewGroup 4 | import androidx.annotation.AttrRes 5 | import androidx.core.view.isGone 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.google.android.material.color.MaterialColors 8 | 9 | class MyAdapter : RecyclerView.Adapter() { 10 | companion object { 11 | const val PAYLOAD_DETAILS = 1 12 | } 13 | 14 | val myModels by lazy { ArrayList() } 15 | 16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = MyViewHolder(parent) 17 | 18 | override fun onBindViewHolder(holder: MyViewHolder, position: Int, payloads: MutableList) { 19 | if (payloads.isEmpty()) { 20 | onBindFullViewHolder(holder, position) 21 | } else { 22 | onBindPayloadViewHolder(holder, position, payloads) 23 | } 24 | } 25 | 26 | /** Do **NOT** use this function. Use [onBindViewHolder] above instead */ 27 | override fun onBindViewHolder(holder: MyViewHolder, position: Int) {} 28 | 29 | private fun onBindFullViewHolder(holder: MyViewHolder, position: Int) { 30 | with(holder.binding) { 31 | @AttrRes val color = myModels[position].color 32 | if (color != 0) { 33 | val backgroundColor = MaterialColors.getColor(root, color) 34 | sivColor.setBackgroundColor(backgroundColor) 35 | } else { 36 | sivColor.isGone = true 37 | } 38 | tvTitle.text = myModels[position].title 39 | tvDetail.text = myModels[position].detail 40 | } 41 | } 42 | 43 | private fun onBindPayloadViewHolder( 44 | holder: MyViewHolder, position: Int, payloads: MutableList 45 | ) = payloads.forEach { 46 | if (it == PAYLOAD_DETAILS) { 47 | holder.binding.tvDetail.text = myModels[position].detail 48 | } 49 | } 50 | 51 | override fun getItemCount() = myModels.size 52 | } -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/base/list/MyItemDecoration.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.ui.base.list 2 | 3 | import android.graphics.Rect 4 | import android.view.View 5 | import androidx.recyclerview.widget.RecyclerView 6 | 7 | class MyItemDecoration( 8 | private val spaceH: Int, 9 | private val spaceV: Int 10 | ) : RecyclerView.ItemDecoration() { 11 | override fun getItemOffsets( 12 | outRect: Rect, 13 | view: View, 14 | parent: RecyclerView, 15 | state: RecyclerView.State 16 | ) { 17 | with(outRect) { 18 | if (parent.getChildAdapterPosition(view) == 0) { 19 | top = spaceV 20 | } 21 | left = spaceH 22 | right = spaceH 23 | bottom = spaceV 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/base/list/MyModel.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.ui.base.list 2 | 3 | import androidx.annotation.AttrRes 4 | 5 | data class MyModel( 6 | val title: String, 7 | var detail: String, 8 | @param:AttrRes val color: Int = 0 9 | ) -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/base/list/MyViewHolder.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.ui.base.list 2 | 3 | import android.view.ViewGroup 4 | import androidx.recyclerview.widget.RecyclerView 5 | import net.imknown.android.forefrontinfo.base.mvvm.viewBinding 6 | import net.imknown.android.forefrontinfo.databinding.MyViewHolderBinding 7 | 8 | class MyViewHolder( 9 | parent: ViewGroup, 10 | val binding: MyViewHolderBinding = parent.viewBinding(MyViewHolderBinding::inflate) 11 | ) : RecyclerView.ViewHolder(binding.root) -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/base/list/res/layout/base_list_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/base/list/res/layout/my_view_holder.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 20 | 21 | 25 | 26 | 34 | 35 | 42 | 43 | 44 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/base/list/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10dp 4 | 5 | 12dp 6 | 12dp 7 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/base/list/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/home/GatewayApi.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.ui.home 2 | 3 | import io.ktor.client.HttpClient 4 | import io.ktor.client.call.body 5 | import io.ktor.client.engine.okhttp.OkHttp 6 | import io.ktor.client.plugins.logging.ANDROID 7 | import io.ktor.client.plugins.logging.LogLevel 8 | import io.ktor.client.plugins.logging.Logger 9 | import io.ktor.client.plugins.logging.Logging 10 | import io.ktor.client.request.get 11 | import io.ktor.client.statement.HttpResponse 12 | import io.ktor.http.headers 13 | import net.imknown.android.forefrontinfo.BuildConfig 14 | import net.imknown.android.forefrontinfo.base.extension.isChinaMainlandTimezone 15 | import net.imknown.android.forefrontinfo.ui.base.JsonIo 16 | import java.io.IOException 17 | import java.net.Proxy 18 | import java.net.ProxySelector 19 | import java.net.SocketAddress 20 | import java.net.URI 21 | 22 | object GatewayApi { 23 | private const val HEADER_REFERER_KEY = "Referer" 24 | private const val HEADER_REFERER_VALUE = BuildConfig.APPLICATION_ID 25 | 26 | private const val REPOSITORY_NAME = "imknown/AndroidLowLevelDetector" 27 | 28 | private const val URL_PREFIX_LLD_JSON_GITEE = "gitee.com/$REPOSITORY_NAME/raw" 29 | private const val URL_PREFIX_LLD_JSON_GITHUB = "raw.githubusercontent.com/$REPOSITORY_NAME" 30 | 31 | suspend fun fetchLldJson(): String { 32 | val urlPrefixLldJson = if (isChinaMainlandTimezone()) { 33 | URL_PREFIX_LLD_JSON_GITEE 34 | } else { 35 | URL_PREFIX_LLD_JSON_GITHUB 36 | } 37 | 38 | val client = HttpClient(OkHttp) { 39 | engine { 40 | config { 41 | // region [Proxy] 42 | // Fix: java.lang.IllegalArgumentException: port out of range:-1 43 | // Steps to reproduce (small probability): Change Wifi proxy from "Manual" to "PAC" 44 | // https://github.com/square/okhttp/issues/6877#issuecomment-1438554879 45 | val proxySelector = object : ProxySelector() { 46 | override fun select(uri: URI?): List = try { 47 | getDefault().select(uri) 48 | } catch (e: Exception) { 49 | e.printStackTrace() 50 | listOf(Proxy.NO_PROXY) 51 | } 52 | 53 | override fun connectFailed(uri: URI?, sa: SocketAddress?, ioe: IOException?) { 54 | getDefault().connectFailed(uri, sa, ioe) 55 | } 56 | } 57 | proxySelector(proxySelector) 58 | // endregion [Proxy] 59 | } 60 | } 61 | 62 | if (BuildConfig.DEBUG) { 63 | install(Logging) { 64 | logger = Logger.ANDROID 65 | level = LogLevel.ALL 66 | } 67 | } 68 | } 69 | 70 | val url = "https://$urlPrefixLldJson/${BuildConfig.GIT_BRANCH}/app/src/main/assets/${JsonIo.LLD_JSON_NAME}" 71 | val response: HttpResponse = client.get(url) { 72 | headers { 73 | append(HEADER_REFERER_KEY, HEADER_REFERER_VALUE) 74 | } 75 | } 76 | return client.use { 77 | response.body() 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/home/HomeFragment.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.ui.home 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.fragment.app.viewModels 7 | import net.imknown.android.forefrontinfo.base.MyApplication 8 | import net.imknown.android.forefrontinfo.base.mvvm.EventObserver 9 | import net.imknown.android.forefrontinfo.ui.base.list.BaseListFragment 10 | import net.imknown.android.forefrontinfo.ui.base.list.MyAdapter 11 | 12 | class HomeFragment : BaseListFragment() { 13 | 14 | companion object { 15 | fun newInstance() = HomeFragment() 16 | } 17 | 18 | override val listViewModel by viewModels() 19 | 20 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 21 | super.onViewCreated(view, savedInstanceState) 22 | 23 | observeLanguageEvent(MyApplication.homeLanguageEvent) 24 | 25 | listViewModel.subtitle.observe(viewLifecycleOwner) { 26 | val actionBar = (activity as AppCompatActivity).supportActionBar 27 | actionBar?.subtitle = MyApplication.getMyString(it.lldDataModeResId, it.dataVersion) 28 | } 29 | 30 | listViewModel.outdatedOrderProp.observe(viewLifecycleOwner, EventObserver { 31 | listViewModel.payloadOutdatedTargetSdkVersionApk(myAdapter.myModels) 32 | }) 33 | 34 | listViewModel.showOutdatedOrderEvent.observe(viewLifecycleOwner, EventObserver { 35 | myAdapter.notifyItemChanged(myAdapter.myModels.lastIndex, MyAdapter.PAYLOAD_DETAILS) 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/home/model/BaseInfo.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.ui.home.model 2 | 3 | import androidx.annotation.Keep 4 | import kotlinx.serialization.Serializable 5 | 6 | @Keep 7 | @Serializable 8 | open class BaseInfo { 9 | lateinit var version: String 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/home/model/Lld.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.ui.home.model 2 | 3 | import androidx.annotation.Keep 4 | import kotlinx.serialization.Serializable 5 | 6 | @Keep 7 | @Serializable 8 | data class Lld( 9 | val scheme: Int, 10 | val android: Androids, 11 | val linux: Linuxes, 12 | val toybox: Toyboxes, 13 | val webView: WebViews 14 | ) : BaseInfo() { 15 | companion object { 16 | const val SCHEME_VERSION = 1 17 | } 18 | 19 | // https://source.android.com/security/enhancements/enhancements9 20 | // https://source.android.com/setup/start/p-release-notes 21 | // https://developer.android.com/about/versions/10 22 | // 23 | // https://en.wikipedia.org/wiki/Android_version_history 24 | // https://developer.android.com/about/dashboards?hl=en 25 | // https://www.bidouille.org/misc/androidcharts 26 | // 27 | // https://mta.qq.com/mta/data/device/os 28 | // https://compass.umeng.com/#hardwareList 29 | // https://tongji.baidu.com/research/app 30 | // https://www.appbrain.com/stats/top-android-sdk-versions 31 | // https://gs.statcounter.com/android-version-market-share/ 32 | @Keep 33 | @Serializable 34 | data class Androids( 35 | // https://source.android.com/security/bulletin#bulletins 36 | // https://source.android.com/setup/start/build-numbers?hl=en#source-code-tags-and-builds 37 | // https://android.googlesource.com/platform/build/+/master/core/version_defaults.mk 38 | val securityPatchLevel: String, 39 | // https://source.android.com/security/bulletin#bulletins 40 | val googlePlaySystemUpdates: String, 41 | // https://source.android.com/setup/start/build-numbers?hl=en#source-code-tags-and-builds 42 | // https://android.googlesource.com/platform/frameworks/base/+refs 43 | val build: Build, 44 | // https://www.android.com 45 | val stable: Android, 46 | // https://source.android.com/security/bulletin/ 47 | val support: Android, 48 | // https://ci.android.com 49 | // https://developer.android.com/preview/overview 50 | val preview: Android, 51 | val internal: Android 52 | ) { 53 | @Keep 54 | @Serializable 55 | data class Android( 56 | val name: String, 57 | val api: String, 58 | val phase: String? = null 59 | ) : BaseInfo() 60 | 61 | // https://android.googlesource.com/platform/build/+refs 62 | // https://android.googlesource.com/platform/build/+/refs/tags/android-12.0.0_r29/core/build_id.mk 63 | // https://android.googlesource.com/platform/build/+/master/core/build_id.mk 64 | // 65 | // https://developers.google.com/android/images 66 | @Keep 67 | @Serializable 68 | data class Build( 69 | val details: List 70 | ) : BaseInfo() { 71 | @Keep 72 | @Serializable 73 | data class Detail( 74 | val id: String, 75 | val revision: String 76 | ) 77 | } 78 | } 79 | 80 | // https://source.android.com/setup/build/building-kernels#downloading 81 | // https://source.android.com/devices/architecture/kernel/android-common 82 | // https://www.kernel.org 83 | // https://en.wikipedia.org/wiki/Linux_kernel_version_history 84 | @Keep 85 | @Serializable 86 | data class Linuxes( 87 | // https://android.googlesource.com/kernel/common 88 | val google: Versions, 89 | // https://android.googlesource.com/kernel/common/+/android-mainline/Makefile 90 | val mainline: BaseInfo 91 | ) { 92 | @Keep 93 | @Serializable 94 | data class Versions( 95 | val versions: List 96 | ) 97 | } 98 | 99 | // https://github.com/landley/toybox 100 | // https://android.googlesource.com/platform/external/toybox/+refs 101 | // https://android.googlesource.com/platform/system/core/+/master/shell_and_utilities/ 102 | @Keep 103 | @Serializable 104 | data class Toyboxes( 105 | // https://android.googlesource.com/platform/external/toybox/+/refs/tags/android-12.0.0_r29/toys.h 106 | val stable: BaseInfo, 107 | // https://android.googlesource.com/platform/external/toybox/+/refs/tags/android-security-10.0.0_r63/main.c 108 | val support: BaseInfo, 109 | // https://android.googlesource.com/platform/external/toybox/+/master/toys.h#135 110 | // https://android.googlesource.com/platform/external/toybox/+/upstream-master/toys.h#135 111 | val master: BaseInfo 112 | ) 113 | 114 | // https://www.chromium.org/developers/calendar 115 | // https://chromiumdash.appspot.com/releases?platform=Android 116 | // 117 | // https://en.wikipedia.org/wiki/Google_Chrome_version_history 118 | @Keep 119 | @Serializable 120 | data class WebViews( 121 | val stable: BaseInfo, 122 | val beta: BaseInfo 123 | ) 124 | } -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/home/model/Subtitle.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.ui.home.model 2 | 3 | import androidx.annotation.StringRes 4 | 5 | data class Subtitle( 6 | @param:StringRes val lldDataModeResId: Int, 7 | val dataVersion: String 8 | ) -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/home/res/drawable/ic_home_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/home/res/values-fr-rFR/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Menu 4 | 5 | Supporté 6 | Non supporté 7 | Non identifié 8 | Activée 9 | Désactivé 10 | 11 | "Version d'Android" 12 | %1$s (API %2$s) 13 | Moi: %1$s\n\nDernière stable: %2$s\nPlus faible prise en charge: %3$s\nDernière aperçu: %4$s\nDernière interne: %5$s 14 | 15 | Id de build Android 16 | Moi: %1$s\nMon system: %2$s\nMon vendor: %3$s\nMon ODM: %4$s\n\nDernière stable: %5$s\n%6$s 17 | %1$s (%2$s) 18 | 19 | Niveau du patch de sécurité Android 20 | Niveau du patch de sécurité Vendor 21 | Moi: %1$s\n\nDernière stable: %2$s 22 | 23 | Niveau de performance 24 | 25 | Version du noyau Linux 26 | Moi: %1$s\n\nDernière support: %2$s\nMainline: %3$s 27 | 28 | Mise à jour A/B seamless 29 | \u0020(Virtuel) 30 | \u0020(Virtuel, rénové) 31 | \u0020(Courant: %s) 32 | 33 | Status System-as-root 34 | Héritage, Android 9 35 | 36 | Status du Partitions dynamiques 37 | \u0020(Rénové) 38 | 39 | Status du Projet Treble 40 | \u0020(Héritage, pas de fragments) 41 | \u0020(Autre) 42 | 43 | Compatibilité GSI 44 | Conforme à Treble 45 | Non conforme à Treble 46 | 47 | Status du Mise à jour du système dynamique(DSU) 48 | 49 | Project Mainline module version\n(Mise à jour du système Google Play) 50 | Moi: %1$s (%2$s)\n\nDernière: %3$s 51 | 52 | Status du NDK Vendor integré 53 | \u0020(Niveau: %s) 54 | 55 | Status APEX 56 | \u0020(Héritage aplati) 57 | 58 | Statut des options pour de développeurs 59 | 60 | Statut de débogage ADB 61 | 62 | "Statut d'authentification ADB" 63 | 64 | Statut de chiffrement 65 | Crypté 66 | "Chiffré mais le mot de passe n'a pas été défini" 67 | Non crypté 68 | 69 | Status SELinux 70 | Mode forcé 71 | Mode permissif 72 | 73 | Version de Toybox intégrée 74 | Moi: %1$s\n\nDernier stable: %2$s\nPlus faible support: %3$s\nMaster: %4$s 75 | 76 | Version WebView 77 | Dernier stable: %1$s\nDernier béta: %2$s 78 | Mon system WebView integré 79 | %1$s ou %2$s 80 | Autonome 81 | Pas installé 82 | "Doit être choisi par l'utilisateur" 83 | Se retirer 84 | Signé 85 | Mon implémentation de WebView 86 | 87 | "API Android à la sortie d'usine: %s\n\n" 88 | Les application system avec targetVersionSdk dépassé 89 | "Pas d'application dépassé" 90 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/home/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #D02B5128 4 | #D07E581F 5 | #D0812F2F 6 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/home/res/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 首页 4 | 5 | 支持 6 | 不支持 7 | 无法辨别 8 | 已启用 9 | 已禁用 10 | 11 | Android 版本 12 | %1$s(API %2$s) 13 | 我的:%1$s\n\n最新稳定:%2$s\n最低支持:%3$s\n最新预览:%4$s\n最新内部:%5$s 14 | 15 | Android 构建 id 16 | %1$s(%2$s) 17 | 我的:%1$s\n我的系统:%2$s\n我的供应商:%3$s\n我的 ODM:%4$s\n\n最新稳定:%5$s\n%6$s 18 | 19 | Android 安全补丁等级 20 | 供应商安全补丁等级 21 | 我的:%1$s\n\n最新稳定:%2$s 22 | 23 | 性能等级 24 | 25 | Linux 内核版本 26 | 我的:%1$s\n\n最新支持:%2$s\nMainline:%3$s 27 | 28 | A/B 无缝升级状态 29 | (虚拟的) 30 | (虚拟的,改造的) 31 | (当前:%s) 32 | 33 | System-as-root 状态 34 | 旧版,Android 9 35 | 36 | 动态分区状态 37 | (改造的) 38 | 39 | Project Treble 状态 40 | (旧版,无片段) 41 | (其他) 42 | 43 | GSI 兼容性 44 | 遵从 Treble 45 | 未遵从 Treble 46 | 47 | 动态系统更新(DSU)状态 48 | 49 | Project Mainline 模块版本\n(Google Play 系统更新) 50 | 我的:%1$s(%2$s)\n\n最新:%3$s 51 | 52 | 内建供应商 NDK 状态 53 | (等级:%s) 54 | 55 | APEX 状态 56 | (旧版扁平的) 57 | 58 | 开发者选项状态 59 | 60 | ADB 调试状态 61 | 62 | ADB 验证状态 63 | 64 | 加密状态 65 | 已加密 66 | 已加密,但密码未设置 67 | 未加密 68 | 69 | SELinux 状态 70 | 强制模式 71 | 宽容模式 72 | 73 | 内建 Toybox 版本 74 | 我的:%1$s\n\n最新稳定:%2$s\n最低支持:%3$s\nMaster:%4$s 75 | 76 | WebView 版本 77 | 最新稳定:%1$s\n最新测试:%2$s 78 | 我的内建 WebView 79 | %1$s 或者 %2$s 80 | 独立的 81 | 未安装 82 | 必须由用户选择 83 | 备用 84 | 已签名 85 | 我的 WebView 实现 86 | 87 | 我的出厂 Android API:%s\n\n 88 | 过时 targetSdkVersion 系统程序 89 | 没有过时的系统程序 90 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/home/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 首頁 4 | 5 | 支援 6 | 不支援 7 | 無法辨別 8 | 已啟用 9 | 已停用 10 | 11 | Android 版本 12 | %1$s(API %2$s) 13 | 我的:%1$s\n\n最新穩定:%2$s\n最低支援:%3$s\n最新預覽:%4$s\n最新內部:%5$s 14 | 15 | Android 構建 ID 16 | %1$s(%2$s) 17 | 我的:%1$s\n我的系統:%2$s\n我的供應商:%3$s\n我的 ODM:%4$s\n\n最新穩定:%5$s\n%6$s 18 | 19 | Android 安全性修補等級 20 | Vendor 安全性修補等級 21 | 我的:%1$s\n\n最新穩定:%2$s 22 | 23 | 性能等級 24 | 25 | Linux Kernel 版本 26 | 我的:%1$s\n\n最新支援:%2$s\nMainline:%3$s 27 | 28 | A/B 無縫升級狀態 29 | (虛擬的) 30 | (虛擬的,改造的) 31 | (目前:%s) 32 | 33 | System-as-root 狀態 34 | 舊版,Android 9 35 | 36 | 動態分區狀態 37 | (改造的) 38 | 39 | Project Treble 狀態 40 | (舊版,無片段) 41 | (其他) 42 | 43 | GSI 相容性 44 | 遵從 Treble 45 | 未遵從 Treble 46 | 47 | 動態系統更新(DSU)狀態 48 | 49 | Project Mainline 模組版本\n(Google Play 系統更新) 50 | 我的:%1$s(%2$s)\n\n最新:%3$s 51 | 52 | 內建 Vendor NDK 狀態 53 | (等級:%s) 54 | 55 | APEX 狀態 56 | (舊版扁平的) 57 | 58 | 開發者選項狀態 59 | 60 | ADB 偵錯狀態 61 | 62 | ADB 驗證狀態 63 | 64 | 加密狀態 65 | 已加密 66 | 已加密 但密碼未設定 67 | 未加密 68 | 69 | SELinux 狀態 70 | 強制模式 71 | 寬鬆模式 72 | 73 | 內建 Toybox 版本 74 | 我的:%1$s\n\n最新穩定:%2$s\n最低支援:%3$s\nMaster:%4$s 75 | 76 | WebView 版本 77 | 最新穩定:%1$s\n最新測試:%2$s 78 | 我的內建 WebView 79 | %1$s 或者 %2$s 80 | 獨立的 81 | 未安裝 82 | 必須由用戶選擇 83 | 備用 84 | 已簽名 85 | 我的 WebView 實現 86 | 87 | 我的出廠 Android API:%s\n\n 88 | 過時 targetSdkVersion 系統程式 89 | 沒有過時的系統程式 90 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/home/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/home/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #D0ACDDB7 4 | #D0FDD18F 5 | #D0FFB1AC 6 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/home/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Home 4 | 5 | Supported 6 | Not supported 7 | Unidentified 8 | Enabled 9 | Disabled 10 | 11 | Android version 12 | %1$s (API %2$s) 13 | Mine: %1$s\n\nLatest stable: %2$s\nLowest support: %3$s\nLatest preview: %4$s\nLatest internal: %5$s 14 | 15 | Android build ids 16 | Mine: %1$s\nMy system: %2$s\nMy vendor: %3$s\nMy ODM: %4$s\n\nLatest stable: %5$s\n%6$s 17 | %1$s (%2$s) 18 | 19 | Android security patch level 20 | Vendor security patch level 21 | Mine: %1$s\n\nLatest stable: %2$s 22 | 23 | Performance class 24 | API %s 25 | 26 | Linux kernel version 27 | Mine: %1$s\n\nLatest support: %2$s\nMainline: %3$s 28 | 29 | A/B seamless update status 30 | \u0020(Virtual) 31 | \u0020(Virtual, retrofitted) 32 | \u0020(Current: %s) 33 | 34 | System-as-root status 35 | Legacy, Android 9 36 | 2-Stage-Init 37 | Recovery 38 | / 39 | 40 | Dynamic partitions status 41 | \u0020(Retrofitted) 42 | 43 | Project Treble status 44 | \u0020(Legacy, no fragments) 45 | \u0020(Other) 46 | 47 | GSI compatibility 48 | Treble compliant 49 | Not Treble compliant 50 | 51 | Dynamic System Update(DSU) status 52 | 53 | Project Mainline module version\n(Google Play system update) 54 | Mine: %1$s (%2$s)\n\nLatest: %3$s 55 | 56 | Built-in Vendor NDK status 57 | \u0020(Level: %s) 58 | 59 | APEX status 60 | \u0020(Legacy flattened) 61 | 62 | Developer options status 63 | 64 | ADB debugging status 65 | 66 | ADB authentication status 67 | 68 | Encryption status 69 | Encrypted 70 | Encrypted but password not been set 71 | Not encrypted 72 | 73 | SELinux status 74 | Enforcing mode 75 | Permissive mode 76 | 77 | Built-in Toybox version 78 | Mine: %1$s\n\nLatest stable: %2$s\nLowest support: %3$s\nMaster: %4$s 79 | 80 | WebView version 81 | Latest stable: %1$s\nLatest beta: %2$s 82 | My built-in WebView 83 | %1$s or %2$s 84 | Standalone 85 | Not installed 86 | \n %s 87 | Must be chosen by user 88 | Fallback 89 | Signed 90 | \n\n 91 | My WebView implementation 92 | 93 | My factory Android API: %s\n\n 94 | Outdated targetSdkVersion system apps 95 | No outdated system apps 96 | %1$s (%2$s) 97 | (%2$s) %1$s 98 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/others/OthersFragment.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.ui.others 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.fragment.app.viewModels 6 | import net.imknown.android.forefrontinfo.base.MyApplication 7 | import net.imknown.android.forefrontinfo.ui.base.list.BaseListFragment 8 | 9 | class OthersFragment : BaseListFragment() { 10 | 11 | companion object { 12 | fun newInstance() = OthersFragment() 13 | } 14 | 15 | override val listViewModel by viewModels() 16 | 17 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 18 | super.onViewCreated(view, savedInstanceState) 19 | 20 | observeLanguageEvent(MyApplication.othersLanguageEvent) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/others/res/drawable/ic_others_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/others/res/values-fr-rFR/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Autres 4 | 5 | Marque 6 | Constructeur 7 | Nom du model 8 | Nom de code 9 | Nom global du produit 10 | Nom du matériel 11 | Modèle de carte mère 12 | Modèle SoC 13 | Fabricant de SoC 14 | SKU ou matériel 15 | Vendor SKU 16 | SKU ou matériel ODM 17 | 18 | 32bit 19 | 64bit 20 | Bit de Binder 21 | Bit de HW Binder 22 | Bit de VND Binder 23 | Bit de processus actuel 24 | Architecture de processus actuel 25 | ABI de processus actuel 26 | Système actuel ABI 27 | 32bit CPU ABIs supportés 28 | 64bit CPU ABIs supportés 29 | 30 | Constructeur de la ROM 31 | Nom de la machine lors du build 32 | Date du build 33 | ROM basé sur (Développement secondaire) 34 | %s empreinte 35 | Empreinte de la ROM originale 36 | "Empreinte digitale d'aperçu de la ROM stock" 37 | ID de build ROM 38 | "ID de build ROM pour l'utilisateur" 39 | Type de build 40 | Tags de build 41 | Git commit hash 42 | "Nom de code de la branche Git (REL: Version stable de production)" 43 | "Révision de l'aperçu du développeur de la pré-version du SDK" 44 | 45 | WebView User-Agent 46 | Noyau Linux 47 | 48 | Version du Bootloader 49 | Version de la bande de base 50 | 51 | **NON REMPLI** 52 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/others/res/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 其他 4 | 5 | 品牌 6 | 制造商 7 | 销售型号 8 | 开发代号 9 | 总产品名称 10 | 硬件名称 11 | 主板型号 12 | SoC 型号 13 | SoC 制造商 14 | 硬件 SKU 15 | 供应商 SKU 16 | ODM 硬件 SKU 17 | 18 | 32 位 19 | 64 位 20 | Binder 位数 21 | HW Binder 位数 22 | VND Binder 位数 23 | 当前进程位数 24 | 当前进程架构 25 | 当前进程 ABI 26 | 当前系统 ABI 27 | 支持的 32 位 CPU ABI 28 | 支持的 64 位 CPU ABI 29 | 30 | ROM 构建者 31 | ROM 构建的主机名 32 | ROM 构建时间 33 | 基于的系统(二次开发) 34 | %s 指纹 35 | 原机系统指纹 36 | 原机系统预览指纹 37 | ROM 构建 ID 38 | 给用户看的 ROM 构建 ID 39 | ROM 构建类型 40 | ROM 构建描述标签 41 | Git 提交的 Hash 42 | Git 分支代号(REL:正式) 43 | 预发行 SDK 开发者预览修订号 44 | 45 | WebView User-Agent 46 | Linux 内核 47 | 48 | Bootloader 版本 49 | 基带版本 50 | 51 | **未填写** 52 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/others/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 其他 4 | 5 | 品牌 6 | 製造商 7 | 銷售型號 8 | 開發代號 9 | 總產品名稱 10 | 硬體名稱 11 | 主板型號 12 | SoC 型號 13 | SoC 製造商 14 | 硬體 SKU 15 | Vendor SKU 16 | ODM 硬體 SKU 17 | 18 | 32 位 19 | 64 位 20 | Binder 位數 21 | HW Binder 位數 22 | VND Binder 位數 23 | 目前處理程序位元數 24 | 目前處理程序架構 25 | 目前處理程序 ABI 26 | 目前系統 ABI 27 | 支援的 32 位 CPU ABI 28 | 支援的 64 位 CPU ABI 29 | 30 | ROM 構建者 31 | ROM 構建的主機名 32 | ROM 構建時間 33 | 基於的系統(二次開發) 34 | %s 指紋 35 | 原機系統指紋 36 | 原機系統預覽指紋 37 | ROM 構建 ID 38 | 給用戶看的 ROM 構建 ID 39 | ROM 構建類型 40 | ROM 構建描述標籤 41 | Git 提交的 Hash 42 | Git 分支代號(REL:正式) 43 | 預先發行 SDK 開發者預覽修訂號 44 | 45 | WebView User-Agent 46 | Linux Kernel 47 | 48 | Bootloader 版本 49 | 基帶版本 50 | 51 | **未填寫** 52 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/others/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Others 4 | 5 | Brand 6 | Manufacturer 7 | Model name for sales 8 | Codename for the developer 9 | Name of overall product 10 | Hardware name 11 | Board model 12 | SoC model 13 | SoC manufacturer 14 | SKU of hardware 15 | Vendor SKU 16 | SKU of ODM hardware 17 | 18 | 32bit 19 | 64bit 20 | Binder bit 21 | HW Binder bit 22 | VND Binder bit 23 | Current process bit 24 | Current process architecture 25 | Current process ABI 26 | Current system ABI 27 | Supported 32bit CPU ABIs 28 | Supported 64bit CPU ABIs 29 | 30 | ROM builder 31 | ROM build machine name 32 | ROM build time 33 | ROM based on (Secondary development) 34 | %s fingerprint 35 | Stock ROM fingerprint 36 | Stock ROM preview fingerprint 37 | ROM build ID 38 | ROM build ID for user 39 | ROM build type 40 | ROM build tags 41 | Git commit hash 42 | Git branch codename (REL: stable release version) 43 | Developer preview revision of pre-release SDK 44 | 45 | WebView User-Agent 46 | Linux kernel 47 | 48 | Bootloader version 49 | Baseband version 50 | 51 | **NOT FILLED** 52 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/prop/PropFragment.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.ui.prop 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.fragment.app.viewModels 6 | import net.imknown.android.forefrontinfo.base.MyApplication 7 | import net.imknown.android.forefrontinfo.ui.base.list.BaseListFragment 8 | 9 | class PropFragment : BaseListFragment() { 10 | 11 | companion object { 12 | fun newInstance() = PropFragment() 13 | } 14 | 15 | override val listViewModel by viewModels() 16 | 17 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 18 | super.onViewCreated(view, savedInstanceState) 19 | 20 | observeLanguageEvent(MyApplication.propLanguageEvent) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/prop/PropViewModel.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.ui.prop 2 | 3 | import android.content.ContentResolver 4 | import android.provider.Settings 5 | import android.util.Log 6 | import androidx.lifecycle.viewModelScope 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.launch 9 | import net.imknown.android.forefrontinfo.R 10 | import net.imknown.android.forefrontinfo.base.MyApplication 11 | import net.imknown.android.forefrontinfo.ui.base.list.BasePureListViewModel 12 | import net.imknown.android.forefrontinfo.ui.base.list.MyModel 13 | import java.util.Locale 14 | import java.util.Properties 15 | import kotlin.reflect.KClass 16 | 17 | class PropViewModel : BasePureListViewModel() { 18 | 19 | companion object { 20 | private const val CMD_GETPROP = "getprop" 21 | 22 | private const val UNIX_LIKE_NEWLINE_ORIGIN = "\\n" 23 | } 24 | 25 | override fun collectModels() = viewModelScope.launch(Dispatchers.IO) { 26 | try { 27 | val tempModels = ArrayList() 28 | 29 | getSystemProp(tempModels) 30 | getSettings(tempModels, Settings.System::class) 31 | getSettings(tempModels, Settings.Secure::class) 32 | getSettings(tempModels, Settings.Global::class) 33 | getBuildProp(tempModels) 34 | 35 | setModels(tempModels) 36 | } catch (e: Exception) { 37 | showError(R.string.lld_json_detect_failed, e) 38 | } 39 | } 40 | 41 | private fun getSystemProp(tempModels: ArrayList) { 42 | val systemProperties = System.getProperties() 43 | val defaultsProperties = Properties::class.java 44 | .getDeclaredField("defaults") 45 | .also { it.isAccessible = true } 46 | .get(systemProperties) as Properties 47 | 48 | (defaultsProperties + systemProperties) 49 | .toList() 50 | .sortedBy { it.first.toString() } 51 | .forEach { 52 | add( 53 | tempModels, 54 | it.first.toString(), 55 | if (it.second == System.lineSeparator()) { 56 | UNIX_LIKE_NEWLINE_ORIGIN 57 | } else { 58 | it.second.toString() 59 | } 60 | ) 61 | } 62 | } 63 | 64 | private fun getSettings(tempModels: ArrayList, subSettingsKClass: KClass) 65 | where T : Settings.NameValueTable { 66 | subSettingsKClass.java.declaredFields 67 | .filter { 68 | it.isAccessible = true 69 | it.type == String::class.java 70 | }.map { it.get(null) as String } 71 | .sortedBy { it.uppercase(Locale.US) } 72 | .forEach { 73 | var key = it 74 | try { 75 | val value = subSettingsKClass.java.getDeclaredMethod( 76 | "getString", 77 | ContentResolver::class.java, 78 | String::class.java 79 | ).invoke(null, MyApplication.instance.contentResolver, key) as? String 80 | ?: MyApplication.getMyString(R.string.build_not_filled) 81 | key = "${subSettingsKClass.qualifiedName}.$key" 82 | tempModels.add(MyModel(key, value)) 83 | } catch (e: Exception) { 84 | Log.e(javaClass.simpleName, e.cause?.message.toString()) 85 | } 86 | } 87 | } 88 | 89 | private fun getBuildProp(tempModels: ArrayList) { 90 | var temp = "" 91 | sh(CMD_GETPROP).output.forEach { 92 | if (it.startsWith("[") && it.endsWith("]")) { 93 | addRawProp(tempModels, it) 94 | } else { 95 | temp += "$it\n" 96 | 97 | if (it.endsWith("]")) { 98 | addRawProp(tempModels, temp) 99 | 100 | temp = "" 101 | } 102 | } 103 | } 104 | } 105 | 106 | private fun addRawProp(tempModels: ArrayList, text: String) { 107 | val result = text.split(": ") 108 | add(tempModels, removeSquareBrackets(result[0]), removeSquareBrackets(result[1])) 109 | } 110 | 111 | private fun removeSquareBrackets(text: String) = 112 | text.substringAfter("[").substringBefore(']').trimIndent() 113 | } -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/prop/res/drawable/ic_prop_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/prop/res/values-fr-rFR/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Propriétés 4 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/prop/res/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 属性 4 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/prop/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 屬性 4 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/prop/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Properties 4 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/res/anim/drop_scale.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/res/layout/main_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 22 | 23 | 24 | 29 | 30 | 37 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/res/menu/bottom_nav_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 18 | 19 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/res/values-fr-rFR/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | CHARGEMENT DES… 4 | HORS-LIGNE (%s) 5 | ENLIGNE (%s) 6 | "Impossible d'atteindre les données en ligne.\nUtilisation des données locales.\n\n%s" 7 | "L'enregistrement des données a échoué.\nVeuillez vérifier votre stockage.\n\n%s" 8 | "Impossible de parser les données.\nEssayez de mettre à jour l'application.\n\n%s" 9 | "La détection a échoué.\nEssayez de mettre à jour l'application.\n\n%s" 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/res/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 加载中… 4 | 离线(%s) 5 | 在线(%s) 6 | 在线数据获取失败。\n使用离线数据代替。\n\n%s 7 | 数据保存失败。\n请检查你的存储。\n\n%s 8 | 数据解析失败。\n请尝试升级本程序。\n\n%s 9 | 检测失败。\n请尝试升级本程序。\n\n%s 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 載入中… 4 | 離線(%s) 5 | 在線上(%s) 6 | 線上數據獲取失敗。\n使用離線數據代替。\n\n%s 7 | 數據儲存失敗。\n請檢查你的儲存。\n\n%s 8 | 數據分析失敗。\n請嘗試升級本程式。\n\n%s 9 | 檢查失敗。\n請嘗試升級本程式。\n\n%s 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | LOADING… 4 | OFFLINE (%s) 5 | ONLINE (%s) 6 | Online data fetching failed.\nUse local data instead.\n\n%s 7 | Data saving failed.\nPlease check your storage.\n\n%s 8 | Data parsing failed.\nPlease try to upgrade this app.\n\n%s 9 | Detecting failed.\nPlease try to upgrade this app.\n\n%s 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/settings/SettingsFragment.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.ui.settings 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.annotation.StringRes 6 | import androidx.core.view.ViewCompat 7 | import androidx.core.view.doOnLayout 8 | import androidx.core.view.updatePadding 9 | import androidx.fragment.app.viewModels 10 | import androidx.preference.ListPreference 11 | import androidx.preference.Preference 12 | import androidx.preference.PreferenceFragmentCompat 13 | import net.imknown.android.forefrontinfo.R 14 | import net.imknown.android.forefrontinfo.base.MyApplication 15 | import net.imknown.android.forefrontinfo.base.extension.isChinaMainlandTimezone 16 | import net.imknown.android.forefrontinfo.base.mvvm.EventObserver 17 | import net.imknown.android.forefrontinfo.base.mvvm.toast 18 | import net.imknown.android.forefrontinfo.base.mvvm.windowInsetsCompatTypes 19 | import net.imknown.android.forefrontinfo.ui.MainActivity 20 | 21 | class SettingsFragment : PreferenceFragmentCompat() { 22 | 23 | companion object { 24 | fun newInstance() = SettingsFragment() 25 | } 26 | 27 | private val settingsViewModel by viewModels() 28 | 29 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 30 | setPreferencesFromResource(R.xml.preferences, rootKey) 31 | } 32 | 33 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 34 | super.onViewCreated(view, savedInstanceState) 35 | 36 | initWindowInsets() 37 | 38 | initViews() 39 | } 40 | 41 | private fun initWindowInsets() { 42 | listView.clipToPadding = false 43 | 44 | ViewCompat.setOnApplyWindowInsetsListener(listView) { rv, windowInsetsCompat -> 45 | val insets = windowInsetsCompat.getInsets(windowInsetsCompatTypes) 46 | (activity as? MainActivity)?.binding?.bottomNavigationView?.doOnLayout { bnv -> 47 | rv.updatePadding( 48 | left = insets.left, 49 | right = insets.right, 50 | bottom = bnv.height 51 | ) 52 | } 53 | 54 | windowInsetsCompat 55 | } 56 | } 57 | 58 | private fun initViews() { 59 | // region [Theme] 60 | settingsViewModel.themesPrefChangeEvent.observe(viewLifecycleOwner, EventObserver { 61 | it?.let { themesValue -> 62 | MyApplication.setMyTheme(themesValue) 63 | } 64 | }) 65 | // endregion [Theme] 66 | 67 | // region [Scroll Bar Mode] 68 | settingsViewModel.scrollBarModeChangedEvent.observe(viewLifecycleOwner, EventObserver { 69 | it?.let { scrollBarMode -> 70 | settingsViewModel.setScrollBarMode(scrollBarMode) 71 | } 72 | }) 73 | 74 | settingsViewModel.changeScrollBarModeEvent.observe(viewLifecycleOwner, EventObserver { 75 | listView.isVerticalScrollBarEnabled = it 76 | }) 77 | 78 | val scrollBarModePref = 79 | findPreference(MyApplication.getMyString(R.string.interface_scroll_bar_key))!! 80 | settingsViewModel.setScrollBarMode(scrollBarModePref.value) 81 | // endregion [Scroll Bar Mode] 82 | 83 | settingsViewModel.showMessageEvent.observe(viewLifecycleOwner, EventObserver { 84 | context?.toast(it) 85 | }) 86 | 87 | val aboutShopPref = findPreference(R.string.about_shop_key) 88 | setOnOpenInExternalListener( 89 | aboutShopPref, if (isChinaMainlandTimezone()) { 90 | R.string.about_shop_china_mainland_uri 91 | } else { 92 | R.string.about_shop_uri 93 | } 94 | ) 95 | 96 | val aboutSourcePref = findPreference(R.string.about_source_key) 97 | setOnOpenInExternalListener(aboutSourcePref, R.string.about_source_uri) 98 | 99 | val aboutPrivacyPolicyPref = findPreference(R.string.about_privacy_policy_key) 100 | setOnOpenInExternalListener(aboutPrivacyPolicyPref, R.string.about_privacy_policy_uri) 101 | 102 | val aboutLicensesPref = findPreference(R.string.about_licenses_key) 103 | setOnOpenInExternalListener(aboutLicensesPref, R.string.about_licenses_uri) 104 | 105 | val aboutTranslatorMoreInfoPref = findPreference(R.string.about_translator_more_info_key) 106 | setOnOpenInExternalListener( 107 | aboutTranslatorMoreInfoPref, 108 | R.string.translator_website 109 | ) 110 | 111 | // region [Version Info] 112 | val versionPref = findPreference(R.string.about_version_key) 113 | settingsViewModel.version.observe(viewLifecycleOwner) { 114 | versionPref.summary = MyApplication.getMyString( 115 | it.id, 116 | it.versionName, 117 | it.versionCode, 118 | it.assetLldVersion, 119 | it.distributor, 120 | it.installer, 121 | it.firstInstallTime, 122 | it.lastUpdateTime 123 | ) 124 | } 125 | 126 | settingsViewModel.setBuiltInDataVersion( 127 | MyApplication.instance.packageName, 128 | MyApplication.instance.packageManager 129 | ) 130 | // endregion [Version Info] 131 | 132 | // region [Version Click] 133 | versionPref.setOnPreferenceClickListener { 134 | settingsViewModel.versionClicked() 135 | 136 | true 137 | } 138 | // endregion [Version Click] 139 | } 140 | 141 | private fun findPreference(@StringRes resId: Int) = 142 | findPreference(MyApplication.getMyString(resId))!! 143 | 144 | private fun setOnOpenInExternalListener(pref: Preference, @StringRes uriResId: Int) { 145 | pref.setOnPreferenceClickListener { 146 | settingsViewModel.openInExternal(uriResId) 147 | 148 | true 149 | } 150 | } 151 | } -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/settings/res/drawable/ic_settings_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/settings/res/layout/preference_widget_material_switch.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/settings/res/values-fr-rFR/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Paramètres 4 | 5 | Interface 6 | Mode de défilement 7 | Cliquez ici pour voir le mode de défilement 8 | Aucun 9 | Normale 10 | 11 | 12 | Thème 13 | Cliquez pour voir le selecteur de thème 14 | Système 15 | Economiseur de batterie 16 | Toujours clair 17 | Toujours Sombre (AMOLED) 18 | 19 | Fonctions 20 | Activer les données en ligne 21 | Lorsque cette option est activée, les données seront préalablement extraites du serveur réseau distant, puis mises en cache sur le stockage local. 22 | 23 | "Commandez d'abord par nom de colis" 24 | "Commandez d'abord les applications obsolètes par nom de package, sinon par targetSdkVersion d'abord." 25 | 26 | À propos 27 | 28 | Aucun navigateur disponible 29 | 30 | Play Store 31 | "Ouvre la page de l'application dans Play Store" 32 | 33 | Code source 34 | "L'adresse GitHub du code sourcce de l'application" 35 | 36 | Politique de confidentialité 37 | "Voyez ce qui se passe quand vous utilisez l'application" 38 | 39 | Licences 40 | Les dépots open-source. 41 | 42 | Français (FR), traducteur: LiteApplication 43 | https://litepress.ddns.net 44 | https://github.com/LiteApplication 45 | 46 | Version 47 | Version: %1$s (Iteration times: %2$d)\nDonnées intégrées: %3$s\nDistributeur: %4$s\nInstallateur: %5$s\nPremière installation: %6$s\nDernière mise à jour: %7$s 48 | 49 | Ligne de commande 50 | 51 | Google 52 | imknown 53 | Débogage public 54 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/settings/res/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 设置 4 | 5 | 界面 6 | 滚动条模式 7 | 点击以显示滚动条模式 8 | 9 | 普通 10 | 11 | 12 | 主题 13 | 点击以显示主题选择器 14 | 跟随系统 15 | 跟随省电模式 16 | 总是亮色 17 | 总是暗色 18 | 19 | 功能 20 | 允许网络数据 21 | 若启用,数据将优先从远端网络服务器获取,并被缓存在本地存储。 22 | 23 | 优先按包名排序 24 | 优先按包名排序过时程序,否则按 targetSdkVersion 优先。 25 | 26 | 关于 27 | 28 | 无可用浏览器 29 | 30 | 应用商店页 31 | 打开本程序商店所在的页面 32 | 33 | 源码 34 | 本程序源码的 GitHub 地址 35 | 36 | 隐私政策 37 | 看看使用本程序时会发生什么 38 | 39 | 协议 40 | 引用的开源库 41 | 42 | 简体中文(中国),翻译:imknown 43 | https://imknown.net 44 | https://github.com/imknown 45 | 46 | 版本 47 | 版本号:%1$s(迭代次数:%2$d)\n内建数据版本:%3$s\n分发者:%4$s\n安装者:%5$s\n首次安装:%6$s\n上次更新:%7$s 48 | 49 | 命令行 50 | 51 | 谷歌 52 | imknown 53 | 公共调试 54 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/settings/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 設定 4 | 5 | 界面 6 | 滾動條模式 7 | 點擊以顯示滾動條模式 8 | 9 | 普通 10 | 11 | 12 | 主題 13 | 點擊以顯示主題選擇器 14 | 跟隨系統 15 | 跟隨省電模式 16 | 總是淺色 17 | 總是深色 18 | 19 | 功能 20 | 允許網路數據 21 | 若啟用,數據將優先從遠端網路伺服器獲取,並被快取在本地儲存。 22 | 23 | 優先按包名排序 24 | 優先按包名排序過時程式,否則按 targetSdkVersion 優先。 25 | 26 | 關於 27 | 28 | 無可用瀏覽器 29 | 30 | 應用商店頁面 31 | 打開本程式商店所在的頁面 32 | 33 | 原始碼 34 | 本程式原始碼的 GitHub 網址 35 | 36 | 隱私政策 37 | 看看使用本程式時會發生什麼 38 | 39 | 協議 40 | 引用的開源庫 41 | 42 | 繁體中文(臺灣),翻譯:bluehomewu 43 | https://github.com/bluehomewu 44 | https://github.com/bluehomewu 45 | 46 | 版本 47 | 版本號:%1$s(疊代次數:%2$d)\n內建數據版本:%3$s\n分發者:%4$s\n安裝者:%5$s\n首次安裝:%6$s\n上次更新:%7$s 48 | 49 | 命令行 50 | 51 | Google 52 | imknown 53 | 公共偵錯 54 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/settings/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @string/interface_themes_follow_system_key 5 | @string/interface_themes_power_saver_key 6 | @string/interface_themes_always_light_key 7 | @string/interface_themes_always_dark_key 8 | 9 | 10 | 11 | @string/interface_themes_follow_system_value 12 | @string/interface_themes_power_saver_value 13 | @string/interface_themes_always_light_value 14 | @string/interface_themes_always_dark_value 15 | 16 | 17 | 18 | @string/interface_no_scroll_bar_key 19 | @string/interface_normal_scroll_bar_key 20 | 21 | 22 | 23 | 24 | @string/interface_no_scroll_bar_value 25 | @string/interface_normal_scroll_bar_value 26 | @string/interface_fast_scroll_bar_value 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/settings/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Settings 4 | 5 | Interface 6 | interface_scroll_bar_key 7 | Scroll bar mode 8 | Click to show the scroll bar mode 9 | None 10 | 0 11 | Normal 12 | 1 13 | 14 | 2 15 | 16 | interface_themes_key 17 | Themes 18 | Click to show the theme chooser 19 | Follow system 20 | -1 21 | Follow power saver 22 | 1 23 | Always light 24 | 2 25 | Always dark 26 | 3 27 | 28 | Function 29 | about_allow_network_data_key 30 | Allow network data 31 | When enabled, data will be prior fetched from remote network server, then be cached on the local storage. 32 | 33 | function_outdated_target_order_by_package_name_key 34 | Order by package name first 35 | Order the outdated apps by package name first, otherwise by the targetSdkVersion first. 36 | 37 | 38 | 39 | About 40 | 41 | No available browser 42 | 43 | about_shop_key 44 | Store page 45 | Open the page of current app in the store 46 | https://play.google.com/store/apps/details?id=net.imknown.android.forefrontinfo 47 | https://github.com/imknown/AndroidLowLevelDetector 48 | 49 | about_source_key 50 | Source code 51 | GitHub address of the app source code 52 | https://github.com/imknown/AndroidLowLevelDetector 53 | 54 | about_privacy_policy_key 55 | Privacy policy 56 | See what will happen when using this app 57 | https://github.com/imknown/AndroidLowLevelDetector/blob/master/GOOGLE_PLAY_PRIVACY_POLICY.md 58 | 59 | about_licenses_key 60 | License 61 | Referenced open-source repositories 62 | https://github.com/imknown/AndroidLowLevelDetector/blob/master/gradle/toml 63 | 64 | about_translator_more_info_key 65 | English (US), translator: imknown 66 | https://imknown.net 67 | https://github.com/imknown 68 | 69 | about_version_key 70 | Version 71 | Version: %1$s (Iteration times: %2$d)\nBuilt-in data version: %3$s\nDistributor: %4$s\nInstaller: %5$s\nFirst install: %6$s\nLast update: %7$s 72 | 🐈==) 🐕===)) 73 | 74 | Command-line 75 | 76 | Google 77 | imknown 78 | Public debug 79 | -------------------------------------------------------------------------------- /app/src/main/java/net/imknown/android/forefrontinfo/ui/settings/res/xml/preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 17 | 18 | 26 | 27 | 28 | 31 | 32 | 38 | 39 | 45 | 46 | 47 | 50 | 55 | 56 | 61 | 62 | 67 | 68 | 73 | 74 | 79 | 80 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /app/src/test/java/net/imknown/android/forefrontinfo/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /base/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libsAndroid.plugins.android.library) 3 | 4 | alias(libsKotlin.plugins.kotlin.android) 5 | } 6 | 7 | android { 8 | namespace = "net.imknown.android.forefrontinfo.base" 9 | 10 | compileSdk = libsBuild.versions.compileSdk.get().toInt() 11 | // compileSdkExtension = libsBuild.versions.compileSdkExtension.get().toInt() 12 | buildToolsVersion = libsBuild.versions.buildTools.get() 13 | val isPreview = libsBuild.versions.isPreview.get().toBoolean() 14 | if (isPreview) { 15 | compileSdkPreview = libsBuild.versions.compileSdkPreview.get() 16 | buildToolsVersion = libsBuild.versions.buildToolsPreview.get() 17 | } 18 | 19 | defaultConfig { 20 | minSdk = libsBuild.versions.minSdk.get().toInt() 21 | 22 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 23 | 24 | consumerProguardFiles("consumer-rules.pro") 25 | } 26 | 27 | compileOptions { 28 | isCoreLibraryDesugaringEnabled = true 29 | } 30 | } 31 | 32 | // region [Toolchain] 33 | // https://developer.android.com/build/jdks 34 | // https://kotlinlang.org/docs/gradle-configure-project.html 35 | // https://docs.gradle.org/current/userguide/toolchains.html 36 | private val javaToolchain = libsBuild.versions.javaToolchain.get().toInt() 37 | kotlin { 38 | jvmToolchain(javaToolchain) 39 | } 40 | // endregion [Toolchain] 41 | 42 | dependencies { 43 | api(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) 44 | 45 | coreLibraryDesugaring(libsAndroid.desugarJdkLibs) 46 | 47 | // region [AndroidX] 48 | androidTestImplementation(libsAndroid.bundles.test) 49 | // endregion [AndroidX] 50 | 51 | // region [3rd Parties] 52 | api(libsThirdParty.libsu) 53 | 54 | // api(libsThirdParty.bundles.shizuku) 55 | // endregion [3rd Parties] 56 | } -------------------------------------------------------------------------------- /base/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imknown/AndroidLowLevelDetector/9b9ffa12d7324fd953cb7eaf34d1b9d66d1e432e/base/consumer-rules.pro -------------------------------------------------------------------------------- /base/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /base/src/androidTest/java/net/imknown/android/forefrontinfo/base/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.base 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("net.imknown.android.forefrontinfo.base", appContext.packageName) 21 | } 22 | } -------------------------------------------------------------------------------- /base/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /base/src/main/java/net/imknown/android/forefrontinfo/base/extension/DateTimeExt.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.base.extension 2 | 3 | import java.time.Instant 4 | import java.time.ZoneId 5 | import java.time.ZonedDateTime 6 | import java.time.format.DateTimeFormatter 7 | 8 | fun String.formatToLocalZonedDatetimeString(): String { 9 | val pattern = "yyyy-MM-dd HH:mm Z" 10 | val formatter = DateTimeFormatter.ofPattern(pattern) 11 | val instant = ZonedDateTime.parse(this, formatter).toInstant() 12 | val datetime = instant.atZone(ZoneId.systemDefault()) 13 | return formatter.format(datetime) 14 | } 15 | 16 | fun Long.formatToLocalZonedDatetimeString(): String { 17 | val pattern = "yyyy-MM-dd HH:mm:ss Z" 18 | val formatter = DateTimeFormatter.ofPattern(pattern) 19 | val instant = Instant.ofEpochMilli(this) 20 | val datetime = instant.atZone(ZoneId.systemDefault()) 21 | return formatter.format(datetime) 22 | } 23 | 24 | fun isChinaMainlandTimezone() = with(ZoneId.systemDefault()) { 25 | id == "Asia/Shanghai" || id == "Asia/Urumqi" 26 | } -------------------------------------------------------------------------------- /base/src/main/java/net/imknown/android/forefrontinfo/base/property/IProperty.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.base.property 2 | 3 | interface IProperty { 4 | fun getString(key: String, default: String): String 5 | fun getBoolean(key: String, default: Boolean): Boolean 6 | } -------------------------------------------------------------------------------- /base/src/main/java/net/imknown/android/forefrontinfo/base/property/PropertyManager.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.base.property 2 | 3 | class PropertyManager(property: IProperty) : IProperty by property { 4 | companion object { 5 | lateinit var instance: PropertyManager 6 | } 7 | } -------------------------------------------------------------------------------- /base/src/main/java/net/imknown/android/forefrontinfo/base/property/impl/DefaultProperty.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.base.property.impl 2 | 3 | import android.annotation.SuppressLint 4 | import net.imknown.android.forefrontinfo.base.property.IProperty 5 | 6 | @SuppressLint("PrivateApi") 7 | object DefaultProperty : IProperty { 8 | private val systemPropertiesClass = Class.forName("android.os.SystemProperties") 9 | 10 | override fun getString(key: String, default: String): String = 11 | systemPropertiesClass.getDeclaredMethod( 12 | "get", String::class.java, String::class.java 13 | ).invoke(null, key, default) as String 14 | 15 | override fun getBoolean(key: String, default: Boolean): Boolean = 16 | systemPropertiesClass.getDeclaredMethod( 17 | "getBoolean", String::class.java, Boolean::class.java 18 | ).invoke(null, key, default) as Boolean 19 | } -------------------------------------------------------------------------------- /base/src/main/java/net/imknown/android/forefrontinfo/base/property/impl/ShizukuProperty.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.base.property.impl 2 | 3 | //object ShizukuProperty : IProperty { 4 | // override fun getString(key: String, default: String): String = 5 | // ShizukuSystemProperties.get(key, default) 6 | // 7 | // override fun getBoolean(key: String, default: Boolean): Boolean = 8 | // ShizukuSystemProperties.getBoolean(key, default) 9 | //} -------------------------------------------------------------------------------- /base/src/main/java/net/imknown/android/forefrontinfo/base/shell/IShell.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.base.shell 2 | 3 | interface IShell { 4 | fun execute(cmd: String): ShellResult 5 | } -------------------------------------------------------------------------------- /base/src/main/java/net/imknown/android/forefrontinfo/base/shell/ShellManager.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.base.shell 2 | 3 | class ShellManager(shell: IShell) : IShell by shell { 4 | companion object { 5 | lateinit var instance: ShellManager 6 | } 7 | } -------------------------------------------------------------------------------- /base/src/main/java/net/imknown/android/forefrontinfo/base/shell/ShellResult.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.base.shell 2 | 3 | class ShellResult( 4 | /** Stdout + Stderr */ 5 | val output: List = emptyList(), 6 | val isSuccess: Boolean = false, 7 | val exitCode: Int? = null 8 | ) -------------------------------------------------------------------------------- /base/src/main/java/net/imknown/android/forefrontinfo/base/shell/impl/DefaultShell.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.base.shell.impl 2 | 3 | import net.imknown.android.forefrontinfo.base.shell.IShell 4 | import net.imknown.android.forefrontinfo.base.shell.ShellResult 5 | 6 | object DefaultShell : IShell { 7 | override fun execute(cmd: String): ShellResult { 8 | val output = mutableListOf() 9 | var exitCode: Int? = null 10 | var isSuccess = false 11 | 12 | try { 13 | val process = Runtime.getRuntime().exec(arrayOf("sh", "-c", "$cmd\nexit")) 14 | process.waitFor() 15 | 16 | exitCode = process.exitValue() 17 | isSuccess = (exitCode == 0) 18 | 19 | val stream = with(process) { if (isSuccess) inputStream else errorStream } 20 | output += stream.bufferedReader().readLines() 21 | } catch (e: Exception) { 22 | output += (e.message ?: "") 23 | } 24 | 25 | return ShellResult(output, isSuccess, exitCode) 26 | } 27 | } -------------------------------------------------------------------------------- /base/src/main/java/net/imknown/android/forefrontinfo/base/shell/impl/LibSuShell.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.base.shell.impl 2 | 3 | import com.topjohnwu.superuser.Shell 4 | import net.imknown.android.forefrontinfo.base.shell.IShell 5 | import net.imknown.android.forefrontinfo.base.shell.ShellResult 6 | 7 | object LibSuShell : IShell { 8 | override fun execute(cmd: String): ShellResult { 9 | val result = Shell.cmd(cmd).exec() 10 | return ShellResult(result.out, result.isSuccess, result.code) 11 | } 12 | } -------------------------------------------------------------------------------- /base/src/test/java/net/imknown/android/forefrontinfo/base/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.base 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } -------------------------------------------------------------------------------- /binderDetector/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import kotlin.collections.plusAssign 2 | 3 | plugins { 4 | alias(libsAndroid.plugins.android.library) 5 | 6 | alias(libsKotlin.plugins.kotlin.android) 7 | } 8 | 9 | android { 10 | namespace = "net.imknown.android.forefrontinfo.binderDetector" 11 | 12 | compileSdk = libsBuild.versions.compileSdk.get().toInt() 13 | // compileSdkExtension = libsBuild.versions.compileSdkExtension.get().toInt() 14 | buildToolsVersion = libsBuild.versions.buildTools.get() 15 | val isPreview = libsBuild.versions.isPreview.get().toBoolean() 16 | if (isPreview) { 17 | compileSdkPreview = libsBuild.versions.compileSdkPreview.get() 18 | buildToolsVersion = libsBuild.versions.buildToolsPreview.get() 19 | } 20 | 21 | defaultConfig { 22 | minSdk = libsBuild.versions.minSdk.get().toInt() 23 | 24 | externalNativeBuild { 25 | cmake { 26 | arguments += listOf("-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang") 27 | 28 | cFlags += listOf("-D__STDC_FORMAT_MACROS") 29 | 30 | cppFlags += listOf("-fexceptions", "-frtti", "-std=c++17") 31 | } 32 | } 33 | } 34 | 35 | ndkVersion = libsBuild.versions.ndk.get() 36 | 37 | externalNativeBuild { 38 | cmake { 39 | path("src/main/cpp/CMakeLists.txt") 40 | version = libsBuild.versions.cmake.get() 41 | } 42 | } 43 | } 44 | 45 | // region [Toolchain] 46 | // https://developer.android.com/build/jdks 47 | // https://kotlinlang.org/docs/gradle-configure-project.html 48 | // https://docs.gradle.org/current/userguide/toolchains.html 49 | private val javaToolchain = libsBuild.versions.javaToolchain.get().toInt() 50 | kotlin { 51 | jvmToolchain(javaToolchain) 52 | } 53 | // endregion [Toolchain] -------------------------------------------------------------------------------- /binderDetector/src/main/cpp/BinderDetector.cpp: -------------------------------------------------------------------------------- 1 | // region [Comments] 2 | // region [Read file, but root needed] 3 | // https://android.googlesource.com/platform/test/vts/+/master/utils/python/controllers/android_device.py#726 4 | // https://android.googlesource.com/platform/test/vts-testcase/kernel/+/master/api/binder/VtsKernelBinderTest.py 5 | // endregion [Read file, but root needed] 6 | 7 | // region [Native] 8 | // "/dev/binder" 9 | // "/dev/binderfs" 10 | // "/dev/binderfs/my-binder" 11 | // "/dev/binderfs/binder-control" 12 | // "/dev/hwbinder" 13 | // "/dev/vndbinder" 14 | // 15 | // https://android.googlesource.com/platform/frameworks/native/+/master/libs/binder/ProcessState.cpp#349 16 | // https://android.googlesource.com/platform/system/libhwbinder/+/master/ProcessState.cpp#406 17 | // https://android.googlesource.com/platform/frameworks/native/+/master/libs/binder/tests/binderDriverInterfaceTest.cpp 18 | // https://android.googlesource.com/platform/external/linux-kselftest/+/master/tools/testing/selftests/filesystems/binderfs/binderfs_test.c#135 19 | // 20 | // https://android.googlesource.com/platform/external/kernel-headers/+/master/original/uapi/linux/android/binder.h#102 21 | // https://android.googlesource.com/platform/bionic/+/master/libc/kernel/uapi/linux/android/binder.h#98 22 | // 23 | // https://android.googlesource.com/platform/external/kernel-headers/+/master/original/uapi/linux/android/binder.h#114 24 | // endregion [Native] 25 | 26 | // region [Doc] 27 | // https://source.android.com/setup/build/gsi#legacy-gsi-build-targets 28 | // https://source.android.com/setup/build/gsi#gsi-build-targets 29 | // endregion [Doc] 30 | // endregion [Comments] 31 | 32 | #include "BinderDetector.h" 33 | 34 | void release(JNIEnv *env, jstring driver, 35 | const char *driverChars, int fd) { 36 | env->ReleaseStringUTFChars(driver, driverChars); 37 | close(fd); 38 | } 39 | 40 | int getErrorNo(JNIEnv *env, jstring driver, 41 | int ret, const char *function, 42 | const char *driverChars, int fd) { 43 | __android_log_print( 44 | ANDROID_LOG_WARN, 45 | "BinderDetector", 46 | "%s: Driver: %s, ret: %d, errorNo: %d, error: %s", 47 | function, driverChars, ret, errno, strerror(errno) 48 | ); 49 | 50 | int errorNo = -abs(errno); 51 | 52 | release(env, driver, driverChars, fd); 53 | 54 | return errorNo; 55 | } 56 | 57 | extern "C" JNIEXPORT jint JNICALL 58 | Java_net_imknown_android_forefrontinfo_binderdetector_BinderDetector_getBinderVersion( 59 | JNIEnv *env, 60 | jobject instance, 61 | jstring driver 62 | ) { 63 | const char *driverChars = env->GetStringUTFChars(driver, nullptr); 64 | int fd = open(driverChars, O_RDONLY | O_CLOEXEC); 65 | if (fd < 0) { 66 | return getErrorNo(env, driver, fd, "open", driverChars, fd); 67 | } 68 | 69 | int version = _IOWR('b', 9, struct binder_version); 70 | int binderVersion = -1; 71 | int ioctlRet = ioctl(fd, version, &binderVersion); 72 | if (ioctlRet < 0) { 73 | return getErrorNo(env, driver, ioctlRet, "ioctl", driverChars, fd); 74 | } 75 | 76 | release(env, driver, driverChars, fd); 77 | 78 | return binderVersion; 79 | } -------------------------------------------------------------------------------- /binderDetector/src/main/cpp/BinderDetector.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | struct binder_version { 11 | __s32 protocol_version; 12 | }; -------------------------------------------------------------------------------- /binderDetector/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about using CMake with Android Studio, read the 2 | # documentation: https://d.android.com/studio/projects/add-native-code.html 3 | 4 | # Sets the minimum version of CMake required to build the native library. 5 | cmake_minimum_required(VERSION 3.10) 6 | 7 | project(BinderDetector) 8 | 9 | set(nativeLibName "BinderDetector") 10 | 11 | # Creates and names a library, sets it as either STATIC 12 | # or SHARED, and provides the relative paths to its source code. 13 | # You can define multiple libraries, and CMake builds them for you. 14 | # Gradle automatically packages shared libraries with your APK. 15 | add_library( 16 | # Sets the name of the library. 17 | ${nativeLibName} 18 | # Sets the library as a shared library. 19 | SHARED 20 | # Provides a relative path to your source file(s). 21 | BinderDetector.cpp 22 | ) 23 | 24 | # Searches for a specified prebuilt library and stores the path as a 25 | # variable. Because CMake includes system libraries in the search path by 26 | # default, you only need to specify the name of the public NDK library 27 | # you want to add. CMake verifies that the library exists before 28 | # completing its build. 29 | find_library( 30 | # Sets the name of the path variable. 31 | log-lib 32 | # Specifies the name of the NDK library that 33 | # you want CMake to locate. 34 | log 35 | ) 36 | 37 | # Specifies libraries CMake should link to your target library. You 38 | # can link multiple libraries, such as libraries you define in this 39 | # build script, prebuilt third-party libraries, or system libraries. 40 | target_link_libraries( 41 | # Specifies the target library. 42 | ${nativeLibName} 43 | # Links the target library to the log library 44 | # included in the NDK. 45 | ${log-lib} 46 | ) -------------------------------------------------------------------------------- /binderDetector/src/main/kotlin/net/imknown/android/forefrontinfo/binderdetector/BinderDetector.kt: -------------------------------------------------------------------------------- 1 | package net.imknown.android.forefrontinfo.binderdetector 2 | 3 | object BinderDetector { 4 | const val PREFIX = "BinderDetector" 5 | 6 | external fun getBinderVersion(driver: String): Int 7 | } -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | alias(libsAndroid.plugins.android.application) apply false 4 | alias(libsAndroid.plugins.android.library) apply false 5 | 6 | alias(libsKotlin.plugins.kotlin.android) apply false 7 | alias(libsKotlin.plugins.kotlinx.serialization) apply false 8 | 9 | alias(libsGoogle.plugins.googleServices) apply false 10 | alias(libsGoogle.plugins.firebase.crashlytics) apply false 11 | } -------------------------------------------------------------------------------- /buildScriptConfig.gradle: -------------------------------------------------------------------------------- 1 | // See: https://github.com/imknown/AndroidLowLevelDetector/blob/master/gradle/toml -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | gradlePluginPortal() 7 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/Ext.kt: -------------------------------------------------------------------------------- 1 | import org.gradle.api.provider.ProviderFactory 2 | import java.time.Instant 3 | import java.time.ZoneId 4 | import java.time.format.DateTimeFormatter 5 | 6 | fun getCurrentDatetime(): String { 7 | val pattern = "yyyyMMdd-HHmm" 8 | val formatter = DateTimeFormatter.ofPattern(pattern) 9 | val instant = Instant.now() 10 | val datetime = instant.atZone(ZoneId.systemDefault()) 11 | return formatter.format(datetime) 12 | } 13 | 14 | fun ProviderFactory.execute(vararg args: Any) = 15 | exec { commandLine(*args) }.standardOutput.asText.get().trim() -------------------------------------------------------------------------------- /buildSrc/src/main/java/Misc.kt: -------------------------------------------------------------------------------- 1 | enum class IssueTracker { 2 | Foss, Firebase 3 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/Versions.kt: -------------------------------------------------------------------------------- 1 | // See: https://github.com/imknown/AndroidLowLevelDetector/blob/master/gradle/toml -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # 6 | # For more details on how to configure your build environment visit 7 | # http://www.gradle.org/docs/current/userguide/build_environment.html 8 | # 9 | # region [Gradle] 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1024M -Dkotlin.daemon.jvm.options\="-Xmx1024M" -XX\:+HeapDumpOnOutOfMemoryError -Dfile.encoding\=UTF-8 -XX\:+UseParallelGC -XX\:MaxMetaspaceSize\=512m 13 | # 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | org.gradle.parallel=true 18 | # 19 | org.gradle.caching=true 20 | org.gradle.configureondemand=true 21 | # 22 | org.gradle.configuration-cache=true 23 | org.gradle.configuration-cache.parallel=true 24 | org.gradle.configuration-cache.integrity-check=true 25 | # 26 | org.gradle.warning.mode=all 27 | # endregion [Gradle] 28 | # 29 | # region [Android] 30 | # AndroidX package structure to make it clearer which packages are bundled with the 31 | # Android operating system, and which are packaged with your app's APK 32 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 33 | android.useAndroidX=true 34 | # Automatically convert third-party libraries to use AndroidX 35 | # android.enableJetifier=true 36 | # 37 | android.enableBuildConfigAsBytecode=true 38 | # 39 | android.debug.obsoleteApi=true 40 | # endregion [Android] 41 | # 42 | # region [Kotlin] 43 | # Kotlin code style for this project: "official" or "obsolete": 44 | kotlin.code.style=official 45 | # endregion [Kotlin] -------------------------------------------------------------------------------- /gradle/toml/android.toml: -------------------------------------------------------------------------------- 1 | # https://maven.google.com 2 | [versions] 3 | # https://developer.android.com/build/releases/gradle-plugin 4 | # https://developer.android.com/studio/releases 5 | # https://androidstudio.googleblog.com/ 6 | # https://maven.google.com/web/index.html#com.android.tools.build:gradle 7 | androidGradlePlugin = "8.11.0-rc01" 8 | androidGradlePlugin-beta = "8.11.0-rc01" 9 | androidGradlePlugin-canary = "8.12.0-alpha03" 10 | 11 | # https://developer.android.com/studio/write/java8-support#library-desugaring 12 | # https://maven.google.com/web/index.html?q=desugar_jdk_libs 13 | # https://github.com/google/desugar_jdk_libs/blob/master/CHANGELOG.md 14 | desugarJdkLibs = "2.1.5" 15 | 16 | # region [AndroidX] 17 | # https://developer.android.com/jetpack/androidx/releases/activity 18 | activity = "1.11.0-rc01" 19 | 20 | # https://developer.android.com/jetpack/androidx/releases/annotation 21 | annotation = "1.9.1" 22 | 23 | annotation-experimental = "1.5.0" 24 | 25 | # https://developer.android.com/jetpack/androidx/releases/appcompat 26 | appcompat = "1.7.1" 27 | 28 | # https://developer.android.com/jetpack/androidx/releases/arch-core 29 | arch-core = "2.2.0" 30 | 31 | # https://developer.android.com/jetpack/androidx/releases/cardview 32 | cardView = "1.0.0" 33 | 34 | # https://developer.android.com/jetpack/androidx/releases/constraintlayout 35 | constraintLayout = "2.2.1" 36 | 37 | # https://developer.android.com/jetpack/androidx/releases/coordinatorlayout 38 | coordinatorLayout = "1.3.0" 39 | 40 | # https://developer.android.com/jetpack/androidx/releases/core 41 | core = "1.16.0" 42 | 43 | # https://developer.android.com/jetpack/androidx/releases/fragment 44 | fragment = "1.8.8" 45 | 46 | # https://developer.android.com/jetpack/androidx/releases/lifecycle 47 | lifecycle = "2.9.1" 48 | 49 | # https://developer.android.com/jetpack/androidx/releases/preference 50 | preference = "1.2.1" 51 | 52 | # https://developer.android.com/jetpack/androidx/releases/recyclerview 53 | recyclerView = "1.4.0" 54 | 55 | # https://developer.android.com/jetpack/androidx/releases/savedstate 56 | savedState = "1.3.0" 57 | 58 | # https://developer.android.com/jetpack/androidx/releases/swiperefreshlayout 59 | swipeRefreshLayout = "1.1.0" 60 | 61 | # https://developer.android.com/jetpack/androidx/releases/webkit 62 | webkit = "1.14.0" 63 | 64 | # https://developer.android.com/jetpack/androidx/releases/test 65 | # region [Test] 66 | test-core = "1.6.1" 67 | test-espresso = "3.6.1" 68 | test-ext-junit = "1.2.1" 69 | # endregion [Test] 70 | # endregion [AndroidX] 71 | 72 | [libraries] 73 | desugarJdkLibs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "desugarJdkLibs" } 74 | 75 | activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activity" } 76 | 77 | annotation = { group = "androidx.annotation", name = "annotation", version.ref = "annotation" } 78 | annotation-experimental = { group = "androidx.annotation", name = "annotation-experimental", version.ref = "annotation-experimental" } 79 | 80 | appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } 81 | appcompat-resources = { group = "androidx.appcompat", name = "appcompat-resources", version.ref = "appcompat" } 82 | 83 | arch-core-common = { group = "androidx.arch.core", name = "core-common", version.ref = "arch-core" } 84 | arch-core-runtime = { group = "androidx.arch.core", name = "core-runtime", version.ref = "arch-core" } 85 | 86 | cardView = { group = "androidx.cardview", name = "cardview", version.ref = "cardView" } 87 | 88 | constraintLayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintLayout" } 89 | 90 | coordinatorLayout = { group = "androidx.coordinatorlayout", name = "coordinatorlayout", version.ref = "coordinatorLayout" } 91 | 92 | core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core" } 93 | 94 | fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "fragment" } 95 | 96 | lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle" } 97 | lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycle" } 98 | lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" } 99 | lifecycle-viewmodel-savedstate = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-savedstate", version.ref = "lifecycle" } 100 | 101 | preference-ktx = { group = "androidx.preference", name = "preference-ktx", version.ref = "preference" } 102 | 103 | recyclerView = { group = "androidx.recyclerview", name = "recyclerview", version.ref = "recyclerView" } 104 | 105 | savedState-ktx = { group = "androidx.savedstate", name = "savedstate-ktx", version.ref = "savedState" } 106 | 107 | swipeRefreshLayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swipeRefreshLayout" } 108 | 109 | webkit = { group = "androidx.webkit", name = "webkit", version.ref = "webkit" } 110 | 111 | test-core-ktx = { group = "androidx.test", name = "core-ktx", version.ref = "test-core" } 112 | test-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "test-espresso" } 113 | test-ext-junit-ktx = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "test-ext-junit" } 114 | 115 | [bundles] 116 | annotation = ["annotation", "annotation-experimental"] 117 | appcompat = ["appcompat", "appcompat-resources"] 118 | arch-core = ["arch-core-common", "arch-core-runtime"] 119 | lifecycle = ["lifecycle-viewmodel-ktx", "lifecycle-livedata-ktx", "lifecycle-runtime-ktx", "lifecycle-viewmodel-savedstate"] 120 | 121 | test = ["test-core-ktx", "test-espresso-core", "test-ext-junit-ktx"] 122 | 123 | [plugins] 124 | android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } 125 | android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" } 126 | -------------------------------------------------------------------------------- /gradle/toml/build.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | isPreview = "false" # true 3 | 4 | # https://developer.android.com/studio/releases/build-tools.html#notes 5 | buildTools = "36.0.0" 6 | buildToolsPreview = "36.0.0" 7 | 8 | minSdk = "21" 9 | 10 | compileSdk = "36" 11 | compileSdkPreview = "C" 12 | 13 | compileSdkExtension = "17" # 36-ext17 (Android 16 Beta 3) 14 | 15 | targetSdk = "36" 16 | targetSdkPreview = "C" 17 | 18 | # https://github.com/android/ndk/releases 19 | ndk = "29.0.13113456 rc1" 20 | # https://cmake.org/cmake/help/latest/release/index.html 21 | cmake = "4.0.2" 22 | 23 | versionCode = "65" 24 | versionName = "1.17.3_unpublished" 25 | 26 | javaToolchain = "21" 27 | 28 | [libraries] 29 | 30 | [bundles] 31 | 32 | [plugins] -------------------------------------------------------------------------------- /gradle/toml/google.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | # https://developers.google.com/android/guides/releases 3 | # https://firebase.google.com/support/release-notes/android 4 | # https://firebase.google.com/docs/android/setup#available-libraries 5 | googleServices-gradlePlugin = "4.4.2" 6 | 7 | # https://firebase.google.com/docs/android/learn-more#bom 8 | firebase-bom = "33.15.0" 9 | 10 | # https://firebase.google.com/docs/crashlytics/get-started?platform=android 11 | # https://firebase.google.com/docs/crashlytics/ndk-reports 12 | firebase-crashlytics-gradlePlugin = "3.0.3" 13 | 14 | # https://github.com/material-components/material-components-android/releases 15 | material = "1.12.0" 16 | 17 | [libraries] 18 | firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebase-bom" } 19 | firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics" } 20 | firebase-crashlytics-ndk = { group = "com.google.firebase", name = "firebase-crashlytics-ndk" } 21 | 22 | material = { group = "com.google.android.material", name = "material", version.ref = "material" } 23 | 24 | [bundles] 25 | firebase = ["firebase-analytics", "firebase-crashlytics-ndk"] 26 | 27 | [plugins] 28 | googleServices = { id = "com.google.gms.google-services", version.ref = "googleServices-gradlePlugin" } 29 | firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebase-crashlytics-gradlePlugin" } 30 | -------------------------------------------------------------------------------- /gradle/toml/kotlin.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | # https://github.com/JetBrains/kotlin/releases 3 | kotlin = "2.2.0-RC2" 4 | 5 | # https://github.com/Kotlin/kotlinx.coroutines/releases 6 | kotlinx-coroutines = "1.10.2" 7 | 8 | # https://github.com/Kotlin/kotlinx.serialization/releases 9 | kotlinx-serialization = "1.8.1" 10 | 11 | # https://github.com/ktorio/ktor/releases 12 | ktor = "3.1.3" 13 | 14 | [libraries] 15 | kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" } 16 | kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-serialization" } 17 | 18 | ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" } 19 | ktor-client-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktor" } 20 | ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging", version.ref = "ktor" } 21 | 22 | [bundles] 23 | ktor-client = ["ktor-client-core", "ktor-client-okhttp", "ktor-client-logging"] 24 | 25 | [plugins] 26 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 27 | 28 | kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } 29 | -------------------------------------------------------------------------------- /gradle/toml/thirdParty.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | # https://github.com/topjohnwu/libsu/releases 3 | libsu = "6.0.0" 4 | 5 | # https://github.com/square/leakcanary/releases 6 | leakCanary = "2.14" 7 | 8 | # https://github.com/G00fY2/version-compare/releases 9 | versionCompare = "1.5.0" 10 | 11 | # https://github.com/RikkaApps/Shizuku-API#add-dependency 12 | shizuku = "13.1.5" 13 | 14 | [libraries] 15 | libsu = { group = "com.github.topjohnwu.libsu", name = "core", version.ref = "libsu" } 16 | 17 | leakCanary-android = { group = "com.squareup.leakcanary", name = "leakcanary-android", version.ref = "leakCanary" } 18 | leakCanary-plumber-android = { group = "com.squareup.leakcanary", name = "plumber-android", version.ref = "leakCanary" } 19 | 20 | versionCompare = { group = "io.github.g00fy2", name = "versioncompare", version.ref = "versionCompare" } 21 | 22 | shizuku-api = { group = "dev.rikka.shizuku", name = "api", version.ref = "shizuku" } 23 | shizuku-provider = { group = "dev.rikka.shizuku", name = "provider", version.ref = "shizuku" } 24 | 25 | [bundles] 26 | leakCanary = ["leakCanary-android"] # "leakCanary-plumber-android" 27 | 28 | shizuku = ["shizuku-api", "shizuku-provider"] 29 | 30 | [plugins] -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imknown/AndroidLowLevelDetector/9b9ffa12d7324fd953cb7eaf34d1b9d66d1e432e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | # https://services.gradle.org/distributions/ 2 | # https://docs.gradle.org/current/userguide/compatibility.html 3 | # https://gradle.org/release-checksums/ 4 | # 5 | # ./gradlew wrapper --gradle-version 8.14.1 --distribution-type all \ 6 | # --gradle-distribution-sha256-sum d7042b3c11565c192041fc8c4703f541b888286404b4f267138c1d094d8ecdca 7 | # 8 | distributionBase=GRADLE_USER_HOME 9 | distributionPath=wrapper/dists 10 | distributionSha256Sum=d7042b3c11565c192041fc8c4703f541b888286404b4f267138c1d094d8ecdca 11 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-all.zip 12 | networkTimeout=10000 13 | validateDistributionUrl=true 14 | zipStoreBase=GRADLE_USER_HOME 15 | zipStorePath=wrapper/dists 16 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /keys/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imknown/AndroidLowLevelDetector/9b9ffa12d7324fd953cb7eaf34d1b9d66d1e432e/keys/debug.keystore -------------------------------------------------------------------------------- /metadata/en-US/changelogs/62.txt: -------------------------------------------------------------------------------- 1 | * Initial F-Droid release 2 | * New translations 3 | * Database updates 4 | * Updated dependencies 5 | -------------------------------------------------------------------------------- /metadata/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | Features: 2 | * Detect Android version 3 | * Detect Android Build Id version 4 | * Detect Android security patch level 5 | * Detect Vendor security patch level 6 | * Detect Project Mainline module version (Google Play system update) 7 | * Detect Linux kernel 8 | * Detect A/B or A-Only 9 | * Detect Dynamic Partitions 10 | * Detect Dynamic System Update(DSU) 11 | * Detect Project Treble 12 | * Detect GSI compatibility 13 | * Detect Binder bitness 14 | * Detect Process/VM architecture 15 | * Detect Vendor NDK 16 | * Detect System-as-root 17 | * Detect (flattened) APEX 18 | * Detect Toybox 19 | * Detect WebView implement 20 | * Detect outdatedTargetSdkVersion apk 21 | * Dark mode supported 22 | * Online/offline mode (fetching data from remote server or local) 23 | * MultiWindow/FreeForm/Foldable/Landscape supported 24 | -------------------------------------------------------------------------------- /metadata/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imknown/AndroidLowLevelDetector/9b9ffa12d7324fd953cb7eaf34d1b9d66d1e432e/metadata/en-US/images/icon.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imknown/AndroidLowLevelDetector/9b9ffa12d7324fd953cb7eaf34d1b9d66d1e432e/metadata/en-US/images/phoneScreenshots/1.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imknown/AndroidLowLevelDetector/9b9ffa12d7324fd953cb7eaf34d1b9d66d1e432e/metadata/en-US/images/phoneScreenshots/2.png -------------------------------------------------------------------------------- /metadata/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | Detect Treble, GSI, Mainline, APEX, system-as-root(SAR), A/B, etc. 2 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.gradle.kotlin.dsl.support.uppercaseFirstChar 2 | 3 | pluginManagement { 4 | repositories { 5 | google { 6 | content { 7 | includeGroupByRegex("com\\.android.*") 8 | includeGroupByRegex("com\\.google.*") 9 | includeGroupByRegex("androidx.*") 10 | } 11 | } 12 | mavenCentral() 13 | gradlePluginPortal() 14 | } 15 | } 16 | 17 | dependencyResolutionManagement { 18 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 19 | repositories { 20 | google() 21 | mavenCentral() 22 | maven("https://jitpack.io") 23 | } 24 | 25 | versionCatalogs { 26 | fun String.toToml() { 27 | val originalName = this 28 | val libName = "libs${originalName.uppercaseFirstChar()}" 29 | create(libName) { 30 | from(files("gradle/toml/$originalName.toml")) 31 | } 32 | } 33 | 34 | "build".toToml() 35 | "android".toToml() 36 | "kotlin".toToml() 37 | "google".toToml() 38 | "thirdParty".toToml() 39 | } 40 | } 41 | 42 | rootProject.name = "AndroidLowLevelDetector" 43 | 44 | include(":binderDetector") 45 | include(":base") 46 | include(":app") 47 | 48 | // region [Build Scan] 49 | // https://gradle.com/scans/gradle/ 50 | // https://docs.gradle.com/develocity/gradle-plugin/current/ 51 | plugins { 52 | // https://plugins.gradle.org/plugin/com.gradle.develocity 53 | id("com.gradle.develocity") version "4.0.2" 54 | } 55 | 56 | develocity { 57 | buildScan { 58 | termsOfUseUrl = "https://gradle.com/help/legal-terms-of-use" 59 | termsOfUseAgree = "yes" 60 | 61 | publishing.onlyIf { false } 62 | } 63 | } 64 | // endregion [Build Scan] --------------------------------------------------------------------------------