├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug-report.md
│ └── feature-request.md
├── dependabot.yml
└── workflows
│ └── push.yml
├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── libs
│ └── sqlite-android-3410200.aar
├── lint.xml
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── aidl
│ └── com
│ │ └── jacopomii
│ │ └── gappsmod
│ │ └── ICoreRootService.aidl
│ ├── java
│ └── com
│ │ └── jacopomii
│ │ └── gappsmod
│ │ ├── application
│ │ └── GAppsModApplication.java
│ │ ├── data
│ │ ├── BooleanFlag.java
│ │ ├── Constants.java
│ │ ├── PhenotypeDBPackageName.java
│ │ └── Version.java
│ │ ├── service
│ │ └── CoreRootService.java
│ │ ├── ui
│ │ ├── activity
│ │ │ ├── MainActivity.java
│ │ │ └── SplashScreenActivity.java
│ │ ├── adapter
│ │ │ ├── BooleanModsRecyclerViewAdapter.java
│ │ │ └── SelectPackageRecyclerViewAdapter.java
│ │ ├── fragment
│ │ │ ├── BooleanModsFragment.java
│ │ │ ├── InformationFragment.java
│ │ │ ├── RevertModsFragment.java
│ │ │ └── SuggestedModsFragment.java
│ │ └── view
│ │ │ ├── FilterableSearchView.java
│ │ │ ├── ProgrammaticMaterialSwitchView.java
│ │ │ ├── SuggestedModsAppHeaderView.java
│ │ │ └── SwitchCardView.java
│ │ └── util
│ │ ├── OnItemClickListener.java
│ │ └── Utils.java
│ ├── proto
│ └── call_screen_i18n.proto
│ └── res
│ ├── drawable
│ ├── ic_arrow_down_24.xml
│ ├── ic_arrow_up_24.xml
│ ├── ic_beta_24.xml
│ ├── ic_error_24.xml
│ ├── ic_fail_24.xml
│ ├── ic_install_24.xml
│ ├── ic_menu_search_24.xml
│ ├── ic_nav_drawer_boolean_mods_24.xml
│ ├── ic_nav_drawer_delete_24.xml
│ ├── ic_nav_drawer_information_24.xml
│ ├── ic_nav_drawer_suggested_mods_24.xml
│ ├── ic_save_24.xml
│ └── ic_success_24.xml
│ ├── layouts
│ ├── activities
│ │ └── layout
│ │ │ ├── activity_main.xml
│ │ │ └── activity_splash_screen.xml
│ ├── dialogs
│ │ └── layout
│ │ │ └── dialog_select_package.xml
│ ├── fragments
│ │ └── layout
│ │ │ ├── fragment_boolean_mods.xml
│ │ │ ├── fragment_information.xml
│ │ │ ├── fragment_revert_mods.xml
│ │ │ └── fragment_suggested_mods.xml
│ └── items
│ │ └── layout
│ │ ├── filterable_searchview.xml
│ │ ├── nav_drawer_header.xml
│ │ ├── package_row.xml
│ │ ├── suggested_mods_app_header.xml
│ │ └── switch_card.xml
│ ├── menu
│ ├── nav_drawer.xml
│ └── search_menu.xml
│ ├── mipmap-anydpi-v26
│ └── ic_launcher.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_foreground.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_foreground.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_foreground.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_foreground.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_foreground.png
│ ├── navigation
│ └── mobile_navigation.xml
│ ├── raw
│ └── silent_wav.wav
│ ├── values-es
│ └── strings.xml
│ ├── values-fr
│ └── strings.xml
│ ├── values-it
│ └── strings.xml
│ ├── values-night
│ └── themes.xml
│ ├── values-notnight-v23
│ └── themes.xml
│ ├── values-notnight-v27
│ └── themes.xml
│ └── values
│ ├── attrs.xml
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── themes.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: jacopotediosi
2 | custom: https://paypal.me/jacopotediosi
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report
3 | about: Report something that isn't working
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Overview
11 | [NOTE]: # ( Give a BRIEF summary about your problem )
12 |
13 |
14 | ## Steps to Reproduce
15 | [NOTE]: # ( Provide a simple set of steps to reproduce this bug. )
16 | 1.
17 | 2.
18 | 3.
19 |
20 | ## Expected Behavior
21 | [NOTE]: # ( Tell us what you expected to happen )
22 |
23 |
24 | ## Actual Behavior
25 | [NOTE]: # ( Tell us what actually happens )
26 |
27 |
28 | ## Screenshots
29 | [NOTE]: # ( If applicable, add screenshots to help explain your problem. )
30 |
31 |
32 | ## System Information
33 | - Device and model:
34 |
35 | - ROM and Android version:
36 |
37 | - Is the Google app you are trying to tweak (e.g., Phone by Google) installed as system app: yes/no
38 |
39 | - Installed Magisk / other SU Manager version:
40 |
41 | [NOTE]: # ( Paste below the output of the `adb shell "dumpsys package com.jacopomii.gappsmod | grep version"` command )
42 | - Installed GAppsMod version:
43 |
44 |
45 | [NOTE]: # ( Paste below the output of the `adb shell "dumpsys package REPLACE_WITH_PACKAGENAME | grep version"` command )
46 | - The version of the Google app you are trying to tweak (e.g., Phone by Google):
47 |
48 |
49 | [NOTE]: # ( Paste below the output of the `adb shell "getprop | grep locale"` command )
50 | - Your device language (locale):
51 |
52 |
53 | [NOTE]: # ( Paste below the output of the `adb shell "getprop | grep iso-country"` command )
54 | - Your location (country of the SIM and country where you are):
55 |
56 |
57 | ## Logcat
58 | [NOTE]: # (
59 | Launch the Google app you are trying to tweak (e.g., Phone by Google) in Debug mode using the `adb shell "am start -D REPLACE_WITH_PACKAGENAME"` command.
60 | Open another terminal and use the `adb logcat > logs.txt` command to start capturing logs.
61 | Perform the necessary steps to replicate the bug, then press CTRL+C to stop capturing logs.
62 | Attach below the resulting logs.txt file.
63 | )
64 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature Request
3 | about: Suggest an idea for a new feature you want
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Summary
11 | [NOTE]: # ( Provide a brief overview of what the new feature is all about )
12 |
13 |
14 | ## Solution
15 | [NOTE]: # ( A clear and concise description of what you want to happen )
16 |
17 |
18 | ## Examples
19 | [NOTE]: # ( Show us a picture or mock-up of your proposal )
20 |
21 |
22 | ## Alternatives
23 | [NOTE]: # ( A clear and concise description of any alternative solutions or features you've considered )
24 |
25 |
26 | ## Context
27 | [NOTE]: # ( Why does this feature matter to you? What unique circumstances do you have? )
28 |
--------------------------------------------------------------------------------
/.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/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "gradle"
9 | directory: "/"
10 | schedule:
11 | interval: "monthly"
12 |
--------------------------------------------------------------------------------
/.github/workflows/push.yml:
--------------------------------------------------------------------------------
1 | name: Assemble on push
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | paths-ignore:
7 | - '.github/**'
8 | - '**.md'
9 | workflow_dispatch:
10 |
11 | jobs:
12 | build:
13 | name: Build on ${{ matrix.os }}
14 | runs-on: ${{ matrix.os }}
15 | strategy:
16 | fail-fast: false
17 | matrix:
18 | os: [ ubuntu-latest ]
19 |
20 | steps:
21 | - name: Checkout
22 | uses: actions/checkout@v3
23 | with:
24 | submodules: 'recursive'
25 | fetch-depth: 0
26 |
27 | - name: Gradle wrapper validation
28 | uses: gradle/wrapper-validation-action@v1
29 |
30 | - name: Set up JDK 17
31 | uses: actions/setup-java@v3
32 | with:
33 | distribution: 'temurin'
34 | java-version: '17'
35 |
36 | - name: Write keystore parameters
37 | run: |
38 | echo keystore.password='${{ secrets.KEYSTORE_PASSWORD }}' >> local.properties
39 | echo keystore.alias='${{ secrets.KEYSTORE_ALIAS }}' >> local.properties
40 | echo keystore.alias_password='${{ secrets.KEYSTORE_ALIAS_PASSWORD }}' >> local.properties
41 | echo keystore.path=`pwd`/keystore.jks >> local.properties
42 | echo "${{ secrets.KEYSTORE_KEY }}" | base64 --decode > keystore.jks
43 |
44 | - name: Gradle Dependency Submission
45 | uses: mikepenz/gradle-dependency-submission@v0.8.6
46 | with:
47 | gradle-build-module: ":app"
48 |
49 | - name: Assemble
50 | uses: gradle/gradle-build-action@v2
51 | with:
52 | arguments: assemble
53 |
54 | - name: Get short commit hash
55 | run: |
56 | echo "LATEST_COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
57 |
58 | - name: Upload debug
59 | if: success()
60 | uses: actions/upload-artifact@v3
61 | with:
62 | name: ${{ github.event.repository.name }}-${{ env.LATEST_COMMIT_HASH }}-debug.apk
63 | path: "app/build/outputs/apk/debug/app-debug.apk"
64 |
65 | - name: Upload release
66 | if: success()
67 | uses: actions/upload-artifact@v3
68 | with:
69 | name: ${{ github.event.repository.name }}-${{ env.LATEST_COMMIT_HASH }}-release.apk
70 | path: "app/build/outputs/apk/release/app-release.apk"
71 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | .idea
4 | .DS_Store
5 | /build
6 | local.properties
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Deprecation notice
2 | ## !!! This project is no longer maintained !!!
3 |
4 | I had started researching how Phenotype DB works as a hobby, and this application was only meant to be a proof of concept.
5 |
6 | Making things work required a huge amount of reverse engineering of Google applications, and today I no longer have time to dedicate to it.
7 |
8 | I've also been told that the structure of Phenotype DB has been recently changed, so I expect this project to no longer work, or to stop working soon.
9 |
10 | If you are looking for a replacement, I suggest you take a look at [GMS-Flags](https://github.com/polodarb/GMS-Flags), a still maintained application created by other users who were part of the community of this project itself.
11 |
12 | # GAppsMod (ex GoogleDialerMod)
13 | The ultimate All-In-One Utility to tweak Google applications.
14 |
15 |
16 | ## Downloads:
17 | - Please visit the [GAppsMod Release Page](https://github.com/jacopotediosi/GAppsMod/releases)
18 |
19 |
20 | ## How do I use it?
21 | - Always make sure you're using the latest beta version of the Google apps you want to tweak to take advantage of the latest features
22 | - Allow root access to GAppsMod, apply any mods you want, then force close and reopen Google apps a few times for them to take effect
23 | - There is no need to keep GAppsMod installed after applying the desired mods, because they (should) survive Google applications updates / reinstalls over time
24 |
25 |
26 | ## How does it work?
27 | In every Android device there is a database, called Phenotype.db, managed by Google Play Services, containing "flags" that affect the behavior of all installed Google applications.
28 |
29 | Some of those flags concern applications core functionalities, while others pertain to hidden or upcoming features that have not yet been released.
30 |
31 | What GAppsMod does is execute SQLite queries on that database and override the configuration files of Google applications to enable or modify their functionality at will.
32 |
33 |
34 | ## Features:
35 | - Supports all arm / arm64 / x86 / x86_64 devices and all Android versions from 5.0 (Lollipop)
36 | - Enable / disable hidden features for all users at once when Android "multiple users" mode is in use
37 | - Allows users to list and change all Phenotype DB boolean flags for all installed Google applications
38 | - A convenient home screen brings together the suggested mods for the most used Google applications
39 |
40 |
41 | ## Currently suggested mods
42 | - For the **Phone** application ([link](https://play.google.com/store/apps/details?id=com.google.android.dialer)):
43 | - Force **enable call recording** feature, even on unsupported devices or in unsupported countries ([ref](https://support.google.com/phoneapp/answer/9803950))
44 | - Enable also **automatic call recording** ("always record") feature based on caller (otherwise only available in India)
45 | - Silence the annoying "registration has started / ended" **call recording announcements** (only on Phone version <= 94.x)
46 | - Force **enable call screening** and "revelio" (advanced automatic call screening) features, even on unsupported devices or in unsupported countries ([ref](https://support.google.com/phoneapp/answer/9118387))
47 | - Allows users to choose the language for call screening
48 | - For the **Messages** application ([link](https://play.google.com/store/apps/details?id=com.google.android.apps.messaging)):
49 | - Force **enable debug menu** (it can also be enabled without mods by entering `*xyzzy*` in the application's search field)
50 | - Force **enable message organization** ("supersort")
51 | - Force **enable marking conversations as unread**
52 | - Force **enable verified SMS** settings menu ([ref](https://support.google.com/messages/answer/9326240))
53 | - Force **enable always sending images by Google Photos links in SMS** ([ref](https://9to5google.com/2022/02/19/messages-google-photos/))
54 | - Force **enable nudges and birthday reminders** ([ref](https://support.google.com/messages/answer/11555591))
55 | - Force **enable Bard AI draft suggestions** ("magic compose") ([ref](https://9to5google.com/2023/05/05/google-messages-magic-compose-ai/))
56 | - Force enable smart features: **spotlights suggestions** ([ref](https://9to5google.com/2023/02/02/google-messages-assistant/)), **stickers suggestions**, **smart compose** ([ref](https://9to5google.com/2020/06/30/gboard-android-smart-compose-google-messages/)), **smart actions (smart reply) in notifications**
57 |
58 | And much more coming soon :)
59 |
60 |
61 | ## Demo
62 | 
63 |
64 |
65 | ## Troubleshooting:
66 | - After enabling / disabling any mod, please force close and reopen a few times the Google application you are trying to mod. You may also need to reboot for the changes to take effect.
67 | - Before to report an issue try to delete Google apps data, to reboot your phone and to try again what didn't work
68 |
69 |
70 | ## Donations
71 | If you really like my work, please consider a donation via [Paypal](https://paypal.me/jacopotediosi) or [Github Sponsor](https://github.com/sponsors/jacopotediosi). Even a small amount will be appreciated.
72 |
73 |
74 | ## Credits:
75 | - Thanks to [Gabriele Rizzo aka shmykelsa](https://github.com/shmykelsa), [Jen94](https://github.com/jen94) and [SAAX by agentdr8](https://gitlab.com/agentdr8/saax) for their [AA-Tweaker](https://github.com/shmykelsa/AA-Tweaker) app, which inspired me making GAppsMod
76 | - [Libsu](https://github.com/topjohnwu/libsu) by [topjohnwu](https://github.com/topjohnwu)
77 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "com.google.protobuf"
4 | id "com.likethesalad.stem"
5 | }
6 |
7 | def keyfile
8 | def keystorePSW
9 | def keystoreAlias
10 | def keystoreAliasPSW
11 |
12 | Properties properties = new Properties()
13 | properties.load(project.rootProject.file("local.properties").newDataInputStream())
14 | def keystoreFilepath = properties.getProperty("keystore.path")
15 |
16 | if (keystoreFilepath) {
17 | keyfile = file(keystoreFilepath)
18 | keystorePSW = properties.getProperty("keystore.password")
19 | keystoreAlias = properties.getProperty("keystore.alias")
20 | keystoreAliasPSW = properties.getProperty("keystore.alias_password")
21 | } else {
22 | // Remember to config your keystore settings in local.properties or in the below lines
23 | keyfile = file("C:/keystore.jks")
24 | keystorePSW = "CHANGEME"
25 | keystoreAlias = "CHANGEME"
26 | keystoreAliasPSW = "CHANGEME"
27 | }
28 |
29 | android {
30 | namespace "com.jacopomii.gappsmod"
31 | compileSdk 33
32 |
33 | defaultConfig {
34 | applicationId "com.jacopomii.gappsmod"
35 | minSdk 21
36 | targetSdk 33
37 | versionCode 400
38 | versionName "4.00"
39 | }
40 |
41 | compileOptions {
42 | sourceCompatibility JavaVersion.VERSION_1_8
43 | targetCompatibility JavaVersion.VERSION_1_8
44 | }
45 |
46 | signingConfigs {
47 | release {
48 | storeFile keyfile
49 | storePassword keystorePSW
50 | keyAlias keystoreAlias
51 | keyPassword keystoreAliasPSW
52 | }
53 | }
54 |
55 | buildTypes {
56 | release {
57 | minifyEnabled true
58 | shrinkResources true
59 | proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
60 | signingConfig signingConfigs.release
61 | }
62 | }
63 |
64 | buildFeatures {
65 | viewBinding true
66 | aidl true
67 | buildConfig true
68 | }
69 |
70 | sourceSets {
71 | main {
72 | res.srcDirs = ["src/main/res",
73 | "src/main/res/layouts/activities",
74 | "src/main/res/layouts/dialogs",
75 | "src/main/res/layouts/fragments",
76 | "src/main/res/layouts/items"]
77 | }
78 | }
79 | }
80 |
81 | dependencies {
82 | // Libsu
83 | def libsuVersion = "5.0.5"
84 | implementation "com.github.topjohnwu.libsu:core:${libsuVersion}"
85 | implementation "com.github.topjohnwu.libsu:service:${libsuVersion}"
86 | implementation "com.github.topjohnwu.libsu:nio:${libsuVersion}"
87 |
88 | // Official SQLite Java Bindings, downloaded from https://www.sqlite.org/download.html
89 | implementation files("libs/sqlite-android-3410200.aar")
90 |
91 | // Advanced toast
92 | implementation "com.github.GrenderG:Toasty:1.5.2"
93 |
94 | // HTTP
95 | implementation "com.android.volley:volley:1.2.1"
96 |
97 | // Protobuf
98 | implementation "com.google.protobuf:protobuf-javalite:3.23.1"
99 |
100 | // Apache Commons
101 | //noinspection GradleDependency
102 | implementation "commons-io:commons-io:2.12.0"
103 | implementation "org.apache.commons:commons-lang3:3.12.0"
104 |
105 | // Navigation drawer
106 | def navigationVersion = "2.5.3"
107 | implementation "androidx.navigation:navigation-fragment:${navigationVersion}"
108 | implementation "androidx.navigation:navigation-ui:${navigationVersion}"
109 |
110 | // FastScroller for RecyclerView
111 | implementation "io.github.l4digital:fastscroll:2.1.0"
112 |
113 | // Other UI Components
114 | implementation "com.google.android.material:material:1.9.0"
115 | implementation "androidx.appcompat:appcompat:1.6.1"
116 | implementation "androidx.constraintlayout:constraintlayout:2.1.4"
117 | }
118 |
119 | protobuf {
120 | protoc {
121 | artifact = "com.google.protobuf:protoc:3.23.1"
122 | }
123 | generateProtoTasks {
124 | all().each { task ->
125 | task.builtins {
126 | java {
127 | option "lite"
128 | }
129 | }
130 | }
131 | }
132 | }
--------------------------------------------------------------------------------
/app/libs/sqlite-android-3410200.aar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jacopotediosi/GAppsMod/6c63c56da89a2be20349e113dc3167b793cfe885/app/libs/sqlite-android-3410200.aar
--------------------------------------------------------------------------------
/app/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/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.
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
22 |
23 | # Keep protobuf autogenerated classes
24 | -keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
25 |
26 | # Keep SQLite Java Bindings classes
27 | -keep class org.sqlite.** { *; }
28 | -keep class org.sqlite.database.** { *; }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
10 |
19 |
20 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/app/src/main/aidl/com/jacopomii/gappsmod/ICoreRootService.aidl:
--------------------------------------------------------------------------------
1 | package com.jacopomii.gappsmod;
2 |
3 | interface ICoreRootService {
4 | IBinder getFileSystemService();
5 |
6 | /**
7 | * Query the GMS and Vending Phenotype DBs to get a list of all package names that have at least
8 | * one Flag set.
9 | *
10 | * @return a {@code HashMap} in the format "Phenotype package name" => "Android package name".
11 | */
12 | Map phenotypeDBGetAllPackageNames();
13 |
14 | /**
15 | * Query the GMS and Vending Phenotype DBs to get a list of all package names that have at least
16 | * one Flag overridden.
17 | *
18 | * @return a {@code HashMap} in the format "Phenotype package name" => "Android package name".
19 | */
20 | Map phenotypeDBGetAllOverriddenPackageNames();
21 |
22 | /**
23 | * Query the GMS/Vending Phenotype DB (based on the {@code phenotypePackageName}) to get the
24 | * Android package name corresponding to a given {@code phenotypePackageName}.
25 | *
26 | * @param phenotypePackageName the Phenotype package name for which the corresponding Android
27 | * package name is to be returned.
28 | * @return the Android package name corresponding to the specified {@code phenotypePackageName}.
29 | * An empty string if the phenotypePackageName couldn't be found.
30 | */
31 | String phenotypeDBGetAndroidPackageNameByPhenotypePackageName(in String phenotypePackageName);
32 |
33 | /**
34 | * Query the GMS/Vending Phenotype DB (based on the {@code phenotypePackageName}), selecting all
35 | * the boolean flags for a given {@code phenotypePackageName}.
36 | *
37 | * @param phenotypePackageName the Phenotype (not Android) package name whose flags are to be
38 | * returned.
39 | * @return a {@code HashMap} that uses the ({@code String}) flag name as the key.
40 | * For performance reasons, the value of this HashMap is a {@code List} structured as follows:
41 | * - Position 0 contains the {@code Boolean} value of the flag, giving priority to the value
42 | * overridden in the FlagOverrides table, if present, over the one contained in the Flags table.
43 | * - Position 1 contains the {@code Boolean} value "changed", which is {@code true} if and only
44 | * if the returned flag is overwritten in the FlagOverrides table and has a different value
45 | * than the one contained in the Flags table; {@code false} otherwise.
46 | */
47 | Map phenotypeDBGetBooleanFlagsOrOverridden(in String phenotypePackageName);
48 |
49 | /**
50 | * Query the GMS/Vending Phenotype DB (based on the {@code phenotypePackageName}) to find out if
51 | * all given {@code flags} are overridden for a given {@code phenotypePackageName}.
52 | * Please note that the fact that a flag is overridden only implies that it is present in the
53 | * FlagOverrides table, and not that its value is different from what is indicated in the Flags
54 | * table.
55 | *
56 | * @param phenotypePackageName the Phenotype (not Android) package name for which to check.
57 | * @param flags the flags to check.
58 | * @return {@code true} if all given {@code flags} are overridden for the given
59 | * {@code phenotypePackageName}; {@code false} otherwise.
60 | */
61 | boolean phenotypeDBAreAllFlagsOverridden(in String phenotypePackageName, in List flags);
62 |
63 | /**
64 | * Remove all flag overrides from the GMS and Vending Phenotype DBs by truncating the
65 | * FlagOverrides table.
66 | * It also clears from the filesystem the Phenotype cache of all applications for which at least
67 | * one flag was overridden.
68 | */
69 | void phenotypeDBDeleteAllFlagOverrides();
70 |
71 | /**
72 | * Delete all flag overrides from the GMS/Vending Phenotype DB (based on the
73 | * {@code phenotypePackageName}) for a given {@code phenotypePackageName}.
74 | * It also clears from the filesystem the Phenotype cache of the application corresponding to
75 | * the given {@code phenotypePackageName}.
76 | *
77 | * @param phenotypePackageName the Phenotype (not Android) package name for which to delete the
78 | * flag overrides.
79 | */
80 | void phenotypeDBDeleteAllFlagOverridesByPhenotypePackageName(in String phenotypePackageName);
81 |
82 | /**
83 | * Delete a given list of flag overrides from the GMS/Vending Phenotype DB (based on the
84 | * {@code phenotypePackageName}) for a given {@code phenotypePackageName}.
85 | * It also clears from the filesystem the Phenotype cache of the application corresponding to
86 | * the given {@code phenotypePackageName}.
87 | *
88 | * @param phenotypePackageName the Phenotype (not Android) package name for which to delete the
89 | * flag overrides.
90 | * @param flags the list of flags to delete.
91 | */
92 | void phenotypeDBDeleteFlagOverrides(in String phenotypePackageName, in List flags);
93 |
94 | /**
95 | * Override the value of a boolean flag in the GMS/Vending Phenotype DB (based on the
96 | * {@code phenotypePackageName}) for a given {@code phenotypePackageName}.
97 | * It also clears from the filesystem the Phenotype cache of the application corresponding to
98 | * the given {@code phenotypePackageName}.
99 | *
100 | * @param phenotypePackageName the Phenotype (not Android) package name for which to override
101 | * the flag.
102 | * @param flag the name of the flag to override.
103 | * @param value the value to override the flag with.
104 | */
105 | void phenotypeDBOverrideBooleanFlag(in String phenotypePackageName, in String flag, in boolean value);
106 |
107 | /**
108 | * Override the value of an extension flag in the GMS/Vending Phenotype DB (based on the
109 | * {@code phenotypePackageName}) for a given {@code phenotypePackageName}.
110 | * It also clears from the filesystem the Phenotype cache of the application corresponding to
111 | * the given {@code phenotypePackageName}.
112 | *
113 | * @param phenotypePackageName the Phenotype (not Android) package name for which to override
114 | * the flag.
115 | * @param flag the name of the flag to override.
116 | * @param value the value to override the flag with.
117 | */
118 | void phenotypeDBOverrideExtensionFlag(in String phenotypePackageName, in String flag, in byte[] value);
119 |
120 | /**
121 | * Override the value of a string flag in the GMS/Vending Phenotype DB (based on the
122 | * {@code phenotypePackageName}) for a given {@code phenotypePackageName}.
123 | * It also clears from the filesystem the Phenotype cache of the application corresponding to
124 | * the given {@code phenotypePackageName}.
125 | *
126 | * @param phenotypePackageName the Phenotype (not Android) package name for which to override
127 | * the flag.
128 | * @param flag the name of the flag to override.
129 | * @param value the value to override the flag with.
130 | */
131 | void phenotypeDBOverrideStringFlag(in String phenotypePackageName, in String flag, in String value);
132 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jacopomii/gappsmod/application/GAppsModApplication.java:
--------------------------------------------------------------------------------
1 | package com.jacopomii.gappsmod.application;
2 |
3 | import android.app.Application;
4 |
5 | import com.google.android.material.color.DynamicColors;
6 |
7 | public class GAppsModApplication extends Application {
8 | @Override
9 | public void onCreate() {
10 | super.onCreate();
11 | DynamicColors.applyToActivitiesIfAvailable(this);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jacopomii/gappsmod/data/BooleanFlag.java:
--------------------------------------------------------------------------------
1 | package com.jacopomii.gappsmod.data;
2 |
3 | public class BooleanFlag {
4 | private final String mFlagName;
5 | private boolean mFlagValue;
6 | private boolean mFlagOverriddenAndChanged;
7 |
8 | public BooleanFlag(String flagName, boolean flagValue, boolean flagOverriddenAndChanged) {
9 | mFlagName = flagName;
10 | mFlagValue = flagValue;
11 | mFlagOverriddenAndChanged = flagOverriddenAndChanged;
12 | }
13 |
14 | public String getFlagName() {
15 | return mFlagName;
16 | }
17 |
18 | public boolean getFlagValue() {
19 | return mFlagValue;
20 | }
21 |
22 | public void setFlagValue(boolean flagValue) {
23 | mFlagValue = flagValue;
24 | }
25 |
26 | public void setFlagOverriddenAndChanged(boolean flagOverriddenAndChanged) {
27 | mFlagOverriddenAndChanged = flagOverriddenAndChanged;
28 | }
29 |
30 | public boolean getFlagOverriddenAndChanged() {
31 | return mFlagOverriddenAndChanged;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jacopomii/gappsmod/data/Constants.java:
--------------------------------------------------------------------------------
1 | package com.jacopomii.gappsmod.data;
2 |
3 | import android.annotation.SuppressLint;
4 |
5 | @SuppressLint("SdCardPath")
6 | public interface Constants {
7 | // Android package names
8 | String GMS_ANDROID_PACKAGE_NAME = "com.google.android.gms";
9 | String VENDING_ANDROID_PACKAGE_NAME = "com.android.vending";
10 | String DIALER_ANDROID_PACKAGE_NAME = "com.google.android.dialer";
11 | String MESSAGES_ANDROID_PACKAGE_NAME = "com.google.android.apps.messaging";
12 |
13 | // Phenotype package names
14 | String DIALER_PHENOTYPE_PACKAGE_NAME = "com.google.android.dialer";
15 | String MESSAGES_PHENOTYPE_PACKAGE_NAME = "com.google.android.apps.messaging#com.google.android.apps.messaging";
16 |
17 | // Google Play links
18 | String GOOGLE_PLAY_DETAILS_LINK = "https://play.google.com/store/apps/details?id=";
19 | String GOOGLE_PLAY_BETA_LINK = "https://play.google.com/apps/testing/";
20 |
21 | // Data / data folders
22 | String DATA_DATA_PREFIX = "/data/data/";
23 | String DIALER_CALLRECORDINGPROMPT = DATA_DATA_PREFIX + DIALER_ANDROID_PACKAGE_NAME + "/files/callrecordingprompt";
24 | String GMS_PHENOTYPE_DB = DATA_DATA_PREFIX + GMS_ANDROID_PACKAGE_NAME + "/databases/phenotype.db";
25 | String VENDING_PHENOTYPE_DB = DATA_DATA_PREFIX + VENDING_ANDROID_PACKAGE_NAME + "/databases/phenotype.db";
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jacopomii/gappsmod/data/PhenotypeDBPackageName.java:
--------------------------------------------------------------------------------
1 | package com.jacopomii.gappsmod.data;
2 |
3 | public class PhenotypeDBPackageName {
4 | private final String mPhenotypePackageName;
5 | private final String mAndroidPackageName;
6 |
7 | public PhenotypeDBPackageName(String phenotypePackageName, String androidPackageName) {
8 | mPhenotypePackageName = phenotypePackageName;
9 | mAndroidPackageName = androidPackageName;
10 | }
11 |
12 | public String getPhenotypePackageName() {
13 | return mPhenotypePackageName;
14 | }
15 |
16 | public String getAndroidPackageName() {
17 | return mAndroidPackageName;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jacopomii/gappsmod/data/Version.java:
--------------------------------------------------------------------------------
1 | package com.jacopomii.gappsmod.data;
2 |
3 | public class Version implements Comparable {
4 | private final String mVersion;
5 |
6 | public Version(String version) {
7 | if (version == null)
8 | throw new IllegalArgumentException("Version can not be null");
9 | if (!version.matches("\\d+(\\.\\d+)*"))
10 | throw new IllegalArgumentException("Invalid version format");
11 | mVersion = version;
12 | }
13 |
14 | public final String getVersion() {
15 | return mVersion;
16 | }
17 |
18 | @Override
19 | public int compareTo(Version that) {
20 | if(that == null)
21 | return 1;
22 | String[] thisParts = this.getVersion().split("\\.");
23 | String[] thatParts = that.getVersion().split("\\.");
24 | int length = Math.max(thisParts.length, thatParts.length);
25 | for(int i = 0; i < length; i++) {
26 | int thisPart = i < thisParts.length ?
27 | Integer.parseInt(thisParts[i]) : 0;
28 | int thatPart = i < thatParts.length ?
29 | Integer.parseInt(thatParts[i]) : 0;
30 | if(thisPart < thatPart)
31 | return -1;
32 | if(thisPart > thatPart)
33 | return 1;
34 | }
35 | return 0;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jacopomii/gappsmod/ui/activity/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.jacopomii.gappsmod.ui.activity;
2 |
3 | import android.content.ComponentName;
4 | import android.content.Intent;
5 | import android.content.ServiceConnection;
6 | import android.os.Bundle;
7 | import android.os.IBinder;
8 | import android.os.RemoteException;
9 |
10 | import androidx.appcompat.app.AppCompatActivity;
11 | import androidx.core.graphics.Insets;
12 | import androidx.core.view.ViewCompat;
13 | import androidx.core.view.WindowCompat;
14 | import androidx.core.view.WindowInsetsCompat;
15 | import androidx.drawerlayout.widget.DrawerLayout;
16 | import androidx.navigation.NavController;
17 | import androidx.navigation.Navigation;
18 | import androidx.navigation.fragment.NavHostFragment;
19 | import androidx.navigation.ui.AppBarConfiguration;
20 | import androidx.navigation.ui.NavigationUI;
21 |
22 | import com.jacopomii.gappsmod.ICoreRootService;
23 | import com.jacopomii.gappsmod.R;
24 | import com.jacopomii.gappsmod.databinding.ActivityMainBinding;
25 | import com.jacopomii.gappsmod.service.CoreRootService;
26 | import com.topjohnwu.superuser.ipc.RootService;
27 | import com.topjohnwu.superuser.nio.FileSystemManager;
28 |
29 | public class MainActivity extends AppCompatActivity {
30 | private AppBarConfiguration mAppBarConfiguration;
31 | private ActivityMainBinding mBinding;
32 |
33 | private boolean mCoreRootServiceBound = false;
34 | private ServiceConnection mCoreRootServiceConnection;
35 | private ICoreRootService mCoreRootServiceIpc;
36 | private FileSystemManager mCoreRootServiceFSManager;
37 |
38 | @Override
39 | protected void onCreate(Bundle savedInstanceState) {
40 | // The savedInstanceState must not be used, otherwise the views (and the fragments contained
41 | // by this activity) are restored before the RootService is started, causing NPE.
42 | super.onCreate(null);
43 |
44 | // Enable edge-to-edge: allows drawing under system bars, preventing Android from
45 | // automatically applying the fitSystemWindows property to the root view.
46 | WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
47 |
48 | // Start CoreRootService connection
49 | Intent intent = new Intent(this, CoreRootService.class);
50 | mCoreRootServiceConnection = new ServiceConnection() {
51 | @Override
52 | public void onServiceConnected(ComponentName name, IBinder service) {
53 | try {
54 | // Set references to the remote coreRootService
55 | mCoreRootServiceBound = true;
56 | mCoreRootServiceIpc = ICoreRootService.Stub.asInterface(service);
57 | mCoreRootServiceFSManager = FileSystemManager.getRemote(mCoreRootServiceIpc.getFileSystemService());
58 |
59 | // Inflate the activity layout and set the content view
60 | mBinding = ActivityMainBinding.inflate(getLayoutInflater());
61 | setContentView(mBinding.getRoot());
62 |
63 | // Set the toolbar
64 | setSupportActionBar(mBinding.toolbar);
65 |
66 | // Set the drawer
67 | DrawerLayout drawer = mBinding.drawerLayout;
68 | mAppBarConfiguration = new AppBarConfiguration.Builder(
69 | R.id.nav_suggested_mods,
70 | R.id.nav_boolean_mods,
71 | R.id.nav_revert_mods,
72 | R.id.nav_information
73 | ).setOpenableLayout(drawer).build();
74 |
75 | // Pass through the window insets to the navHostFragment child views, except the top system bar
76 | ViewCompat.setOnApplyWindowInsetsListener(mBinding.navHostFragment, (view, insets) -> {
77 | WindowInsetsCompat insetsCompat = new WindowInsetsCompat.Builder(insets)
78 | .setInsets(WindowInsetsCompat.Type.systemBars(), Insets.of(
79 | insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.ime()).left,
80 | 0,
81 | insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.ime()).right,
82 | insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.ime()).bottom))
83 | .build();
84 | return ViewCompat.onApplyWindowInsets(view, insetsCompat);
85 | });
86 |
87 | // Set the navigation controller
88 | NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(mBinding.navHostFragment.getId());
89 | if (navHostFragment != null) {
90 | NavController navController = navHostFragment.getNavController();
91 | NavigationUI.setupActionBarWithNavController(MainActivity.this, navController, mAppBarConfiguration);
92 | NavigationUI.setupWithNavController(mBinding.navView, navController);
93 | }
94 | } catch (RemoteException e) {
95 | e.printStackTrace();
96 | }
97 | }
98 |
99 | @Override
100 | public void onServiceDisconnected(ComponentName name) {
101 | mCoreRootServiceBound = false;
102 | mCoreRootServiceIpc = null;
103 | mCoreRootServiceFSManager = null;
104 | }
105 | };
106 | RootService.bind(intent, mCoreRootServiceConnection);
107 | }
108 |
109 | public FileSystemManager getCoreRootServiceFSManager() {
110 | return mCoreRootServiceFSManager;
111 | }
112 |
113 | public ICoreRootService getCoreRootServiceIpc() {
114 | return mCoreRootServiceIpc;
115 | }
116 |
117 | @Override
118 | public boolean onSupportNavigateUp() {
119 | NavController navController = Navigation.findNavController(this, mBinding.navHostFragment.getId());
120 | return NavigationUI.navigateUp(navController, mAppBarConfiguration)
121 | || super.onSupportNavigateUp();
122 | }
123 |
124 | @Override
125 | public void onBackPressed() {
126 | if (mBinding.drawerLayout.isOpen())
127 | mBinding.drawerLayout.close();
128 | else
129 | finishAffinity();
130 | }
131 |
132 | @Override
133 | protected void onDestroy() {
134 | if (mCoreRootServiceBound)
135 | RootService.unbind(mCoreRootServiceConnection);
136 | super.onDestroy();
137 | }
138 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jacopomii/gappsmod/ui/activity/SplashScreenActivity.java:
--------------------------------------------------------------------------------
1 | package com.jacopomii.gappsmod.ui.activity;
2 |
3 | import static com.jacopomii.gappsmod.data.Constants.GMS_ANDROID_PACKAGE_NAME;
4 | import static com.jacopomii.gappsmod.data.Constants.GOOGLE_PLAY_DETAILS_LINK;
5 | import static com.jacopomii.gappsmod.data.Constants.GMS_PHENOTYPE_DB;
6 | import static com.jacopomii.gappsmod.util.Utils.checkUpdateAvailable;
7 | import static com.jacopomii.gappsmod.util.Utils.openGooglePlay;
8 |
9 | import android.annotation.SuppressLint;
10 | import android.content.ComponentName;
11 | import android.content.Intent;
12 | import android.content.ServiceConnection;
13 | import android.net.Uri;
14 | import android.os.Bundle;
15 | import android.os.IBinder;
16 | import android.os.RemoteException;
17 | import android.view.View;
18 | import android.widget.ImageView;
19 |
20 | import androidx.appcompat.app.AppCompatActivity;
21 |
22 | import com.google.android.material.dialog.MaterialAlertDialogBuilder;
23 | import com.google.android.material.progressindicator.CircularProgressIndicator;
24 | import com.jacopomii.gappsmod.BuildConfig;
25 | import com.jacopomii.gappsmod.ICoreRootService;
26 | import com.jacopomii.gappsmod.R;
27 | import com.jacopomii.gappsmod.databinding.ActivitySplashScreenBinding;
28 | import com.jacopomii.gappsmod.service.CoreRootService;
29 | import com.topjohnwu.superuser.Shell;
30 | import com.topjohnwu.superuser.ipc.RootService;
31 | import com.topjohnwu.superuser.nio.FileSystemManager;
32 |
33 | import java.util.concurrent.CountDownLatch;
34 |
35 | @SuppressLint("CustomSplashScreen")
36 | public class SplashScreenActivity extends AppCompatActivity {
37 | static {
38 | // Set Libsu settings before creating the main shell
39 | Shell.enableVerboseLogging = BuildConfig.DEBUG;
40 | Shell.setDefaultBuilder(Shell.Builder.create().setFlags(Shell.FLAG_MOUNT_MASTER));
41 | }
42 |
43 | private ActivitySplashScreenBinding mBinding;
44 |
45 | private final CountDownLatch mRootCheckPassed = new CountDownLatch(1);
46 | private final CountDownLatch mCoreRootServiceConnected = new CountDownLatch(1);
47 | private final CountDownLatch mGMSPhenotypeCheckPassed = new CountDownLatch(1);
48 | private final CountDownLatch mUpdateCheckFinished = new CountDownLatch(1);
49 |
50 | private boolean mCoreRootServiceBound = false;
51 | private ServiceConnection mCoreRootServiceConnection;
52 | private FileSystemManager mCoreRootServiceFSManager;
53 |
54 | @Override
55 | protected void onCreate(Bundle savedInstanceState) {
56 | super.onCreate(savedInstanceState);
57 |
58 | // Inflate the activity layout and set the content view
59 | mBinding = ActivitySplashScreenBinding.inflate(getLayoutInflater());
60 | setContentView(R.layout.activity_splash_screen);
61 |
62 | // Start CoreRootService connection
63 | Intent intent = new Intent(this, CoreRootService.class);
64 | mCoreRootServiceConnection = new ServiceConnection() {
65 | @Override
66 | public void onServiceConnected(ComponentName name, IBinder service) {
67 | // Set references to the remote coreRootService
68 | mCoreRootServiceBound = true;
69 | ICoreRootService ipc = ICoreRootService.Stub.asInterface(service);
70 | try {
71 | mCoreRootServiceFSManager = FileSystemManager.getRemote(ipc.getFileSystemService());
72 | mCoreRootServiceConnected.countDown();
73 | } catch (RemoteException e) {
74 | e.printStackTrace();
75 | }
76 |
77 | // Update the UI
78 | setCheckUIDone(mBinding.circularRootService.getId(), mBinding.doneRootService.getId(), mCoreRootServiceConnected.getCount() == 0);
79 | }
80 |
81 | @Override
82 | public void onServiceDisconnected(ComponentName name) {
83 | mCoreRootServiceBound = false;
84 | mCoreRootServiceFSManager = null;
85 | }
86 | };
87 | RootService.bind(intent, mCoreRootServiceConnection);
88 |
89 | // Root permission check
90 | new Thread() {
91 | @Override
92 | public void run() {
93 | // Check root
94 | if (checkRoot()) {
95 | mRootCheckPassed.countDown();
96 | } else {
97 | runOnUiThread(() ->
98 | new MaterialAlertDialogBuilder(SplashScreenActivity.this)
99 | .setCancelable(false)
100 | .setMessage(R.string.root_access_denied)
101 | .setPositiveButton(R.string.exit, (dialog, i) -> System.exit(0))
102 | .show());
103 | }
104 |
105 | // Update the UI
106 | setCheckUIDone(mBinding.circularRoot.getId(), mBinding.doneRoot.getId(), mRootCheckPassed.getCount() == 0);
107 | }
108 | }.start();
109 |
110 | // GMS Phenotype DB check
111 | new Thread() {
112 | @Override
113 | public void run() {
114 | try {
115 | // Wait for root check to pass
116 | mRootCheckPassed.await();
117 | // Wait for coreRootService to connect
118 | mCoreRootServiceConnected.await();
119 |
120 | // Check the GMS Phenotype DB
121 | if (checkGMSPhenotypeDB()) {
122 | mGMSPhenotypeCheckPassed.countDown();
123 | } else {
124 | runOnUiThread(() ->
125 | new MaterialAlertDialogBuilder(SplashScreenActivity.this)
126 | .setCancelable(false)
127 | .setMessage(getString(R.string.phenotype_db_does_not_exist_gms))
128 | .setPositiveButton(R.string.install, (dialogInterface, i) -> openGooglePlay(SplashScreenActivity.this, GOOGLE_PLAY_DETAILS_LINK + GMS_ANDROID_PACKAGE_NAME))
129 | .setNegativeButton(R.string.exit, (dialog, which) -> System.exit(0))
130 | .setNeutralButton(R.string.continue_anyway, (dialogInterface, i) -> mGMSPhenotypeCheckPassed.countDown())
131 | .show());
132 | }
133 |
134 | // Update the UI
135 | setCheckUIDone(mBinding.circularPhenotypeGms.getId(), mBinding.donePhenotypeGms.getId(), mGMSPhenotypeCheckPassed.getCount() == 0);
136 | } catch (InterruptedException e) {
137 | e.printStackTrace();
138 | }
139 | }
140 | }.start();
141 |
142 | // Update available check
143 | new Thread() {
144 | @Override
145 | public void run() {
146 | // Check if updates are available
147 | if (!checkUpdateAvailable(SplashScreenActivity.this)) {
148 | mUpdateCheckFinished.countDown();
149 | } else {
150 | runOnUiThread(() ->
151 | new MaterialAlertDialogBuilder(SplashScreenActivity.this)
152 | .setCancelable(false)
153 | .setMessage(R.string.new_version_alert)
154 | .setPositiveButton(
155 | R.string.github,
156 | (dialogInterface, i) -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.github_link) + "/releases")))
157 | )
158 | .setNegativeButton(R.string.continue_anyway, (dialogInterface, i) -> mUpdateCheckFinished.countDown())
159 | .show());
160 | }
161 |
162 | // Update the UI
163 | setCheckUIDone(mBinding.circularUpdates.getId(), mBinding.doneUpdates.getId(), mUpdateCheckFinished.getCount() == 0);
164 | }
165 | }.start();
166 |
167 | // End splash screen and go to the main activity
168 | new Thread() {
169 | @Override
170 | public void run() {
171 | try {
172 | // Wait for all checks to pass and for all operations to finish
173 | mRootCheckPassed.await();
174 | mCoreRootServiceConnected.await();
175 | mGMSPhenotypeCheckPassed.await();
176 | mUpdateCheckFinished.await();
177 |
178 | // This is just for aesthetics: I don't want the splashscreen to be too fast
179 | Thread.sleep(1000);
180 |
181 | // Start the main activity
182 | Intent intent = new Intent(SplashScreenActivity.this, MainActivity.class);
183 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
184 | startActivity(intent);
185 | } catch (InterruptedException e) {
186 | e.printStackTrace();
187 | }
188 | }
189 | }.start();
190 | }
191 |
192 | private boolean checkRoot() {
193 | return Shell.getShell().isRoot();
194 | }
195 |
196 | private boolean checkGMSPhenotypeDB() {
197 | return mCoreRootServiceFSManager.getFile(GMS_PHENOTYPE_DB).exists();
198 | }
199 |
200 | private void setCheckUIDone(int circularID, int doneImageID, boolean success) {
201 | CircularProgressIndicator circular = findViewById(circularID);
202 | ImageView doneImage = findViewById(doneImageID);
203 | runOnUiThread(() -> {
204 | circular.setVisibility(View.GONE);
205 | doneImage.setImageResource(success ? R.drawable.ic_success_24 : R.drawable.ic_fail_24);
206 | doneImage.setVisibility(View.VISIBLE);
207 | });
208 | }
209 |
210 | @Override
211 | protected void onDestroy() {
212 | if (mCoreRootServiceBound)
213 | RootService.unbind(mCoreRootServiceConnection);
214 | super.onDestroy();
215 | }
216 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jacopomii/gappsmod/ui/adapter/BooleanModsRecyclerViewAdapter.java:
--------------------------------------------------------------------------------
1 | package com.jacopomii.gappsmod.ui.adapter;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.content.res.TypedArray;
6 | import android.graphics.Color;
7 | import android.os.RemoteException;
8 | import android.view.LayoutInflater;
9 | import android.view.ViewGroup;
10 | import android.widget.Filter;
11 | import android.widget.Filterable;
12 | import android.widget.TextView;
13 |
14 | import androidx.annotation.NonNull;
15 | import androidx.recyclerview.widget.RecyclerView;
16 |
17 | import com.google.android.material.card.MaterialCardView;
18 | import com.jacopomii.gappsmod.ICoreRootService;
19 | import com.jacopomii.gappsmod.R;
20 | import com.jacopomii.gappsmod.data.BooleanFlag;
21 | import com.jacopomii.gappsmod.databinding.SwitchCardBinding;
22 | import com.jacopomii.gappsmod.ui.view.ProgrammaticMaterialSwitchView;
23 | import com.l4digital.fastscroll.FastScroller;
24 |
25 | import org.json.JSONException;
26 | import org.json.JSONObject;
27 |
28 | import java.util.ArrayList;
29 | import java.util.List;
30 | import java.util.Map;
31 | import java.util.TreeMap;
32 |
33 | @SuppressWarnings("unchecked")
34 | public class BooleanModsRecyclerViewAdapter extends RecyclerView.Adapter implements Filterable, FastScroller.SectionIndexer {
35 | private final Context mContext;
36 |
37 | private List mFlagsList = new ArrayList<>();
38 | private List mFlagsListFiltered = new ArrayList<>();
39 | private String mPhenotypePackageName = null;
40 | private CharSequence mLastFilterPerformed = null;
41 |
42 | private final ICoreRootService mCoreRootServiceIpc;
43 |
44 | public BooleanModsRecyclerViewAdapter(Context context, ICoreRootService coreRootServiceIpc) {
45 | mContext = context;
46 | mCoreRootServiceIpc = coreRootServiceIpc;
47 | }
48 |
49 | @SuppressLint("NotifyDataSetChanged")
50 | public void selectPhenotypePackageName(String phenotypePackageName) {
51 | mPhenotypePackageName = phenotypePackageName;
52 |
53 | try {
54 | mFlagsList = new ArrayList<>();
55 | TreeMap> map = new TreeMap>(mCoreRootServiceIpc.phenotypeDBGetBooleanFlagsOrOverridden(phenotypePackageName));
56 | for (Map.Entry> flag : map.entrySet()) {
57 | String flagName = flag.getKey();
58 | List