feat(GmsCore support): Reduce amount of necessary changes and add update check (#6582)

This commit is contained in:
oSumAtrIX 2026-02-15 19:37:38 +01:00 committed by GitHub
parent 6f70167369
commit 650e6a2710
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 571 additions and 696 deletions

View file

@ -1,8 +1,5 @@
package app.revanced.extension.shared;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.requests.Route.Method.GET;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
@ -17,46 +14,53 @@ import android.os.PowerManager;
import android.provider.Settings;
import android.util.Pair;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.requests.Requester;
import app.revanced.extension.shared.requests.Route;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.ui.CustomDialog;
import org.json.JSONObject;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Locale;
import app.revanced.extension.shared.requests.Requester;
import app.revanced.extension.shared.requests.Route;
import app.revanced.extension.shared.ui.CustomDialog;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.requests.Route.Method.GET;
@SuppressWarnings("unused")
public class GmsCoreSupport {
private static final String GMS_CORE_PACKAGE_NAME
= getGmsCoreVendorGroupId() + ".android.gms";
private static final Uri GMS_CORE_PROVIDER
= Uri.parse("content://" + getGmsCoreVendorGroupId() + ".android.gsf.gservices/prefix");
private static final String DONT_KILL_MY_APP_URL
= "https://dontkillmyapp.com/";
private static final Route DONT_KILL_MY_APP_MANUFACTURER_API
= new Route(GET, "/api/v2/{manufacturer}.json");
private static final String DONT_KILL_MY_APP_NAME_PARAMETER
= "?app=MicroG";
private static final String BUILD_MANUFACTURER
= Build.MANUFACTURER.toLowerCase(Locale.ROOT).replace(" ", "-");
private static GmsCore gmsCore = GmsCore.UNKNOWN;
/**
* If a manufacturer specific page exists on DontKillMyApp.
*/
@Nullable
private static volatile Boolean DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED;
private static String getOriginalPackageName() {
return null; // Modified during patching.
static {
for (GmsCore core : GmsCore.values()) {
if (core.getGroupId().equals(getGmsCoreVendorGroupId())) {
GmsCoreSupport.gmsCore = core;
break;
}
}
}
/**
* Injection point.
*/
public static void checkGmsCore(Activity context) {
gmsCore.check(context);
}
private static String getOriginalPackageName() {
return null; // Modified during patching.
}
private static String getGmsCoreVendorGroupId() {
return "app.revanced"; // Modified during patching.
}
/**
* @return If the current package name is the same as the original unpatched app.
* If `GmsCore support` was not included during patching, this returns true;
* If `GmsCore support` was not included during patching, this returns true;
*/
public static boolean isPackageNameOriginal() {
String originalPackageName = getOriginalPackageName();
@ -64,203 +68,336 @@ public class GmsCoreSupport {
|| originalPackageName.equals(Utils.getContext().getPackageName());
}
private static void open(String queryOrLink) {
Logger.printInfo(() -> "Opening link: " + queryOrLink);
Intent intent;
try {
// Check if queryOrLink is a valid URL.
new URL(queryOrLink);
intent = new Intent(Intent.ACTION_VIEW, Uri.parse(queryOrLink));
} catch (MalformedURLException e) {
intent = new Intent(Intent.ACTION_WEB_SEARCH);
intent.putExtra(SearchManager.QUERY, queryOrLink);
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Utils.getContext().startActivity(intent);
// Gracefully exit, otherwise the broken app will continue to run.
System.exit(0);
}
private static void showBatteryOptimizationDialog(Activity context,
String dialogMessageRef,
String positiveButtonTextRef,
DialogInterface.OnClickListener onPositiveClickListener) {
// Use a delay to allow the activity to finish initializing.
// Otherwise, if device is in dark mode the dialog is shown with wrong color scheme.
Utils.runOnMainThreadDelayed(() -> {
// Create the custom dialog.
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
context,
str("gms_core_dialog_title"), // Title.
str(dialogMessageRef), // Message.
null, // No EditText.
str(positiveButtonTextRef), // OK button text.
() -> onPositiveClickListener.onClick(null, 0), // Convert DialogInterface.OnClickListener to Runnable.
null, // No Cancel button action.
null, // No Neutral button text.
null, // No Neutral button action.
true // Dismiss dialog when onNeutralClick.
);
Dialog dialog = dialogPair.first;
// Do not set cancelable to false, to allow using back button to skip the action,
// just in case the battery change can never be satisfied.
dialog.setCancelable(true);
// Show the dialog
Utils.showDialog(context, dialog);
}, 100);
}
/**
* Injection point.
*/
public static void checkGmsCore(Activity context) {
try {
// Verify the user has not included GmsCore for a root installation.
// GmsCore Support changes the package name, but with a mounted installation
// all manifest changes are ignored and the original package name is used.
if (isPackageNameOriginal()) {
Logger.printInfo(() -> "App is mounted with root, but GmsCore patch was included");
// Cannot use localize text here, since the app will load resources
// from the unpatched app and all patch strings are missing.
Utils.showToastLong("The 'GmsCore support' patch breaks mount installations");
// Do not exit. If the app exits before launch completes (and without
// opening another activity), then on some devices such as Pixel phone Android 10
// no toast will be shown and the app will continually relaunch
// with the appearance of a hung app.
}
// Verify GmsCore is installed.
private enum GmsCore {
REVANCED("app.revanced", "https://github.com/revanced/gmscore/releases/latest", () -> {
try {
PackageManager manager = context.getPackageManager();
manager.getPackageInfo(GMS_CORE_PACKAGE_NAME, PackageManager.GET_ACTIVITIES);
} catch (PackageManager.NameNotFoundException exception) {
Logger.printInfo(() -> "GmsCore was not found");
// Cannot show a dialog and must show a toast,
// because on some installations the app crashes before a dialog can be displayed.
Utils.showToastLong(str("gms_core_toast_not_installed_message"));
open(getGmsCoreDownload());
return;
}
// Check if GmsCore is whitelisted from battery optimizations.
if (isAndroidAutomotive(context)) {
// Ignore Android Automotive devices (Google built-in),
// as there is no way to disable battery optimizations.
Logger.printDebug(() -> "Device is Android Automotive");
} else if (batteryOptimizationsEnabled(context)) {
Logger.printInfo(() -> "GmsCore is not whitelisted from battery optimizations");
showBatteryOptimizationDialog(context,
"gms_core_dialog_not_whitelisted_using_battery_optimizations_message",
"gms_core_dialog_continue_text",
(dialog, id) -> openGmsCoreDisableBatteryOptimizationsIntent(context));
return;
}
// Check if GmsCore is currently running in the background.
var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER);
//noinspection TryFinallyCanBeTryWithResources
try {
if (client == null) {
Logger.printInfo(() -> "GmsCore is not running in the background");
checkIfDontKillMyAppSupportsManufacturer();
showBatteryOptimizationDialog(context,
"gms_core_dialog_not_whitelisted_not_allowed_in_background_message",
"gms_core_dialog_open_website_text",
(dialog, id) -> openDontKillMyApp());
}
} finally {
if (client != null) client.close();
}
} catch (Exception ex) {
Logger.printException(() -> "checkGmsCore failure", ex);
}
}
@SuppressLint("BatteryLife") // Permission is part of GmsCore
private static void openGmsCoreDisableBatteryOptimizationsIntent(Activity activity) {
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.fromParts("package", GMS_CORE_PACKAGE_NAME, null));
activity.startActivityForResult(intent, 0);
}
private static void checkIfDontKillMyAppSupportsManufacturer() {
Utils.runOnBackgroundThread(() -> {
try {
final long start = System.currentTimeMillis();
HttpURLConnection connection = Requester.getConnectionFromRoute(
DONT_KILL_MY_APP_URL, DONT_KILL_MY_APP_MANUFACTURER_API, BUILD_MANUFACTURER);
"https://api.github.com",
new Route(GET, "/repos/revanced/gmscore/releases/latest")
);
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
final boolean supported = connection.getResponseCode() == 200;
Logger.printInfo(() -> "Manufacturer is " + (supported ? "" : "NOT ")
+ "listed on DontKillMyApp: " + BUILD_MANUFACTURER
+ " fetch took: " + (System.currentTimeMillis() - start) + "ms");
DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED = supported;
int responseCode = connection.getResponseCode();
if (responseCode != 200) {
Logger.printDebug(() -> "GitHub API returned status code: " + responseCode);
return null;
}
// Parse the response
JSONObject releaseData = Requester.parseJSONObject(connection);
String tagName = releaseData.optString("tag_name", "");
connection.disconnect();
if (tagName.isEmpty()) {
Logger.printDebug(() -> "No tag_name found in GitHub release data");
return null;
}
if (tagName.startsWith("v")) tagName = tagName.substring(1);
return tagName;
} catch (Exception ex) {
Logger.printInfo(() -> "Could not check if manufacturer is listed on DontKillMyApp: "
+ BUILD_MANUFACTURER, ex);
DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED = null;
Logger.printInfo(() -> "Failed to fetch latest GmsCore version from GitHub", ex);
return null;
}
});
}
}),
UNKNOWN(getGmsCoreVendorGroupId(), getGmsCoreVendorGroupId() + "android.gms", () -> null);
private static void openDontKillMyApp() {
final Boolean manufacturerSupported = DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED;
private static final String DONT_KILL_MY_APP_URL
= "https://dontkillmyapp.com/";
private static final Route DONT_KILL_MY_APP_MANUFACTURER_API
= new Route(GET, "/api/v2/{manufacturer}.json");
private static final String DONT_KILL_MY_APP_NAME_PARAMETER
= "?app=MicroG";
private static final String BUILD_MANUFACTURER
= Build.MANUFACTURER.toLowerCase(Locale.ROOT).replace(" ", "-");
String manufacturerPageToOpen;
if (manufacturerSupported == null) {
// Fetch has not completed yet. Only happens on extremely slow internet connections
// and the user spends less than 1 second reading what's on screen.
// Instead of waiting for the fetch (which may timeout),
// open the website without a vendor.
manufacturerPageToOpen = "";
} else if (manufacturerSupported) {
manufacturerPageToOpen = BUILD_MANUFACTURER;
} else {
// No manufacturer specific page exists. Open the general page.
manufacturerPageToOpen = "general";
/**
* If a manufacturer specific page exists on DontKillMyApp.
*/
@Nullable
private volatile Boolean dontKillMyAppManufacturerSupported;
private final String groupId;
private final String packageName;
private final String downloadQuery;
private final GetLatestVersion getLatestVersion;
private final Uri gmsCoreProvider;
GmsCore(String groupId, String downloadQuery, GetLatestVersion getLatestVersion) {
this.groupId = groupId;
this.packageName = groupId + ".android.gms";
this.gmsCoreProvider = Uri.parse("content://" + groupId + ".android.gsf.gservices/prefix");
this.downloadQuery = downloadQuery;
this.getLatestVersion = getLatestVersion;
}
open(DONT_KILL_MY_APP_URL + manufacturerPageToOpen + DONT_KILL_MY_APP_NAME_PARAMETER);
}
/**
* @return If GmsCore is not whitelisted from battery optimizations.
*/
private static boolean batteryOptimizationsEnabled(Context context) {
//noinspection ObsoleteSdkInt
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// Android 5.0 does not have battery optimization settings.
return false;
String getGroupId() {
return groupId;
}
void check(Activity context) {
checkInstallation(context);
checkUpdates(context);
}
private void checkInstallation(Activity context) {
try {
// Verify the user has not included GmsCore for a root installation.
// GmsCore Support changes the package name, but with a mounted installation
// all manifest changes are ignored and the original package name is used.
if (isPackageNameOriginal()) {
Logger.printInfo(() -> "App is mounted with root, but GmsCore patch was included");
// Cannot use localize text here, since the app will load resources
// from the unpatched app and all patch strings are missing.
Utils.showToastLong("The 'GmsCore support' patch breaks mount installations");
// Do not exit. If the app exits before launch completes (and without
// opening another activity), then on some devices such as Pixel phone Android 10
// no toast will be shown and the app will continually relaunch
// with the appearance of a hung app.
}
// Verify GmsCore is installed.
try {
PackageManager manager = context.getPackageManager();
manager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
} catch (PackageManager.NameNotFoundException exception) {
Logger.printInfo(() -> "GmsCore was not found");
// Cannot show a dialog and must show a toast,
// because on some installations the app crashes before a dialog can be displayed.
Utils.showToastLong(str("revanced_gms_core_toast_not_installed_message"));
open(downloadQuery);
return;
}
// Check if GmsCore is whitelisted from battery optimizations.
if (isAndroidAutomotive(context)) {
// Ignore Android Automotive devices (Google built-in),
// as there is no way to disable battery optimizations.
Logger.printDebug(() -> "Device is Android Automotive");
} else if (batteryOptimizationsEnabled(context)) {
Logger.printInfo(() -> "GmsCore is not whitelisted from battery optimizations");
showBatteryOptimizationDialog(context,
"revanced_gms_core_dialog_not_whitelisted_using_battery_optimizations_message",
"revanced_gms_core_dialog_continue_text",
(dialog, id) -> openGmsCoreDisableBatteryOptimizationsIntent(context));
return;
}
// Check if GmsCore is currently running in the background.
var client = context.getContentResolver().acquireContentProviderClient(gmsCoreProvider);
//noinspection TryFinallyCanBeTryWithResources
try {
if (client == null) {
Logger.printInfo(() -> "GmsCore is not running in the background");
checkIfDontKillMyAppSupportsManufacturer();
showBatteryOptimizationDialog(context,
"revanced_gms_core_dialog_not_whitelisted_not_allowed_in_background_message",
"gmsrevanced_gms_core_log_open_website_text",
(dialog, id) -> openDontKillMyApp());
}
} finally {
if (client != null) client.close();
}
} catch (Exception ex) {
Logger.printException(() -> "checkGmsCore failure", ex);
}
}
private void checkUpdates(Activity context) {
if (!BaseSettings.GMS_CORE_CHECK_UPDATES.get()) {
Logger.printDebug(() -> "GmsCore update check is disabled in settings");
return;
}
Utils.runOnBackgroundThread(() -> {
try {
PackageManager manager = context.getPackageManager();
String installedVersion = manager.getPackageInfo(packageName, 0).versionName;
Logger.printDebug(() -> "Installed GmsCore version: " + installedVersion);
String latestVersion = getLatestVersion.get();
if (latestVersion == null || latestVersion.isEmpty()) {
Logger.printDebug(() -> "Could not get latest GmsCore version");
Utils.showToastLong(str("revanced_gms_core_toast_update_check_failed_message"));
return;
}
Logger.printDebug(() -> "Latest GmsCore version on GitHub: " + latestVersion);
// Compare versions
if (!installedVersion.equals(latestVersion)) {
Logger.printInfo(() -> "GmsCore update available. Installed: " + installedVersion
+ ", Latest: " + latestVersion);
showUpdateDialog(context, installedVersion, latestVersion);
} else {
Logger.printDebug(() -> "GmsCore is up to date");
}
} catch (Exception ex) {
Logger.printInfo(() -> "Could not check GmsCore updates", ex);
Utils.showToastLong(str("revanced_gms_core_toast_update_check_failed_message"));
}
});
}
private void open(String queryOrLink) {
Logger.printInfo(() -> "Opening link: " + queryOrLink);
Intent intent;
try {
// Check if queryOrLink is a valid URL.
new URL(queryOrLink);
intent = new Intent(Intent.ACTION_VIEW, Uri.parse(queryOrLink));
} catch (MalformedURLException e) {
intent = new Intent(Intent.ACTION_WEB_SEARCH);
intent.putExtra(SearchManager.QUERY, queryOrLink);
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Utils.getContext().startActivity(intent);
// Gracefully exit, otherwise the broken app will continue to run.
System.exit(0);
}
private void showUpdateDialog(Activity context, String installedVersion, String latestVersion) {
// Use a delay to allow the activity to finish initializing.
// Otherwise, if device is in dark mode the dialog is shown with wrong color scheme.
Utils.runOnMainThreadDelayed(() -> {
try {
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
context,
str("revanced_gms_core_dialog_title"),
String.format(str("revanced_gms_core_update_available_message"), latestVersion, installedVersion),
null,
str("revanced_gms_core_dialog_open_website_text"),
() -> open(downloadQuery),
() -> {
},
str("revanced_gms_core_dialog_cancel_text"),
null,
true
);
Dialog dialog = dialogPair.first;
dialog.setCancelable(true);
Utils.showDialog(context, dialog);
} catch (Exception ex) {
Logger.printException(() -> "Failed to show GmsCore update dialog", ex);
}
}, 100);
}
private static void showBatteryOptimizationDialog(Activity context,
String dialogMessageRef,
String positiveButtonTextRef,
DialogInterface.OnClickListener onPositiveClickListener) {
// Use a delay to allow the activity to finish initializing.
// Otherwise, if device is in dark mode the dialog is shown with wrong color scheme.
Utils.runOnMainThreadDelayed(() -> {
// Create the custom dialog.
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
context,
str("revanced_gms_core_dialog_title"), // Title.
str(dialogMessageRef), // Message.
null, // No EditText.
str(positiveButtonTextRef), // OK button text.
() -> onPositiveClickListener.onClick(null, 0), // Convert DialogInterface.OnClickListener to Runnable.
null, // No Cancel button action.
null, // No Neutral button text.
null, // No Neutral button action.
true // Dismiss dialog when onNeutralClick.
);
Dialog dialog = dialogPair.first;
// Do not set cancelable to false to allow using back button to skip the action,
// just in case the battery change can never be satisfied.
dialog.setCancelable(true);
// Show the dialog
Utils.showDialog(context, dialog);
}, 100);
}
@SuppressLint("BatteryLife") // Permission is part of GmsCore
private void openGmsCoreDisableBatteryOptimizationsIntent(Activity activity) {
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.fromParts("package", packageName, null));
activity.startActivityForResult(intent, 0);
}
private void checkIfDontKillMyAppSupportsManufacturer() {
Utils.runOnBackgroundThread(() -> {
try {
final long start = System.currentTimeMillis();
HttpURLConnection connection = Requester.getConnectionFromRoute(
DONT_KILL_MY_APP_URL, DONT_KILL_MY_APP_MANUFACTURER_API, BUILD_MANUFACTURER);
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
final boolean supported = connection.getResponseCode() == 200;
Logger.printInfo(() -> "Manufacturer is " + (supported ? "" : "NOT ")
+ "listed on DontKillMyApp: " + BUILD_MANUFACTURER
+ " fetch took: " + (System.currentTimeMillis() - start) + "ms");
dontKillMyAppManufacturerSupported = supported;
} catch (Exception ex) {
Logger.printInfo(() -> "Could not check if manufacturer is listed on DontKillMyApp: "
+ BUILD_MANUFACTURER, ex);
dontKillMyAppManufacturerSupported = null;
}
});
}
private void openDontKillMyApp() {
final Boolean manufacturerSupported = dontKillMyAppManufacturerSupported;
String manufacturerPageToOpen;
if (manufacturerSupported == null) {
// Fetch has not completed yet. Only happens on extremely slow internet connections
// and the user spends less than 1 second reading what's on screen.
// Instead of waiting for the fetch (which may timeout),
// open the website without a vendor.
manufacturerPageToOpen = "";
} else if (manufacturerSupported) {
manufacturerPageToOpen = BUILD_MANUFACTURER;
} else {
// No manufacturer specific page exists. Open the general page.
manufacturerPageToOpen = "general";
}
open(DONT_KILL_MY_APP_URL + manufacturerPageToOpen + DONT_KILL_MY_APP_NAME_PARAMETER);
}
/**
* @return If GmsCore is not whitelisted from battery optimizations.
*/
private boolean batteryOptimizationsEnabled(Context context) {
//noinspection ObsoleteSdkInt
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// Android 5.0 does not have battery optimization settings.
return false;
}
var powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
return !powerManager.isIgnoringBatteryOptimizations(packageName);
}
private boolean isAndroidAutomotive(Context context) {
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
}
var powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
return !powerManager.isIgnoringBatteryOptimizations(GMS_CORE_PACKAGE_NAME);
}
private static boolean isAndroidAutomotive(Context context) {
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
}
private static String getGmsCoreDownload() {
final var vendorGroupId = getGmsCoreVendorGroupId();
//noinspection SwitchStatementWithTooFewBranches
return switch (vendorGroupId) {
case "app.revanced" -> "https://github.com/revanced/gmscore/releases/latest";
default -> vendorGroupId + ".android.gms";
};
}
private static String getGmsCoreVendorGroupId() {
return "app.revanced"; // Modified during patching.
@FunctionalInterface
private interface GetLatestVersion {
String get();
}
}

