Showing preview only (276K chars total). Download the full file or copy to clipboard to get everything.
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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<issue id="UnsafeNativeCodeLocation">
<ignore path="src/main/res/raw/sqlite3_arm" />
<ignore path="src/main/res/raw/sqlite3_x86" />
</issue>
</lint>
================================================
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
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<application
android:name=".application.GAppsModApplication"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="DataExtractionRules"
tools:replace="android:allowBackup">
<activity
android:name=".ui.activity.SplashScreenActivity"
android:exported="true"
android:screenOrientation="portrait"
android:theme="@style/AppTheme"
tools:ignore="LockedOrientationActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.activity.MainActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="true"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize" />
</application>
</manifest>
================================================
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.<br>
* For performance reasons, the value of this HashMap is a {@code List} structured as follows:<br>
* - 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.<br>
* - 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<String> 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<String> 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<Version> {
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<String, String> phenotypeDBGetAllPackageNames() {
return CoreRootService.this.phenotypeDBGetAllPackageNames();
}
@Override
public Map<String, String> phenotypeDBGetAllOverriddenPackageNames() {
return CoreRootService.this.phenotypeDBGetAllOverriddenPackageNames();
}
@Override
public String phenotypeDBGetAndroidPackageNameByPhenotypePackageName(String phenotypePackageName) {
return CoreRootService.this.phenotypeDBGetAndroidPackageNameByPhenotypePackageName(phenotypePackageName);
}
@Override
public Map<String, List<Object>> phenotypeDBGetBooleanFlagsOrOverridden(String phenotypePackageName) {
return CoreRootService.this.phenotypeDBGetBooleanFlagsOrOverridden(phenotypePackageName);
}
@Override
public boolean phenotypeDBAreAllFlagsOverridden(String phenotypePackageName, List<String> 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<String> 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<String, String> phenotypeDBGetAllPackageNames() {
HashMap<String, String> 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<String, String> phenotypeDBGetAllOverriddenPackageNames() {
HashMap<String, String> 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<String, List<Object>> phenotypeDBGetBooleanFlagsOrOverridden(String phenotypePackageName) {
Map<String, List<Object>> 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<String> flags) {
boolean areAllFlagsOverridden = false;
SQLiteDatabase phenotypeDB = getPhenotypeDBByPhenotypePackageName(phenotypePackageName);
String sql = "SELECT DISTINCT name FROM FlagOverrides WHERE packageName=? AND name IN (" + createInQueryString(flags.size()) + ")";
List<String> 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<String> 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<String> flags, boolean deletePackagePhenotypeCache) {
SQLiteDatabase phenotypeDB = getPhenotypeDBByPhenotypePackageName(phenotypePackageName);
String whereClause = "packageName=? AND name IN (" + createInQueryString(flags.size()) + ")";
List<String> 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<BooleanModsRecyclerViewAdapter.ViewHolder> implements Filterable, FastScroller.SectionIndexer {
private final Context mContext;
private List<BooleanFlag> mFlagsList = new ArrayList<>();
private List<BooleanFlag> 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<String, List<Object>> map = new TreeMap<String, List<Object>>(mCoreRootServiceIpc.phenotypeDBGetBooleanFlagsOrOverridden(phenotypePackageName));
for (Map.Entry<String, List<Object>> flag : map.entrySet()) {
String flagName = flag.getKey();
List<Object> flagData = flag.getValue();
Boolean flagValue = (Boolean) flagData.get(0);
Boolean flagOverriddenAndChanged = (Boolean) flagData.get(1);
mFlagsList.add(new BooleanFlag(flagName, flagValue, flagOverriddenAndChanged));
}
if (mLastFilterPerformed != null) {
getFilter().filter(mLastFilterPerformed);
} else {
mFlagsListFiltered = mFlagsList;
notifyDataSetChanged();
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// Initialize binding and viewHolder
SwitchCardBinding binding = SwitchCardBinding.inflate(LayoutInflater.from(mContext), parent, false);
ViewHolder viewHolder = new ViewHolder(binding);
// Set setOnCheckedChangeListener on list items
viewHolder.mSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
int checkedPosition = viewHolder.getAdapterPosition();
BooleanFlag checkedBooleanFlag = mFlagsListFiltered.get(checkedPosition);
String checkedBooleanFlagName = checkedBooleanFlag.getFlagName();
checkedBooleanFlag.setFlagValue(isChecked);
try {
mCoreRootServiceIpc.phenotypeDBOverrideBooleanFlag(mPhenotypePackageName, checkedBooleanFlagName, isChecked);
} catch (RemoteException e) {
e.printStackTrace();
}
checkedBooleanFlag.setFlagOverriddenAndChanged(!checkedBooleanFlag.getFlagOverriddenAndChanged());
notifyItemChanged(checkedPosition);
});
// Return viewHolder
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
// Get the boolean flag
BooleanFlag booleanFlag = mFlagsListFiltered.get(position);
// Update switch text
holder.mTextView.setText(booleanFlag.getFlagName());
// Update the switch checked status without triggering any existing listener
holder.mSwitch.setCheckedProgrammatically(booleanFlag.getFlagValue());
// Change background color for cards containing overridden and changed flags
TypedArray typedArray = mContext.getTheme().obtainStyledAttributes(R.styleable.ViewStyle);
int colorSurface = typedArray.getColor(R.styleable.ViewStyle_colorSurface, Color.WHITE);
int colorSurfaceVariant = typedArray.getColor(R.styleable.ViewStyle_colorSecondaryContainer, Color.LTGRAY);
typedArray.recycle();
int cardBackgroundColor = booleanFlag.getFlagOverriddenAndChanged() ? colorSurfaceVariant : colorSurface;
((MaterialCardView) holder.itemView).setCardBackgroundColor(cardBackgroundColor);
}
@Override
public int getItemCount() {
return mFlagsListFiltered.size();
}
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence charSequence) {
mLastFilterPerformed = charSequence;
try {
JSONObject filterConfig = new JSONObject(charSequence.toString());
String key = filterConfig.getString("key");
boolean enabled = filterConfig.getBoolean("enabled");
boolean disabled = filterConfig.getBoolean("disabled");
boolean changed = filterConfig.getBoolean("changed");
boolean unchanged = filterConfig.getBoolean("unchanged");
List<BooleanFlag> flagsListFiltered = new ArrayList<>();
for (BooleanFlag booleanFlag : mFlagsList) {
if (booleanFlag.getFlagName().toLowerCase().contains(key.toLowerCase())) {
boolean flagValue = booleanFlag.getFlagValue();
boolean flagChanged = booleanFlag.getFlagOverriddenAndChanged();
if (((enabled && flagValue) || (disabled && !flagValue)) && ((changed && flagChanged) || (unchanged && !flagChanged)))
flagsListFiltered.add(booleanFlag);
}
}
mFlagsListFiltered = flagsListFiltered;
} catch (JSONException e) {
e.printStackTrace();
}
FilterResults filterResults = new FilterResults();
filterResults.values = mFlagsListFiltered;
filterResults.count = mFlagsListFiltered.size();
return filterResults;
}
@SuppressLint("NotifyDataSetChanged")
@Override
protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
mFlagsListFiltered = (List<BooleanFlag>) filterResults.values;
notifyDataSetChanged();
}
};
}
@Override
public CharSequence getSectionText(int position) {
return mFlagsListFiltered.get(position).getFlagName().substring(0, 1);
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private final TextView mTextView;
private final ProgrammaticMaterialSwitchView mSwitch;
public ViewHolder(SwitchCardBinding binding) {
super(binding.getRoot());
mTextView = binding.switchCardTextview;
mSwitch = binding.switchCardSwitch;
}
}
}
================================================
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/adapter/SelectPackageRecyclerViewAdapter.java
================================================
package com.jacopomii.gappsmod.ui.adapter;
import static com.jacopomii.gappsmod.util.Utils.getApplicationLabelOrUnknown;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.RemoteException;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.recyclerview.widget.RecyclerView;
import com.jacopomii.gappsmod.ICoreRootService;
import com.jacopomii.gappsmod.R;
import com.jacopomii.gappsmod.data.PhenotypeDBPackageName;
import com.jacopomii.gappsmod.databinding.PackageRowBinding;
import com.jacopomii.gappsmod.util.OnItemClickListener;
import com.l4digital.fastscroll.FastScroller;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@SuppressWarnings("unchecked")
public class SelectPackageRecyclerViewAdapter extends RecyclerView.Adapter<SelectPackageRecyclerViewAdapter.ViewHolder> implements Filterable, FastScroller.SectionIndexer {
private final Context mContext;
private List<PhenotypeDBPackageName> mPackageList = new ArrayList<>();
private List<PhenotypeDBPackageName> mPackageListFiltered = new ArrayList<>();
private final ICoreRootService mCoreRootServiceIpc;
private final OnItemClickListener mOnItemClickListener;
@SuppressLint("NotifyDataSetChanged")
public SelectPackageRecyclerViewAdapter(Context context, ICoreRootService coreRootServiceIpc, OnItemClickListener onItemClickListener) {
mContext = context;
mCoreRootServiceIpc = coreRootServiceIpc;
mOnItemClickListener = onItemClickListener;
try {
mPackageList = new ArrayList<>();
TreeMap<String, String> map = new TreeMap<String, String>(mCoreRootServiceIpc.phenotypeDBGetAllPackageNames());
for (Map.Entry<String, String> packageName : map.entrySet())
mPackageList.add(new PhenotypeDBPackageName(packageName.getKey(), packageName.getValue()));
mPackageListFiltered = mPackageList;
notifyDataSetChanged();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// Initialize binding and viewHolder
PackageRowBinding binding = PackageRowBinding.inflate(LayoutInflater.from(mContext), parent, false);
ViewHolder viewHolder = new ViewHolder(binding);
// Set onClickListener on list rows
viewHolder.mRow.setOnClickListener(v -> {
int position = viewHolder.getAdapterPosition();
mOnItemClickListener.onItemClick(mPackageListFiltered.get(position).getPhenotypePackageName());
});
// Return viewHolder
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
String phenotypePackageName = mPackageListFiltered.get(position).getPhenotypePackageName();
String androidPackageName = mPackageListFiltered.get(position).getAndroidPackageName();
PackageManager packageManager = mContext.getPackageManager();
Drawable packageIcon;
try {
packageIcon = packageManager.getApplicationIcon(androidPackageName);
} catch (PackageManager.NameNotFoundException e) {
packageIcon = AppCompatResources.getDrawable(mContext, R.drawable.ic_error_24);
if (packageIcon != null) {
TypedArray typedArray = mContext.getTheme().obtainStyledAttributes(R.styleable.ViewStyle);
int colorError = typedArray.getColor(R.styleable.ViewStyle_colorError, Color.RED);
typedArray.recycle();
packageIcon.mutate().setTint(colorError);
}
}
holder.mPackageIcon.setImageDrawable(packageIcon);
String appName = getApplicationLabelOrUnknown(mContext, androidPackageName);
holder.mAppName.setText(appName);
holder.mPhenotypePackageName.setText(phenotypePackageName);
}
@Override
public int getItemCount() {
return mPackageListFiltered.size();
}
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence charSequence) {
String keyLowercase = charSequence.toString().toLowerCase();
List<PhenotypeDBPackageName> packageListFiltered = new ArrayList<>();
for (PhenotypeDBPackageName phenotypeDBPackageName : mPackageList) {
String phenotypePackageNameLowercase = phenotypeDBPackageName.getPhenotypePackageName().toLowerCase();
String appNameLowercase = getApplicationLabelOrUnknown(mContext, phenotypeDBPackageName.getAndroidPackageName()).toLowerCase();
if (phenotypePackageNameLowercase.contains(keyLowercase) || appNameLowercase.contains(keyLowercase))
packageListFiltered.add(phenotypeDBPackageName);
}
mPackageListFiltered = packageListFiltered;
FilterResults filterResults = new FilterResults();
filterResults.values = mPackageListFiltered;
filterResults.count = mPackageListFiltered.size();
return filterResults;
}
@SuppressLint("NotifyDataSetChanged")
@Override
protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
mPackageListFiltered = (List<PhenotypeDBPackageName>) filterResults.values;
notifyDataSetChanged();
}
};
}
@Override
public CharSequence getSectionText(int position) {
// Get raw Phenotype package name
String phenotypePackageName = mPackageListFiltered.get(position).getPhenotypePackageName();
// Initialize indexEnd
int indexEnd = 0;
// Try to split package name by dot character: look for the first 4 parts, if there aren't try 3, 2 and so on
for (int i = 4; i >= 0; i--) {
int indexEndTmp = StringUtils.ordinalIndexOf(phenotypePackageName, ".", i);
if (indexEndTmp != -1) {
indexEnd = indexEndTmp;
break;
}
}
// Increment indexEnd by 2 to show two more characters beyond the found parts
if (indexEnd + 2 <= phenotypePackageName.length())
indexEnd += 2;
else
indexEnd = phenotypePackageName.length();
// Return the phenotypePackageName parsed substring
return phenotypePackageName.substring(0, indexEnd);
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private final LinearLayout mRow;
private final ImageView mPackageIcon;
private final TextView mAppName;
private final TextView mPhenotypePackageName;
public ViewHolder(PackageRowBinding binding) {
super(binding.getRoot());
mRow = binding.row;
mPackageIcon = binding.packageIcon;
mAppName = binding.appName;
mPhenotypePackageName = binding.phenotypePackageName;
}
}
}
================================================
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/fragment/BooleanModsFragment.java
================================================
package com.jacopomii.gappsmod.ui.fragment;
import static com.jacopomii.gappsmod.util.Utils.showSelectPackageDialog;
import android.app.Activity;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.SearchView;
import androidx.core.view.MenuProvider;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.jacopomii.gappsmod.ICoreRootService;
import com.jacopomii.gappsmod.R;
import com.jacopomii.gappsmod.databinding.FragmentBooleanModsBinding;
import com.jacopomii.gappsmod.ui.activity.MainActivity;
import com.jacopomii.gappsmod.ui.adapter.BooleanModsRecyclerViewAdapter;
import com.jacopomii.gappsmod.ui.view.FilterableSearchView;
import com.l4digital.fastscroll.FastScrollRecyclerView;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.concurrent.atomic.AtomicBoolean;
public class BooleanModsFragment extends Fragment {
private FragmentBooleanModsBinding mBinding;
private BooleanModsRecyclerViewAdapter mFlagsRecyclerViewAdapter;
private ICoreRootService mCoreRootServiceIpc;
private String flagsFilterKey = "";
private boolean flagsFilterEnabled = true;
private boolean flagsFilterDisabled = true;
private boolean flagsFilterChanged = true;
private boolean flagsFilterUnchanged = true;
public BooleanModsFragment() {
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Activity activity = getActivity();
if (activity instanceof MainActivity)
mCoreRootServiceIpc = ((MainActivity) activity).getCoreRootServiceIpc();
else
throw new RuntimeException("SuggestedModsFragment can be attached only to the MainActivity");
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// View bindings
mBinding = FragmentBooleanModsBinding.inflate(getLayoutInflater());
// Setup menu
setupMenu();
// Select package
TextView selectPackage = mBinding.selectPackage;
// Initialize the selectPackageDialogOpened
AtomicBoolean selectPackageDialogOpened = new AtomicBoolean(false);
// Select package onClick
selectPackage.setOnClickListener(v -> {
// Clear focus from other views
View currentFocus = requireActivity().getCurrentFocus();
if (currentFocus != null) currentFocus.clearFocus();
// If the select package dialog isn't already opened
if (!selectPackageDialogOpened.get()) {
// Set the selectPackageDialogOpened to true
selectPackageDialogOpened.set(true);
// Show the select package dialog
showSelectPackageDialog(getContext(), mCoreRootServiceIpc, item -> {
// The item received by the listener here is the Phenotype package name chosen by the user
// Update the select package textview
selectPackage.setText((String) item);
// Update the selectPackageRecyclerView adapter
mFlagsRecyclerViewAdapter.selectPhenotypePackageName((String) item);
// Set the selectPackageDialogOpened to false
selectPackageDialogOpened.set(false);
}, dialog -> {
// Set the selectPackageDialogOpened to false dismissing the dialog
selectPackageDialogOpened.set(false);
});
}
});
// Flags recyclerview
FastScrollRecyclerView flagsRecyclerView = mBinding.recyclerview;
// Initialize the flagsRecyclerView adapter
mFlagsRecyclerViewAdapter = new BooleanModsRecyclerViewAdapter(getContext(), mCoreRootServiceIpc);
// Disable fast scroll if the flagsRecyclerView is empty or changes to empty
flagsRecyclerView.setFastScrollEnabled(mFlagsRecyclerViewAdapter.getItemCount() != 0);
mFlagsRecyclerViewAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
flagsRecyclerView.setFastScrollEnabled(mFlagsRecyclerViewAdapter.getItemCount() != 0);
}
});
// Set flagsRecyclerView items padding
flagsRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
int padding = (int) getResources().getDimension(R.dimen.margin_generic);
int itemPosition = parent.getChildAdapterPosition(view);
if (itemPosition == 0) outRect.top = padding;
else if (itemPosition == mFlagsRecyclerViewAdapter.getItemCount() - 1)
outRect.bottom = padding;
outRect.left = padding;
outRect.right = padding;
}
});
// Set the flagsRecyclerView LayoutManager and Adapter
flagsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
flagsRecyclerView.setAdapter(mFlagsRecyclerViewAdapter);
// Return the fragment view
return mBinding.getRoot();
}
private void setupMenu() {
requireActivity().addMenuProvider(new MenuProvider() {
@Override
public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
// Inflate the menu layout
menuInflater.inflate(R.menu.search_menu, menu);
// Initialize the menuSearchIcon
MenuItem menuSearchIcon = menu.findItem(R.id.menu_search_icon);
// Initialize filterableSearchView and the additional filter container
FilterableSearchView filterableSearchView = (FilterableSearchView) menuSearchIcon.getActionView();
filterableSearchView.setQueryHint(getString(R.string.search_by_flag));
filterableSearchView.setFilterContainer(mBinding.filterContainer, false);
// Initialize the filterEnabledStatusSpinner
String[] filterEnabledStatusSpinnerChoices = new String[]{getString(R.string.enabled_and_disabled), getString(R.string.enabled_only), getString(R.string.disabled_only)};
mBinding.filterEnabledStatusSpinner.setAdapter(new ArrayAdapter<>(requireContext(), android.R.layout.simple_spinner_dropdown_item, filterEnabledStatusSpinnerChoices));
mBinding.filterEnabledStatusSpinner.setOnItemClickListener((parent, view, position, id) -> {
flagsFilterEnabled = position == 0 || position == 1;
flagsFilterDisabled = position == 0 || position == 2;
applyFlagsFilters();
});
// Initialize the filterChangedStatusSpinner
String[] filterChangedStatusSpinnerChoices = new String[]{getString(R.string.changed_and_unchanged), getString(R.string.changed_only), getString(R.string.unchanged_only)};
mBinding.filterChangedStatusSpinner.setAdapter(new ArrayAdapter<>(requireContext(), android.R.layout.simple_spinner_dropdown_item, filterChangedStatusSpinnerChoices));
mBinding.filterChangedStatusSpinner.setOnItemClickListener((parent, view, position, id) -> {
flagsFilterChanged = position == 0 || position == 1;
flagsFilterUnchanged = position == 0 || position == 2;
applyFlagsFilters();
});
// Set flags filters to default values
resetFlagsFilters();
// Handle menuSearchIcon expand / collapse actions
menuSearchIcon.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
return true;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
// When the search view is collapsed, flag filters need to be reset and applied
resetFlagsFilters();
applyFlagsFilters();
return true;
}
});
// Handle filterableSearchView search query changes
filterableSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
flagsFilterKey = newText;
applyFlagsFilters();
return false;
}
});
}
@Override
public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {
return false;
}
}, getViewLifecycleOwner(), Lifecycle.State.RESUMED);
}
private void resetFlagsFilters() {
flagsFilterKey = "";
flagsFilterEnabled = true;
flagsFilterDisabled = true;
flagsFilterChanged = true;
flagsFilterUnchanged = true;
mBinding.filterEnabledStatusSpinner.setText(mBinding.filterEnabledStatusSpinner.getAdapter().getItem(0).toString(), false);
mBinding.filterChangedStatusSpinner.setText(mBinding.filterChangedStatusSpinner.getAdapter().getItem(0).toString(), false);
}
private void applyFlagsFilters() {
try {
JSONObject filterConfig = new JSONObject();
filterConfig.put("key", flagsFilterKey);
filterConfig.put("enabled", flagsFilterEnabled);
filterConfig.put("disabled", flagsFilterDisabled);
filterConfig.put("changed", flagsFilterChanged);
filterConfig.put("unchanged", flagsFilterUnchanged);
mFlagsRecyclerViewAdapter.getFilter().filter(filterConfig.toString());
} catch (JSONException e) {
e.printStackTrace();
}
}
}
================================================
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/fragment/InformationFragment.java
================================================
package com.jacopomii.gappsmod.ui.fragment;
import android.os.Bundle;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.jacopomii.gappsmod.databinding.FragmentInformationBinding;
public class InformationFragment extends Fragment {
FragmentInformationBinding mBinding;
public InformationFragment() {}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mBinding = FragmentInformationBinding.inflate(getLayoutInflater());
// Links aren't clickable workaround
mBinding.madeWithLoveByJacopoTediosi.setMovementMethod(LinkMovementMethod.getInstance());
return mBinding.getRoot();
}
}
================================================
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/fragment/RevertModsFragment.java
================================================
package com.jacopomii.gappsmod.ui.fragment;
import static com.jacopomii.gappsmod.data.Constants.DIALER_CALLRECORDINGPROMPT;
import static com.jacopomii.gappsmod.data.Constants.DIALER_PHENOTYPE_PACKAGE_NAME;
import static com.jacopomii.gappsmod.util.Utils.showSelectPackageDialog;
import android.app.Activity;
import android.os.Bundle;
import android.os.RemoteException;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.jacopomii.gappsmod.ICoreRootService;
import com.jacopomii.gappsmod.R;
import com.jacopomii.gappsmod.databinding.FragmentRevertModsBinding;
import com.jacopomii.gappsmod.ui.activity.MainActivity;
import com.topjohnwu.superuser.nio.ExtendedFile;
import com.topjohnwu.superuser.nio.FileSystemManager;
import java.util.concurrent.atomic.AtomicBoolean;
import es.dmoral.toasty.Toasty;
public class RevertModsFragment extends Fragment {
FragmentRevertModsBinding mBinding;
private ICoreRootService mCoreRootServiceIpc;
private FileSystemManager mCoreRootServiceFSManager;
public RevertModsFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Activity activity = getActivity();
if (activity instanceof MainActivity) {
mCoreRootServiceIpc = ((MainActivity) activity).getCoreRootServiceIpc();
mCoreRootServiceFSManager = ((MainActivity) activity).getCoreRootServiceFSManager();
} else {
throw new RuntimeException("RevertModsFragment can be attached only to the MainActivity");
}
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// View bindings
mBinding = FragmentRevertModsBinding.inflate(getLayoutInflater());
// Select package for revert mods for specific package
TextView selectPackage = mBinding.selectPackage;
// Initialize the selectPackageDialogOpened
AtomicBoolean selectPackageDialogOpened = new AtomicBoolean(false);
// Select package onClick
selectPackage.setOnClickListener(v -> {
// If the select package dialog isn't already opened
if (!selectPackageDialogOpened.get()) {
// Set the selectPackageDialogOpened to true
selectPackageDialogOpened.set(true);
// Show the select package dialog
showSelectPackageDialog(
getContext(),
mCoreRootServiceIpc,
item -> {
// The item received by the listener here is the Phenotype package name chosen by the user
// Update the select package textview
selectPackage.setText((String) item);
// Enable the revertModsSelectedPackageButton
mBinding.revertModsSelectedPackageButton.setEnabled(true);
},
dialog -> {
// Set the selectPackageDialogOpened to false dismissing the dialog
selectPackageDialogOpened.set(false);
});
}
});
// Revert mods for the selected package button
mBinding.revertModsSelectedPackageButton.setOnClickListener(v -> {
String selectedPhenotypePackageName = selectPackage.getText().toString();
new MaterialAlertDialogBuilder(requireContext())
.setMessage(String.format(getResources().getString(R.string.revert_mods_for_the_selected_package_confirm), selectedPhenotypePackageName))
.setNegativeButton(getString(R.string.no), (dialog, which) -> {
})
.setPositiveButton(getString(R.string.yes), (dialog, which) -> {
try {
// Delete all flag overrides for the selected package from Phenotype DB
mCoreRootServiceIpc.phenotypeDBDeleteAllFlagOverridesByPhenotypePackageName(selectedPhenotypePackageName);
// If the selected package was the Dialer
if (selectedPhenotypePackageName.equals(DIALER_PHENOTYPE_PACKAGE_NAME)) {
// Delete the com.google.android.dialer callrecordingprompt folder (if it exists)
ExtendedFile callRecordingPromptFolder = mCoreRootServiceFSManager.getFile(DIALER_CALLRECORDINGPROMPT);
if (callRecordingPromptFolder.exists()) {
//noinspection ResultOfMethodCallIgnored
callRecordingPromptFolder.delete();
}
}
// UI confirmation to the user
Toasty.success(requireContext(), getString(R.string.done), Toast.LENGTH_LONG, true).show();
} catch (RemoteException e) {
e.printStackTrace();
Toasty.error(requireContext(), getString(R.string.an_error_has_occurred), Toast.LENGTH_LONG, true).show();
}
}).show();
}
);
// Revert all mods button
mBinding.revertAllModsButton.setOnClickListener(v ->
new MaterialAlertDialogBuilder(requireContext())
.setMessage(R.string.revert_mods_for_all_packages_confirm)
.setNegativeButton(getString(R.string.no), (dialog, which) -> {
})
.setPositiveButton(getString(R.string.yes), (dialog, which) -> {
try {
// Delete all flag overrides from Phenotype DB
mCoreRootServiceIpc.phenotypeDBDeleteAllFlagOverrides();
// Delete the com.google.android.dialer callrecordingprompt folder
ExtendedFile callRecordingPromptFolder = mCoreRootServiceFSManager.getFile(DIALER_CALLRECORDINGPROMPT);
if (callRecordingPromptFolder.exists()) {
//noinspection ResultOfMethodCallIgnored
callRecordingPromptFolder.delete();
}
// UI confirmation to the user
Toasty.success(requireContext(), getString(R.string.done), Toast.LENGTH_LONG, true).show();
} catch (RemoteException e) {
e.printStackTrace();
Toasty.error(requireContext(), getString(R.string.an_error_has_occurred), Toast.LENGTH_LONG, true).show();
}
}).show()
);
return mBinding.getRoot();
}
}
================================================
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/fragment/SuggestedModsFragment.java
================================================
package com.jacopomii.gappsmod.ui.fragment;
import static android.Manifest.permission.CAPTURE_AUDIO_OUTPUT;
import static com.jacopomii.gappsmod.data.Constants.DIALER_ANDROID_PACKAGE_NAME;
import static com.jacopomii.gappsmod.data.Constants.DIALER_CALLRECORDINGPROMPT;
import static com.jacopomii.gappsmod.data.Constants.DIALER_PHENOTYPE_PACKAGE_NAME;
import static com.jacopomii.gappsmod.data.Constants.GOOGLE_PLAY_BETA_LINK;
import static com.jacopomii.gappsmod.data.Constants.GOOGLE_PLAY_DETAILS_LINK;
import static com.jacopomii.gappsmod.data.Constants.MESSAGES_ANDROID_PACKAGE_NAME;
import static com.jacopomii.gappsmod.data.Constants.MESSAGES_PHENOTYPE_PACKAGE_NAME;
import static com.jacopomii.gappsmod.util.Utils.copyFile;
import static com.jacopomii.gappsmod.util.Utils.openGooglePlay;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.RemoteException;
import android.telephony.TelephonyManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.protobuf.ByteString;
import com.jacopomii.gappsmod.ICoreRootService;
import com.jacopomii.gappsmod.R;
import com.jacopomii.gappsmod.databinding.FragmentSuggestedModsBinding;
import com.jacopomii.gappsmod.protos.Call_screen_i18n_config;
import com.jacopomii.gappsmod.ui.activity.MainActivity;
import com.jacopomii.gappsmod.ui.view.ProgrammaticMaterialSwitchView;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.nio.ExtendedFile;
import com.topjohnwu.superuser.nio.FileSystemManager;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@SuppressWarnings("FieldCanBeLocal")
public class SuggestedModsFragment extends Fragment {
private FragmentSuggestedModsBinding mBinding;
private ICoreRootService mCoreRootServiceIpc;
private FileSystemManager mCoreRootServiceFSManager;
// The following boolean flags force-enable Call Recording features in Dialer app
private final HashMap<String, Boolean> DIALER_ENABLE_CALL_RECORDING_FLAGS = new HashMap<String, Boolean>() {{
// Enable Call Recording feature
put("G__enable_call_recording", true);
put("enable_call_recording_system_feature", true);
// Enable Call Recording also for Google Fi / Fides (e2e calls, etc)
put("CallRecording__enable_call_recording_for_fi", true);
// Bypass country-related restrictions for call recording feature
put("G__force_within_call_recording_geofence_value", true);
// Bypass country-related restrictions for automatic call recording ("always record") feature
put("G__force_within_crosby_geofence_value", true);
// Allow the usage of the above two "force geofence" flags
put("G__use_call_recording_geofence_overrides", true);
// Show call recording button
put("enable_tidepods_call_recording", true);
}};
// The following extensionVal flags concern the announcement audio played when a call recording starts or ends in Dialer app.
// To silence announcements, we set them as empty stringVal flags.
private final HashMap<String, String> DIALER_SILENCE_CALL_RECORDING_ALERTS_FLAGS = new HashMap<String, String>() {{
// The following flag contains a protobuf list of countries where the use of embedded audio is enforced.
// If its value is an empty string, the Dialer will by default use TTS to generate audio call recording alerts.
put("CallRecording__call_recording_countries_with_built_in_audio_file", "");
// The following flags are no longer used in recent versions of the Dialer and remain here for backwards compatibility.
// They were used to contain a protobuf list of countries where the use of embedded or TTS audio was enforced.
put("CallRecording__call_recording_force_enable_built_in_audio_file_countries", "");
put("CallRecording__call_recording_force_enable_tts_countries", "");
// The following flag contains a protobuf hashset with country-language matches, used by Dialer to generate call recording audio alerts via TTS
// in the right language. If its value is an empty string, TTS will always fall back to en_US (hardcoded in the Dialer sources).
put("CallRecording__call_recording_countries", "");
}};
private final String DIALER_CALLRECORDINGPROMPT_STARTING_VOICE_US = "starting_voice-en_US.wav";
private final String DIALER_CALLRECORDINGPROMPT_ENDING_VOICE_US = "ending_voice-en_US.wav";
// Dialer versionCode 10681248 (94.x) is the last version in which we can silence call recording alerts. In newer versions Google patched our hack.
private final int DIALER_SILENCE_CALL_RECORDING_ALERTS_MAX_VERSION = 10681248;
// The following boolean flags force-enable Call Screen / Revelio features in Dialer app
private final HashMap<String, Boolean> DIALER_ENABLE_CALL_SCREEN_FLAGS = new HashMap<String, Boolean>() {{
// Enable Call Screen feature for both calls and video-calls
put("G__speak_easy_enabled", true);
put("enable_video_calling_screen", true);
// Bypass Call Screen locale restrictions
put("G__speak_easy_bypass_locale_check", true);
// Enable translations for additional locales
put("enable_call_screen_i18n_tidepods", true);
// Enable the "listen in" button, which is located at the bottom right during screening
put("G__speak_easy_enable_listen_in_button", true);
// Enable the Call Screen Demo page in Dialer settings
put("enable_call_screen_demo", true);
// Enable the "See transcript" button in call history, which allows to read call screen transcripts and listen to recordings
put("G__enable_speakeasy_details", true);
// Enable Revelio,an advanced version of the Call Screen which allows to automatically filter calls
put("G__enable_revelio", true);
put("G__enable_revelio_on_bluetooth", true);
put("G__enable_revelio_on_wired_headset", true);
// Bypass Revelio locale restrictions
put("G__bypass_revelio_roaming_check", true);
// Enable translations for additional locales also for Revelio
put("G__enable_tidepods_revelio", true);
// Enable the Dialer settings option to save screened call audio (it does not depend on the Call Recording feature, but depends on Revelio)
put("G__enable_call_screen_saving_audio", true);
// Enable the saving of the transcript also for Revelio
put("enable_revelio_transcript", true);
}};
// The following extensionVal flag contains a protobuf (see call_screen_i18n.proto for its definition)
// which matches the languages to be used for the Call Screen feature to the supported countries in Dialer app
private final String DIALER_CALL_SCREEN_I18N_CONFIG_FLAG = "CallScreenI18n__call_screen_i18n_config";
// The following boolean flag force-enables debug menu in Messages app
private final HashMap<String, Boolean> MESSAGES_ENABLE_DEBUG_MENU_FLAGS = new HashMap<String, Boolean>() {{
put("bugle_phenotype__debug_menu_default_available", true);
}};
// The following boolean flag force-enables marking conversations as unread in Messages app
private final HashMap<String, Boolean> MESSAGES_ENABLE_MARKING_CONVERSATIONS_UNREAD_FLAGS = new HashMap<String, Boolean>() {{
put("bugle_phenotype__enable_mark_as_unread", true);
}};
// The following boolean flags force-enable Message Organization (Super Sort) features in Messages app
private final HashMap<String, Boolean> MESSAGES_ENABLE_MESSAGE_ORGANIZATION_FLAGS = new HashMap<String, Boolean>() {{
// Enable super sort
put("bugle_phenotype__conversation_labels_enabled", true);
// Enable "all" category (this flag may be superfluous)
put("bugle_phenotype__supersort_badge_all_filter", true);
// Enable donation banner
put("bugle_phenotype__supersort_enable_update_donation_banner", true);
// Enable OTP auto deletion
put("bugle_phenotype__enable_otp_auto_deletion", true);
// Classify messages also in the foreground (don't use only workmanager)
put("bugle_phenotype__supersort_use_only_work_manager", false);
// I don't know what is it. In the Messages code I see that it's about "generating annotations" for "money, coupon, account number, percentage"
put("bugle_phenotype__enable_supersort_annotators", true);
// QPBC = Participant Based Quick Classification. I don't know how it works.
put("bugle_phenotype__supersort_enable_qpbc", true);
}};
// The following boolean flag force-enables verified SMS settings menu in Messages app
private final HashMap<String, Boolean> MESSAGES_ENABLE_VERIFIED_SMS_FLAGS = new HashMap<String, Boolean>() {{
put("bugle_phenotype__enabled_verified_sms", true);
}};
// The following boolean flag force-enables sending images via GPhotos links in Messages app
private final HashMap<String, Boolean> MESSAGES_ENABLE_IMAGES_VIA_GPHOTOS_FLAGS = new HashMap<String, Boolean>() {{
put("bugle_phenotype__enable_google_photos_image_by_link", true);
}};
// The following boolean flags force-enable nudges and birthday reminders in Messages app
private final HashMap<String, Boolean> MESSAGES_ENABLE_NUDGES_FLAGS = new HashMap<String, Boolean>() {{
// Enable nudges and birthday reminders
put("bugle_phenotype__enable_nudge", true);
put("bugle_phenotype__enable_birthday_nudge", true);
put("bugle_phenotype__enable_birthday_suggestions", true);
// Enable banners
put("bugle_phenotype__enable_nudge_banner", true);
put("bugle_phenotype__enable_birthday_banner", true);
put("bugle_phenotype__enable_save_birthday_banner", true);
// Enable settings pages
put("bugle_phenotype__enable_birthday_nudge_setting", true);
put("bugle_phenotype__enable_birthday_banner_settings_button", true);
// Leave the settings menu lines separate
put("bugle_phenotype__combing_nudge_settings", false);
}};
// The following boolean flags force-enable spotlights suggestions settings menu in Messages app
private final HashMap<String, Boolean> MESSAGES_ENABLE_SPOTLIGHTS_FLAGS = new HashMap<String, Boolean>() {{
// Enable spotlights
put("bugle_phenotype__enable_spotlights", true);
// Enable spotlights settings page
put("bugle_phenotype__enable_spotlight_settings_page", true);
// Enable new settings page layout, otherwise it won't show up
put("bugle_phenotype__enable_smarts_settings_page_v2", true);
// Enable additional spotlights features
put("bugle_phenotype__enable_spotlights_google_search", true);
}};
// The following boolean flag force-enables smart compose (predictive writing) settings menu in Messages app
private final HashMap<String, Boolean> MESSAGES_ENABLE_SMART_COMPOSE_FLAGS = new HashMap<String, Boolean>() {{
put("bugle_phenotype__enable_smart_compose", true);
}};
// The following boolean flags force-enable magic compose (draft suggestions with Bard AI) in Messages app
private final HashMap<String, Boolean> MESSAGES_ENABLE_MAGIC_COMPOSE_FLAGS = new HashMap<String, Boolean>() {{
// Enable magic compose view
put("bugle_phenotype__enable_magic_compose_view", true);
// Idk what is it, but it has to be true to effectively enable magic compose
put("bugle_phenotype__enable_combined_magic_compose", true);
// Enable all additional functionalities for magic compose (e.g., feedback and multiple writing styles)
put("bugle_phenotype__enable_additional_functionalities_for_magic_compose", true);
// Enable magic compose also in xms
put("bugle_phenotype__magic_compose_enabled_in_xms", true);
}};
// The following boolean flag force-enables smart actions (smart reply) in notifications in Messages app
private final HashMap<String, Boolean> MESSAGES_ENABLE_SMART_ACTIONS_IN_NOTIFICATIONS_FLAGS = new HashMap<String, Boolean>() {{
put("bugle_phenotype__enable_smart_actions_in_notifications", true);
}};
// The following boolean flag force-enables suggested stickers settings menu in Messages app
private final HashMap<String, Boolean> MESSAGES_ENABLE_SUGGESTED_STICKERS_FLAGS = new HashMap<String, Boolean>() {{
put("bugle_phenotype__sticker_suggestions_setting_enabled", true);
}};
public SuggestedModsFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Activity activity = getActivity();
if (activity instanceof MainActivity) {
mCoreRootServiceIpc = ((MainActivity) activity).getCoreRootServiceIpc();
mCoreRootServiceFSManager = ((MainActivity) activity).getCoreRootServiceFSManager();
} else {
throw new RuntimeException("SuggestedModsFragment can be attached only to the MainActivity");
}
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mBinding = FragmentSuggestedModsBinding.inflate(getLayoutInflater());
// GDialer
try {
// Beta and Install buttons actions
mBinding.dialerAppHeader.getBetaButton().setOnClickListener(v -> openGooglePlay(requireContext(), GOOGLE_PLAY_BETA_LINK + DIALER_ANDROID_PACKAGE_NAME));
mBinding.dialerAppHeader.getInstallButton().setOnClickListener(v -> openGooglePlay(requireContext(), GOOGLE_PLAY_DETAILS_LINK + DIALER_ANDROID_PACKAGE_NAME));
// Check if application is installed
requireContext().getPackageManager().getApplicationInfo(DIALER_ANDROID_PACKAGE_NAME, 0);
// Check if application has CAPTURE_AUDIO_OUTPUT permission
if (requireContext().getPackageManager().checkPermission(CAPTURE_AUDIO_OUTPUT, DIALER_ANDROID_PACKAGE_NAME) != PackageManager.PERMISSION_GRANTED)
mBinding.dialerPermissionAlert.setVisibility(View.VISIBLE);
// dialerForceEnableCallRecordingSwitch
ProgrammaticMaterialSwitchView dialerForceEnableCallRecordingSwitch = mBinding.dialerForceEnableCallRecording.getSwitch();
boolean dialerForceEnableCallRecordingSwitchChecked = modCheckAreAllFlagsOverridden(DIALER_PHENOTYPE_PACKAGE_NAME, new ArrayList<>(DIALER_ENABLE_CALL_RECORDING_FLAGS.keySet()));
dialerForceEnableCallRecordingSwitch.setCheckedProgrammatically(dialerForceEnableCallRecordingSwitchChecked);
dialerForceEnableCallRecordingSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> modSetBooleanFlags(isChecked, DIALER_PHENOTYPE_PACKAGE_NAME, DIALER_ENABLE_CALL_RECORDING_FLAGS));
dialerForceEnableCallRecordingSwitch.setEnabled(true);
// dialerSilenceCallRecordingAlertsSwitch
ProgrammaticMaterialSwitchView dialerSilenceCallRecordingAlertsSwitch = mBinding.dialerSilenceCallRecordingAlerts.getSwitch();
boolean dialerSilenceCallRecordingAlertsSwitchChecked = false;
try {
ExtendedFile startingVoiceFile = mCoreRootServiceFSManager.getFile(DIALER_CALLRECORDINGPROMPT, DIALER_CALLRECORDINGPROMPT_STARTING_VOICE_US);
ExtendedFile endingVoiceFile = mCoreRootServiceFSManager.getFile(DIALER_CALLRECORDINGPROMPT, DIALER_CALLRECORDINGPROMPT_STARTING_VOICE_US);
if (startingVoiceFile.exists() && endingVoiceFile.exists()) {
InputStream silentVoiceInputStream;
InputStream startingVoiceInputStream = startingVoiceFile.newInputStream();
silentVoiceInputStream = getResources().openRawResource(R.raw.silent_wav);
boolean isStartingVoiceSilenced = IOUtils.contentEquals(silentVoiceInputStream, startingVoiceInputStream);
startingVoiceInputStream.close();
silentVoiceInputStream.close();
InputStream endingVoiceInputStream = endingVoiceFile.newInputStream();
silentVoiceInputStream = getResources().openRawResource(R.raw.silent_wav);
boolean isEndingVoiceSilenced = IOUtils.contentEquals(silentVoiceInputStream, endingVoiceInputStream);
endingVoiceInputStream.close();
silentVoiceInputStream.close();
dialerSilenceCallRecordingAlertsSwitchChecked = modCheckAreAllFlagsOverridden(DIALER_PHENOTYPE_PACKAGE_NAME, new ArrayList<>(DIALER_SILENCE_CALL_RECORDING_ALERTS_FLAGS.keySet())) &&
isStartingVoiceSilenced && isEndingVoiceSilenced;
}
} catch (IOException e) {
e.printStackTrace();
}
try {
// If Dialer version > SILENCE_CALL_RECORDING_ALERTS_MAX_VERSION the dialerSilenceCallRecordingAlertsSwitch must remain disabled
if (requireContext().getPackageManager().getPackageInfo(DIALER_ANDROID_PACKAGE_NAME, 0).versionCode > DIALER_SILENCE_CALL_RECORDING_ALERTS_MAX_VERSION) {
// If the dialerSilenceCallRecordingAlertsSwitch was enabled in previous versions of GAppsMod, the silenceCallRecordingAlerts mod must be automatically disabled
if (dialerSilenceCallRecordingAlertsSwitchChecked) {
dialerSilenceCallRecordingAlerts(false);
}
// Otherwise, the dialerSilenceCallRecordingAlertsSwitch should be loaded as usual
} else {
dialerSilenceCallRecordingAlertsSwitch.setCheckedProgrammatically(dialerSilenceCallRecordingAlertsSwitchChecked);
dialerSilenceCallRecordingAlertsSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> dialerSilenceCallRecordingAlerts(isChecked));
dialerSilenceCallRecordingAlertsSwitch.setEnabled(true);
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
// dialerForceEnableCallScreenSwitch
ProgrammaticMaterialSwitchView dialerForceEnableCallScreenSwitch = mBinding.dialerForceEnableCallScreen.getSwitch();
boolean dialerForceEnableCallScreenSwitchChecked = modCheckAreAllFlagsOverridden(DIALER_PHENOTYPE_PACKAGE_NAME, Collections.singletonList(DIALER_CALL_SCREEN_I18N_CONFIG_FLAG));
dialerForceEnableCallScreenSwitch.setCheckedProgrammatically(dialerForceEnableCallScreenSwitchChecked);
dialerForceEnableCallScreenSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> dialerForceEnableCallScreen(isChecked));
dialerForceEnableCallScreenSwitch.setEnabled(true);
} catch (PackageManager.NameNotFoundException e) {
mBinding.dialerNotInstalledAlert.setVisibility(View.VISIBLE);
}
// GMessages
try {
// Beta and Install buttons actions
mBinding.messagesAppHeader.getBetaButton().setOnClickListener(v -> openGooglePlay(requireContext(), GOOGLE_PLAY_BETA_LINK + MESSAGES_ANDROID_PACKAGE_NAME));
mBinding.messagesAppHeader.getInstallButton().setOnClickListener(v -> openGooglePlay(requireContext(), GOOGLE_PLAY_DETAILS_LINK + MESSAGES_ANDROID_PACKAGE_NAME));
// Check if application is installed
requireContext().getPackageManager().getApplicationInfo(MESSAGES_ANDROID_PACKAGE_NAME, 0);
// messagesForceEnableDebugMenuSwitch
ProgrammaticMaterialSwitchView messagesForceEnableDebugMenuSwitch = mBinding.messagesForceEnableDebugMenu.getSwitch();
boolean messagesForceEnableDebugMenuSwitchChecked = modCheckAreAllFlagsOverridden(MESSAGES_PHENOTYPE_PACKAGE_NAME, new ArrayList<>(MESSAGES_ENABLE_DEBUG_MENU_FLAGS.keySet()));
messagesForceEnableDebugMenuSwitch.setCheckedProgrammatically(messagesForceEnableDebugMenuSwitchChecked);
messagesForceEnableDebugMenuSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> modSetBooleanFlags(isChecked, MESSAGES_PHENOTYPE_PACKAGE_NAME, MESSAGES_ENABLE_DEBUG_MENU_FLAGS));
messagesForceEnableDebugMenuSwitch.setEnabled(true);
// messagesForceEnableMarkingMessageThreadsUnreadSwitch
ProgrammaticMaterialSwitchView messagesForceEnableMarkingMessageThreadsUnreadSwitch = mBinding.messagesForceEnableMarkingMessageThreadsUnread.getSwitch();
boolean messagesForceEnableMarkingMessageThreadsUnreadSwitchChecked = modCheckAreAllFlagsOverridden(MESSAGES_PHENOTYPE_PACKAGE_NAME, new ArrayList<>(MESSAGES_ENABLE_MARKING_CONVERSATIONS_UNREAD_FLAGS.keySet()));
messagesForceEnableMarkingMessageThreadsUnreadSwitch.setCheckedProgrammatically(messagesForceEnableMarkingMessageThreadsUnreadSwitchChecked);
messagesForceEnableMarkingMessageThreadsUnreadSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> modSetBooleanFlags(isChecked, MESSAGES_PHENOTYPE_PACKAGE_NAME, MESSAGES_ENABLE_MARKING_CONVERSATIONS_UNREAD_FLAGS));
messagesForceEnableMarkingMessageThreadsUnreadSwitch.setEnabled(true);
// messagesForceEnableMessageOrganizationSwitch
ProgrammaticMaterialSwitchView messagesForceEnableMessageOrganizationSwitch = mBinding.messagesForceEnableMessageOrganization.getSwitch();
boolean messagesForceEnableMessageOrganizationSwitchChecked = modCheckAreAllFlagsOverridden(MESSAGES_PHENOTYPE_PACKAGE_NAME, new ArrayList<>(MESSAGES_ENABLE_MESSAGE_ORGANIZATION_FLAGS.keySet()));
messagesForceEnableMessageOrganizationSwitch.setCheckedProgrammatically(messagesForceEnableMessageOrganizationSwitchChecked);
messagesForceEnableMessageOrganizationSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> modSetBooleanFlags(isChecked, MESSAGES_PHENOTYPE_PACKAGE_NAME, MESSAGES_ENABLE_MESSAGE_ORGANIZATION_FLAGS));
messagesForceEnableMessageOrganizationSwitch.setEnabled(true);
// messagesForceEnableVerifiedSmsSwitch
ProgrammaticMaterialSwitchView messagesForceEnableVerifiedSmsSwitch = mBinding.messagesForceEnableVerifiedSms.getSwitch();
boolean messagesForceEnableVerifiedSmsSwitchChecked = modCheckAreAllFlagsOverridden(MESSAGES_PHENOTYPE_PACKAGE_NAME, new ArrayList<>(MESSAGES_ENABLE_VERIFIED_SMS_FLAGS.keySet()));
messagesForceEnableVerifiedSmsSwitch.setCheckedProgrammatically(messagesForceEnableVerifiedSmsSwitchChecked);
messagesForceEnableVerifiedSmsSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> modSetBooleanFlags(isChecked, MESSAGES_PHENOTYPE_PACKAGE_NAME, MESSAGES_ENABLE_VERIFIED_SMS_FLAGS));
messagesForceEnableVerifiedSmsSwitch.setEnabled(true);
// messagesForceEnableGphotosSwitch
ProgrammaticMaterialSwitchView messagesForceEnableGphotosSwitch = mBinding.messagesForceEnableGphotos.getSwitch();
boolean messagesForceEnableGphotosSwitchChecked = modCheckAreAllFlagsOverridden(MESSAGES_PHENOTYPE_PACKAGE_NAME, new ArrayList<>(MESSAGES_ENABLE_IMAGES_VIA_GPHOTOS_FLAGS.keySet()));
messagesForceEnableGphotosSwitch.setCheckedProgrammatically(messagesForceEnableGphotosSwitchChecked);
messagesForceEnableGphotosSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> modSetBooleanFlags(isChecked, MESSAGES_PHENOTYPE_PACKAGE_NAME, MESSAGES_ENABLE_IMAGES_VIA_GPHOTOS_FLAGS));
messagesForceEnableGphotosSwitch.setEnabled(true);
// messagesForceEnableNudgesSwitch
ProgrammaticMaterialSwitchView messagesForceEnableNudgesSwitch = mBinding.messagesForceEnableNudges.getSwitch();
boolean messagesForceEnableNudgesSwitchChecked = modCheckAreAllFlagsOverridden(MESSAGES_PHENOTYPE_PACKAGE_NAME, new ArrayList<>(MESSAGES_ENABLE_NUDGES_FLAGS.keySet()));
messagesForceEnableNudgesSwitch.setCheckedProgrammatically(messagesForceEnableNudgesSwitchChecked);
messagesForceEnableNudgesSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> modSetBooleanFlags(isChecked, MESSAGES_PHENOTYPE_PACKAGE_NAME, MESSAGES_ENABLE_NUDGES_FLAGS));
messagesForceEnableNudgesSwitch.setEnabled(true);
// messagesForceEnableSpotlightsSwitch
ProgrammaticMaterialSwitchView messagesForceEnableSpotlightsSwitch = mBinding.messagesForceEnableSpotlights.getSwitch();
boolean messagesForceEnableSpotlightsSwitchChecked = modCheckAreAllFlagsOverridden(MESSAGES_PHENOTYPE_PACKAGE_NAME, new ArrayList<>(MESSAGES_ENABLE_SPOTLIGHTS_FLAGS.keySet()));
messagesForceEnableSpotlightsSwitch.setCheckedProgrammatically(messagesForceEnableSpotlightsSwitchChecked);
messagesForceEnableSpotlightsSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> modSetBooleanFlags(isChecked, MESSAGES_PHENOTYPE_PACKAGE_NAME, MESSAGES_ENABLE_SPOTLIGHTS_FLAGS));
messagesForceEnableSpotlightsSwitch.setEnabled(true);
// messagesForceEnableSmartComposeSwitch
ProgrammaticMaterialSwitchView messagesForceEnableSmartComposeSwitch = mBinding.messagesForceEnableSmartCompose.getSwitch();
boolean messagesForceEnableSmartComposeSwitchChecked = modCheckAreAllFlagsOverridden(MESSAGES_PHENOTYPE_PACKAGE_NAME, new ArrayList<>(MESSAGES_ENABLE_SMART_COMPOSE_FLAGS.keySet()));
messagesForceEnableSmartComposeSwitch.setCheckedProgrammatically(messagesForceEnableSmartComposeSwitchChecked);
messagesForceEnableSmartComposeSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> modSetBooleanFlags(isChecked, MESSAGES_PHENOTYPE_PACKAGE_NAME, MESSAGES_ENABLE_SMART_COMPOSE_FLAGS));
messagesForceEnableSmartComposeSwitch.setEnabled(true);
// messagesForceEnableMagicComposeSwitch
ProgrammaticMaterialSwitchView messagesForceEnableMagicComposeSwitch = mBinding.messagesForceEnableMagicCompose.getSwitch();
boolean messagesForceEnableMagicComposeSwitchChecked = modCheckAreAllFlagsOverridden(MESSAGES_PHENOTYPE_PACKAGE_NAME, new ArrayList<>(MESSAGES_ENABLE_MAGIC_COMPOSE_FLAGS.keySet()));
messagesForceEnableMagicComposeSwitch.setCheckedProgrammatically(messagesForceEnableMagicComposeSwitchChecked);
messagesForceEnableMagicComposeSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> modSetBooleanFlags(isChecked, MESSAGES_PHENOTYPE_PACKAGE_NAME, MESSAGES_ENABLE_MAGIC_COMPOSE_FLAGS));
messagesForceEnableMagicComposeSwitch.setEnabled(true);
// messagesForceEnableSmartActionsInNotificationsSwitch
ProgrammaticMaterialSwitchView messagesForceEnableSmartActionsInNotificationsSwitch = mBinding.messagesForceEnableSmartActionsInNotifications.getSwitch();
boolean messagesForceEnableSmartActionsInNotificationsSwitchChecked = modCheckAreAllFlagsOverridden(MESSAGES_PHENOTYPE_PACKAGE_NAME, new ArrayList<>(MESSAGES_ENABLE_SMART_ACTIONS_IN_NOTIFICATIONS_FLAGS.keySet()));
messagesForceEnableSmartActionsInNotificationsSwitch.setCheckedProgrammatically(messagesForceEnableSmartActionsInNotificationsSwitchChecked);
messagesForceEnableSmartActionsInNotificationsSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> modSetBooleanFlags(isChecked, MESSAGES_PHENOTYPE_PACKAGE_NAME, MESSAGES_ENABLE_SMART_ACTIONS_IN_NOTIFICATIONS_FLAGS));
messagesForceEnableSmartActionsInNotificationsSwitch.setEnabled(true);
// messagesForceEnableSuggestedStickersSwitch
ProgrammaticMaterialSwitchView messagesForceEnableSuggestedStickersSwitch = mBinding.messagesForceEnableSuggestedStickers.getSwitch();
boolean messagesForceEnableSuggestedStickersSwitchChecked = modCheckAreAllFlagsOverridden(MESSAGES_PHENOTYPE_PACKAGE_NAME, new ArrayList<>(MESSAGES_ENABLE_SUGGESTED_STICKERS_FLAGS.keySet()));
messagesForceEnableSuggestedStickersSwitch.setCheckedProgrammatically(messagesForceEnableSuggestedStickersSwitchChecked);
messagesForceEnableSuggestedStickersSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> modSetBooleanFlags(isChecked, MESSAGES_PHENOTYPE_PACKAGE_NAME, MESSAGES_ENABLE_SUGGESTED_STICKERS_FLAGS));
messagesForceEnableSuggestedStickersSwitch.setEnabled(true);
} catch (PackageManager.NameNotFoundException e) {
mBinding.messagesNotInstalledAlert.setVisibility(View.VISIBLE);
}
return mBinding.getRoot();
}
private boolean modCheckAreAllFlagsOverridden(String phenotypePackageName, List<String> flags) {
try {
return mCoreRootServiceIpc.phenotypeDBAreAllFlagsOverridden(phenotypePackageName, flags);
} catch (RemoteException e) {
e.printStackTrace();
return false;
}
}
private void modSetBooleanFlags(boolean enableMod, String phenotypePackageName, HashMap<String, Boolean> flags) {
if (enableMod) {
for (Map.Entry<String, Boolean> flag : flags.entrySet()) {
try {
mCoreRootServiceIpc.phenotypeDBOverrideBooleanFlag(phenotypePackageName, flag.getKey(), flag.getValue());
} catch (RemoteException e) {
e.printStackTrace();
}
}
} else {
modDeleteFlagOverrides(phenotypePackageName, new ArrayList<>(flags.keySet()));
}
}
private void modSetStringFlags(boolean enableMod, String phenotypePackageName, HashMap<String, String> flags) {
if (enableMod) {
for (Map.Entry<String, String> flag : flags.entrySet()) {
try {
mCoreRootServiceIpc.phenotypeDBOverrideStringFlag(phenotypePackageName, flag.getKey(), flag.getValue());
} catch (RemoteException e) {
e.printStackTrace();
}
}
} else {
modDeleteFlagOverrides(phenotypePackageName, new ArrayList<>(flags.keySet()));
}
}
private void modDeleteFlagOverrides(String phenotypePackageName, List<String> flags) {
try {
mCoreRootServiceIpc.phenotypeDBDeleteFlagOverrides(phenotypePackageName, flags);
} catch (RemoteException e) {
e.printStackTrace();
}
}
private void dialerSilenceCallRecordingAlerts(boolean enableMod) {
// Set flags (or delete overrides)
modSetStringFlags(enableMod, DIALER_PHENOTYPE_PACKAGE_NAME, DIALER_SILENCE_CALL_RECORDING_ALERTS_FLAGS);
// Apply additional mods
if (enableMod) {
try {
// Create CALLRECORDINGPROMPT folder
ExtendedFile callRecordingPromptDir = mCoreRootServiceFSManager.getFile(DIALER_CALLRECORDINGPROMPT);
if (callRecordingPromptDir.mkdir() || (callRecordingPromptDir.exists() && callRecordingPromptDir.isDirectory())) {
// Overwrite the two alert files with an empty audio
ExtendedFile startingVoice = mCoreRootServiceFSManager.getFile(callRecordingPromptDir, DIALER_CALLRECORDINGPROMPT_STARTING_VOICE_US);
ExtendedFile endingVoice = mCoreRootServiceFSManager.getFile(callRecordingPromptDir, DIALER_CALLRECORDINGPROMPT_ENDING_VOICE_US);
copyFile(getResources().openRawResource(R.raw.silent_wav), startingVoice.newOutputStream());
copyFile(getResources().openRawResource(R.raw.silent_wav), endingVoice.newOutputStream());
// Set the right permissions to files and folders
final int uid = requireActivity().getPackageManager().getApplicationInfo(DIALER_ANDROID_PACKAGE_NAME, 0).uid;
Shell.cmd(
String.format("chown -R %s:%s %s", uid, uid, DIALER_CALLRECORDINGPROMPT),
String.format("chmod 755 %s", DIALER_CALLRECORDINGPROMPT),
String.format("chmod 444 %s/*", DIALER_CALLRECORDINGPROMPT),
String.format("restorecon -R %s", DIALER_CALLRECORDINGPROMPT)
).exec();
}
} catch (PackageManager.NameNotFoundException | IOException e) {
e.printStackTrace();
}
} else {
// Delete callrecordingprompt folder
ExtendedFile callRecordingPromptFolder = mCoreRootServiceFSManager.getFile(DIALER_CALLRECORDINGPROMPT);
if (callRecordingPromptFolder.exists()) {
//noinspection ResultOfMethodCallIgnored
callRecordingPromptFolder.delete();
}
}
}
private void dialerForceEnableCallScreen(boolean enableMod) {
if (enableMod) {
// Ask the user what language the Call Screen feature should use
String[] supportedLanguages = {"en", "en-AU", "en-GB", "en-IN", "ja-JP", "fr-FR", "hi-IN", "de-DE", "it-IT", "es-ES"};
final int[] chosenLanguageIndex = {0};
new MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.choose_a_language_for_call_screen)
.setSingleChoiceItems(supportedLanguages, chosenLanguageIndex[0], (dialog, which) -> chosenLanguageIndex[0] = which)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
// Update boolean flags
modSetBooleanFlags(true, DIALER_PHENOTYPE_PACKAGE_NAME, DIALER_ENABLE_CALL_SCREEN_FLAGS);
// Override the call screen i18n config extension flag with the user desired language
TelephonyManager telephonyManager = (TelephonyManager) requireActivity().getSystemService(Context.TELEPHONY_SERVICE);
String simCountryIso = telephonyManager.getSimCountryIso();
String chosenLanguage = supportedLanguages[chosenLanguageIndex[0]];
Call_screen_i18n_config call_screen_i18n_config = Call_screen_i18n_config.newBuilder()
.addCountryConfigs(
Call_screen_i18n_config.CountryConfig.newBuilder()
.setCountry(simCountryIso)
.setLanguageConfig(
Call_screen_i18n_config.LanguageConfig.newBuilder()
.addLanguages(
Call_screen_i18n_config.Language.newBuilder()
.setLanguageCode(chosenLanguage)
.setA6(
Call_screen_i18n_config.A6.newBuilder()
.setA7(ByteString.copyFrom(new byte[]{2}))
)
)
)
).build();
try {
mCoreRootServiceIpc.phenotypeDBOverrideExtensionFlag(DIALER_PHENOTYPE_PACKAGE_NAME, DIALER_CALL_SCREEN_I18N_CONFIG_FLAG, call_screen_i18n_config.toByteArray());
} catch (RemoteException e) {
e.printStackTrace();
}
})
.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.cancel())
.setOnCancelListener(dialog -> mBinding.dialerForceEnableCallScreen.getSwitch().setCheckedProgrammatically(false))
.show();
} else {
// Delete flag overrides
modDeleteFlagOverrides(DIALER_PHENOTYPE_PACKAGE_NAME, new ArrayList<>(DIALER_ENABLE_CALL_SCREEN_FLAGS.keySet()));
modDeleteFlagOverrides(DIALER_PHENOTYPE_PACKAGE_NAME, Collections.singletonList(DIALER_CALL_SCREEN_I18N_CONFIG_FLAG));
}
}
}
================================================
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/view/FilterableSearchView.java
================================================
package com.jacopomii.gappsmod.ui.view;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import androidx.appcompat.view.CollapsibleActionView;
import androidx.appcompat.widget.SearchView;
import androidx.core.content.res.ResourcesCompat;
import com.jacopomii.gappsmod.R;
import com.jacopomii.gappsmod.databinding.FilterableSearchviewBinding;
/**
* A View, containing a {@link SearchView}, to which an additional View can optionally be connected
* as a container to contain components for filtering the search, via the
* {@link #setFilterContainer} method. When a filterContainer is set, a button appears next to the
* SearchView that allows the user to manually show / hide the filterContainer.
*/
// Here I use the deprecated CollapsibleActionView interface because otherwise the
// onActionViewExpanded and onActionViewCollapsed methods are never called, idk why
@SuppressWarnings("deprecation")
public class FilterableSearchView extends LinearLayout implements CollapsibleActionView {
FilterableSearchviewBinding mBinding;
private final Context mContext;
private View mFilterContainer;
private boolean mIsFilterContainerVisible;
private boolean mFilterContainerAutoExpand;
public FilterableSearchView(Context context) {
super(context);
mContext = context;
init();
}
public FilterableSearchView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}
private void init() {
mBinding = FilterableSearchviewBinding.inflate(LayoutInflater.from(mContext), this, true);
mBinding.collapseFilterContainerButton.setOnClickListener(v -> {
if (mFilterContainer != null) setFilterContainerVisibility(!mIsFilterContainerVisible);
});
}
/**
* Connects a filter container to the SearchView, which can be shown / hidden by the user
* using a special button.
*
* @param filterContainer the filter container to attach.
* @param filterContainerAutoExpand whether the filter container should open itself when the
* SearchView is expanded.
*/
public void setFilterContainer(View filterContainer, boolean filterContainerAutoExpand) {
mFilterContainer = filterContainer;
mFilterContainerAutoExpand = filterContainerAutoExpand;
mBinding.collapseFilterContainerButton.setVisibility(VISIBLE);
}
/**
* Sets the hint text to display in the query text field of the SearchView.
*
* @param hint the hint text to display or {@code null}.
*/
public void setQueryHint(CharSequence hint) {
mBinding.searchView.setQueryHint(hint);
}
/**
* Sets a listener for user actions within the SearchView.
*
* @param listener the listener object that receives callbacks when the user performs actions
* in the SearchView such as clicking on buttons or typing a query.
*/
public void setOnQueryTextListener(SearchView.OnQueryTextListener listener) {
mBinding.searchView.setOnQueryTextListener(listener);
}
@Override
public void onActionViewExpanded() {
if (mFilterContainer != null && mFilterContainerAutoExpand)
setFilterContainerVisibility(true);
mBinding.searchView.onActionViewExpanded();
}
@Override
public void onActionViewCollapsed() {
if (mFilterContainer != null) setFilterContainerVisibility(false);
mBinding.searchView.onActionViewCollapsed();
}
private void setFilterContainerVisibility(boolean visible) {
mIsFilterContainerVisible = visible;
int newFilterContainerVisibility;
int newCollapseFilterButtonDrawableID;
if (visible) {
newFilterContainerVisibility = View.VISIBLE;
newCollapseFilterButtonDrawableID = R.drawable.ic_arrow_up_24;
} else {
newFilterContainerVisibility = View.GONE;
newCollapseFilterButtonDrawableID = R.drawable.ic_arrow_down_24;
}
mFilterContainer.setVisibility(newFilterContainerVisibility);
Drawable newCollapseFilterButtonDrawable = ResourcesCompat.getDrawable(getResources(), newCollapseFilterButtonDrawableID, null);
mBinding.collapseFilterContainerButton.setImageDrawable(newCollapseFilterButtonDrawable);
}
}
================================================
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/view/ProgrammaticMaterialSwitchView.java
================================================
package com.jacopomii.gappsmod.ui.view;
import android.content.Context;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.materialswitch.MaterialSwitch;
/**
* A {@link MaterialSwitch} that allows to programmatically set the checked / unchecked state
* without triggering the onCheckedChangeListener.
*/
public class ProgrammaticMaterialSwitchView extends MaterialSwitch {
private OnCheckedChangeListener mOnCheckedChangeListener = null;
public ProgrammaticMaterialSwitchView(@NonNull Context context) {
super(context);
}
public ProgrammaticMaterialSwitchView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public ProgrammaticMaterialSwitchView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void setOnCheckedChangeListener(@Nullable OnCheckedChangeListener listener) {
mOnCheckedChangeListener = listener;
super.setOnCheckedChangeListener(listener);
}
/**
* Programmatically change the checked state of the switch without calling any
* onCheckedChangeListener. Please note that any previously set onCheckedChangeListener will be
* preserved, even if this method does not call it.
*
* @param checked {@code true} to check the switch, {@code false} to uncheck it.
*/
public void setCheckedProgrammatically(boolean checked) {
super.setOnCheckedChangeListener(null);
super.setChecked(checked);
super.setOnCheckedChangeListener(mOnCheckedChangeListener);
}
}
================================================
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/view/SuggestedModsAppHeaderView.java
================================================
package com.jacopomii.gappsmod.ui.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import com.google.android.material.button.MaterialButton;
import com.jacopomii.gappsmod.R;
import com.jacopomii.gappsmod.databinding.SuggestedModsAppHeaderBinding;
/**
* The application name header used by the "Suggested Mods" fragment.
* It includes a large title for the app name and two localized buttons "Beta" and "Install".
*/
public class SuggestedModsAppHeaderView extends LinearLayout {
final SuggestedModsAppHeaderBinding mBinding;
public SuggestedModsAppHeaderView(Context context) {
super(context);
mBinding = SuggestedModsAppHeaderBinding.inflate(LayoutInflater.from(context), this, true);
}
public SuggestedModsAppHeaderView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mBinding = SuggestedModsAppHeaderBinding.inflate(LayoutInflater.from(context), this, true);
final TypedArray xmlAttrs = context.obtainStyledAttributes(attrs, R.styleable.SuggestedModsAppHeaderView);
final String appName = xmlAttrs.getString(R.styleable.SuggestedModsAppHeaderView_app_name);
xmlAttrs.recycle();
mBinding.appName.setText(appName);
}
public MaterialButton getBetaButton() {
return mBinding.betaButton;
}
public MaterialButton getInstallButton() {
return mBinding.installButton;
}
}
================================================
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/view/SwitchCardView.java
================================================
package com.jacopomii.gappsmod.ui.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import com.jacopomii.gappsmod.R;
import com.jacopomii.gappsmod.databinding.SwitchCardBinding;
/**
* A card that contains a {@link android.widget.TextView} and a
* {@link ProgrammaticMaterialSwitchView} on a single line.
* The text will be rendered in a separate textview from the switch to prevent accidentally
* clicking on the text from triggering the switch.
*/
public class SwitchCardView extends LinearLayout {
final SwitchCardBinding mBinding;
public SwitchCardView(Context context) {
super(context);
mBinding = SwitchCardBinding.inflate(LayoutInflater.from(context), this, true);
}
public SwitchCardView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mBinding = SwitchCardBinding.inflate(LayoutInflater.from(context), this, true);
final TypedArray xmlAttrs = context.obtainStyledAttributes(attrs, R.styleable.SwitchCardView);
final String text = xmlAttrs.getString(R.styleable.SwitchCardView_text);
final boolean enabled = xmlAttrs.getBoolean(R.styleable.SwitchCardView_enabled, true);
xmlAttrs.recycle();
mBinding.switchCardTextview.setText(text);
mBinding.switchCardSwitch.setEnabled(enabled);
}
public ProgrammaticMaterialSwitchView getSwitch() {
return mBinding.switchCardSwitch;
}
}
================================================
FILE: app/src/main/java/com/jacopomii/gappsmod/util/OnItemClickListener.java
================================================
package com.jacopomii.gappsmod.util;
/**
* A generic interface to handle clicks on recyclerview rows.
*/
public interface OnItemClickListener {
void onItemClick(Object item);
}
================================================
FILE: app/src/main/java/com/jacopomii/gappsmod/util/Utils.java
================================================
package com.jacopomii.gappsmod.util;
import static com.jacopomii.gappsmod.data.Constants.VENDING_ANDROID_PACKAGE_NAME;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.CheckBox;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.RequestFuture;
import com.android.volley.toolbox.Volley;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.jacopomii.gappsmod.BuildConfig;
import com.jacopomii.gappsmod.ICoreRootService;
import com.jacopomii.gappsmod.R;
import com.jacopomii.gappsmod.data.Version;
import com.jacopomii.gappsmod.databinding.DialogSelectPackageBinding;
import com.jacopomii.gappsmod.ui.adapter.SelectPackageRecyclerViewAdapter;
import com.l4digital.fastscroll.FastScrollRecyclerView;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class Utils {
public static void copyFile(InputStream inputStream, OutputStream outputStream) throws IOException {
byte[] buffer = new byte[1024];
int read;
while ((read = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, read);
}
inputStream.close();
outputStream.flush();
outputStream.close();
}
public static void openGooglePlay(Context context, String googlePlayLink) {
try {
Intent appStoreIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(googlePlayLink));
appStoreIntent.setPackage(VENDING_ANDROID_PACKAGE_NAME);
context.startActivity(appStoreIntent);
} catch (ActivityNotFoundException exception) {
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(googlePlayLink)));
}
}
public static boolean checkUpdateAvailable(Context context) {
RequestQueue requestQueue = Volley.newRequestQueue(context);
RequestFuture<JSONObject> future = RequestFuture.newFuture();
requestQueue.add(
new JsonObjectRequest(
Request.Method.GET,
context.getString(R.string.github_api_link) + "/releases/latest",
null,
future,
future
)
);
try {
JSONObject response = future.get();
Version actualVersion = new Version(BuildConfig.VERSION_NAME);
Version fetchedVersion = new Version(response.getString("tag_name").substring(1));
if (actualVersion.compareTo(fetchedVersion) < 0)
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* This method generates strings used for IN queries.
* It creates string containing "?" characters repeated {@code size} times and separated by ",".
*
* @param size size of the items.
* @return IN query string of the form ?,?,?,?.
*/
public static String createInQueryString(int size) {
StringBuilder stringBuilder = new StringBuilder();
String separator = "";
for (int i = 0; i < size; i++) {
stringBuilder.append(separator);
stringBuilder.append("?");
separator = ",";
}
return stringBuilder.toString();
}
/**
* This method returns, given an {@code androidPackageName}, the label of the corresponding
* application, or a localized string "Unknown" if the application is not installed.
*
* @param context context.
* @param androidPackageName the Android package name of the application to get the label for.
* @return the application label if the application exists; The localized string
* {@link R.string#unknown} otherwise.
*/
public static String getApplicationLabelOrUnknown(Context context, String androidPackageName) {
String applicationLabel = context.getString(R.string.unknown);
try {
PackageManager packageManager = context.getPackageManager();
ApplicationInfo applicationInfo = packageManager.getApplicationInfo(androidPackageName, 0);
if (applicationInfo != null)
applicationLabel = (String) (packageManager.getApplicationLabel(applicationInfo));
} catch (PackageManager.NameNotFoundException ignored) {
}
return applicationLabel;
}
// Static variables for showSelectPackageDialog()
private static CharSequence lastPackageSearched = null;
private static Boolean lastPackageSearchedRemember = true;
/**
* Show the "Select Package" dialog, a custom view to select package names contained in the
* Phenotype DB with search and fastscroll features.
*
* @param context context.
* @param coreRootServiceIpc a {@code ICoreRootService} instance.
* @param onItemClickListener an implementation of the {@link OnItemClickListener} interface,
* to perform actions after the user has selected a package.
* The received item is a string containing the selected Phenotype
* (not Android) package name.
*/
public static void showSelectPackageDialog(Context context, ICoreRootService coreRootServiceIpc, OnItemClickListener onItemClickListener, DialogInterface.OnDismissListener onDismissListener) {
// Dialog builder
MaterialAlertDialogBuilder selectPackageDialogBuilder = new MaterialAlertDialogBuilder(context);
// Inflate dialog layout
DialogSelectPackageBinding dialogSelectPackageBinding = DialogSelectPackageBinding.inflate(LayoutInflater.from(context));
selectPackageDialogBuilder.setView(dialogSelectPackageBinding.getRoot());
// Create dialog
AlertDialog selectPackageDialog = selectPackageDialogBuilder.create();
// Set dialog custom height and width
selectPackageDialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
selectPackageDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
// Set dialog onDismissListener
selectPackageDialog.setOnDismissListener(onDismissListener);
// Dialog components
SearchView selectPackageSearchView = dialogSelectPackageBinding.searchview;
FastScrollRecyclerView selectPackageRecyclerView = dialogSelectPackageBinding.recyclerview;
CheckBox SelectPackageRememberCheckbox = dialogSelectPackageBinding.remembercheckbox;
// Initialize the dialog adapter
SelectPackageRecyclerViewAdapter selectPackageRecyclerViewAdapter = new SelectPackageRecyclerViewAdapter(context, coreRootServiceIpc, item -> {
// Pass the received item to the caller onItemClickListener
onItemClickListener.onItemClick(item);
// Dismiss dialog
selectPackageDialog.dismiss();
});
// Disable fast scroll if the selectPackageRecyclerView is empty or changes to empty
selectPackageRecyclerView.setFastScrollEnabled(selectPackageRecyclerViewAdapter.getItemCount() != 0);
selectPackageRecyclerViewAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
selectPackageRecyclerView.setFastScrollEnabled(selectPackageRecyclerViewAdapter.getItemCount() != 0);
}
});
// Set the dialog selectPackageRecyclerView LayoutManager and Adapter
selectPackageRecyclerView.setLayoutManager(new LinearLayoutManager(context));
selectPackageRecyclerView.setAdapter(selectPackageRecyclerViewAdapter);
// Add list dividers to the selectPackageRecyclerView
selectPackageRecyclerView.addItemDecoration(new DividerItemDecoration(selectPackageRecyclerView.getContext(), DividerItemDecoration.VERTICAL));
// Dialog filter
selectPackageSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
lastPackageSearched = newText;
selectPackageRecyclerViewAdapter.getFilter().filter(newText);
return false;
}
});
// Remember last package searched
SelectPackageRememberCheckbox.setChecked(lastPackageSearchedRemember);
SelectPackageRememberCheckbox.setOnCheckedChangeListener((buttonView, isChecked) -> {
lastPackageSearchedRemember = isChecked;
lastPackageSearched = selectPackageSearchView.getQuery();
});
if (lastPackageSearched != null && lastPackageSearchedRemember)
selectPackageSearchView.setQuery(lastPackageSearched, true);
// Show dialog
selectPackageDialog.show();
}
}
================================================
FILE: app/src/main/proto/call_screen_i18n.proto
================================================
syntax = "proto3";
package com.jacopomii.gappsmod.protos;
option java_multiple_files = true;
option java_package = "com.jacopomii.gappsmod.protos";
option java_outer_classname = "CallScreenI18nProtos";
/*
Reversed from Dialer. Hex 0a140a026974120e0a0c0a0569742d495412030a0102 corresponds to the following JSON.
{
"countryConfigs":[{
"country": "it",
"languageConfig": {
"languages":[{
"languageCode":"it-IT",
"a6":{
"a7":2
}
}]
}
}]
}
*/
message Call_screen_i18n_config {
message A6 {
bytes a7 = 1;
}
message Language {
string languageCode = 1;
A6 a6 = 2;
}
message LanguageConfig {
repeated Language languages = 1;
}
message CountryConfig {
string country = 1;
LanguageConfig languageConfig = 2;
}
repeated CountryConfig countryConfigs = 1;
}
================================================
FILE: app/src/main/res/drawable/ic_arrow_down_24.xml
================================================
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M7.41,8.59L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_arrow_up_24.xml
================================================
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_beta_24.xml
================================================
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19.8,18.4L14,10.67V6.5l1.35,-1.69C15.61,4.48 15.38,4 14.96,4H9.04C8.62,4 8.39,4.48 8.65,4.81L10,6.5v4.17L4.2,18.4C3.71,19.06 4.18,20 5,20h14C19.82,20 20.29,19.06 19.8,18.4z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_error_24.xml
================================================
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_fail_24.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:tint="#f44336" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="@android:color/white" android:pathData="M18.3,5.71L18.3,5.71c-0.39,-0.39 -1.02,-0.39 -1.41,0L12,10.59L7.11,5.7c-0.39,-0.39 -1.02,-0.39 -1.41,0l0,0c-0.39,0.39 -0.39,1.02 0,1.41L10.59,12L5.7,16.89c-0.39,0.39 -0.39,1.02 0,1.41l0,0c0.39,0.39 1.02,0.39 1.41,0L12,13.41l4.89,4.89c0.39,0.39 1.02,0.39 1.41,0l0,0c0.39,-0.39 0.39,-1.02 0,-1.41L13.41,12l4.89,-4.89C18.68,6.73 18.68,6.09 18.3,5.71z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_install_24.xml
================================================
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M5,20h14v-2H5V20zM19,9h-4V3H9v6H5l7,7L19,9z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_menu_search_24.xml
================================================
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_nav_drawer_boolean_mods_24.xml
================================================
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M17,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h10c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5zM17,15c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_nav_drawer_delete_24.xml
================================================
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_nav_drawer_information_24.xml
================================================
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_nav_drawer_suggested_mods_24.xml
================================================
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M11,21h-1l1,-7H7.5c-0.58,0 -0.57,-0.32 -0.38,-0.66 0.19,-0.34 0.05,-0.08 0.07,-0.12C8.48,10.94 10.42,7.54 13,3h1l-1,7h3.5c0.49,0 0.56,0.33 0.47,0.51l-0.07,0.15C12.96,17.55 11,21 11,21z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_save_24.xml
================================================
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_success_24.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/holo_green_dark"
android:pathData="M18,6.7l-8.48,8.48l-3.54,-3.54c-0.39,-0.39 -1.02,-0.39 -1.41,0l0,0c-0.39,0.39 -0.39,1.02 0,1.41l4.24,4.24c0.39,0.39 1.02,0.39 1.41,0l9.18,-9.18c0.39,-0.39 0.39,-1.03 -0.01,-1.42l0,0C19.02,6.31 18.39,6.31 18,6.7z" />
</vector>
================================================
FILE: app/src/main/res/layouts/activities/layout/activity_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".ui.activity.MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:liftOnScroll="false">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:navGraph="@navigation/mobile_navigation" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:drawerLayoutCornerSize="0dp"
app:headerLayout="@layout/nav_drawer_header"
app:menu="@menu/nav_drawer" />
</androidx.drawerlayout.widget.DrawerLayout>
================================================
FILE: app/src/main/res/layouts/activities/layout/activity_splash_screen.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:animationCache="true"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/l1">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/l2"
android:gravity="center"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="70dp"
android:importantForAccessibility="no"
app:srcCompat="@mipmap/ic_launcher" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/l1"
android:fontFamily="sans-serif-condensed-medium"
android:text="@string/app_name"
android:textSize="34sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="@dimen/l_50"
android:animateLayoutChanges="true"
android:orientation="horizontal">
<ImageView
android:id="@+id/done_root"
android:layout_width="32dp"
android:layout_height="32dp"
android:importantForAccessibility="no"
android:visibility="gone" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/circular_root"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
app:indicatorSize="24dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="@dimen/l_50"
android:text="@string/splash_screen_root"
android:textAppearance="?attr/textAppearanceBody1" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="@dimen/l_50"
android:animateLayoutChanges="true"
android:orientation="horizontal">
<ImageView
android:id="@+id/done_root_service"
android:layout_width="32dp"
android:layout_height="32dp"
android:importantForAccessibility="no"
android:visibility="gone" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/circular_root_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
app:indicatorSize="24dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="@dimen/l_50"
android:text="@string/splash_screen_root_service"
android:textAppearance="?attr/textAppearanceBody1" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="@dimen/l_50"
android:animateLayoutChanges="true"
android:orientation="horizontal">
<ImageView
android:id="@+id/done_phenotype_gms"
android:layout_width="32dp"
android:layout_height="32dp"
android:importantForAccessibility="no"
android:visibility="gone" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/circular_phenotype_gms"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
app:indicatorSize="24dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="@dimen/l_50"
android:text="@string/splash_screen_phenotype_gms"
android:textAppearance="?attr/textAppearanceBody1" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="@dimen/l_50"
android:animateLayoutChanges="true"
android:orientation="horizontal">
<ImageView
android:id="@+id/done_updates"
android:layout_width="32dp"
android:layout_height="32dp"
android:importantForAccessibility="no"
android:visibility="gone" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/circular_updates"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
app:indicatorSize="24dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="@dimen/l_50"
android:text="@string/splash_screen_updates"
android:textAppearance="?attr/textAppearanceBody1" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
================================================
FILE: app/src/main/res/layouts/dialogs/layout/dialog_select_package.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingHorizontal="@dimen/l_50"
android:paddingVertical="@dimen/l1">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/l_50"
android:text="@string/select_package"
android:textAlignment="viewStart"
android:textAppearance="?attr/textAppearanceTitleLarge" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<androidx.appcompat.widget.SearchView
android:id="@+id/searchview"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginVertical="@dimen/l_50"
android:layout_weight="1"
app:iconifiedByDefault="false"
app:queryHint="@string/search_by_package_or_app"
app:searchIcon="@null" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<CheckBox
android:id="@+id/remembercheckbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="0dp"
tools:ignore="TouchTargetSizeCheck" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/remember"
app:srcCompat="@drawable/ic_save_24"
app:tint="?attr/colorControlNormal" />
</LinearLayout>
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.l4digital.fastscroll.FastScrollRecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:bubbleColor="?attr/colorSecondary"
app:bubbleSize="small"
app:bubbleTextColor="?attr/colorOnSecondary"
app:bubbleTextSize="15sp"
app:handleColor="?attr/colorSecondary"
app:hideScrollbar="false"
app:trackColor="?attr/colorSecondary" />
</FrameLayout>
</LinearLayout>
================================================
FILE: app/src/main/res/layouts/fragments/layout/fragment_boolean_mods.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.fragment.BooleanModsFragment">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:liftOnScroll="false">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="?android:dividerHorizontal"
android:orientation="vertical"
android:showDividers="middle">
<LinearLayout
android:id="@+id/filter_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="@dimen/l1"
android:paddingVertical="@dimen/l_50"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/l_50"
android:text="@string/additional_filters" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.Dense.ExposedDropdownMenu"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/l_50"
android:layout_weight="1"
android:hint="@string/enabled_status_filter">
<AutoCompleteTextView
android:id="@+id/filter_enabled_status_spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:inputType="none"
android:singleLine="true"
tools:ignore="LabelFor,TextContrastCheck,VisualLintTextFieldSize" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.Dense.ExposedDropdownMenu"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/l_50"
android:layout_weight="1"
android:hint="@string/changed_status_filter">
<AutoCompleteTextView
android:id="@+id/filter_changed_status_spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:inputType="none"
android:singleLine="true"
tools:ignore="LabelFor,TextContrastCheck,VisualLintTextFieldSize" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="@dimen/l_50"
android:gravity="center_vertical"
android:paddingHorizontal="@dimen/l1">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/l_50"
android:text="@string/package_spinner_label" />
<TextView
android:id="@+id/select_package"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:hint="@string/select_package"
app:drawableEndCompat="@drawable/ic_arrow_down_24"
app:drawableTint="?attr/colorControlNormal"
tools:ignore="TextContrastCheck" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.l4digital.fastscroll.FastScrollRecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:fitsSystemWindows="true"
app:bubbleColor="?attr/colorSecondary"
app:bubbleTextColor="?attr/colorOnSecondary"
app:handleColor="?attr/colorSecondary"
app:hideScrollbar="false"
app:trackColor="?attr/colorSecondary" />
</FrameLayout>
</LinearLayout>
================================================
FILE: app/src/main/res/layouts/fragments/layout/fragment_information.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:fitsSystemWindows="true"
tools:context=".ui.fragment.SuggestedModsFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/margin_generic">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/l1"
android:text="@string/what_is_app_name"
android:textAlignment="viewStart"
android:textAppearance="?attr/textAppearanceTitleLarge" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:justificationMode="inter_word"
android:paddingBottom="@dimen/l1"
android:text="@string/what_is_it_explanation"
android:textAlignment="viewStart"
tools:ignore="VisualLintLongText"
tools:targetApi="o" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/l1"
android:text="@string/how_it_works"
android:textAlignment="viewStart"
android:textAppearance="?attr/textAppearanceTitleLarge" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:justificationMode="inter_word"
android:paddingBottom="@dimen/l1"
android:text="@string/how_it_works_explanation"
android:textAlignment="viewStart"
tools:ignore="VisualLintLongText"
tools:targetApi="o" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/l1"
android:text="@string/credits"
android:textAlignment="viewStart"
android:textAppearance="?attr/textAppearanceTitleLarge" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:justificationMode="inter_word"
android:paddingBottom="@dimen/l1"
android:text="@string/if_bug_found_open_github_issue"
android:textAlignment="viewStart"
tools:targetApi="o" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoLink="web"
android:paddingBottom="@dimen/l_75"
android:text="@string/github_link"
android:textAlignment="viewStart"
tools:ignore="TouchTargetSizeCheck" />
<TextView
android:id="@+id/made_with_love_by_jacopo_tediosi"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/made_with_love_by"
android:textAlignment="viewStart" />
</LinearLayout>
</ScrollView>
================================================
FILE: app/src/main/res/layouts/fragments/layout/fragment_revert_mods.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:fitsSystemWindows="true"
tools:context=".ui.fragment.RevertModsFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/margin_generic">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:justificationMode="inter_word"
android:text="@string/revert_mods_for_the_selected_package_explanation"
android:textAlignment="viewStart"
tools:targetApi="o" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="@dimen/l1_5"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/l_50"
android:text="@string/package_spinner_label" />
<TextView
android:id="@+id/select_package"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:hint="@string/select_package"
app:drawableEndCompat="@drawable/ic_arrow_down_24"
app:drawableTint="?attr/colorControlNormal"
tools:ignore="TextContrastCheck" />
</LinearLayout>
<Button
android:id="@+id/revert_mods_selected_package_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/l1_5"
android:backgroundTint="?attr/colorError"
android:enabled="false"
android:text="@string/revert_mods_for_the_selected_package"
android:textColor="?attr/colorOnError"
tools:ignore="VisualLintButtonSize" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="@dimen/l2"
android:text="@string/or"
android:textAlignment="center"
android:textAllCaps="true"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="@dimen/l1_5"
android:justificationMode="inter_word"
android:text="@string/revert_mods_for_all_packages_explanation"
android:textAlignment="viewStart"
tools:targetApi="o" />
<Button
android:id="@+id/revert_all_mods_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="?attr/colorError"
android:text="@string/revert_mods_for_all_packages"
android:textColor="?attr/colorOnError"
tools:ignore="VisualLintButtonSize" />
</LinearLayout>
</ScrollView>
================================================
FILE: app/src/main/res/layouts/fragments/layout/fragment_suggested_mods.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:fitsSystemWindows="true"
tools:context=".ui.fragment.SuggestedModsFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/margin_generic">
<com.jacopomii.gappsmod.ui.view.SuggestedModsAppHeaderView
android:id="@+id/dialer_app_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:app_name="@string/dialer_app_name" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/dialer_not_installed_alert"
style="?attr/materialCardViewElevatedStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/l_25"
android:layout_marginBottom="@dimen/l_50"
android:visibility="gone"
app:cardBackgroundColor="?attr/colorErrorContainer"
app:cardElevation="4dp"
app:contentPadding="@dimen/l1"
tools:visibility="visible">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:justificationMode="inter_word"
android:text="@string/app_not_installed_error"
android:textAlignment="viewStart"
android:textColor="?attr/colorOnErrorContainer"
tools:targetApi="o" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/dialer_permission_alert"
style="?attr/materialCardViewElevatedStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/l_25"
android:layout_marginBottom="@dimen/l_50"
android:visibility="gone"
app:cardBackgroundColor="?attr/colorErrorContainer"
app:cardElevation="4dp"
app:contentPadding="@dimen/l1"
tools:visibility="visible">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:justificationMode="inter_word"
android:text="@string/dialer_permission_alert"
android:textAlignment="viewStart"
android:textColor="?attr/colorOnErrorContainer"
tools:targetApi="o" />
</com.google.android.material.card.MaterialCardView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.jacopomii.gappsmod.ui.view.SwitchCardView
android:id="@+id/dialer_force_enable_call_recording"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:enabled="false"
app:text="@string/force_enable_call_recording" />
<com.jacopomii.gappsmod.ui.view.SwitchCardView
android:id="@+id/dialer_silence_call_recording_alerts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:enabled="false"
app:text="@string/silence_call_recording_alerts" />
<com.jacopomii.gappsmod.ui.view.SwitchCardView
android:id="@+id/dialer_force_enable_call_screen"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:enabled="false"
app:text="@string/force_enable_call_screen" />
</LinearLayout>
<com.jacopomii.gappsmod.ui.view.SuggestedModsAppHeaderView
android:id="@+id/messages_app_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/l1"
app:app_name="@string/messages_app_name" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/messages_not_installed_alert"
style="?attr/materialCardViewElevatedStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/l_25"
android:layout_marginBottom="@dimen/l_50"
android:visibility="gone"
app:cardBackgroundColor="?attr/colorErrorContainer"
app:cardElevation="4dp"
app:contentPadding="@dimen/l1"
tools:visibility="visible">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:justificationMode="inter_word"
android:text="@string/app_not_installed_error"
android:textAlignment="viewStart"
android:textColor="?attr/colorOnErrorContainer"
tools:targetApi="o" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.jacopomii
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
SYMBOL INDEX (139 symbols across 20 files)
FILE: app/src/main/java/com/jacopomii/gappsmod/application/GAppsModApplication.java
class GAppsModApplication (line 7) | public class GAppsModApplication extends Application {
method onCreate (line 8) | @Override
FILE: app/src/main/java/com/jacopomii/gappsmod/data/BooleanFlag.java
class BooleanFlag (line 3) | public class BooleanFlag {
method BooleanFlag (line 8) | public BooleanFlag(String flagName, boolean flagValue, boolean flagOve...
method getFlagName (line 14) | public String getFlagName() {
method getFlagValue (line 18) | public boolean getFlagValue() {
method setFlagValue (line 22) | public void setFlagValue(boolean flagValue) {
method setFlagOverriddenAndChanged (line 26) | public void setFlagOverriddenAndChanged(boolean flagOverriddenAndChang...
method getFlagOverriddenAndChanged (line 30) | public boolean getFlagOverriddenAndChanged() {
FILE: app/src/main/java/com/jacopomii/gappsmod/data/Constants.java
type Constants (line 5) | @SuppressLint("SdCardPath")
FILE: app/src/main/java/com/jacopomii/gappsmod/data/PhenotypeDBPackageName.java
class PhenotypeDBPackageName (line 3) | public class PhenotypeDBPackageName {
method PhenotypeDBPackageName (line 7) | public PhenotypeDBPackageName(String phenotypePackageName, String andr...
method getPhenotypePackageName (line 12) | public String getPhenotypePackageName() {
method getAndroidPackageName (line 16) | public String getAndroidPackageName() {
FILE: app/src/main/java/com/jacopomii/gappsmod/data/Version.java
class Version (line 3) | public class Version implements Comparable<Version> {
method Version (line 6) | public Version(String version) {
method getVersion (line 14) | public final String getVersion() {
method compareTo (line 18) | @Override
FILE: app/src/main/java/com/jacopomii/gappsmod/service/CoreRootService.java
class CoreRootService (line 37) | @SuppressWarnings("SameParameterValue")
method onBind (line 50) | @Override
method onUnbind (line 67) | @Override
method onDestroy (line 75) | @Override
class CoreRootServiceIPC (line 82) | private class CoreRootServiceIPC extends ICoreRootService.Stub {
method getFileSystemService (line 83) | @Override
method phenotypeDBGetAllPackageNames (line 88) | @Override
method phenotypeDBGetAllOverriddenPackageNames (line 93) | @Override
method phenotypeDBGetAndroidPackageNameByPhenotypePackageName (line 98) | @Override
method phenotypeDBGetBooleanFlagsOrOverridden (line 103) | @Override
method phenotypeDBAreAllFlagsOverridden (line 108) | @Override
method phenotypeDBDeleteAllFlagOverrides (line 113) | @Override
method phenotypeDBDeleteAllFlagOverridesByPhenotypePackageName (line 118) | @Override
method phenotypeDBDeleteFlagOverrides (line 123) | @Override
method phenotypeDBOverrideBooleanFlag (line 128) | @Override
method phenotypeDBOverrideExtensionFlag (line 133) | @Override
method phenotypeDBOverrideStringFlag (line 138) | @Override
method phenotypeDBGetAllPackageNames (line 145) | private Map<String, String> phenotypeDBGetAllPackageNames() {
method phenotypeDBGetAllOverriddenPackageNames (line 175) | private Map<String, String> phenotypeDBGetAllOverriddenPackageNames() {
method phenotypeDBGetAndroidPackageNameByPhenotypePackageName (line 205) | private String phenotypeDBGetAndroidPackageNameByPhenotypePackageName(...
method phenotypeDBGetBooleanFlagsOrOverridden (line 221) | private Map<String, List<Object>> phenotypeDBGetBooleanFlagsOrOverridd...
method phenotypeDBAreAllFlagsOverridden (line 252) | private boolean phenotypeDBAreAllFlagsOverridden(String phenotypePacka...
method phenotypeDBDeleteAllFlagOverrides (line 272) | private void phenotypeDBDeleteAllFlagOverrides(boolean deletePackagePh...
method phenotypeDBDeleteAllFlagOverridesByPhenotypePackageName (line 294) | private void phenotypeDBDeleteAllFlagOverridesByPhenotypePackageName(S...
method phenotypeDBDeleteFlagOverrides (line 307) | private void phenotypeDBDeleteFlagOverrides(String phenotypePackageNam...
method phenotypeDBOverrideBooleanFlag (line 324) | private void phenotypeDBOverrideBooleanFlag(String phenotypePackageNam...
method phenotypeDBOverrideExtensionFlag (line 352) | private void phenotypeDBOverrideExtensionFlag(String phenotypePackageN...
method phenotypeDBOverrideStringFlag (line 380) | private void phenotypeDBOverrideStringFlag(String phenotypePackageName...
method getPhenotypeDBByPhenotypePackageName (line 416) | private SQLiteDatabase getPhenotypeDBByPhenotypePackageName(String phe...
method killPackageAndDeletePhenotypeCache (line 431) | private void killPackageAndDeletePhenotypeCache(String phenotypePackag...
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/activity/MainActivity.java
class MainActivity (line 29) | public class MainActivity extends AppCompatActivity {
method onCreate (line 38) | @Override
method getCoreRootServiceFSManager (line 109) | public FileSystemManager getCoreRootServiceFSManager() {
method getCoreRootServiceIpc (line 113) | public ICoreRootService getCoreRootServiceIpc() {
method onSupportNavigateUp (line 117) | @Override
method onBackPressed (line 124) | @Override
method onDestroy (line 132) | @Override
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/activity/SplashScreenActivity.java
class SplashScreenActivity (line 35) | @SuppressLint("CustomSplashScreen")
method onCreate (line 54) | @Override
method checkRoot (line 192) | private boolean checkRoot() {
method checkGMSPhenotypeDB (line 196) | private boolean checkGMSPhenotypeDB() {
method setCheckUIDone (line 200) | private void setCheckUIDone(int circularID, int doneImageID, boolean s...
method onDestroy (line 210) | @Override
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/adapter/BooleanModsRecyclerViewAdapter.java
class BooleanModsRecyclerViewAdapter (line 33) | @SuppressWarnings("unchecked")
method BooleanModsRecyclerViewAdapter (line 44) | public BooleanModsRecyclerViewAdapter(Context context, ICoreRootServic...
method selectPhenotypePackageName (line 49) | @SuppressLint("NotifyDataSetChanged")
method onCreateViewHolder (line 75) | @NonNull
method onBindViewHolder (line 103) | @Override
method getItemCount (line 123) | @Override
method getFilter (line 128) | @Override
method getSectionText (line 172) | @Override
class ViewHolder (line 177) | public static class ViewHolder extends RecyclerView.ViewHolder {
method ViewHolder (line 181) | public ViewHolder(SwitchCardBinding binding) {
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/adapter/SelectPackageRecyclerViewAdapter.java
class SelectPackageRecyclerViewAdapter (line 38) | @SuppressWarnings("unchecked")
method SelectPackageRecyclerViewAdapter (line 49) | @SuppressLint("NotifyDataSetChanged")
method onCreateViewHolder (line 69) | @NonNull
method onBindViewHolder (line 86) | @Override
method getItemCount (line 113) | @Override
method getFilter (line 118) | @Override
method getSectionText (line 149) | @Override
class ViewHolder (line 176) | public static class ViewHolder extends RecyclerView.ViewHolder {
method ViewHolder (line 182) | public ViewHolder(PackageRowBinding binding) {
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/fragment/BooleanModsFragment.java
class BooleanModsFragment (line 39) | public class BooleanModsFragment extends Fragment {
method BooleanModsFragment (line 52) | public BooleanModsFragment() {
method onCreate (line 55) | @Override
method onCreateView (line 66) | @Override
method setupMenu (line 155) | private void setupMenu() {
method resetFlagsFilters (line 230) | private void resetFlagsFilters() {
method applyFlagsFilters (line 240) | private void applyFlagsFilters() {
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/fragment/InformationFragment.java
class InformationFragment (line 14) | public class InformationFragment extends Fragment {
method InformationFragment (line 17) | public InformationFragment() {}
method onCreate (line 19) | @Override
method onCreateView (line 24) | @Override
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/fragment/RevertModsFragment.java
class RevertModsFragment (line 31) | public class RevertModsFragment extends Fragment {
method RevertModsFragment (line 37) | public RevertModsFragment() {
method onCreate (line 40) | @Override
method onCreateView (line 53) | @Override
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/fragment/SuggestedModsFragment.java
class SuggestedModsFragment (line 49) | @SuppressWarnings("FieldCanBeLocal")
method SuggestedModsFragment (line 219) | public SuggestedModsFragment() {
method onCreate (line 222) | @Override
method onCreateView (line 235) | @Override
method modCheckAreAllFlagsOverridden (line 407) | private boolean modCheckAreAllFlagsOverridden(String phenotypePackageN...
method modSetBooleanFlags (line 416) | private void modSetBooleanFlags(boolean enableMod, String phenotypePac...
method modSetStringFlags (line 430) | private void modSetStringFlags(boolean enableMod, String phenotypePack...
method modDeleteFlagOverrides (line 444) | private void modDeleteFlagOverrides(String phenotypePackageName, List<...
method dialerSilenceCallRecordingAlerts (line 452) | private void dialerSilenceCallRecordingAlerts(boolean enableMod) {
method dialerForceEnableCallScreen (line 490) | private void dialerForceEnableCallScreen(boolean enableMod) {
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/view/FilterableSearchView.java
class FilterableSearchView (line 25) | @SuppressWarnings("deprecation")
method FilterableSearchView (line 34) | public FilterableSearchView(Context context) {
method FilterableSearchView (line 40) | public FilterableSearchView(Context context, AttributeSet attrs) {
method init (line 46) | private void init() {
method setFilterContainer (line 61) | public void setFilterContainer(View filterContainer, boolean filterCon...
method setQueryHint (line 72) | public void setQueryHint(CharSequence hint) {
method setOnQueryTextListener (line 82) | public void setOnQueryTextListener(SearchView.OnQueryTextListener list...
method onActionViewExpanded (line 86) | @Override
method onActionViewCollapsed (line 93) | @Override
method setFilterContainerVisibility (line 99) | private void setFilterContainerVisibility(boolean visible) {
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/view/ProgrammaticMaterialSwitchView.java
class ProgrammaticMaterialSwitchView (line 15) | public class ProgrammaticMaterialSwitchView extends MaterialSwitch {
method ProgrammaticMaterialSwitchView (line 18) | public ProgrammaticMaterialSwitchView(@NonNull Context context) {
method ProgrammaticMaterialSwitchView (line 22) | public ProgrammaticMaterialSwitchView(@NonNull Context context, @Nulla...
method ProgrammaticMaterialSwitchView (line 26) | public ProgrammaticMaterialSwitchView(@NonNull Context context, @Nulla...
method setOnCheckedChangeListener (line 30) | @Override
method setCheckedProgrammatically (line 43) | public void setCheckedProgrammatically(boolean checked) {
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/view/SuggestedModsAppHeaderView.java
class SuggestedModsAppHeaderView (line 19) | public class SuggestedModsAppHeaderView extends LinearLayout {
method SuggestedModsAppHeaderView (line 22) | public SuggestedModsAppHeaderView(Context context) {
method SuggestedModsAppHeaderView (line 28) | public SuggestedModsAppHeaderView(Context context, @Nullable Attribute...
method getBetaButton (line 40) | public MaterialButton getBetaButton() {
method getInstallButton (line 44) | public MaterialButton getInstallButton() {
FILE: app/src/main/java/com/jacopomii/gappsmod/ui/view/SwitchCardView.java
class SwitchCardView (line 20) | public class SwitchCardView extends LinearLayout {
method SwitchCardView (line 23) | public SwitchCardView(Context context) {
method SwitchCardView (line 29) | public SwitchCardView(Context context, @Nullable AttributeSet attrs) {
method getSwitch (line 43) | public ProgrammaticMaterialSwitchView getSwitch() {
FILE: app/src/main/java/com/jacopomii/gappsmod/util/OnItemClickListener.java
type OnItemClickListener (line 6) | public interface OnItemClickListener {
method onItemClick (line 7) | void onItemClick(Object item);
FILE: app/src/main/java/com/jacopomii/gappsmod/util/Utils.java
class Utils (line 43) | public class Utils {
method copyFile (line 44) | public static void copyFile(InputStream inputStream, OutputStream outp...
method openGooglePlay (line 55) | public static void openGooglePlay(Context context, String googlePlayLi...
method checkUpdateAvailable (line 65) | public static boolean checkUpdateAvailable(Context context) {
method createInQueryString (line 99) | public static String createInQueryString(int size) {
method getApplicationLabelOrUnknown (line 119) | public static String getApplicationLabelOrUnknown(Context context, Str...
method showSelectPackageDialog (line 148) | public static void showSelectPackageDialog(Context context, ICoreRootS...
Condensed preview — 82 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (273K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 62,
"preview": "github: jacopotediosi\ncustom: https://paypal.me/jacopotediosi\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug-report.md",
"chars": 1775,
"preview": "---\nname: Bug Report\nabout: Report something that isn't working\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n## Overview\n[N"
},
{
"path": ".github/ISSUE_TEMPLATE/feature-request.md",
"chars": 600,
"preview": "---\nname: Feature Request\nabout: Suggest an idea for a new feature you want\ntitle: ''\nlabels: enhancement\nassignees: ''\n"
},
{
"path": ".github/dependabot.yml",
"chars": 434,
"preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
},
{
"path": ".github/workflows/push.yml",
"chars": 2067,
"preview": "name: Assemble on push\n\non:\n push:\n branches: [ main ]\n paths-ignore:\n - '.github/**'\n - '**.md'\n work"
},
{
"path": ".gitignore",
"chars": 54,
"preview": "*.iml\n.gradle\n.idea\n.DS_Store\n/build\nlocal.properties\n"
},
{
"path": "README.md",
"chars": 5413,
"preview": "# Deprecation notice\n## !!! This project is no longer maintained !!!\n\nI had started researching how Phenotype DB works a"
},
{
"path": "app/.gitignore",
"chars": 6,
"preview": "/build"
},
{
"path": "app/build.gradle",
"chars": 3741,
"preview": "plugins {\n id \"com.android.application\"\n id \"com.google.protobuf\"\n id \"com.likethesalad.stem\"\n}\n\ndef keyfile\nde"
},
{
"path": "app/lint.xml",
"chars": 218,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n <issue id=\"UnsafeNativeCodeLocation\">\n <ignore path=\"src/main/r"
},
{
"path": "app/proguard-rules.pro",
"chars": 971,
"preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
},
{
"path": "app/src/main/AndroidManifest.xml",
"chars": 1545,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:to"
},
{
"path": "app/src/main/aidl/com/jacopomii/gappsmod/ICoreRootService.aidl",
"chars": 6633,
"preview": "package com.jacopomii.gappsmod;\n\ninterface ICoreRootService {\n IBinder getFileSystemService();\n\n /**\n * Query t"
},
{
"path": "app/src/main/java/com/jacopomii/gappsmod/application/GAppsModApplication.java",
"chars": 324,
"preview": "package com.jacopomii.gappsmod.application;\n\nimport android.app.Application;\n\nimport com.google.android.material.color.D"
},
{
"path": "app/src/main/java/com/jacopomii/gappsmod/data/BooleanFlag.java",
"chars": 883,
"preview": "package com.jacopomii.gappsmod.data;\n\npublic class BooleanFlag {\n private final String mFlagName;\n private boolean"
},
{
"path": "app/src/main/java/com/jacopomii/gappsmod/data/Constants.java",
"chars": 1262,
"preview": "package com.jacopomii.gappsmod.data;\n\nimport android.annotation.SuppressLint;\n\n@SuppressLint(\"SdCardPath\")\npublic interf"
},
{
"path": "app/src/main/java/com/jacopomii/gappsmod/data/PhenotypeDBPackageName.java",
"chars": 553,
"preview": "package com.jacopomii.gappsmod.data;\n\npublic class PhenotypeDBPackageName {\n private final String mPhenotypePackageNa"
},
{
"path": "app/src/main/java/com/jacopomii/gappsmod/data/Version.java",
"chars": 1203,
"preview": "package com.jacopomii.gappsmod.data;\n\npublic class Version implements Comparable<Version> {\n private final String mVe"
},
{
"path": "app/src/main/java/com/jacopomii/gappsmod/service/CoreRootService.java",
"chars": 19689,
"preview": "package com.jacopomii.gappsmod.service;\n\nimport static com.jacopomii.gappsmod.data.Constants.DATA_DATA_PREFIX;\nimport st"
},
{
"path": "app/src/main/java/com/jacopomii/gappsmod/ui/activity/MainActivity.java",
"chars": 6223,
"preview": "package com.jacopomii.gappsmod.ui.activity;\n\nimport android.content.ComponentName;\nimport android.content.Intent;\nimport"
},
{
"path": "app/src/main/java/com/jacopomii/gappsmod/ui/activity/SplashScreenActivity.java",
"chars": 9510,
"preview": "package com.jacopomii.gappsmod.ui.activity;\n\nimport static com.jacopomii.gappsmod.data.Constants.GMS_ANDROID_PACKAGE_NAM"
},
{
"path": "app/src/main/java/com/jacopomii/gappsmod/ui/adapter/BooleanModsRecyclerViewAdapter.java",
"chars": 7913,
"preview": "package com.jacopomii.gappsmod.ui.adapter;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimpo"
},
{
"path": "app/src/main/java/com/jacopomii/gappsmod/ui/adapter/SelectPackageRecyclerViewAdapter.java",
"chars": 7719,
"preview": "package com.jacopomii.gappsmod.ui.adapter;\n\nimport static com.jacopomii.gappsmod.util.Utils.getApplicationLabelOrUnknown"
},
{
"path": "app/src/main/java/com/jacopomii/gappsmod/ui/fragment/BooleanModsFragment.java",
"chars": 10937,
"preview": "package com.jacopomii.gappsmod.ui.fragment;\n\nimport static com.jacopomii.gappsmod.util.Utils.showSelectPackageDialog;\n\ni"
},
{
"path": "app/src/main/java/com/jacopomii/gappsmod/ui/fragment/InformationFragment.java",
"chars": 999,
"preview": "package com.jacopomii.gappsmod.ui.fragment;\n\nimport android.os.Bundle;\nimport android.text.method.LinkMovementMethod;\nim"
},
{
"path": "app/src/main/java/com/jacopomii/gappsmod/ui/fragment/RevertModsFragment.java",
"chars": 7534,
"preview": "package com.jacopomii.gappsmod.ui.fragment;\n\nimport static com.jacopomii.gappsmod.data.Constants.DIALER_CALLRECORDINGPRO"
},
{
"path": "app/src/main/java/com/jacopomii/gappsmod/ui/fragment/SuggestedModsFragment.java",
"chars": 36986,
"preview": "package com.jacopomii.gappsmod.ui.fragment;\n\nimport static android.Manifest.permission.CAPTURE_AUDIO_OUTPUT;\nimport stat"
},
{
"path": "app/src/main/java/com/jacopomii/gappsmod/ui/view/FilterableSearchView.java",
"chars": 4564,
"preview": "package com.jacopomii.gappsmod.ui.view;\n\nimport android.content.Context;\nimport android.graphics.drawable.Drawable;\nimpo"
},
{
"path": "app/src/main/java/com/jacopomii/gappsmod/ui/view/ProgrammaticMaterialSwitchView.java",
"chars": 1725,
"preview": "package com.jacopomii.gappsmod.ui.view;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\n\nimport andro"
},
{
"path": "app/src/main/java/com/jacopomii/gappsmod/ui/view/SuggestedModsAppHeaderView.java",
"chars": 1592,
"preview": "package com.jacopomii.gappsmod.ui.view;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport a"
},
{
"path": "app/src/main/java/com/jacopomii/gappsmod/ui/view/SwitchCardView.java",
"chars": 1606,
"preview": "package com.jacopomii.gappsmod.ui.view;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport a"
},
{
"path": "app/src/main/java/com/jacopomii/gappsmod/util/OnItemClickListener.java",
"chars": 183,
"preview": "package com.jacopomii.gappsmod.util;\n\n/**\n * A generic interface to handle clicks on recyclerview rows.\n */\npublic inter"
},
{
"path": "app/src/main/java/com/jacopomii/gappsmod/util/Utils.java",
"chars": 9795,
"preview": "package com.jacopomii.gappsmod.util;\n\nimport static com.jacopomii.gappsmod.data.Constants.VENDING_ANDROID_PACKAGE_NAME;\n"
},
{
"path": "app/src/main/proto/call_screen_i18n.proto",
"chars": 929,
"preview": "syntax = \"proto3\";\r\n\r\npackage com.jacopomii.gappsmod.protos;\r\n\r\noption java_multiple_files = true;\r\noption java_package "
},
{
"path": "app/src/main/res/drawable/ic_arrow_down_24.xml",
"chars": 339,
"preview": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n andr"
},
{
"path": "app/src/main/res/drawable/ic_arrow_up_24.xml",
"chars": 328,
"preview": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n andr"
},
{
"path": "app/src/main/res/drawable/ic_beta_24.xml",
"chars": 453,
"preview": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n andr"
},
{
"path": "app/src/main/res/drawable/ic_error_24.xml",
"chars": 384,
"preview": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n andr"
},
{
"path": "app/src/main/res/drawable/ic_fail_24.xml",
"chars": 658,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:tint=\""
},
{
"path": "app/src/main/res/drawable/ic_install_24.xml",
"chars": 323,
"preview": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n andr"
},
{
"path": "app/src/main/res/drawable/ic_menu_search_24.xml",
"chars": 528,
"preview": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n andr"
},
{
"path": "app/src/main/res/drawable/ic_nav_drawer_boolean_mods_24.xml",
"chars": 427,
"preview": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n andr"
},
{
"path": "app/src/main/res/drawable/ic_nav_drawer_delete_24.xml",
"chars": 363,
"preview": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n andr"
},
{
"path": "app/src/main/res/drawable/ic_nav_drawer_information_24.xml",
"chars": 383,
"preview": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n andr"
},
{
"path": "app/src/main/res/drawable/ic_nav_drawer_suggested_mods_24.xml",
"chars": 464,
"preview": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n andr"
},
{
"path": "app/src/main/res/drawable/ic_save_24.xml",
"chars": 448,
"preview": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n andr"
},
{
"path": "app/src/main/res/drawable/ic_success_24.xml",
"chars": 542,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:wi"
},
{
"path": "app/src/main/res/layouts/activities/layout/activity_main.xml",
"chars": 2093,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.drawerlayout.widget.DrawerLayout xmlns:android=\"http://schemas.android."
},
{
"path": "app/src/main/res/layouts/activities/layout/activity_splash_screen.xml",
"chars": 6493,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layouts/dialogs/layout/dialog_select_package.xml",
"chars": 2740,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layouts/fragments/layout/fragment_boolean_mods.xml",
"chars": 5816,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layouts/fragments/layout/fragment_information.xml",
"chars": 3361,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:"
},
{
"path": "app/src/main/res/layouts/fragments/layout/fragment_revert_mods.xml",
"chars": 3534,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:"
},
{
"path": "app/src/main/res/layouts/fragments/layout/fragment_suggested_mods.xml",
"chars": 9830,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:"
},
{
"path": "app/src/main/res/layouts/items/layout/filterable_searchview.xml",
"chars": 1106,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layouts/items/layout/nav_drawer_header.xml",
"chars": 981,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layouts/items/layout/package_row.xml",
"chars": 1351,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layouts/items/layout/suggested_mods_app_header.xml",
"chars": 1884,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layouts/items/layout/switch_card.xml",
"chars": 1588,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas."
},
{
"path": "app/src/main/res/menu/nav_drawer.xml",
"chars": 1150,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tools="
},
{
"path": "app/src/main/res/menu/search_menu.xml",
"chars": 497,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app=\"h"
},
{
"path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
"chars": 325,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "app/src/main/res/navigation/mobile_navigation.xml",
"chars": 1271,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<navigation xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:"
},
{
"path": "app/src/main/res/values/attrs.xml",
"chars": 751,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <declare-styleable name=\"SwitchCardView\">\n <!-- The text a"
},
{
"path": "app/src/main/res/values/colors.xml",
"chars": 3347,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <!-- Light theme colors -->\n <color name=\"md_theme_light_prima"
},
{
"path": "app/src/main/res/values/dimens.xml",
"chars": 420,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <dimen name=\"margin_generic\">16dp</dimen>\n\n <dimen name=\"l_125"
},
{
"path": "app/src/main/res/values/strings.xml",
"chars": 7457,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n <!-- App name -->\n <string name=\"app_name\" translatabl"
},
{
"path": "app/src/main/res/values/themes.xml",
"chars": 2413,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <!-- Application light theme -->\n <style name=\"AppTheme\" paren"
},
{
"path": "app/src/main/res/values-es/strings.xml",
"chars": 7536,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n <!-- GApp names, taken from https://play.google.com/store"
},
{
"path": "app/src/main/res/values-fr/strings.xml",
"chars": 7929,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n <!-- GApp names, taken from https://play.google.com/store"
},
{
"path": "app/src/main/res/values-it/strings.xml",
"chars": 7534,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n <!-- GApp names, taken from https://play.google.com/store"
},
{
"path": "app/src/main/res/values-night/themes.xml",
"chars": 2627,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <!-- Application dark theme -->\n <style name=\"AppTheme\" parent"
},
{
"path": "app/src/main/res/values-notnight-v23/themes.xml",
"chars": 479,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <!-- Override application light theme on API 23+ (because on lowe"
},
{
"path": "app/src/main/res/values-notnight-v27/themes.xml",
"chars": 709,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <!-- Override application light theme on API 27+ (because on lowe"
},
{
"path": "build.gradle",
"chars": 590,
"preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nbuildscript {\n re"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 233,
"preview": "#Wed Apr 26 10:10:02 CEST 2023\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\:/"
},
{
"path": "gradle.properties",
"chars": 1003,
"preview": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will ov"
},
{
"path": "gradlew",
"chars": 5766,
"preview": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0"
},
{
"path": "gradlew.bat",
"chars": 2674,
"preview": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "settings.gradle",
"chars": 253,
"preview": "dependencyResolutionManagement {\n repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n repositories {\n "
}
]
// ... and 2 more files (download for full content)
About this extraction
This page contains the full source code of the jacopotediosi/GoogleDialerMod GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 82 files (251.0 KB), approximately 59.4k tokens, and a symbol index with 139 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.