Repository: jacopotediosi/GoogleDialerMod
Branch: main
Commit: 6c63c56da89a
Files: 82
Total size: 251.0 KB
Directory structure:
gitextract_6qvd41v1/
├── .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
│ ├── navigation/
│ │ └── mobile_navigation.xml
│ ├── values/
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ ├── 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
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
github: jacopotediosi
custom: https://paypal.me/jacopotediosi
================================================
FILE: .github/ISSUE_TEMPLATE/bug-report.md
================================================
---
name: Bug Report
about: Report something that isn't working
title: ''
labels: bug
assignees: ''
---
## Overview
[NOTE]: # ( Give a BRIEF summary about your problem )
## Steps to Reproduce
[NOTE]: # ( Provide a simple set of steps to reproduce this bug. )
1.
2.
3.
## Expected Behavior
[NOTE]: # ( Tell us what you expected to happen )
## Actual Behavior
[NOTE]: # ( Tell us what actually happens )
## Screenshots
[NOTE]: # ( If applicable, add screenshots to help explain your problem. )
## System Information
- Device and model:
- ROM and Android version:
- Is the Google app you are trying to tweak (e.g., Phone by Google) installed as system app: yes/no
- Installed Magisk / other SU Manager version:
[NOTE]: # ( Paste below the output of the `adb shell "dumpsys package com.jacopomii.gappsmod | grep version"` command )
- Installed GAppsMod version:
[NOTE]: # ( Paste below the output of the `adb shell "dumpsys package REPLACE_WITH_PACKAGENAME | grep version"` command )
- The version of the Google app you are trying to tweak (e.g., Phone by Google):
[NOTE]: # ( Paste below the output of the `adb shell "getprop | grep locale"` command )
- Your device language (locale):
[NOTE]: # ( Paste below the output of the `adb shell "getprop | grep iso-country"` command )
- Your location (country of the SIM and country where you are):
## Logcat
[NOTE]: # (
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.
Open another terminal and use the `adb logcat > logs.txt` command to start capturing logs.
Perform the necessary steps to replicate the bug, then press CTRL+C to stop capturing logs.
Attach below the resulting logs.txt file.
)
================================================
FILE: .github/ISSUE_TEMPLATE/feature-request.md
================================================
---
name: Feature Request
about: Suggest an idea for a new feature you want
title: ''
labels: enhancement
assignees: ''
---
## Summary
[NOTE]: # ( Provide a brief overview of what the new feature is all about )
## Solution
[NOTE]: # ( A clear and concise description of what you want to happen )
## Examples
[NOTE]: # ( Show us a picture or mock-up of your proposal )
## Alternatives
[NOTE]: # ( A clear and concise description of any alternative solutions or features you've considered )
## Context
[NOTE]: # ( Why does this feature matter to you? What unique circumstances do you have? )
================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "gradle"
directory: "/"
schedule:
interval: "monthly"
================================================
FILE: .github/workflows/push.yml
================================================
name: Assemble on push
on:
push:
branches: [ main ]
paths-ignore:
- '.github/**'
- '**.md'
workflow_dispatch:
jobs:
build:
name: Build on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest ]
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'recursive'
fetch-depth: 0
- name: Gradle wrapper validation
uses: gradle/wrapper-validation-action@v1
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- name: Write keystore parameters
run: |
echo keystore.password='${{ secrets.KEYSTORE_PASSWORD }}' >> local.properties
echo keystore.alias='${{ secrets.KEYSTORE_ALIAS }}' >> local.properties
echo keystore.alias_password='${{ secrets.KEYSTORE_ALIAS_PASSWORD }}' >> local.properties
echo keystore.path=`pwd`/keystore.jks >> local.properties
echo "${{ secrets.KEYSTORE_KEY }}" | base64 --decode > keystore.jks
- name: Gradle Dependency Submission
uses: mikepenz/gradle-dependency-submission@v0.8.6
with:
gradle-build-module: ":app"
- name: Assemble
uses: gradle/gradle-build-action@v2
with:
arguments: assemble
- name: Get short commit hash
run: |
echo "LATEST_COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Upload debug
if: success()
uses: actions/upload-artifact@v3
with:
name: ${{ github.event.repository.name }}-${{ env.LATEST_COMMIT_HASH }}-debug.apk
path: "app/build/outputs/apk/debug/app-debug.apk"
- name: Upload release
if: success()
uses: actions/upload-artifact@v3
with:
name: ${{ github.event.repository.name }}-${{ env.LATEST_COMMIT_HASH }}-release.apk
path: "app/build/outputs/apk/release/app-release.apk"
================================================
FILE: .gitignore
================================================
*.iml
.gradle
.idea
.DS_Store
/build
local.properties
================================================
FILE: README.md
================================================
# Deprecation notice
## !!! This project is no longer maintained !!!
I had started researching how Phenotype DB works as a hobby, and this application was only meant to be a proof of concept.
Making things work required a huge amount of reverse engineering of Google applications, and today I no longer have time to dedicate to it.
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.
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.
# GAppsMod (ex GoogleDialerMod)
The ultimate All-In-One Utility to tweak Google applications.
## Downloads:
- Please visit the [GAppsMod Release Page](https://github.com/jacopotediosi/GAppsMod/releases)
## How do I use it?
- 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
- 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
- There is no need to keep GAppsMod installed after applying the desired mods, because they (should) survive Google applications updates / reinstalls over time
## How does it work?
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.
Some of those flags concern applications core functionalities, while others pertain to hidden or upcoming features that have not yet been released.
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.
## Features:
- Supports all arm / arm64 / x86 / x86_64 devices and all Android versions from 5.0 (Lollipop)
- Enable / disable hidden features for all users at once when Android "multiple users" mode is in use
- Allows users to list and change all Phenotype DB boolean flags for all installed Google applications
- A convenient home screen brings together the suggested mods for the most used Google applications
## Currently suggested mods
- For the **Phone** application ([link](https://play.google.com/store/apps/details?id=com.google.android.dialer)):
- Force **enable call recording** feature, even on unsupported devices or in unsupported countries ([ref](https://support.google.com/phoneapp/answer/9803950))
- Enable also **automatic call recording** ("always record") feature based on caller (otherwise only available in India)
- Silence the annoying "registration has started / ended" **call recording announcements** (only on Phone version <= 94.x)
- 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))
- Allows users to choose the language for call screening
- For the **Messages** application ([link](https://play.google.com/store/apps/details?id=com.google.android.apps.messaging)):
- Force **enable debug menu** (it can also be enabled without mods by entering `*xyzzy*` in the application's search field)
- Force **enable message organization** ("supersort")
- Force **enable marking conversations as unread**
- Force **enable verified SMS** settings menu ([ref](https://support.google.com/messages/answer/9326240))
- Force **enable always sending images by Google Photos links in SMS** ([ref](https://9to5google.com/2022/02/19/messages-google-photos/))
- Force **enable nudges and birthday reminders** ([ref](https://support.google.com/messages/answer/11555591))
- Force **enable Bard AI draft suggestions** ("magic compose") ([ref](https://9to5google.com/2023/05/05/google-messages-magic-compose-ai/))
- 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**
And much more coming soon :)
## Demo

## Troubleshooting:
- 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.
- Before to report an issue try to delete Google apps data, to reboot your phone and to try again what didn't work
## Donations
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.
## Credits:
- 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
- [Libsu](https://github.com/topjohnwu/libsu) by [topjohnwu](https://github.com/topjohnwu)
================================================
FILE: app/.gitignore
================================================
/build
================================================
FILE: app/build.gradle
================================================
plugins {
id "com.android.application"
id "com.google.protobuf"
id "com.likethesalad.stem"
}
def keyfile
def keystorePSW
def keystoreAlias
def keystoreAliasPSW
Properties properties = new Properties()
properties.load(project.rootProject.file("local.properties").newDataInputStream())
def keystoreFilepath = properties.getProperty("keystore.path")
if (keystoreFilepath) {
keyfile = file(keystoreFilepath)
keystorePSW = properties.getProperty("keystore.password")
keystoreAlias = properties.getProperty("keystore.alias")
keystoreAliasPSW = properties.getProperty("keystore.alias_password")
} else {
// Remember to config your keystore settings in local.properties or in the below lines
keyfile = file("C:/keystore.jks")
keystorePSW = "CHANGEME"
keystoreAlias = "CHANGEME"
keystoreAliasPSW = "CHANGEME"
}
android {
namespace "com.jacopomii.gappsmod"
compileSdk 33
defaultConfig {
applicationId "com.jacopomii.gappsmod"
minSdk 21
targetSdk 33
versionCode 400
versionName "4.00"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
signingConfigs {
release {
storeFile keyfile
storePassword keystorePSW
keyAlias keystoreAlias
keyPassword keystoreAliasPSW
}
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
signingConfig signingConfigs.release
}
}
buildFeatures {
viewBinding true
aidl true
buildConfig true
}
sourceSets {
main {
res.srcDirs = ["src/main/res",
"src/main/res/layouts/activities",
"src/main/res/layouts/dialogs",
"src/main/res/layouts/fragments",
"src/main/res/layouts/items"]
}
}
}
dependencies {
// Libsu
def libsuVersion = "5.0.5"
implementation "com.github.topjohnwu.libsu:core:${libsuVersion}"
implementation "com.github.topjohnwu.libsu:service:${libsuVersion}"
implementation "com.github.topjohnwu.libsu:nio:${libsuVersion}"
// Official SQLite Java Bindings, downloaded from https://www.sqlite.org/download.html
implementation files("libs/sqlite-android-3410200.aar")
// Advanced toast
implementation "com.github.GrenderG:Toasty:1.5.2"
// HTTP
implementation "com.android.volley:volley:1.2.1"
// Protobuf
implementation "com.google.protobuf:protobuf-javalite:3.23.1"
// Apache Commons
//noinspection GradleDependency
implementation "commons-io:commons-io:2.12.0"
implementation "org.apache.commons:commons-lang3:3.12.0"
// Navigation drawer
def navigationVersion = "2.5.3"
implementation "androidx.navigation:navigation-fragment:${navigationVersion}"
implementation "androidx.navigation:navigation-ui:${navigationVersion}"
// FastScroller for RecyclerView
implementation "io.github.l4digital:fastscroll:2.1.0"
// Other UI Components
implementation "com.google.android.material:material:1.9.0"
implementation "androidx.appcompat:appcompat:1.6.1"
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.23.1"
}
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option "lite"
}
}
}
}
}
================================================
FILE: app/lint.xml
================================================
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
# Keep protobuf autogenerated classes
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
# Keep SQLite Java Bindings classes
-keep class org.sqlite.** { *; }
-keep class org.sqlite.database.** { *; }
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
================================================
FILE: app/src/main/aidl/com/jacopomii/gappsmod/ICoreRootService.aidl
================================================
package com.jacopomii.gappsmod;
interface ICoreRootService {
IBinder getFileSystemService();
/**
* Query the GMS and Vending Phenotype DBs to get a list of all package names that have at least
* one Flag set.
*
* @return a {@code HashMap} in the format "Phenotype package name" => "Android package name".
*/
Map phenotypeDBGetAllPackageNames();
/**
* Query the GMS and Vending Phenotype DBs to get a list of all package names that have at least
* one Flag overridden.
*
* @return a {@code HashMap} in the format "Phenotype package name" => "Android package name".
*/
Map phenotypeDBGetAllOverriddenPackageNames();
/**
* Query the GMS/Vending Phenotype DB (based on the {@code phenotypePackageName}) to get the
* Android package name corresponding to a given {@code phenotypePackageName}.
*
* @param phenotypePackageName the Phenotype package name for which the corresponding Android
* package name is to be returned.
* @return the Android package name corresponding to the specified {@code phenotypePackageName}.
* An empty string if the phenotypePackageName couldn't be found.
*/
String phenotypeDBGetAndroidPackageNameByPhenotypePackageName(in String phenotypePackageName);
/**
* Query the GMS/Vending Phenotype DB (based on the {@code phenotypePackageName}), selecting all
* the boolean flags for a given {@code phenotypePackageName}.
*
* @param phenotypePackageName the Phenotype (not Android) package name whose flags are to be
* returned.
* @return a {@code HashMap} that uses the ({@code String}) flag name as the key.
* For performance reasons, the value of this HashMap is a {@code List} structured as follows:
* - Position 0 contains the {@code Boolean} value of the flag, giving priority to the value
* overridden in the FlagOverrides table, if present, over the one contained in the Flags table.
* - Position 1 contains the {@code Boolean} value "changed", which is {@code true} if and only
* if the returned flag is overwritten in the FlagOverrides table and has a different value
* than the one contained in the Flags table; {@code false} otherwise.
*/
Map phenotypeDBGetBooleanFlagsOrOverridden(in String phenotypePackageName);
/**
* Query the GMS/Vending Phenotype DB (based on the {@code phenotypePackageName}) to find out if
* all given {@code flags} are overridden for a given {@code phenotypePackageName}.
* Please note that the fact that a flag is overridden only implies that it is present in the
* FlagOverrides table, and not that its value is different from what is indicated in the Flags
* table.
*
* @param phenotypePackageName the Phenotype (not Android) package name for which to check.
* @param flags the flags to check.
* @return {@code true} if all given {@code flags} are overridden for the given
* {@code phenotypePackageName}; {@code false} otherwise.
*/
boolean phenotypeDBAreAllFlagsOverridden(in String phenotypePackageName, in List flags);
/**
* Remove all flag overrides from the GMS and Vending Phenotype DBs by truncating the
* FlagOverrides table.
* It also clears from the filesystem the Phenotype cache of all applications for which at least
* one flag was overridden.
*/
void phenotypeDBDeleteAllFlagOverrides();
/**
* Delete all flag overrides from the GMS/Vending Phenotype DB (based on the
* {@code phenotypePackageName}) for a given {@code phenotypePackageName}.
* It also clears from the filesystem the Phenotype cache of the application corresponding to
* the given {@code phenotypePackageName}.
*
* @param phenotypePackageName the Phenotype (not Android) package name for which to delete the
* flag overrides.
*/
void phenotypeDBDeleteAllFlagOverridesByPhenotypePackageName(in String phenotypePackageName);
/**
* Delete a given list of flag overrides from the GMS/Vending Phenotype DB (based on the
* {@code phenotypePackageName}) for a given {@code phenotypePackageName}.
* It also clears from the filesystem the Phenotype cache of the application corresponding to
* the given {@code phenotypePackageName}.
*
* @param phenotypePackageName the Phenotype (not Android) package name for which to delete the
* flag overrides.
* @param flags the list of flags to delete.
*/
void phenotypeDBDeleteFlagOverrides(in String phenotypePackageName, in List flags);
/**
* Override the value of a boolean flag in the GMS/Vending Phenotype DB (based on the
* {@code phenotypePackageName}) for a given {@code phenotypePackageName}.
* It also clears from the filesystem the Phenotype cache of the application corresponding to
* the given {@code phenotypePackageName}.
*
* @param phenotypePackageName the Phenotype (not Android) package name for which to override
* the flag.
* @param flag the name of the flag to override.
* @param value the value to override the flag with.
*/
void phenotypeDBOverrideBooleanFlag(in String phenotypePackageName, in String flag, in boolean value);
/**
* Override the value of an extension flag in the GMS/Vending Phenotype DB (based on the
* {@code phenotypePackageName}) for a given {@code phenotypePackageName}.
* It also clears from the filesystem the Phenotype cache of the application corresponding to
* the given {@code phenotypePackageName}.
*
* @param phenotypePackageName the Phenotype (not Android) package name for which to override
* the flag.
* @param flag the name of the flag to override.
* @param value the value to override the flag with.
*/
void phenotypeDBOverrideExtensionFlag(in String phenotypePackageName, in String flag, in byte[] value);
/**
* Override the value of a string flag in the GMS/Vending Phenotype DB (based on the
* {@code phenotypePackageName}) for a given {@code phenotypePackageName}.
* It also clears from the filesystem the Phenotype cache of the application corresponding to
* the given {@code phenotypePackageName}.
*
* @param phenotypePackageName the Phenotype (not Android) package name for which to override
* the flag.
* @param flag the name of the flag to override.
* @param value the value to override the flag with.
*/
void phenotypeDBOverrideStringFlag(in String phenotypePackageName, in String flag, in String value);
}
================================================
FILE: app/src/main/java/com/jacopomii/gappsmod/application/GAppsModApplication.java
================================================
package com.jacopomii.gappsmod.application;
import android.app.Application;
import com.google.android.material.color.DynamicColors;
public class GAppsModApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
DynamicColors.applyToActivitiesIfAvailable(this);
}
}
================================================
FILE: app/src/main/java/com/jacopomii/gappsmod/data/BooleanFlag.java
================================================
package com.jacopomii.gappsmod.data;
public class BooleanFlag {
private final String mFlagName;
private boolean mFlagValue;
private boolean mFlagOverriddenAndChanged;
public BooleanFlag(String flagName, boolean flagValue, boolean flagOverriddenAndChanged) {
mFlagName = flagName;
mFlagValue = flagValue;
mFlagOverriddenAndChanged = flagOverriddenAndChanged;
}
public String getFlagName() {
return mFlagName;
}
public boolean getFlagValue() {
return mFlagValue;
}
public void setFlagValue(boolean flagValue) {
mFlagValue = flagValue;
}
public void setFlagOverriddenAndChanged(boolean flagOverriddenAndChanged) {
mFlagOverriddenAndChanged = flagOverriddenAndChanged;
}
public boolean getFlagOverriddenAndChanged() {
return mFlagOverriddenAndChanged;
}
}
================================================
FILE: app/src/main/java/com/jacopomii/gappsmod/data/Constants.java
================================================
package com.jacopomii.gappsmod.data;
import android.annotation.SuppressLint;
@SuppressLint("SdCardPath")
public interface Constants {
// Android package names
String GMS_ANDROID_PACKAGE_NAME = "com.google.android.gms";
String VENDING_ANDROID_PACKAGE_NAME = "com.android.vending";
String DIALER_ANDROID_PACKAGE_NAME = "com.google.android.dialer";
String MESSAGES_ANDROID_PACKAGE_NAME = "com.google.android.apps.messaging";
// Phenotype package names
String DIALER_PHENOTYPE_PACKAGE_NAME = "com.google.android.dialer";
String MESSAGES_PHENOTYPE_PACKAGE_NAME = "com.google.android.apps.messaging#com.google.android.apps.messaging";
// Google Play links
String GOOGLE_PLAY_DETAILS_LINK = "https://play.google.com/store/apps/details?id=";
String GOOGLE_PLAY_BETA_LINK = "https://play.google.com/apps/testing/";
// Data / data folders
String DATA_DATA_PREFIX = "/data/data/";
String DIALER_CALLRECORDINGPROMPT = DATA_DATA_PREFIX + DIALER_ANDROID_PACKAGE_NAME + "/files/callrecordingprompt";
String GMS_PHENOTYPE_DB = DATA_DATA_PREFIX + GMS_ANDROID_PACKAGE_NAME + "/databases/phenotype.db";
String VENDING_PHENOTYPE_DB = DATA_DATA_PREFIX + VENDING_ANDROID_PACKAGE_NAME + "/databases/phenotype.db";
}
================================================
FILE: app/src/main/java/com/jacopomii/gappsmod/data/PhenotypeDBPackageName.java
================================================
package com.jacopomii.gappsmod.data;
public class PhenotypeDBPackageName {
private final String mPhenotypePackageName;
private final String mAndroidPackageName;
public PhenotypeDBPackageName(String phenotypePackageName, String androidPackageName) {
mPhenotypePackageName = phenotypePackageName;
mAndroidPackageName = androidPackageName;
}
public String getPhenotypePackageName() {
return mPhenotypePackageName;
}
public String getAndroidPackageName() {
return mAndroidPackageName;
}
}
================================================
FILE: app/src/main/java/com/jacopomii/gappsmod/data/Version.java
================================================
package com.jacopomii.gappsmod.data;
public class Version implements Comparable {
private final String mVersion;
public Version(String version) {
if (version == null)
throw new IllegalArgumentException("Version can not be null");
if (!version.matches("\\d+(\\.\\d+)*"))
throw new IllegalArgumentException("Invalid version format");
mVersion = version;
}
public final String getVersion() {
return mVersion;
}
@Override
public int compareTo(Version that) {
if(that == null)
return 1;
String[] thisParts = this.getVersion().split("\\.");
String[] thatParts = that.getVersion().split("\\.");
int length = Math.max(thisParts.length, thatParts.length);
for(int i = 0; i < length; i++) {
int thisPart = i < thisParts.length ?
Integer.parseInt(thisParts[i]) : 0;
int thatPart = i < thatParts.length ?
Integer.parseInt(thatParts[i]) : 0;
if(thisPart < thatPart)
return -1;
if(thisPart > thatPart)
return 1;
}
return 0;
}
}
================================================
FILE: app/src/main/java/com/jacopomii/gappsmod/service/CoreRootService.java
================================================
package com.jacopomii.gappsmod.service;
import static com.jacopomii.gappsmod.data.Constants.DATA_DATA_PREFIX;
import static com.jacopomii.gappsmod.data.Constants.GMS_PHENOTYPE_DB;
import static com.jacopomii.gappsmod.data.Constants.VENDING_ANDROID_PACKAGE_NAME;
import static com.jacopomii.gappsmod.data.Constants.VENDING_PHENOTYPE_DB;
import static com.jacopomii.gappsmod.util.Utils.createInQueryString;
import static org.sqlite.database.sqlite.SQLiteDatabase.OPEN_READWRITE;
import static org.sqlite.database.sqlite.SQLiteDatabase.openDatabase;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.os.IBinder;
import android.os.Process;
import androidx.annotation.NonNull;
import com.jacopomii.gappsmod.ICoreRootService;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.ipc.RootService;
import com.topjohnwu.superuser.nio.ExtendedFile;
import com.topjohnwu.superuser.nio.FileSystemManager;
import org.apache.commons.io.FileUtils;
import org.sqlite.database.sqlite.SQLiteDatabase;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@SuppressWarnings("SameParameterValue")
public class CoreRootService extends RootService {
static {
// Only load the library when this class is loaded in a root process.
// The classloader will load this class (and call this static block) in the non-root process because we accessed it when constructing the Intent to send.
// Add this check so we don't unnecessarily load native code that'll never be used.
if (Process.myUid() == 0)
System.loadLibrary("sqliteX");
}
private SQLiteDatabase GMSPhenotypeDB = null;
private SQLiteDatabase VendingPhenotypeDB = null;
@Override
public IBinder onBind(@NonNull Intent intent) {
try {
GMSPhenotypeDB = openDatabase(GMS_PHENOTYPE_DB, null, OPEN_READWRITE);
} catch (Exception e) {
e.printStackTrace();
}
try {
VendingPhenotypeDB = openDatabase(VENDING_PHENOTYPE_DB, null, OPEN_READWRITE);
} catch (Exception e) {
e.printStackTrace();
}
return new CoreRootServiceIPC();
}
@Override
public boolean onUnbind(@NonNull Intent intent) {
if (GMSPhenotypeDB != null && GMSPhenotypeDB.isOpen()) GMSPhenotypeDB.close();
if (VendingPhenotypeDB != null && VendingPhenotypeDB.isOpen()) VendingPhenotypeDB.close();
super.onUnbind(intent);
return false;
}
@Override
public void onDestroy() {
if (GMSPhenotypeDB != null && GMSPhenotypeDB.isOpen()) GMSPhenotypeDB.close();
if (VendingPhenotypeDB != null && VendingPhenotypeDB.isOpen()) VendingPhenotypeDB.close();
super.onDestroy();
}
private class CoreRootServiceIPC extends ICoreRootService.Stub {
@Override
public IBinder getFileSystemService() {
return FileSystemManager.getService();
}
@Override
public Map phenotypeDBGetAllPackageNames() {
return CoreRootService.this.phenotypeDBGetAllPackageNames();
}
@Override
public Map phenotypeDBGetAllOverriddenPackageNames() {
return CoreRootService.this.phenotypeDBGetAllOverriddenPackageNames();
}
@Override
public String phenotypeDBGetAndroidPackageNameByPhenotypePackageName(String phenotypePackageName) {
return CoreRootService.this.phenotypeDBGetAndroidPackageNameByPhenotypePackageName(phenotypePackageName);
}
@Override
public Map> phenotypeDBGetBooleanFlagsOrOverridden(String phenotypePackageName) {
return CoreRootService.this.phenotypeDBGetBooleanFlagsOrOverridden(phenotypePackageName);
}
@Override
public boolean phenotypeDBAreAllFlagsOverridden(String phenotypePackageName, List flags) {
return CoreRootService.this.phenotypeDBAreAllFlagsOverridden(phenotypePackageName, flags);
}
@Override
public void phenotypeDBDeleteAllFlagOverrides() {
CoreRootService.this.phenotypeDBDeleteAllFlagOverrides(true);
}
@Override
public void phenotypeDBDeleteAllFlagOverridesByPhenotypePackageName(String phenotypePackageName) {
CoreRootService.this.phenotypeDBDeleteAllFlagOverridesByPhenotypePackageName(phenotypePackageName, true);
}
@Override
public void phenotypeDBDeleteFlagOverrides(String phenotypePackageName, List flags) {
CoreRootService.this.phenotypeDBDeleteFlagOverrides(phenotypePackageName, flags, true);
}
@Override
public void phenotypeDBOverrideBooleanFlag(String phenotypePackageName, String flag, boolean value) {
CoreRootService.this.phenotypeDBOverrideBooleanFlag(phenotypePackageName, flag, value, true);
}
@Override
public void phenotypeDBOverrideExtensionFlag(String phenotypePackageName, String flag, byte[] value) {
CoreRootService.this.phenotypeDBOverrideExtensionFlag(phenotypePackageName, flag, value, true);
}
@Override
public void phenotypeDBOverrideStringFlag(String phenotypePackageName, String flag, String value) {
CoreRootService.this.phenotypeDBOverrideStringFlag(phenotypePackageName, flag, value, true);
}
}
private Map phenotypeDBGetAllPackageNames() {
HashMap map = new HashMap<>();
String sql = "SELECT Flags.packageName as phenotypePackageName, Packages.androidPackageName " +
"FROM Flags, Packages " +
"WHERE phenotypePackageName=Packages.packageName " +
"GROUP BY phenotypePackageName " +
"ORDER BY phenotypePackageName ASC";
try {
try (Cursor cursor = GMSPhenotypeDB.rawQuery(sql, null)) {
while (cursor.moveToNext())
map.put(cursor.getString(0), cursor.getString(1));
}
} catch (Exception e) {
e.printStackTrace();
}
try {
try (Cursor cursor = VendingPhenotypeDB.rawQuery(sql, null)) {
while (cursor.moveToNext())
map.put(cursor.getString(0), cursor.getString(1));
}
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
private Map phenotypeDBGetAllOverriddenPackageNames() {
HashMap map = new HashMap<>();
String sql = "SELECT FlagOverrides.packageName as phenotypePackageName, Packages.androidPackageName " +
"FROM FlagOverrides, Packages " +
"WHERE phenotypePackageName=Packages.packageName " +
"GROUP BY phenotypePackageName " +
"ORDER BY phenotypePackageName ASC";
try {
try (Cursor cursor = GMSPhenotypeDB.rawQuery(sql, null)) {
while (cursor.moveToNext())
map.put(cursor.getString(0), cursor.getString(1));
}
} catch (Exception e) {
e.printStackTrace();
}
try {
try (Cursor cursor = VendingPhenotypeDB.rawQuery(sql, null)) {
while (cursor.moveToNext())
map.put(cursor.getString(0), cursor.getString(1));
}
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
private String phenotypeDBGetAndroidPackageNameByPhenotypePackageName(String phenotypePackageName) {
SQLiteDatabase phenotypeDB = getPhenotypeDBByPhenotypePackageName(phenotypePackageName);
String sql = "SELECT androidPackageName FROM Packages WHERE packageName=? LIMIT 1";
String[] selectionArgs = {phenotypePackageName};
try {
try (Cursor cursor = phenotypeDB.rawQuery(sql, selectionArgs)) {
if (cursor.moveToFirst()) return cursor.getString(0);
}
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
private Map> phenotypeDBGetBooleanFlagsOrOverridden(String phenotypePackageName) {
Map> map = new HashMap<>();
SQLiteDatabase phenotypeDB = getPhenotypeDBByPhenotypePackageName(phenotypePackageName);
String sql = "SELECT DISTINCT f.name AS name, COALESCE(fo.boolVal, f.boolVal) AS boolVal, CASE WHEN fo.boolVal != f.boolVal THEN 1 ELSE 0 END AS changed " +
"FROM Flags f " +
"LEFT JOIN FlagOverrides fo ON f.packageName = fo.packageName AND f.name = fo.name " +
"WHERE f.packageName = ? AND f.user = '' AND (f.boolVal IS NOT NULL OR fo.boolVal IS NOT NULL)" +
"UNION ALL " +
"SELECT DISTINCT fo.name AS name, fo.boolVal, 1 AS changed " +
"FROM FlagOverrides fo " +
"LEFT JOIN Flags f ON f.packageName = fo.packageName AND f.name = fo.name " +
"WHERE fo.packageName = ? AND f.name IS NULL AND fo.user = '' AND fo.boolVal IS NOT NULL";
String[] selectionArgs = {phenotypePackageName, phenotypePackageName};
try {
try (Cursor cursor = phenotypeDB.rawQuery(sql, selectionArgs)) {
while (cursor.moveToNext()) {
String name = cursor.getString(0);
Boolean boolVal = cursor.getInt(1) != 0;
Boolean changed = cursor.getInt(2) != 0;
map.put(name, Arrays.asList(boolVal, changed));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
private boolean phenotypeDBAreAllFlagsOverridden(String phenotypePackageName, List flags) {
boolean areAllFlagsOverridden = false;
SQLiteDatabase phenotypeDB = getPhenotypeDBByPhenotypePackageName(phenotypePackageName);
String sql = "SELECT DISTINCT name FROM FlagOverrides WHERE packageName=? AND name IN (" + createInQueryString(flags.size()) + ")";
List selectionArgs = new ArrayList<>();
selectionArgs.add(phenotypePackageName);
selectionArgs.addAll(flags);
try {
try (Cursor cursor = phenotypeDB.rawQuery(sql, selectionArgs.toArray(new String[0]))) {
areAllFlagsOverridden = cursor.getCount() == flags.size();
}
} catch (Exception e) {
e.printStackTrace();
}
return areAllFlagsOverridden;
}
private void phenotypeDBDeleteAllFlagOverrides(boolean deletePackagePhenotypeCache) {
Set overriddenPhenotypePackageNames = phenotypeDBGetAllOverriddenPackageNames().keySet();
try {
GMSPhenotypeDB.delete("FlagOverrides", null, null);
} catch (Exception e) {
e.printStackTrace();
}
try {
VendingPhenotypeDB.delete("FlagOverrides", null, null);
} catch (Exception e) {
e.printStackTrace();
}
if (deletePackagePhenotypeCache) {
for (String phenotypePackageName : overriddenPhenotypePackageNames) {
killPackageAndDeletePhenotypeCache(phenotypePackageName);
}
}
}
private void phenotypeDBDeleteAllFlagOverridesByPhenotypePackageName(String phenotypePackageName, boolean deletePackagePhenotypeCache) {
SQLiteDatabase phenotypeDB = getPhenotypeDBByPhenotypePackageName(phenotypePackageName);
try {
phenotypeDB.delete("FlagOverrides", "packageName=?", new String[]{phenotypePackageName});
} catch (Exception e) {
e.printStackTrace();
}
if (deletePackagePhenotypeCache)
killPackageAndDeletePhenotypeCache(phenotypePackageName);
}
private void phenotypeDBDeleteFlagOverrides(String phenotypePackageName, List flags, boolean deletePackagePhenotypeCache) {
SQLiteDatabase phenotypeDB = getPhenotypeDBByPhenotypePackageName(phenotypePackageName);
String whereClause = "packageName=? AND name IN (" + createInQueryString(flags.size()) + ")";
List whereArgs = new ArrayList<>();
whereArgs.add(phenotypePackageName);
whereArgs.addAll(flags);
try {
phenotypeDB.delete("FlagOverrides", whereClause, whereArgs.toArray(new String[0]));
} catch (Exception e) {
e.printStackTrace();
}
if (deletePackagePhenotypeCache)
killPackageAndDeletePhenotypeCache(phenotypePackageName);
}
private void phenotypeDBOverrideBooleanFlag(String phenotypePackageName, String flag, boolean value, boolean deletePackagePhenotypeCache) {
phenotypeDBDeleteFlagOverrides(phenotypePackageName, Collections.singletonList(flag), false);
SQLiteDatabase phenotypeDB = getPhenotypeDBByPhenotypePackageName(phenotypePackageName);
String sql = "SELECT DISTINCT user FROM Flags WHERE packageName = ?";
String[] selectionArgs = {phenotypePackageName};
try {
try (Cursor cursor = phenotypeDB.rawQuery(sql, selectionArgs)) {
while (cursor.moveToNext()) {
ContentValues contentValues = new ContentValues();
contentValues.put("packageName", phenotypePackageName);
contentValues.put("flagType", 0);
contentValues.put("name", flag);
contentValues.put("user", cursor.getString(0));
contentValues.put("boolVal", (value ? "1" : "0"));
contentValues.put("committed", 0);
phenotypeDB.insertWithOnConflict("FlagOverrides", null, contentValues, SQLiteDatabase.CONFLICT_REPLACE);
}
}
} catch (Exception e) {
e.printStackTrace();
}
if (deletePackagePhenotypeCache)
killPackageAndDeletePhenotypeCache(phenotypePackageName);
}
private void phenotypeDBOverrideExtensionFlag(String phenotypePackageName, String flag, byte[] value, boolean deletePackagePhenotypeCache) {
phenotypeDBDeleteFlagOverrides(phenotypePackageName, Collections.singletonList(flag), false);
SQLiteDatabase phenotypeDB = getPhenotypeDBByPhenotypePackageName(phenotypePackageName);
String sql = "SELECT DISTINCT user FROM Flags WHERE packageName = ?";
String[] selectionArgs = {phenotypePackageName};
try {
try (Cursor cursor = phenotypeDB.rawQuery(sql, selectionArgs)) {
while (cursor.moveToNext()) {
ContentValues contentValues = new ContentValues();
contentValues.put("packageName", phenotypePackageName);
contentValues.put("flagType", 0);
contentValues.put("name", flag);
contentValues.put("user", cursor.getString(0));
contentValues.put("extensionVal", value);
contentValues.put("committed", 0);
phenotypeDB.insertWithOnConflict("FlagOverrides", null, contentValues, SQLiteDatabase.CONFLICT_REPLACE);
}
}
} catch (Exception e) {
e.printStackTrace();
}
if (deletePackagePhenotypeCache)
killPackageAndDeletePhenotypeCache(phenotypePackageName);
}
private void phenotypeDBOverrideStringFlag(String phenotypePackageName, String flag, String value, boolean deletePackagePhenotypeCache) {
phenotypeDBDeleteFlagOverrides(phenotypePackageName, Collections.singletonList(flag), false);
SQLiteDatabase phenotypeDB = getPhenotypeDBByPhenotypePackageName(phenotypePackageName);
String sql = "SELECT DISTINCT user FROM Flags WHERE packageName = ?";
String[] selectionArgs = {phenotypePackageName};
try {
try (Cursor cursor = phenotypeDB.rawQuery(sql, selectionArgs)) {
while (cursor.moveToNext()) {
ContentValues contentValues = new ContentValues();
contentValues.put("packageName", phenotypePackageName);
contentValues.put("flagType", 0);
contentValues.put("name", flag);
contentValues.put("user", cursor.getString(0));
contentValues.put("stringVal", value);
contentValues.put("committed", 0);
phenotypeDB.insertWithOnConflict("FlagOverrides", null, contentValues, SQLiteDatabase.CONFLICT_REPLACE);
}
}
} catch (Exception e) {
e.printStackTrace();
}
if (deletePackagePhenotypeCache)
killPackageAndDeletePhenotypeCache(phenotypePackageName);
}
/**
* Get the correct database (GMS or Vending) based on the {@code phenotypePackageName}.
*
* @param phenotypePackageName the Phenotype (not Android) package name for which to get the
* Phenotype DB.
* @return a {@code SQLiteDatabase} reference to the GMS or Vending Phenotype DB.
* The returned object may be {@code null} if there was an error opening the database.
*/
private SQLiteDatabase getPhenotypeDBByPhenotypePackageName(String phenotypePackageName) {
if (phenotypePackageName.equals("com.google.android.finsky.regular") || phenotypePackageName.equals("com.google.android.finsky.stable")) {
return VendingPhenotypeDB;
} else {
return GMSPhenotypeDB;
}
}
/**
* Kill and delete the Phenotype cache files of the Android application corresponding to the
* given {@code phenotypePackageName}.
*
* @param phenotypePackageName the Phenotype (not Android) package name to kill and to delete
* the Phenotype cache files for.
*/
private void killPackageAndDeletePhenotypeCache(String phenotypePackageName) {
String androidPackageName = phenotypeDBGetAndroidPackageNameByPhenotypePackageName(phenotypePackageName);
// Kill the android application corresponding to the phenotypePackageName
Shell.cmd("am kill all " + androidPackageName).exec();
// Delete application phenotype Cache
ExtendedFile phenotypeCache = FileSystemManager.getLocal().getFile(DATA_DATA_PREFIX + androidPackageName + "/files/phenotype");
if (phenotypeCache.exists()) {
try {
FileUtils.deleteDirectory(phenotypeCache);
} catch (IOException | IllegalArgumentException e) {
e.printStackTrace();
}
}
// If the application is Vending, additional cache files need to be deleted
if (androidPackageName.equals(VENDING_ANDROID_PACKAGE_NAME)) {
ExtendedFile[] VendingFiles = FileSystemManager.getLocal().getFile(DATA_DATA_PREFIX + androidPackageName + "/files").listFiles();
if (VendingFiles != null) {
for (ExtendedFile file : VendingFiles) {
if (file.getName().startsWith("experiment-flags")) {
//noinspection ResultOfMethodCallIgnored
file.delete();
}
}
}
}
}
}
================================================
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/activity/MainActivity.java
================================================
package com.jacopomii.gappsmod.ui.activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import com.jacopomii.gappsmod.ICoreRootService;
import com.jacopomii.gappsmod.R;
import com.jacopomii.gappsmod.databinding.ActivityMainBinding;
import com.jacopomii.gappsmod.service.CoreRootService;
import com.topjohnwu.superuser.ipc.RootService;
import com.topjohnwu.superuser.nio.FileSystemManager;
public class MainActivity extends AppCompatActivity {
private AppBarConfiguration mAppBarConfiguration;
private ActivityMainBinding mBinding;
private boolean mCoreRootServiceBound = false;
private ServiceConnection mCoreRootServiceConnection;
private ICoreRootService mCoreRootServiceIpc;
private FileSystemManager mCoreRootServiceFSManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
// The savedInstanceState must not be used, otherwise the views (and the fragments contained
// by this activity) are restored before the RootService is started, causing NPE.
super.onCreate(null);
// Enable edge-to-edge: allows drawing under system bars, preventing Android from
// automatically applying the fitSystemWindows property to the root view.
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
// Start CoreRootService connection
Intent intent = new Intent(this, CoreRootService.class);
mCoreRootServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
try {
// Set references to the remote coreRootService
mCoreRootServiceBound = true;
mCoreRootServiceIpc = ICoreRootService.Stub.asInterface(service);
mCoreRootServiceFSManager = FileSystemManager.getRemote(mCoreRootServiceIpc.getFileSystemService());
// Inflate the activity layout and set the content view
mBinding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(mBinding.getRoot());
// Set the toolbar
setSupportActionBar(mBinding.toolbar);
// Set the drawer
DrawerLayout drawer = mBinding.drawerLayout;
mAppBarConfiguration = new AppBarConfiguration.Builder(
R.id.nav_suggested_mods,
R.id.nav_boolean_mods,
R.id.nav_revert_mods,
R.id.nav_information
).setOpenableLayout(drawer).build();
// Pass through the window insets to the navHostFragment child views, except the top system bar
ViewCompat.setOnApplyWindowInsetsListener(mBinding.navHostFragment, (view, insets) -> {
WindowInsetsCompat insetsCompat = new WindowInsetsCompat.Builder(insets)
.setInsets(WindowInsetsCompat.Type.systemBars(), Insets.of(
insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.ime()).left,
0,
insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.ime()).right,
insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.ime()).bottom))
.build();
return ViewCompat.onApplyWindowInsets(view, insetsCompat);
});
// Set the navigation controller
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(mBinding.navHostFragment.getId());
if (navHostFragment != null) {
NavController navController = navHostFragment.getNavController();
NavigationUI.setupActionBarWithNavController(MainActivity.this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(mBinding.navView, navController);
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mCoreRootServiceBound = false;
mCoreRootServiceIpc = null;
mCoreRootServiceFSManager = null;
}
};
RootService.bind(intent, mCoreRootServiceConnection);
}
public FileSystemManager getCoreRootServiceFSManager() {
return mCoreRootServiceFSManager;
}
public ICoreRootService getCoreRootServiceIpc() {
return mCoreRootServiceIpc;
}
@Override
public boolean onSupportNavigateUp() {
NavController navController = Navigation.findNavController(this, mBinding.navHostFragment.getId());
return NavigationUI.navigateUp(navController, mAppBarConfiguration)
|| super.onSupportNavigateUp();
}
@Override
public void onBackPressed() {
if (mBinding.drawerLayout.isOpen())
mBinding.drawerLayout.close();
else
finishAffinity();
}
@Override
protected void onDestroy() {
if (mCoreRootServiceBound)
RootService.unbind(mCoreRootServiceConnection);
super.onDestroy();
}
}
================================================
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/activity/SplashScreenActivity.java
================================================
package com.jacopomii.gappsmod.ui.activity;
import static com.jacopomii.gappsmod.data.Constants.GMS_ANDROID_PACKAGE_NAME;
import static com.jacopomii.gappsmod.data.Constants.GOOGLE_PLAY_DETAILS_LINK;
import static com.jacopomii.gappsmod.data.Constants.GMS_PHENOTYPE_DB;
import static com.jacopomii.gappsmod.util.Utils.checkUpdateAvailable;
import static com.jacopomii.gappsmod.util.Utils.openGooglePlay;
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.View;
import android.widget.ImageView;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.progressindicator.CircularProgressIndicator;
import com.jacopomii.gappsmod.BuildConfig;
import com.jacopomii.gappsmod.ICoreRootService;
import com.jacopomii.gappsmod.R;
import com.jacopomii.gappsmod.databinding.ActivitySplashScreenBinding;
import com.jacopomii.gappsmod.service.CoreRootService;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.ipc.RootService;
import com.topjohnwu.superuser.nio.FileSystemManager;
import java.util.concurrent.CountDownLatch;
@SuppressLint("CustomSplashScreen")
public class SplashScreenActivity extends AppCompatActivity {
static {
// Set Libsu settings before creating the main shell
Shell.enableVerboseLogging = BuildConfig.DEBUG;
Shell.setDefaultBuilder(Shell.Builder.create().setFlags(Shell.FLAG_MOUNT_MASTER));
}
private ActivitySplashScreenBinding mBinding;
private final CountDownLatch mRootCheckPassed = new CountDownLatch(1);
private final CountDownLatch mCoreRootServiceConnected = new CountDownLatch(1);
private final CountDownLatch mGMSPhenotypeCheckPassed = new CountDownLatch(1);
private final CountDownLatch mUpdateCheckFinished = new CountDownLatch(1);
private boolean mCoreRootServiceBound = false;
private ServiceConnection mCoreRootServiceConnection;
private FileSystemManager mCoreRootServiceFSManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Inflate the activity layout and set the content view
mBinding = ActivitySplashScreenBinding.inflate(getLayoutInflater());
setContentView(R.layout.activity_splash_screen);
// Start CoreRootService connection
Intent intent = new Intent(this, CoreRootService.class);
mCoreRootServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// Set references to the remote coreRootService
mCoreRootServiceBound = true;
ICoreRootService ipc = ICoreRootService.Stub.asInterface(service);
try {
mCoreRootServiceFSManager = FileSystemManager.getRemote(ipc.getFileSystemService());
mCoreRootServiceConnected.countDown();
} catch (RemoteException e) {
e.printStackTrace();
}
// Update the UI
setCheckUIDone(mBinding.circularRootService.getId(), mBinding.doneRootService.getId(), mCoreRootServiceConnected.getCount() == 0);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mCoreRootServiceBound = false;
mCoreRootServiceFSManager = null;
}
};
RootService.bind(intent, mCoreRootServiceConnection);
// Root permission check
new Thread() {
@Override
public void run() {
// Check root
if (checkRoot()) {
mRootCheckPassed.countDown();
} else {
runOnUiThread(() ->
new MaterialAlertDialogBuilder(SplashScreenActivity.this)
.setCancelable(false)
.setMessage(R.string.root_access_denied)
.setPositiveButton(R.string.exit, (dialog, i) -> System.exit(0))
.show());
}
// Update the UI
setCheckUIDone(mBinding.circularRoot.getId(), mBinding.doneRoot.getId(), mRootCheckPassed.getCount() == 0);
}
}.start();
// GMS Phenotype DB check
new Thread() {
@Override
public void run() {
try {
// Wait for root check to pass
mRootCheckPassed.await();
// Wait for coreRootService to connect
mCoreRootServiceConnected.await();
// Check the GMS Phenotype DB
if (checkGMSPhenotypeDB()) {
mGMSPhenotypeCheckPassed.countDown();
} else {
runOnUiThread(() ->
new MaterialAlertDialogBuilder(SplashScreenActivity.this)
.setCancelable(false)
.setMessage(getString(R.string.phenotype_db_does_not_exist_gms))
.setPositiveButton(R.string.install, (dialogInterface, i) -> openGooglePlay(SplashScreenActivity.this, GOOGLE_PLAY_DETAILS_LINK + GMS_ANDROID_PACKAGE_NAME))
.setNegativeButton(R.string.exit, (dialog, which) -> System.exit(0))
.setNeutralButton(R.string.continue_anyway, (dialogInterface, i) -> mGMSPhenotypeCheckPassed.countDown())
.show());
}
// Update the UI
setCheckUIDone(mBinding.circularPhenotypeGms.getId(), mBinding.donePhenotypeGms.getId(), mGMSPhenotypeCheckPassed.getCount() == 0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
// Update available check
new Thread() {
@Override
public void run() {
// Check if updates are available
if (!checkUpdateAvailable(SplashScreenActivity.this)) {
mUpdateCheckFinished.countDown();
} else {
runOnUiThread(() ->
new MaterialAlertDialogBuilder(SplashScreenActivity.this)
.setCancelable(false)
.setMessage(R.string.new_version_alert)
.setPositiveButton(
R.string.github,
(dialogInterface, i) -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.github_link) + "/releases")))
)
.setNegativeButton(R.string.continue_anyway, (dialogInterface, i) -> mUpdateCheckFinished.countDown())
.show());
}
// Update the UI
setCheckUIDone(mBinding.circularUpdates.getId(), mBinding.doneUpdates.getId(), mUpdateCheckFinished.getCount() == 0);
}
}.start();
// End splash screen and go to the main activity
new Thread() {
@Override
public void run() {
try {
// Wait for all checks to pass and for all operations to finish
mRootCheckPassed.await();
mCoreRootServiceConnected.await();
mGMSPhenotypeCheckPassed.await();
mUpdateCheckFinished.await();
// This is just for aesthetics: I don't want the splashscreen to be too fast
Thread.sleep(1000);
// Start the main activity
Intent intent = new Intent(SplashScreenActivity.this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
private boolean checkRoot() {
return Shell.getShell().isRoot();
}
private boolean checkGMSPhenotypeDB() {
return mCoreRootServiceFSManager.getFile(GMS_PHENOTYPE_DB).exists();
}
private void setCheckUIDone(int circularID, int doneImageID, boolean success) {
CircularProgressIndicator circular = findViewById(circularID);
ImageView doneImage = findViewById(doneImageID);
runOnUiThread(() -> {
circular.setVisibility(View.GONE);
doneImage.setImageResource(success ? R.drawable.ic_success_24 : R.drawable.ic_fail_24);
doneImage.setVisibility(View.VISIBLE);
});
}
@Override
protected void onDestroy() {
if (mCoreRootServiceBound)
RootService.unbind(mCoreRootServiceConnection);
super.onDestroy();
}
}
================================================
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/adapter/BooleanModsRecyclerViewAdapter.java
================================================
package com.jacopomii.gappsmod.ui.adapter;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.RemoteException;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.card.MaterialCardView;
import com.jacopomii.gappsmod.ICoreRootService;
import com.jacopomii.gappsmod.R;
import com.jacopomii.gappsmod.data.BooleanFlag;
import com.jacopomii.gappsmod.databinding.SwitchCardBinding;
import com.jacopomii.gappsmod.ui.view.ProgrammaticMaterialSwitchView;
import com.l4digital.fastscroll.FastScroller;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@SuppressWarnings("unchecked")
public class BooleanModsRecyclerViewAdapter extends RecyclerView.Adapter implements Filterable, FastScroller.SectionIndexer {
private final Context mContext;
private List mFlagsList = new ArrayList<>();
private List mFlagsListFiltered = new ArrayList<>();
private String mPhenotypePackageName = null;
private CharSequence mLastFilterPerformed = null;
private final ICoreRootService mCoreRootServiceIpc;
public BooleanModsRecyclerViewAdapter(Context context, ICoreRootService coreRootServiceIpc) {
mContext = context;
mCoreRootServiceIpc = coreRootServiceIpc;
}
@SuppressLint("NotifyDataSetChanged")
public void selectPhenotypePackageName(String phenotypePackageName) {
mPhenotypePackageName = phenotypePackageName;
try {
mFlagsList = new ArrayList<>();
TreeMap> map = new TreeMap>(mCoreRootServiceIpc.phenotypeDBGetBooleanFlagsOrOverridden(phenotypePackageName));
for (Map.Entry> flag : map.entrySet()) {
String flagName = flag.getKey();
List