View file

@ -28,6 +28,8 @@ public class BaseSettings {
public static final BooleanSetting SETTINGS_SEARCH_HISTORY = new BooleanSetting("revanced_settings_search_history", TRUE, true);
public static final StringSetting SETTINGS_SEARCH_ENTRIES = new StringSetting("revanced_settings_search_entries", "");
public static final BooleanSetting GMS_CORE_CHECK_UPDATES = new BooleanSetting("revanced_gms_core_check_updates", true, true);
//
// Settings shared by YouTube and YouTube Music.
//

View file

@ -27,5 +27,4 @@ private fun gmsCoreSupportResourcePatch(
toPackageName = REVANCED_MAGAZINES_PACKAGE_NAME,
spoofedPackageSignature = "24bb24c05e47e0aefa68a58a766179d9b613a666",
gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption,
addStringResources = false,
)

View file

@ -22,7 +22,6 @@ private fun gmsCoreSupportResourcePatch(
) = app.revanced.patches.shared.misc.gms.gmsCoreSupportResourcePatch(
fromPackageName = PHOTOS_PACKAGE_NAME,
toPackageName = REVANCED_PHOTOS_PACKAGE_NAME,
addStringResources = false,
spoofedPackageSignature = "24bb24c05e47e0aefa68a58a766179d9b613a600",
gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption,
)

View file

@ -4,15 +4,17 @@ import app.revanced.patcher.patch.Option
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
import app.revanced.patches.music.misc.fileprovider.fileProviderPatch
import app.revanced.patches.music.misc.gms.Constants.MUSIC_PACKAGE_NAME
import app.revanced.patches.music.misc.gms.Constants.REVANCED_MUSIC_PACKAGE_NAME
import app.revanced.patches.music.misc.settings.PreferenceScreen
import app.revanced.patches.music.misc.settings.settingsPatch
import app.revanced.patches.music.misc.spoof.spoofVideoStreamsPatch
import app.revanced.patches.music.misc.fileprovider.fileProviderPatch
import app.revanced.patches.shared.castContextFetchFingerprint
import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch
import app.revanced.patches.shared.misc.settings.preference.IntentPreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.primeMethodFingerprint
@Suppress("unused")
@ -32,8 +34,8 @@ val gmsCoreSupportPatch = gmsCoreSupportPatch(
compatibleWith(
MUSIC_PACKAGE_NAME(
"7.29.52",
"8.10.52"
)
"8.10.52",
),
)
}
@ -50,21 +52,27 @@ private fun gmsCoreSupportResourcePatch(
val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption
PreferenceScreen.MISC.addPreferences(
IntentPreference(
"microg_settings",
intent = IntentPreference.Intent("", "org.microg.gms.ui.SettingsActivity") {
"$gmsCoreVendorGroupId.android.gms"
}
)
PreferenceScreenPreference(
"revanced_gms_core_screen",
preferences = setOf(
SwitchPreference("revanced_gms_core_check_updates"),
IntentPreference(
"revanced_gms_core_settings",
intent = IntentPreference.Intent("", "org.microg.gms.ui.SettingsActivity") {
"$gmsCoreVendorGroupId.android.gms"
},
),
),
),
)
}
},
) {
dependsOn(
addResourcesPatch,
settingsPatch,
fileProviderPatch(
MUSIC_PACKAGE_NAME,
REVANCED_MUSIC_PACKAGE_NAME
)
REVANCED_MUSIC_PACKAGE_NAME,
),
)
}

