diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/GmsCoreSupport.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/GmsCoreSupport.java index 3b544371b..71e815f81 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/GmsCoreSupport.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/GmsCoreSupport.java @@ -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 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 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 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(); } } diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java index e9dc280a8..f7c6a3215 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java @@ -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. // diff --git a/patches/src/main/kotlin/app/revanced/patches/googlenews/misc/gms/GmsCoreSupportPatch.kt b/patches/src/main/kotlin/app/revanced/patches/googlenews/misc/gms/GmsCoreSupportPatch.kt index 630e737cc..d03ce31e7 100644 --- a/patches/src/main/kotlin/app/revanced/patches/googlenews/misc/gms/GmsCoreSupportPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/googlenews/misc/gms/GmsCoreSupportPatch.kt @@ -27,5 +27,4 @@ private fun gmsCoreSupportResourcePatch( toPackageName = REVANCED_MAGAZINES_PACKAGE_NAME, spoofedPackageSignature = "24bb24c05e47e0aefa68a58a766179d9b613a666", gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption, - addStringResources = false, ) diff --git a/patches/src/main/kotlin/app/revanced/patches/googlephotos/misc/gms/GmsCoreSupportPatch.kt b/patches/src/main/kotlin/app/revanced/patches/googlephotos/misc/gms/GmsCoreSupportPatch.kt index 75d265be3..3ed14a29d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/googlephotos/misc/gms/GmsCoreSupportPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/googlephotos/misc/gms/GmsCoreSupportPatch.kt @@ -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, ) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/gms/GmsCoreSupportPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/gms/GmsCoreSupportPatch.kt index 0d6b319d8..1a7a58db3 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/gms/GmsCoreSupportPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/gms/GmsCoreSupportPatch.kt @@ -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, + ), ) } diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/gms/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/gms/Fingerprints.kt index 27eebb8ab..354837011 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/gms/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/gms/Fingerprints.kt @@ -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() diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/gms/GmsCoreSupportPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/gms/GmsCoreSupportPatch.kt index fb773457f..59c63f2da 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/gms/GmsCoreSupportPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/gms/GmsCoreSupportPatch.kt @@ -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()?.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()?.string == fromPackageName + } + val register = primeMethod.getInstruction(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, 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, - 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", - "" to "", - ) + 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() + + val APP_AUTHORITIES = mutableSetOf() +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch.kt index 2375c4211..9986ed9a0 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch.kt @@ -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, ) } diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index e41691719..1363e85a8 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -101,23 +101,31 @@ To translate new languages or improve the existing translations, visit translate and changes made here must also be made there. --> - GmsCore Settings - Settings for GmsCore + GmsCore + Settings related to GmsCore + Check for GmsCore updates + Checking for updates is enabled + Checking for updates is disabled + Open GmsCore Settings + Settings of GmsCore - MicroG GmsCore is not installed. Install it. - Action needed - "MicroG GmsCore does not have permission to run in the background. + MicroG GmsCore is not installed. Install it. + Action needed + Failed to check for MicroG GmsCore updates + A new version (%s) of MicroG GmsCore is available. Currently, you are using version %s. + "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." - Open website - "MicroG GmsCore battery optimizations must be disabled to prevent issues. + Open website + Cancel + "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." - Continue + Continue Spoof video streams