From eddcb6c2f4c5a7a8a37132114d70788d47eb28f9 Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Fri, 3 Apr 2026 10:27:28 +0200 Subject: [PATCH] XSharedPreferences: Fix NoSuchMethodError on Android 17 Beta 3 Starting with Android 17 Beta 3 (detected in framework.jar, though not yet published to AOSP), the internal API for XmlUtils has changed its method signature. Specifically, the return type of readMapXml was modified from HashMap to the more generic Map interface. Old signature: ``` Lcom/android/internal/util/XmlUtils;->readMapXml(Ljava/io/InputStream;)Ljava/util/HashMap; ``` New signature: ``` Lcom/android/internal/util/XmlUtils;->readMapXml(Ljava/io/InputStream;)Ljava/util/Map; `` We migrate the call to use reflection. --- .../com/android/internal/util/XmlUtils.java | 14 -------- .../android/xposed/XSharedPreferences.java | 36 +++++++++++++++++-- 2 files changed, 33 insertions(+), 17 deletions(-) delete mode 100644 hiddenapi/stubs/src/main/java/com/android/internal/util/XmlUtils.java diff --git a/hiddenapi/stubs/src/main/java/com/android/internal/util/XmlUtils.java b/hiddenapi/stubs/src/main/java/com/android/internal/util/XmlUtils.java deleted file mode 100644 index 3e35527e6..000000000 --- a/hiddenapi/stubs/src/main/java/com/android/internal/util/XmlUtils.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.android.internal.util; - -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; - -public class XmlUtils { - @SuppressWarnings("rawtypes") - public static final HashMap readMapXml(InputStream in) throws XmlPullParserException, IOException { - throw new UnsupportedOperationException("STUB"); - } -} diff --git a/legacy/src/main/java/de/robv/android/xposed/XSharedPreferences.java b/legacy/src/main/java/de/robv/android/xposed/XSharedPreferences.java index 47cd192ee..e3976013c 100644 --- a/legacy/src/main/java/de/robv/android/xposed/XSharedPreferences.java +++ b/legacy/src/main/java/de/robv/android/xposed/XSharedPreferences.java @@ -6,8 +6,6 @@ import android.os.Environment; import android.preference.PreferenceManager; -import com.android.internal.util.XmlUtils; - import org.lsposed.lspd.util.Utils.Log; import org.matrix.vector.impl.core.VectorServiceClient; import org.matrix.vector.impl.utils.VectorMetaDataReader; @@ -18,6 +16,8 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.nio.file.AccessDeniedException; import java.nio.file.ClosedWatchServiceException; import java.nio.file.Path; @@ -43,6 +43,7 @@ public final class XSharedPreferences implements SharedPreferences { private static final String TAG = "XSharedPreferences"; private static final HashMap sWatcherKeyInstances = new HashMap<>(); private static final Object sContent = new Object(); + private static final Method sReadMapXmlMethod; private static Thread sWatcherDaemon = null; private static WatchService sWatcher; @@ -55,6 +56,19 @@ public final class XSharedPreferences implements SharedPreferences { private long mFileSize; private WatchKey mWatchKey; + static { + Method method = null; + try { + // Find the class and method once during class initialization + Class xmlUtils = Class.forName("com.android.internal.util.XmlUtils"); + method = xmlUtils.getDeclaredMethod("readMapXml", InputStream.class); + method.setAccessible(true); + } catch (Exception e) { + Log.e(TAG, "Failed to find com.android.internal.util.XmlUtils.readMapXml", e); + } + sReadMapXmlMethod = method; + } + private static void initWatcherDaemon() { sWatcherDaemon = new Thread() { @Override @@ -304,6 +318,22 @@ public void run() { }.start(); } + private Map performReadMapXml(InputStream str) throws XmlPullParserException, IOException { + try { + if (sReadMapXmlMethod == null) throw new IOException("Internal API not found"); + return (Map) sReadMapXmlMethod.invoke(null, str); + } catch (InvocationTargetException e) { + // Unwrap and throw the real error + Throwable cause = e.getCause(); + if (cause instanceof XmlPullParserException) throw (XmlPullParserException) cause; + if (cause instanceof IOException) throw (IOException) cause; + if (cause instanceof RuntimeException) throw (RuntimeException) cause; + throw new RuntimeException(cause); + } catch (IllegalAccessException e) { + throw new IOException("Access denied to internal API", e); + } + } + @SuppressWarnings({"rawtypes", "unchecked"}) private void loadFromDiskLocked() { if (mLoaded) { @@ -315,7 +345,7 @@ private void loadFromDiskLocked() { try { result = SELinuxHelper.getAppDataFileService().getFileInputStream(mFilename, mFileSize, mLastModified); if (result.stream != null) { - map = XmlUtils.readMapXml(result.stream); + map = performReadMapXml(result.stream); result.stream.close(); } else { // The file is unchanged, keep the current values