View file

@ -24,7 +24,7 @@ internal val serviceCheckFingerprint = fingerprint {
strings("Google Play Services not available")
}
internal val gmsCoreSupportFingerprint = fingerprint {
internal val getGmsCoreVendorGroupIdFingerprint = fingerprint {
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
returns("Ljava/lang/String;")
parameters()

View file

@ -2,26 +2,18 @@ package app.revanced.patches.shared.misc.gms
import app.revanced.patcher.Fingerprint
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.instructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.BytecodePatchBuilder
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.Option
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.ResourcePatchBuilder
import app.revanced.patcher.patch.ResourcePatchContext
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patcher.patch.*
import app.revanced.patches.all.misc.packagename.changePackageNamePatch
import app.revanced.patches.all.misc.packagename.setOrGetFallbackPackageName
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.gms.Constants.ACTIONS
import app.revanced.patches.shared.misc.gms.Constants.AUTHORITIES
import app.revanced.patches.shared.misc.gms.Constants.PERMISSIONS
import app.revanced.util.getReference
import app.revanced.util.returnEarly
import app.revanced.patches.shared.misc.gms.Constants.APP_AUTHORITIES
import app.revanced.patches.shared.misc.gms.Constants.APP_PERMISSIONS
import app.revanced.patches.shared.misc.gms.Constants.GMS_AUTHORITIES
import app.revanced.patches.shared.misc.gms.Constants.GMS_PERMISSIONS
import app.revanced.util.*
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@ -29,8 +21,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction21c
import com.android.tools.smali.dexlib2.iface.reference.StringReference
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableStringReference
import com.android.tools.smali.dexlib2.util.MethodUtil
import org.w3c.dom.Element
import org.w3c.dom.Node
import java.net.URI
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/shared/GmsCoreSupport;"
@ -69,10 +60,7 @@ fun gmsCoreSupportPatch(
val gmsCoreVendorGroupIdOption = stringOption(
key = "gmsCoreVendorGroupId",
default = "app.revanced",
values =
mapOf(
"ReVanced" to "app.revanced",
),
values = mapOf("ReVanced" to "app.revanced"),
title = "GmsCore vendor group ID",
description = "The vendor's group ID for GmsCore.",
required = true,
@ -84,9 +72,9 @@ fun gmsCoreSupportPatch(
extensionPatch,
)
val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption
execute {
val gmsCoreVendorGroupId = gmsCoreVendorGroupIdOption.value!!
fun transformStringReferences(transform: (str: String) -> String?) = classes.forEach {
val mutableClass by lazy {
proxy(it).mutableClass
@ -118,80 +106,54 @@ fun gmsCoreSupportPatch(
}
}
// region Collection of transformations that are applied to all strings.
fun commonTransform(referencedString: String): String? = when (referencedString) {
fun transformPackages(string: String): String? = when (string) {
"com.google",
"com.google.android.gms",
in PERMISSIONS,
in ACTIONS,
in AUTHORITIES,
-> referencedString.replace("com.google", gmsCoreVendorGroupId!!)
in GMS_PERMISSIONS,
in GMS_AUTHORITIES,
-> if (string.startsWith("com.google")) {
string.replace("com.google", gmsCoreVendorGroupId)
} else {
"$gmsCoreVendorGroupId.$string"
}
in APP_PERMISSIONS,
in APP_AUTHORITIES,
-> "$toPackageName.$string"
// No vendor prefix for whatever reason...
"subscribedfeeds" -> "$gmsCoreVendorGroupId.subscribedfeeds"
else -> null
}
fun contentUrisTransform(str: String): String? {
// only when content:// uri
if (str.startsWith("content://")) {
// check if matches any authority
for (authority in AUTHORITIES) {
val uriPrefix = "content://$authority"
if (str.startsWith(uriPrefix)) {
return str.replace(
uriPrefix,
"content://${authority.replace("com.google", gmsCoreVendorGroupId!!)}",
)
}
fun transformContentUrlAuthority(string: String) = if (!string.startsWith("content://")) {
null
} else {
runCatching { URI.create(string) }.map {
when (it.authority) {
in GMS_AUTHORITIES ->
if (it.authority.startsWith("com.google")) {
string.replace("com.google", gmsCoreVendorGroupId)
} else {
string.replace(
it.authority,
"$gmsCoreVendorGroupId.${it.authority}",
)
}
in APP_AUTHORITIES ->
string.replace(it.authority, "$toPackageName.${it.authority}")
else -> null
}
// gms also has a 'subscribedfeeds' authority, check for that one too
val subFeedsUriPrefix = "content://subscribedfeeds"
if (str.startsWith(subFeedsUriPrefix)) {
return str.replace(subFeedsUriPrefix, "content://$gmsCoreVendorGroupId.subscribedfeeds")
}
}
return null
}.getOrNull()
}
fun packageNameTransform(fromPackageName: String, toPackageName: String): (String) -> String? = { string ->
when (string) {
"$fromPackageName.SuggestionProvider",
"$fromPackageName.fileprovider",
-> string.replace(fromPackageName, toPackageName)
else -> null
}
}
fun transformPrimeMethod(packageName: String) {
primeMethodFingerprint!!.method.apply {
var register = 2
val index = instructions.indexOfFirst {
if (it.getReference<StringReference>()?.string != fromPackageName) return@indexOfFirst false
register = (it as OneRegisterInstruction).registerA
return@indexOfFirst true
}
replaceInstruction(index, "const-string v$register, \"$packageName\"")
}
}
// endregion
val packageName = setOrGetFallbackPackageName(toPackageName)
// Transform all strings using all provided transforms, first match wins.
val transformations = arrayOf(
::commonTransform,
::contentUrisTransform,
packageNameTransform(fromPackageName, packageName),
::transformPackages,
::transformContentUrlAuthority,
)
transformStringReferences transform@{ string ->
transformations.forEach { transform ->
transform(string)?.let { transformedString -> return@transform transformedString }
@ -201,16 +163,26 @@ fun gmsCoreSupportPatch(
}
// Specific method that needs to be patched.
primeMethodFingerprint?.let { transformPrimeMethod(packageName) }
if (primeMethodFingerprint?.methodOrNull != null) {
val primeMethod = primeMethodFingerprint.method
val index = primeMethod.indexOfFirstInstruction {
getReference<StringReference>()?.string == fromPackageName
}
val register = primeMethod.getInstruction<OneRegisterInstruction>(index).registerA
primeMethod.replaceInstruction(
index,
"const-string v$register, \"$packageName\"",
)
}
// Return these methods early to prevent the app from crashing.
earlyReturnFingerprints.forEach { it.method.returnEarly() }
serviceCheckFingerprint.method.returnEarly()
// Google Play Utility is not present in all apps, so we need to check if it's present.
if (googlePlayUtilityFingerprint.methodOrNull != null) {
googlePlayUtilityFingerprint.method.returnEarly(0)
}
googlePlayUtilityFingerprint.methodOrNull?.returnEarly(0)
// Set original and patched package names for extension to use.
originalPackageNameExtensionFingerprint.method.returnEarly(fromPackageName)
@ -219,11 +191,11 @@ fun gmsCoreSupportPatch(
mainActivityOnCreateFingerprint.method.addInstruction(
0,
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->" +
"checkGmsCore(Landroid/app/Activity;)V"
"checkGmsCore(Landroid/app/Activity;)V",
)
// Change the vendor of GmsCore in the extension.
gmsCoreSupportFingerprint.method.returnEarly(gmsCoreVendorGroupId!!)
getGmsCoreVendorGroupIdFingerprint.method.returnEarly(gmsCoreVendorGroupId)
executeBlock()
}
@ -231,274 +203,6 @@ fun gmsCoreSupportPatch(
block()
}
/**
* A collection of permissions, intents and content provider authorities
* that are present in GmsCore which need to be transformed.
*/
private object Constants {
/**
* All permissions.
*/
val PERMISSIONS = setOf(
"com.google.android.c2dm.permission.RECEIVE",
"com.google.android.c2dm.permission.SEND",
"com.google.android.gms.auth.api.phone.permission.SEND",
"com.google.android.gms.permission.AD_ID",
"com.google.android.gms.permission.AD_ID_NOTIFICATION",
"com.google.android.gms.permission.CAR_FUEL",
"com.google.android.gms.permission.CAR_INFORMATION",
"com.google.android.gms.permission.CAR_MILEAGE",
"com.google.android.gms.permission.CAR_SPEED",
"com.google.android.gms.permission.CAR_VENDOR_EXTENSION",
"com.google.android.googleapps.permission.GOOGLE_AUTH",
"com.google.android.googleapps.permission.GOOGLE_AUTH.cp",
"com.google.android.googleapps.permission.GOOGLE_AUTH.local",
"com.google.android.googleapps.permission.GOOGLE_AUTH.mail",
"com.google.android.googleapps.permission.GOOGLE_AUTH.writely",
"com.google.android.gtalkservice.permission.GTALK_SERVICE",
"com.google.android.providers.gsf.permission.READ_GSERVICES",
)
/**
* All intent actions.
*/
val ACTIONS = setOf(
"com.google.android.c2dm.intent.RECEIVE",
"com.google.android.c2dm.intent.REGISTER",
"com.google.android.c2dm.intent.REGISTRATION",
"com.google.android.c2dm.intent.UNREGISTER",
"com.google.android.contextmanager.service.ContextManagerService.START",
"com.google.android.gcm.intent.SEND",
"com.google.android.gms.accounts.ACCOUNT_SERVICE",
"com.google.android.gms.accountsettings.ACCOUNT_PREFERENCES_SETTINGS",
"com.google.android.gms.accountsettings.action.BROWSE_SETTINGS",
"com.google.android.gms.accountsettings.action.VIEW_SETTINGS",
"com.google.android.gms.accountsettings.MY_ACCOUNT",
"com.google.android.gms.accountsettings.PRIVACY_SETTINGS",
"com.google.android.gms.accountsettings.SECURITY_SETTINGS",
"com.google.android.gms.ads.gservice.START",
"com.google.android.gms.ads.identifier.service.EVENT_ATTESTATION",
"com.google.android.gms.ads.service.CACHE",
"com.google.android.gms.ads.service.CONSENT_LOOKUP",
"com.google.android.gms.ads.service.HTTP",
"com.google.android.gms.analytics.service.START",
"com.google.android.gms.app.settings.GoogleSettingsLink",
"com.google.android.gms.appstate.service.START",
"com.google.android.gms.appusage.service.START",
"com.google.android.gms.asterism.service.START",
"com.google.android.gms.audiomodem.service.AudioModemService.START",
"com.google.android.gms.audit.service.START",
"com.google.android.gms.auth.account.authapi.START",
"com.google.android.gms.auth.account.authenticator.auto.service.START",
"com.google.android.gms.auth.account.authenticator.chromeos.START",
"com.google.android.gms.auth.account.authenticator.tv.service.START",
"com.google.android.gms.auth.account.data.service.START",
"com.google.android.gms.auth.api.credentials.PICKER",
"com.google.android.gms.auth.api.credentials.service.START",
"com.google.android.gms.auth.api.identity.service.authorization.START",
"com.google.android.gms.auth.api.identity.service.credentialsaving.START",
"com.google.android.gms.auth.api.identity.service.signin.START",
"com.google.android.gms.auth.api.phone.service.InternalService.START",
"com.google.android.gms.auth.api.signin.service.START",
"com.google.android.gms.auth.be.appcert.AppCertService",
"com.google.android.gms.auth.blockstore.service.START",
"com.google.android.gms.auth.config.service.START",
"com.google.android.gms.auth.cryptauth.cryptauthservice.START",
"com.google.android.gms.auth.GOOGLE_SIGN_IN",
"com.google.android.gms.auth.login.LOGIN",
"com.google.android.gms.auth.proximity.devicesyncservice.START",
"com.google.android.gms.auth.proximity.securechannelservice.START",
"com.google.android.gms.auth.proximity.START",
"com.google.android.gms.auth.service.START",
"com.google.android.gms.backup.ACTION_BACKUP_SETTINGS",
"com.google.android.gms.backup.G1_BACKUP",
"com.google.android.gms.backup.G1_RESTORE",
"com.google.android.gms.backup.GMS_MODULE_RESTORE",
"com.google.android.gms.beacon.internal.IBleService.START",
"com.google.android.gms.car.service.START",
"com.google.android.gms.carrierauth.service.START",
"com.google.android.gms.cast.firstparty.START",
"com.google.android.gms.cast.remote_display.service.START",
"com.google.android.gms.cast.service.BIND_CAST_DEVICE_CONTROLLER_SERVICE",
"com.google.android.gms.cast_mirroring.service.START",
"com.google.android.gms.checkin.BIND_TO_SERVICE",
"com.google.android.gms.chromesync.service.START",
"com.google.android.gms.clearcut.service.START",
"com.google.android.gms.common.account.CHOOSE_ACCOUNT",
"com.google.android.gms.common.download.START",
"com.google.android.gms.common.service.START",
"com.google.android.gms.common.telemetry.service.START",
"com.google.android.gms.config.START",
"com.google.android.gms.constellation.service.START",
"com.google.android.gms.credential.manager.service.firstparty.START",
"com.google.android.gms.deviceconnection.service.START",
"com.google.android.gms.drive.ApiService.RESET_AFTER_BOOT",
"com.google.android.gms.drive.ApiService.START",
"com.google.android.gms.drive.ApiService.STOP",
"com.google.android.gms.droidguard.service.INIT",
"com.google.android.gms.droidguard.service.PING",
"com.google.android.gms.droidguard.service.START",
"com.google.android.gms.enterprise.loader.service.START",
"com.google.android.gms.facs.cache.service.START",
"com.google.android.gms.facs.internal.service.START",
"com.google.android.gms.feedback.internal.IFeedbackService",
"com.google.android.gms.fido.credentialstore.internal_service.START",
"com.google.android.gms.fido.fido2.privileged.START",
"com.google.android.gms.fido.fido2.regular.START",
"com.google.android.gms.fido.fido2.zeroparty.START",
"com.google.android.gms.fido.sourcedevice.service.START",
"com.google.android.gms.fido.targetdevice.internal_service.START",
"com.google.android.gms.fido.u2f.privileged.START",
"com.google.android.gms.fido.u2f.thirdparty.START",
"com.google.android.gms.fido.u2f.zeroparty.START",
"com.google.android.gms.fitness.BleApi",
"com.google.android.gms.fitness.ConfigApi",
"com.google.android.gms.fitness.GoalsApi",
"com.google.android.gms.fitness.GoogleFitnessService.START",
"com.google.android.gms.fitness.HistoryApi",
"com.google.android.gms.fitness.InternalApi",
"com.google.android.gms.fitness.RecordingApi",
"com.google.android.gms.fitness.SensorsApi",
"com.google.android.gms.fitness.SessionsApi",
"com.google.android.gms.fonts.service.START",
"com.google.android.gms.freighter.service.START",
"com.google.android.gms.games.internal.connect.service.START",
"com.google.android.gms.games.PLAY_GAMES_UPGRADE",
"com.google.android.gms.games.service.START",
"com.google.android.gms.gass.START",
"com.google.android.gms.gmscompliance.service.START",
"com.google.android.gms.googlehelp.HELP",
"com.google.android.gms.googlehelp.service.GoogleHelpService.START",
"com.google.android.gms.growth.service.START",
"com.google.android.gms.herrevad.services.LightweightNetworkQualityAndroidService.START",
"com.google.android.gms.icing.INDEX_SERVICE",
"com.google.android.gms.icing.LIGHTWEIGHT_INDEX_SERVICE",
"com.google.android.gms.identity.service.BIND",
"com.google.android.gms.inappreach.service.START",
"com.google.android.gms.instantapps.START",
"com.google.android.gms.kids.service.START",
"com.google.android.gms.languageprofile.service.START",
"com.google.android.gms.learning.internal.dynamitesupport.START",
"com.google.android.gms.learning.intservice.START",
"com.google.android.gms.learning.predictor.START",
"com.google.android.gms.learning.trainer.START",
"com.google.android.gms.learning.training.background.START",
"com.google.android.gms.location.places.GeoDataApi",
"com.google.android.gms.location.places.PlaceDetectionApi",
"com.google.android.gms.location.places.PlacesApi",
"com.google.android.gms.location.reporting.service.START",
"com.google.android.gms.location.settings.LOCATION_HISTORY",
"com.google.android.gms.location.settings.LOCATION_REPORTING_SETTINGS",
"com.google.android.gms.locationsharing.api.START",
"com.google.android.gms.locationsharingreporter.service.START",
"com.google.android.gms.lockbox.service.START",
"com.google.android.gms.matchstick.lighter.service.START",
"com.google.android.gms.mdm.services.DeviceManagerApiService.START",
"com.google.android.gms.mdm.services.START",
"com.google.android.gms.mdns.service.START",
"com.google.android.gms.measurement.START",
"com.google.android.gms.nearby.bootstrap.service.NearbyBootstrapService.START",
"com.google.android.gms.nearby.connection.service.START",
"com.google.android.gms.nearby.fastpair.START",
"com.google.android.gms.nearby.messages.service.NearbyMessagesService.START",
"com.google.android.gms.nearby.sharing.service.NearbySharingService.START",
"com.google.android.gms.nearby.sharing.START_SERVICE",
"com.google.android.gms.notifications.service.START",
"com.google.android.gms.ocr.service.internal.START",
"com.google.android.gms.ocr.service.START",
"com.google.android.gms.oss.licenses.service.START",
"com.google.android.gms.payse.service.BIND",
"com.google.android.gms.people.contactssync.service.START",
"com.google.android.gms.people.service.START",
"com.google.android.gms.phenotype.service.START",
"com.google.android.gms.photos.autobackup.service.START",
"com.google.android.gms.playlog.service.START",
"com.google.android.gms.plus.service.default.INTENT",
"com.google.android.gms.plus.service.image.INTENT",
"com.google.android.gms.plus.service.internal.START",
"com.google.android.gms.plus.service.START",
"com.google.android.gms.potokens.service.START",
"com.google.android.gms.pseudonymous.service.START",
"com.google.android.gms.rcs.START",
"com.google.android.gms.reminders.service.START",
"com.google.android.gms.romanesco.MODULE_BACKUP_AGENT",
"com.google.android.gms.romanesco.service.START",
"com.google.android.gms.safetynet.service.START",
"com.google.android.gms.scheduler.ACTION_PROXY_SCHEDULE",
"com.google.android.gms.search.service.SEARCH_AUTH_START",
"com.google.android.gms.semanticlocation.service.START_ODLH",
"com.google.android.gms.sesame.service.BIND",
"com.google.android.gms.settings.EXPOSURE_NOTIFICATION_SETTINGS",
"com.google.android.gms.setup.auth.SecondDeviceAuth.START",
"com.google.android.gms.signin.service.START",
"com.google.android.gms.smartdevice.d2d.SourceDeviceService.START",
"com.google.android.gms.smartdevice.d2d.TargetDeviceService.START",
"com.google.android.gms.smartdevice.directtransfer.SourceDirectTransferService.START",
"com.google.android.gms.smartdevice.directtransfer.TargetDirectTransferService.START",
"com.google.android.gms.smartdevice.postsetup.PostSetupService.START",
"com.google.android.gms.smartdevice.setup.accounts.AccountsService.START",
"com.google.android.gms.smartdevice.wifi.START_WIFI_HELPER_SERVICE",
"com.google.android.gms.social.location.activity.service.START",
"com.google.android.gms.speech.service.START",
"com.google.android.gms.statementservice.EXECUTE",
"com.google.android.gms.stats.ACTION_UPLOAD_DROPBOX_ENTRIES",
"com.google.android.gms.tapandpay.service.BIND",
"com.google.android.gms.telephonyspam.service.START",
"com.google.android.gms.testsupport.service.START",
"com.google.android.gms.thunderbird.service.START",
"com.google.android.gms.trustagent.BridgeApi.START",
"com.google.android.gms.trustagent.StateApi.START",
"com.google.android.gms.trustagent.trustlet.trustletmanagerservice.BIND",
"com.google.android.gms.trustlet.bluetooth.service.BIND",
"com.google.android.gms.trustlet.connectionlessble.service.BIND",
"com.google.android.gms.trustlet.face.service.BIND",
"com.google.android.gms.trustlet.nfc.service.BIND",
"com.google.android.gms.trustlet.onbody.service.BIND",
"com.google.android.gms.trustlet.place.service.BIND",
"com.google.android.gms.trustlet.voiceunlock.service.BIND",
"com.google.android.gms.udc.service.START",
"com.google.android.gms.update.START_API_SERVICE",
"com.google.android.gms.update.START_SERVICE",
"com.google.android.gms.update.START_SINGLE_USER_API_SERVICE",
"com.google.android.gms.update.START_TV_API_SERVICE",
"com.google.android.gms.usagereporting.service.START",
"com.google.android.gms.userlocation.service.START",
"com.google.android.gms.vehicle.cabin.service.START",
"com.google.android.gms.vehicle.climate.service.START",
"com.google.android.gms.vehicle.info.service.START",
"com.google.android.gms.wallet.service.BIND",
"com.google.android.gms.walletp2p.service.firstparty.BIND",
"com.google.android.gms.walletp2p.service.zeroparty.BIND",
"com.google.android.gms.wearable.BIND",
"com.google.android.gms.wearable.BIND_LISTENER",
"com.google.android.gms.wearable.DATA_CHANGED",
"com.google.android.gms.wearable.MESSAGE_RECEIVED",
"com.google.android.gms.wearable.NODE_CHANGED",
"com.google.android.gsf.action.GET_GLS",
"com.google.android.location.settings.LOCATION_REPORTING_SETTINGS",
"com.google.android.mdd.service.START",
"com.google.android.mdh.service.listener.START",
"com.google.android.mdh.service.START",
"com.google.android.mobstore.service.START",
"com.google.firebase.auth.api.gms.service.START",
"com.google.firebase.dynamiclinks.service.START",
"com.google.iid.TOKEN_REQUEST",
"com.google.android.gms.location.places.ui.PICK_PLACE",
)
/**
* All content provider authorities.
*/
val AUTHORITIES = setOf(
"com.google.android.gms.auth.accounts",
"com.google.android.gms.chimera",
"com.google.android.gms.fonts",
"com.google.android.gms.phenotype",
"com.google.android.gsf.gservices",
"com.google.settings",
)
}
/**
* Abstract resource patch that allows Google apps to run without root and under a different package name
* by using GmsCore instead of Google Play Services.
@ -510,129 +214,139 @@ private object Constants {
* @param executeBlock The additional execution block of the patch.
* @param block The additional block to build the patch.
*/
fun gmsCoreSupportResourcePatch( // This is here only for binary compatibility.
fun gmsCoreSupportResourcePatch(
fromPackageName: String,
toPackageName: String,
spoofedPackageSignature: String,
gmsCoreVendorGroupIdOption: Option<String>,
executeBlock: ResourcePatchContext.() -> Unit = {},
block: ResourcePatchBuilder.() -> Unit = {},
) = gmsCoreSupportResourcePatch(
fromPackageName,
toPackageName,
spoofedPackageSignature,
gmsCoreVendorGroupIdOption,
true,
executeBlock,
block
)
/**
* Abstract resource patch that allows Google apps to run without root and under a different package name
* by using GmsCore instead of Google Play Services.
*
* @param fromPackageName The package name of the original app.
* @param toPackageName The package name to fall back to if no custom package name is specified in patch options.
* @param spoofedPackageSignature The signature of the package to spoof to.
* @param gmsCoreVendorGroupIdOption The option to get the vendor group ID of GmsCore.
* @param addStringResources If the GmsCore shared strings should be added to the patched app.
* @param executeBlock The additional execution block of the patch.
* @param block The additional block to build the patch.
*/
// TODO: On the next major release make this public and delete the public overloaded constructor.
internal fun gmsCoreSupportResourcePatch(
fromPackageName: String,
toPackageName: String,
spoofedPackageSignature: String,
gmsCoreVendorGroupIdOption: Option<String>,
addStringResources: Boolean = true,
executeBlock: ResourcePatchContext.() -> Unit = {},
block: ResourcePatchBuilder.() -> Unit = {},
) = resourcePatch {
dependsOn(
changePackageNamePatch,
addResourcesPatch,
)
val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption
val gmsCoreVendorGroupId = gmsCoreVendorGroupIdOption.value!!
execute {
// Some patches don't use shared String resources so there's no need to add them.
if (addStringResources) {
addResources("shared", "misc.gms.gmsCoreSupportResourcePatch")
}
addResources("shared", "misc.gms.gmsCoreSupportResourcePatch")
/**
* Add metadata to manifest to support spoofing the package name and signature of GmsCore.
*/
fun addSpoofingMetadata() {
fun Node.adoptChild(
tagName: String,
block: Element.() -> Unit,
) {
val child = ownerDocument.createElement(tagName)
child.block()
appendChild(child)
document("AndroidManifest.xml").use { document ->
document.getElementsByTagName("permission").asSequence().forEach { node ->
val nameElement = node.attributes.getNamedItem("android:name")
nameElement.textContent = toPackageName + nameElement.textContent
}
document("AndroidManifest.xml").use { document ->
val applicationNode =
document
.getElementsByTagName("application")
.item(0)
// Spoof package name and signature.
applicationNode.adoptChild("meta-data") {
setAttribute("android:name", "$gmsCoreVendorGroupId.android.gms.SPOOFED_PACKAGE_NAME")
setAttribute("android:value", fromPackageName)
}
applicationNode.adoptChild("meta-data") {
setAttribute("android:name", "$gmsCoreVendorGroupId.android.gms.SPOOFED_PACKAGE_SIGNATURE")
setAttribute("android:value", spoofedPackageSignature)
}
// GmsCore presence detection in extension.
applicationNode.adoptChild("meta-data") {
// TODO: The name of this metadata should be dynamic.
setAttribute("android:name", "app.revanced.MICROG_PACKAGE_NAME")
setAttribute("android:value", "$gmsCoreVendorGroupId.android.gms")
document.getElementsByTagName("uses-permission").asSequence().forEach { node ->
val nameElement = node.attributes.getNamedItem("android:name")
if (nameElement.textContent in GMS_PERMISSIONS) {
nameElement.textContent.replace("com.google", gmsCoreVendorGroupId)
}
}
}
/**
* Patch the manifest to support GmsCore.
*/
fun patchManifest() {
val packageName = setOrGetFallbackPackageName(toPackageName)
document.getElementsByTagName("provider").asSequence().forEach { node ->
val providerElement = node.attributes.getNamedItem("android:authorities")
val transformations = mapOf(
"package=\"$fromPackageName" to "package=\"$packageName",
"android:authorities=\"$fromPackageName" to "android:authorities=\"$packageName",
"$fromPackageName.permission.C2D_MESSAGE" to "$packageName.permission.C2D_MESSAGE",
"$fromPackageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" to "$packageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION",
"com.google.android.c2dm" to "$gmsCoreVendorGroupId.android.c2dm",
"com.google.android.libraries.photos.api.mars" to "$gmsCoreVendorGroupId.android.apps.photos.api.mars",
"</queries>" to "<package android:name=\"$gmsCoreVendorGroupId.android.gms\"/></queries>",
)
providerElement.textContent = providerElement.textContent.split(";")
.joinToString(";") { authority ->
if (authority.startsWith("com.google")) {
authority.replace("com.google", gmsCoreVendorGroupId)
} else {
"$gmsCoreVendorGroupId.$authority"
}
}
}
val manifest = get("AndroidManifest.xml")
manifest.writeText(
transformations.entries.fold(manifest.readText()) { acc, (from, to) ->
acc.replace(
from,
to,
document.getNode("manifest")
.attributes.getNamedItem("package").textContent =
setOrGetFallbackPackageName(toPackageName)
document.getNode("queries").appendChild(
document.createElement("package").apply {
attributes.setNamedItem(
document.createAttribute("android:name").apply {
textContent = "$gmsCoreVendorGroupId.android.gms"
},
)
},
)
}
patchManifest()
addSpoofingMetadata()
val applicationNode = document.getNode("application")
// Spoof package name and signature.
applicationNode.appendChild(
document.createElement("meta-data").apply {
setAttribute(
"android:name",
"$gmsCoreVendorGroupId.android.gms.SPOOFED_PACKAGE_NAME",
)
setAttribute("android:value", fromPackageName)
},
)
applicationNode.appendChild(
document.createElement("meta-data").apply {
setAttribute(
"android:name",
"$gmsCoreVendorGroupId.android.gms.SPOOFED_PACKAGE_SIGNATURE",
)
setAttribute("android:value", spoofedPackageSignature)
},
)
// GmsCore presence detection in extension.
applicationNode.appendChild(
document.createElement("meta-data").apply {
// TODO: The name of this metadata should be dynamic.
setAttribute("android:name", "app.revanced.MICROG_PACKAGE_NAME")
setAttribute("android:value", "$gmsCoreVendorGroupId.android.gms")
},
)
}
executeBlock()
}
block()
}
private object Constants {
val GMS_PERMISSIONS = setOf(
"com.google.android.providers.gsf.permission.READ_GSERVICES",
"com.google.android.c2dm.permission.RECEIVE",
"com.google.android.c2dm.permission.SEND",
"com.google.android.gtalkservice.permission.GTALK_SERVICE",
"com.google.android.googleapps.permission.GOOGLE_AUTH",
"com.google.android.googleapps.permission.GOOGLE_AUTH.cp",
"com.google.android.googleapps.permission.GOOGLE_AUTH.local",
"com.google.android.googleapps.permission.GOOGLE_AUTH.mail",
"com.google.android.googleapps.permission.GOOGLE_AUTH.writely",
"com.google.android.gms.permission.ACTIVITY_RECOGNITION",
"com.google.android.gms.permission.AD_ID",
"com.google.android.gms.permission.AD_ID_NOTIFICATION",
"com.google.android.gms.auth.api.phone.permission.SEND",
"com.google.android.gms.permission.CAR_INFORMATION",
"com.google.android.gms.permission.CAR_SPEED",
"com.google.android.gms.permission.CAR_FUEL",
"com.google.android.gms.permission.CAR_MILEAGE",
"com.google.android.gms.permission.CAR_VENDOR_EXTENSION",
"com.google.android.gms.locationsharingreporter.periodic.STATUS_UPDATE",
"com.google.android.gms.auth.permission.GOOGLE_ACCOUNT_CHANGE",
)
val GMS_AUTHORITIES = setOf(
"google.android.gms.fileprovider",
"com.google.android.gms.auth.accounts",
"com.google.android.gms.chimera",
"com.google.android.gms.fonts",
"com.google.android.gms.phenotype",
"com.google.android.gsf.gservices",
"com.google.settings",
"subscribedfeeds",
)
val APP_PERMISSIONS = mutableSetOf<String>()
val APP_AUTHORITIES = mutableSetOf<String>()
}

View file

@ -6,6 +6,8 @@ import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.castContextFetchFingerprint
import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch
import app.revanced.patches.shared.misc.settings.preference.IntentPreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.primeMethodFingerprint
import app.revanced.patches.youtube.layout.buttons.overlay.hidePlayerOverlayButtonsPatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
@ -39,7 +41,7 @@ val gmsCoreSupportPatch = gmsCoreSupportPatch(
"20.07.39",
"20.13.41",
"20.14.43",
)
),
)
}
@ -56,18 +58,24 @@ private fun gmsCoreSupportResourcePatch(
val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption
PreferenceScreen.MISC.addPreferences(
IntentPreference(
"microg_settings",
intent = IntentPreference.Intent("", "org.microg.gms.ui.SettingsActivity") {
"$gmsCoreVendorGroupId.android.gms"
}
)
PreferenceScreenPreference(
"revanced_gms_core_screen",
preferences = setOf(
SwitchPreference("revanced_gms_core_check_updates"),
IntentPreference(
"revanced_gms_core_settings",
intent = IntentPreference.Intent("", "org.microg.gms.ui.SettingsActivity") {
"$gmsCoreVendorGroupId.android.gms"
},
),
),
),
)
}
},
) {
dependsOn(
addResourcesPatch,
settingsPatch,
accountCredentialsInvalidTextPatch
accountCredentialsInvalidTextPatch,
)
}

View file

@ -101,23 +101,31 @@ To translate new languages or improve the existing translations, visit translate
and changes made here must also be made there. -->
</patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch">
<string name="microg_settings_title">GmsCore Settings</string>
<string name="microg_settings_summary">Settings for GmsCore</string>
<string name="revanced_gms_core_screen_title">GmsCore</string>
<string name="revanced_gms_core_screen_summary">Settings related to GmsCore</string>
<string name="revanced_gms_core_check_updates_title">Check for GmsCore updates</string>
<string name="revanced_gms_core_check_updates_summary_on">Checking for updates is enabled</string>
<string name="revanced_gms_core_check_updates_summary_off">Checking for updates is disabled</string>
<string name="revanced_gms_core_settings_title">Open GmsCore Settings</string>
<string name="revanced_gms_core_settings_summary">Settings of GmsCore</string>
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
<string name="gms_core_toast_not_installed_message">MicroG GmsCore is not installed. Install it.</string>
<string name="gms_core_dialog_title">Action needed</string>
<string name="gms_core_dialog_not_whitelisted_not_allowed_in_background_message">"MicroG GmsCore does not have permission to run in the background.
<string name="revanced_gms_core_toast_not_installed_message">MicroG GmsCore is not installed. Install it.</string>
<string name="revanced_gms_core_dialog_title">Action needed</string>
<string name="revanced_gms_core_toast_update_check_failed_message">Failed to check for MicroG GmsCore updates</string>
<string name="revanced_gms_core_update_available_message">A new version (%s) of MicroG GmsCore is available. Currently, you are using version %s.</string>
<string name="revanced_gms_core_dialog_not_whitelisted_not_allowed_in_background_message">"MicroG GmsCore does not have permission to run in the background.
Follow the \"Don\'t kill my app\" guide for your phone, and apply the instructions to your MicroG installation.
This is required for the app to work."</string>
<string name="gms_core_dialog_open_website_text">Open website</string>
<string name="gms_core_dialog_not_whitelisted_using_battery_optimizations_message">"MicroG GmsCore battery optimizations must be disabled to prevent issues.
<string name="revanced_gms_core_dialog_open_website_text">Open website</string>
<string name="revanced_gms_core_dialog_cancel_text">Cancel</string>
<string name="revanced_gms_core_dialog_not_whitelisted_using_battery_optimizations_message">"MicroG GmsCore battery optimizations must be disabled to prevent issues.
Disabling battery optimizations for MicroG will not negatively affect battery usage.
Tap the continue button and allow optimization changes."</string>
<string name="gms_core_dialog_continue_text">Continue</string>
<string name="revanced_gms_core_dialog_continue_text">Continue</string>
</patch>
<patch id="misc.fix.playback.spoofVideoStreamsPatch">
<string name="revanced_spoof_video_streams_screen_title">Spoof video streams</string>