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
9 changed files with 571 additions and 696 deletions
@@ -1,8 +1,5 @@
package app.revanced.extension.shared; 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.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
@@ -17,24 +14,96 @@ import android.os.PowerManager;
import android.provider.Settings; import android.provider.Settings;
import android.util.Pair; import android.util.Pair;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import androidx.annotation.Nullable; 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.HttpURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.Locale; import java.util.Locale;
import app.revanced.extension.shared.requests.Requester; import static app.revanced.extension.shared.StringRef.str;
import app.revanced.extension.shared.requests.Route; import static app.revanced.extension.shared.requests.Route.Method.GET;
import app.revanced.extension.shared.ui.CustomDialog;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class GmsCoreSupport { public class GmsCoreSupport {
private static final String GMS_CORE_PACKAGE_NAME private static GmsCore gmsCore = GmsCore.UNKNOWN;
= getGmsCoreVendorGroupId() + ".android.gms";
private static final Uri GMS_CORE_PROVIDER static {
= Uri.parse("content://" + getGmsCoreVendorGroupId() + ".android.gsf.gservices/prefix"); 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;
*/
public static boolean isPackageNameOriginal() {
String originalPackageName = getOriginalPackageName();
return originalPackageName == null
|| originalPackageName.equals(Utils.getContext().getPackageName());
}
private enum GmsCore {
REVANCED("app.revanced", "https://github.com/revanced/gmscore/releases/latest", () -> {
try {
HttpURLConnection connection = Requester.getConnectionFromRoute(
"https://api.github.com",
new Route(GET, "/repos/revanced/gmscore/releases/latest")
);
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
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(() -> "Failed to fetch latest GmsCore version from GitHub", ex);
return null;
}
}),
UNKNOWN(getGmsCoreVendorGroupId(), getGmsCoreVendorGroupId() + "android.gms", () -> null);
private static final String DONT_KILL_MY_APP_URL private static final String DONT_KILL_MY_APP_URL
= "https://dontkillmyapp.com/"; = "https://dontkillmyapp.com/";
private static final Route DONT_KILL_MY_APP_MANUFACTURER_API private static final Route DONT_KILL_MY_APP_MANUFACTURER_API
@@ -48,78 +117,33 @@ public class GmsCoreSupport {
* If a manufacturer specific page exists on DontKillMyApp. * If a manufacturer specific page exists on DontKillMyApp.
*/ */
@Nullable @Nullable
private static volatile Boolean DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED; private volatile Boolean dontKillMyAppManufacturerSupported;
private static String getOriginalPackageName() { private final String groupId;
return null; // Modified during patching. 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;
} }
/** String getGroupId() {
* @return If the current package name is the same as the original unpatched app. return groupId;
* If `GmsCore support` was not included during patching, this returns true;
*/
public static boolean isPackageNameOriginal() {
String originalPackageName = getOriginalPackageName();
return originalPackageName == null
|| originalPackageName.equals(Utils.getContext().getPackageName());
} }
private static void open(String queryOrLink) { void check(Activity context) {
Logger.printInfo(() -> "Opening link: " + queryOrLink); checkInstallation(context);
checkUpdates(context);
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, private void checkInstallation(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 { try {
// Verify the user has not included GmsCore for a root installation. // Verify the user has not included GmsCore for a root installation.
// GmsCore Support changes the package name, but with a mounted installation // GmsCore Support changes the package name, but with a mounted installation
@@ -139,13 +163,14 @@ public class GmsCoreSupport {
// Verify GmsCore is installed. // Verify GmsCore is installed.
try { try {
PackageManager manager = context.getPackageManager(); PackageManager manager = context.getPackageManager();
manager.getPackageInfo(GMS_CORE_PACKAGE_NAME, PackageManager.GET_ACTIVITIES); manager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
} catch (PackageManager.NameNotFoundException exception) { } catch (PackageManager.NameNotFoundException exception) {
Logger.printInfo(() -> "GmsCore was not found"); Logger.printInfo(() -> "GmsCore was not found");
// Cannot show a dialog and must show a toast, // Cannot show a dialog and must show a toast,
// because on some installations the app crashes before a dialog can be displayed. // because on some installations the app crashes before a dialog can be displayed.
Utils.showToastLong(str("gms_core_toast_not_installed_message")); Utils.showToastLong(str("revanced_gms_core_toast_not_installed_message"));
open(getGmsCoreDownload());
open(downloadQuery);
return; return;
} }
@@ -158,14 +183,14 @@ public class GmsCoreSupport {
Logger.printInfo(() -> "GmsCore is not whitelisted from battery optimizations"); Logger.printInfo(() -> "GmsCore is not whitelisted from battery optimizations");
showBatteryOptimizationDialog(context, showBatteryOptimizationDialog(context,
"gms_core_dialog_not_whitelisted_using_battery_optimizations_message", "revanced_gms_core_dialog_not_whitelisted_using_battery_optimizations_message",
"gms_core_dialog_continue_text", "revanced_gms_core_dialog_continue_text",
(dialog, id) -> openGmsCoreDisableBatteryOptimizationsIntent(context)); (dialog, id) -> openGmsCoreDisableBatteryOptimizationsIntent(context));
return; return;
} }
// Check if GmsCore is currently running in the background. // Check if GmsCore is currently running in the background.
var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER); var client = context.getContentResolver().acquireContentProviderClient(gmsCoreProvider);
//noinspection TryFinallyCanBeTryWithResources //noinspection TryFinallyCanBeTryWithResources
try { try {
if (client == null) { if (client == null) {
@@ -173,8 +198,8 @@ public class GmsCoreSupport {
checkIfDontKillMyAppSupportsManufacturer(); checkIfDontKillMyAppSupportsManufacturer();
showBatteryOptimizationDialog(context, showBatteryOptimizationDialog(context,
"gms_core_dialog_not_whitelisted_not_allowed_in_background_message", "revanced_gms_core_dialog_not_whitelisted_not_allowed_in_background_message",
"gms_core_dialog_open_website_text", "gmsrevanced_gms_core_log_open_website_text",
(dialog, id) -> openDontKillMyApp()); (dialog, id) -> openDontKillMyApp());
} }
} finally { } finally {
@@ -185,14 +210,133 @@ public class GmsCoreSupport {
} }
} }
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 @SuppressLint("BatteryLife") // Permission is part of GmsCore
private static void openGmsCoreDisableBatteryOptimizationsIntent(Activity activity) { private void openGmsCoreDisableBatteryOptimizationsIntent(Activity activity) {
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.fromParts("package", GMS_CORE_PACKAGE_NAME, null)); intent.setData(Uri.fromParts("package", packageName, null));
activity.startActivityForResult(intent, 0); activity.startActivityForResult(intent, 0);
} }
private static void checkIfDontKillMyAppSupportsManufacturer() { private void checkIfDontKillMyAppSupportsManufacturer() {
Utils.runOnBackgroundThread(() -> { Utils.runOnBackgroundThread(() -> {
try { try {
final long start = System.currentTimeMillis(); final long start = System.currentTimeMillis();
@@ -205,17 +349,17 @@ public class GmsCoreSupport {
Logger.printInfo(() -> "Manufacturer is " + (supported ? "" : "NOT ") Logger.printInfo(() -> "Manufacturer is " + (supported ? "" : "NOT ")
+ "listed on DontKillMyApp: " + BUILD_MANUFACTURER + "listed on DontKillMyApp: " + BUILD_MANUFACTURER
+ " fetch took: " + (System.currentTimeMillis() - start) + "ms"); + " fetch took: " + (System.currentTimeMillis() - start) + "ms");
DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED = supported; dontKillMyAppManufacturerSupported = supported;
} catch (Exception ex) { } catch (Exception ex) {
Logger.printInfo(() -> "Could not check if manufacturer is listed on DontKillMyApp: " Logger.printInfo(() -> "Could not check if manufacturer is listed on DontKillMyApp: "
+ BUILD_MANUFACTURER, ex); + BUILD_MANUFACTURER, ex);
DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED = null; dontKillMyAppManufacturerSupported = null;
} }
}); });
} }
private static void openDontKillMyApp() { private void openDontKillMyApp() {
final Boolean manufacturerSupported = DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED; final Boolean manufacturerSupported = dontKillMyAppManufacturerSupported;
String manufacturerPageToOpen; String manufacturerPageToOpen;
if (manufacturerSupported == null) { if (manufacturerSupported == null) {
@@ -237,30 +381,23 @@ public class GmsCoreSupport {
/** /**
* @return If GmsCore is not whitelisted from battery optimizations. * @return If GmsCore is not whitelisted from battery optimizations.
*/ */
private static boolean batteryOptimizationsEnabled(Context context) { private boolean batteryOptimizationsEnabled(Context context) {
//noinspection ObsoleteSdkInt //noinspection ObsoleteSdkInt
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// Android 5.0 does not have battery optimization settings. // Android 5.0 does not have battery optimization settings.
return false; return false;
} }
var powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); var powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
return !powerManager.isIgnoringBatteryOptimizations(GMS_CORE_PACKAGE_NAME); return !powerManager.isIgnoringBatteryOptimizations(packageName);
} }
private static boolean isAndroidAutomotive(Context context) { private boolean isAndroidAutomotive(Context context) {
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); 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() { @FunctionalInterface
return "app.revanced"; // Modified during patching. private interface GetLatestVersion {
String get();
} }
} }
@@ -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 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 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. // Settings shared by YouTube and YouTube Music.
// //
@@ -27,5 +27,4 @@ private fun gmsCoreSupportResourcePatch(
toPackageName = REVANCED_MAGAZINES_PACKAGE_NAME, toPackageName = REVANCED_MAGAZINES_PACKAGE_NAME,
spoofedPackageSignature = "24bb24c05e47e0aefa68a58a766179d9b613a666", spoofedPackageSignature = "24bb24c05e47e0aefa68a58a766179d9b613a666",
gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption, gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption,
addStringResources = false,
) )
@@ -22,7 +22,6 @@ private fun gmsCoreSupportResourcePatch(
) = app.revanced.patches.shared.misc.gms.gmsCoreSupportResourcePatch( ) = app.revanced.patches.shared.misc.gms.gmsCoreSupportResourcePatch(
fromPackageName = PHOTOS_PACKAGE_NAME, fromPackageName = PHOTOS_PACKAGE_NAME,
toPackageName = REVANCED_PHOTOS_PACKAGE_NAME, toPackageName = REVANCED_PHOTOS_PACKAGE_NAME,
addStringResources = false,
spoofedPackageSignature = "24bb24c05e47e0aefa68a58a766179d9b613a600", spoofedPackageSignature = "24bb24c05e47e0aefa68a58a766179d9b613a600",
gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption, gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption,
) )
@@ -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.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.music.misc.extension.sharedExtensionPatch 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.MUSIC_PACKAGE_NAME
import app.revanced.patches.music.misc.gms.Constants.REVANCED_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.PreferenceScreen
import app.revanced.patches.music.misc.settings.settingsPatch import app.revanced.patches.music.misc.settings.settingsPatch
import app.revanced.patches.music.misc.spoof.spoofVideoStreamsPatch 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.castContextFetchFingerprint
import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch 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.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.shared.primeMethodFingerprint
@Suppress("unused") @Suppress("unused")
@@ -32,8 +34,8 @@ val gmsCoreSupportPatch = gmsCoreSupportPatch(
compatibleWith( compatibleWith(
MUSIC_PACKAGE_NAME( MUSIC_PACKAGE_NAME(
"7.29.52", "7.29.52",
"8.10.52" "8.10.52",
) ),
) )
} }
@@ -50,21 +52,27 @@ private fun gmsCoreSupportResourcePatch(
val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption
PreferenceScreen.MISC.addPreferences( PreferenceScreen.MISC.addPreferences(
PreferenceScreenPreference(
"revanced_gms_core_screen",
preferences = setOf(
SwitchPreference("revanced_gms_core_check_updates"),
IntentPreference( IntentPreference(
"microg_settings", "revanced_gms_core_settings",
intent = IntentPreference.Intent("", "org.microg.gms.ui.SettingsActivity") { intent = IntentPreference.Intent("", "org.microg.gms.ui.SettingsActivity") {
"$gmsCoreVendorGroupId.android.gms" "$gmsCoreVendorGroupId.android.gms"
} },
),
),
),
) )
) },
}
) { ) {
dependsOn( dependsOn(
addResourcesPatch, addResourcesPatch,
settingsPatch, settingsPatch,
fileProviderPatch( fileProviderPatch(
MUSIC_PACKAGE_NAME, MUSIC_PACKAGE_NAME,
REVANCED_MUSIC_PACKAGE_NAME REVANCED_MUSIC_PACKAGE_NAME,
) ),
) )
} }
@@ -24,7 +24,7 @@ internal val serviceCheckFingerprint = fingerprint {
strings("Google Play Services not available") strings("Google Play Services not available")
} }
internal val gmsCoreSupportFingerprint = fingerprint { internal val getGmsCoreVendorGroupIdFingerprint = fingerprint {
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC) accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
returns("Ljava/lang/String;") returns("Ljava/lang/String;")
parameters() parameters()
@@ -2,26 +2,18 @@ package app.revanced.patches.shared.misc.gms
import app.revanced.patcher.Fingerprint import app.revanced.patcher.Fingerprint
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction 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.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.BytecodePatchBuilder import app.revanced.patcher.patch.*
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.patches.all.misc.packagename.changePackageNamePatch import app.revanced.patches.all.misc.packagename.changePackageNamePatch
import app.revanced.patches.all.misc.packagename.setOrGetFallbackPackageName import app.revanced.patches.all.misc.packagename.setOrGetFallbackPackageName
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch 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.APP_AUTHORITIES
import app.revanced.patches.shared.misc.gms.Constants.AUTHORITIES import app.revanced.patches.shared.misc.gms.Constants.APP_PERMISSIONS
import app.revanced.patches.shared.misc.gms.Constants.PERMISSIONS import app.revanced.patches.shared.misc.gms.Constants.GMS_AUTHORITIES
import app.revanced.util.getReference import app.revanced.patches.shared.misc.gms.Constants.GMS_PERMISSIONS
import app.revanced.util.returnEarly import app.revanced.util.*
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction 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.iface.reference.StringReference
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableStringReference import com.android.tools.smali.dexlib2.immutable.reference.ImmutableStringReference
import com.android.tools.smali.dexlib2.util.MethodUtil import com.android.tools.smali.dexlib2.util.MethodUtil
import org.w3c.dom.Element import java.net.URI
import org.w3c.dom.Node
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/shared/GmsCoreSupport;" internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/shared/GmsCoreSupport;"
@@ -69,10 +60,7 @@ fun gmsCoreSupportPatch(
val gmsCoreVendorGroupIdOption = stringOption( val gmsCoreVendorGroupIdOption = stringOption(
key = "gmsCoreVendorGroupId", key = "gmsCoreVendorGroupId",
default = "app.revanced", default = "app.revanced",
values = values = mapOf("ReVanced" to "app.revanced"),
mapOf(
"ReVanced" to "app.revanced",
),
title = "GmsCore vendor group ID", title = "GmsCore vendor group ID",
description = "The vendor's group ID for GmsCore.", description = "The vendor's group ID for GmsCore.",
required = true, required = true,
@@ -84,9 +72,9 @@ fun gmsCoreSupportPatch(
extensionPatch, extensionPatch,
) )
val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption
execute { execute {
val gmsCoreVendorGroupId = gmsCoreVendorGroupIdOption.value!!
fun transformStringReferences(transform: (str: String) -> String?) = classes.forEach { fun transformStringReferences(transform: (str: String) -> String?) = classes.forEach {
val mutableClass by lazy { val mutableClass by lazy {
proxy(it).mutableClass proxy(it).mutableClass
@@ -118,80 +106,54 @@ fun gmsCoreSupportPatch(
} }
} }
// region Collection of transformations that are applied to all strings. fun transformPackages(string: String): String? = when (string) {
fun commonTransform(referencedString: String): String? = when (referencedString) {
"com.google", "com.google",
"com.google.android.gms", "com.google.android.gms",
in PERMISSIONS, in GMS_PERMISSIONS,
in ACTIONS, in GMS_AUTHORITIES,
in AUTHORITIES, -> if (string.startsWith("com.google")) {
-> referencedString.replace("com.google", gmsCoreVendorGroupId!!) 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 else -> null
} }
fun contentUrisTransform(str: String): String? { fun transformContentUrlAuthority(string: String) = if (!string.startsWith("content://")) {
// only when content:// uri null
if (str.startsWith("content://")) { } else {
// check if matches any authority runCatching { URI.create(string) }.map {
for (authority in AUTHORITIES) { when (it.authority) {
val uriPrefix = "content://$authority" in GMS_AUTHORITIES ->
if (str.startsWith(uriPrefix)) { if (it.authority.startsWith("com.google")) {
return str.replace( string.replace("com.google", gmsCoreVendorGroupId)
uriPrefix, } else {
"content://${authority.replace("com.google", gmsCoreVendorGroupId!!)}", string.replace(
it.authority,
"$gmsCoreVendorGroupId.${it.authority}",
) )
} }
}
// gms also has a 'subscribedfeeds' authority, check for that one too in APP_AUTHORITIES ->
val subFeedsUriPrefix = "content://subscribedfeeds" string.replace(it.authority, "$toPackageName.${it.authority}")
if (str.startsWith(subFeedsUriPrefix)) {
return str.replace(subFeedsUriPrefix, "content://$gmsCoreVendorGroupId.subscribedfeeds")
}
}
return null
}
fun packageNameTransform(fromPackageName: String, toPackageName: String): (String) -> String? = { string ->
when (string) {
"$fromPackageName.SuggestionProvider",
"$fromPackageName.fileprovider",
-> string.replace(fromPackageName, toPackageName)
else -> null else -> null
} }
}.getOrNull()
} }
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) val packageName = setOrGetFallbackPackageName(toPackageName)
// Transform all strings using all provided transforms, first match wins.
val transformations = arrayOf( val transformations = arrayOf(
::commonTransform, ::transformPackages,
::contentUrisTransform, ::transformContentUrlAuthority,
packageNameTransform(fromPackageName, packageName),
) )
transformStringReferences transform@{ string -> transformStringReferences transform@{ string ->
transformations.forEach { transform -> transformations.forEach { transform ->
transform(string)?.let { transformedString -> return@transform transformedString } transform(string)?.let { transformedString -> return@transform transformedString }
@@ -201,16 +163,26 @@ fun gmsCoreSupportPatch(
} }
// Specific method that needs to be patched. // 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. // Return these methods early to prevent the app from crashing.
earlyReturnFingerprints.forEach { it.method.returnEarly() } earlyReturnFingerprints.forEach { it.method.returnEarly() }
serviceCheckFingerprint.method.returnEarly() serviceCheckFingerprint.method.returnEarly()
// Google Play Utility is not present in all apps, so we need to check if it's present. // Google Play Utility is not present in all apps, so we need to check if it's present.
if (googlePlayUtilityFingerprint.methodOrNull != null) { googlePlayUtilityFingerprint.methodOrNull?.returnEarly(0)
googlePlayUtilityFingerprint.method.returnEarly(0)
}
// Set original and patched package names for extension to use. // Set original and patched package names for extension to use.
originalPackageNameExtensionFingerprint.method.returnEarly(fromPackageName) originalPackageNameExtensionFingerprint.method.returnEarly(fromPackageName)
@@ -219,11 +191,11 @@ fun gmsCoreSupportPatch(
mainActivityOnCreateFingerprint.method.addInstruction( mainActivityOnCreateFingerprint.method.addInstruction(
0, 0,
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->" + "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. // Change the vendor of GmsCore in the extension.
gmsCoreSupportFingerprint.method.returnEarly(gmsCoreVendorGroupId!!) getGmsCoreVendorGroupIdFingerprint.method.returnEarly(gmsCoreVendorGroupId)
executeBlock() executeBlock()
} }
@@ -231,274 +203,6 @@ fun gmsCoreSupportPatch(
block() 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 * 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. * 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 executeBlock The additional execution block of the patch.
* @param block The additional block to build the patch. * @param block The additional block to build the patch.
*/ */
fun gmsCoreSupportResourcePatch( // This is here only for binary compatibility. fun gmsCoreSupportResourcePatch(
fromPackageName: String, fromPackageName: String,
toPackageName: String, toPackageName: String,
spoofedPackageSignature: String, spoofedPackageSignature: String,
gmsCoreVendorGroupIdOption: Option<String>, gmsCoreVendorGroupIdOption: Option<String>,
executeBlock: ResourcePatchContext.() -> Unit = {}, executeBlock: ResourcePatchContext.() -> Unit = {},
block: ResourcePatchBuilder.() -> 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 { ) = resourcePatch {
dependsOn( dependsOn(
changePackageNamePatch, changePackageNamePatch,
addResourcesPatch, addResourcesPatch,
) )
val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption val gmsCoreVendorGroupId = gmsCoreVendorGroupIdOption.value!!
execute { 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("AndroidManifest.xml").use { document ->
val applicationNode = document.getElementsByTagName("permission").asSequence().forEach { node ->
document val nameElement = node.attributes.getNamedItem("android:name")
.getElementsByTagName("application") nameElement.textContent = toPackageName + nameElement.textContent
.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") { document.getElementsByTagName("uses-permission").asSequence().forEach { node ->
setAttribute("android:name", "$gmsCoreVendorGroupId.android.gms.SPOOFED_PACKAGE_SIGNATURE") val nameElement = node.attributes.getNamedItem("android:name")
setAttribute("android:value", spoofedPackageSignature) if (nameElement.textContent in GMS_PERMISSIONS) {
nameElement.textContent.replace("com.google", gmsCoreVendorGroupId)
}
} }
// GmsCore presence detection in extension. document.getElementsByTagName("provider").asSequence().forEach { node ->
applicationNode.adoptChild("meta-data") { val providerElement = node.attributes.getNamedItem("android:authorities")
// TODO: The name of this metadata should be dynamic.
setAttribute("android:name", "app.revanced.MICROG_PACKAGE_NAME") providerElement.textContent = providerElement.textContent.split(";")
setAttribute("android:value", "$gmsCoreVendorGroupId.android.gms") .joinToString(";") { authority ->
if (authority.startsWith("com.google")) {
authority.replace("com.google", gmsCoreVendorGroupId)
} else {
"$gmsCoreVendorGroupId.$authority"
} }
} }
} }
/** document.getNode("manifest")
* Patch the manifest to support GmsCore. .attributes.getNamedItem("package").textContent =
*/ setOrGetFallbackPackageName(toPackageName)
fun patchManifest() {
val packageName = setOrGetFallbackPackageName(toPackageName)
val transformations = mapOf( document.getNode("queries").appendChild(
"package=\"$fromPackageName" to "package=\"$packageName", document.createElement("package").apply {
"android:authorities=\"$fromPackageName" to "android:authorities=\"$packageName", attributes.setNamedItem(
"$fromPackageName.permission.C2D_MESSAGE" to "$packageName.permission.C2D_MESSAGE", document.createAttribute("android:name").apply {
"$fromPackageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" to "$packageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION", textContent = "$gmsCoreVendorGroupId.android.gms"
"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>",
)
val manifest = get("AndroidManifest.xml")
manifest.writeText(
transformations.entries.fold(manifest.readText()) { acc, (from, to) ->
acc.replace(
from,
to,
) )
}, },
) )
}
patchManifest() val applicationNode = document.getNode("application")
addSpoofingMetadata()
// 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() executeBlock()
} }
block() 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>()
}
@@ -6,6 +6,8 @@ import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.castContextFetchFingerprint import app.revanced.patches.shared.castContextFetchFingerprint
import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch 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.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.shared.primeMethodFingerprint
import app.revanced.patches.youtube.layout.buttons.overlay.hidePlayerOverlayButtonsPatch import app.revanced.patches.youtube.layout.buttons.overlay.hidePlayerOverlayButtonsPatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
@@ -39,7 +41,7 @@ val gmsCoreSupportPatch = gmsCoreSupportPatch(
"20.07.39", "20.07.39",
"20.13.41", "20.13.41",
"20.14.43", "20.14.43",
) ),
) )
} }
@@ -56,18 +58,24 @@ private fun gmsCoreSupportResourcePatch(
val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption
PreferenceScreen.MISC.addPreferences( PreferenceScreen.MISC.addPreferences(
PreferenceScreenPreference(
"revanced_gms_core_screen",
preferences = setOf(
SwitchPreference("revanced_gms_core_check_updates"),
IntentPreference( IntentPreference(
"microg_settings", "revanced_gms_core_settings",
intent = IntentPreference.Intent("", "org.microg.gms.ui.SettingsActivity") { intent = IntentPreference.Intent("", "org.microg.gms.ui.SettingsActivity") {
"$gmsCoreVendorGroupId.android.gms" "$gmsCoreVendorGroupId.android.gms"
} },
),
),
),
) )
) },
}
) { ) {
dependsOn( dependsOn(
addResourcesPatch, addResourcesPatch,
settingsPatch, settingsPatch,
accountCredentialsInvalidTextPatch accountCredentialsInvalidTextPatch,
) )
} }
@@ -101,23 +101,31 @@ To translate new languages or improve the existing translations, visit translate
and changes made here must also be made there. --> and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<string name="microg_settings_title">GmsCore Settings</string> <string name="revanced_gms_core_screen_title">GmsCore</string>
<string name="microg_settings_summary">Settings for 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. --> <!-- 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="revanced_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="revanced_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_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. 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> This is required for the app to work."</string>
<string name="gms_core_dialog_open_website_text">Open website</string> <string name="revanced_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_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. Disabling battery optimizations for MicroG will not negatively affect battery usage.
Tap the continue button and allow optimization changes."</string> 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>
<patch id="misc.fix.playback.spoofVideoStreamsPatch"> <patch id="misc.fix.playback.spoofVideoStreamsPatch">
<string name="revanced_spoof_video_streams_screen_title">Spoof video streams</string> <string name="revanced_spoof_video_streams_screen_title">Spoof video streams</string>