diff --git a/app/src/main/java/org/autojs/autojs/ui/main/scripts/ApkInfoDialogManager.kt b/app/src/main/java/org/autojs/autojs/ui/main/scripts/ApkInfoDialogManager.kt index 6feaf5f0..8da44cae 100644 --- a/app/src/main/java/org/autojs/autojs/ui/main/scripts/ApkInfoDialogManager.kt +++ b/app/src/main/java/org/autojs/autojs/ui/main/scripts/ApkInfoDialogManager.kt @@ -6,6 +6,7 @@ import android.content.pm.PackageInfo import android.content.pm.PackageManager import android.content.pm.PackageManager.GET_META_DATA import android.os.Build +import android.util.Log import android.view.LayoutInflater import android.view.View.MeasureSpec.UNSPECIFIED import androidx.appcompat.content.res.AppCompatResources @@ -37,6 +38,8 @@ import java.io.File object ApkInfoDialogManager { + private const val TAG = "ApkInfoDialogManager" + @JvmStatic @JvmOverloads @SuppressLint("SetTextI18n") @@ -179,7 +182,11 @@ object ApkInfoDialogManager { private fun getApkInfo(apkFile: File): ApkInfo? = runCatching { ApkFile(apkFile).use { parser -> - val meta = runCatching { parser.apkMeta }.getOrNull() + val meta = runCatching { parser.apkMeta } + .onFailure { + Log.d(TAG, "Failed to parse apk meta: ${apkFile.absolutePath}", it) + } + .getOrNull() val label = meta?.label val packageName = meta?.packageName val minSdkVersion = meta?.minSdkVersion?.toIntOrNull() diff --git a/modules/apk-parser/src/main/java/net/dongliu/apk/parser/AbstractApkFile.java b/modules/apk-parser/src/main/java/net/dongliu/apk/parser/AbstractApkFile.java index 43aa99ab..8f2bf2da 100644 --- a/modules/apk-parser/src/main/java/net/dongliu/apk/parser/AbstractApkFile.java +++ b/modules/apk-parser/src/main/java/net/dongliu/apk/parser/AbstractApkFile.java @@ -47,6 +47,7 @@ import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Set; +import java.util.IllformedLocaleException; /** * Common Apk Parser methods. @@ -332,9 +333,17 @@ public abstract class AbstractApkFile implements Closeable { } final ByteBuffer buffer = ByteBuffer.wrap(data); final ResourceTableParser resourceTableParser = new ResourceTableParser(buffer); - resourceTableParser.parse(); - this.resourceTable = resourceTableParser.getResourceTable(); - this.locales = resourceTableParser.locales; + try { + resourceTableParser.parse(); + this.resourceTable = resourceTableParser.getResourceTable(); + this.locales = resourceTableParser.locales; + } catch (final IllformedLocaleException | IllegalArgumentException e) { + // 容错回退: 当资源表中的语言/地区字段不合法或解析实现不兼容时, + // 退化为无资源表模式,允许后续 Manifest 解析继续进行。 + this.resourceTable = new ResourceTable(null); + this.locales = Collections.emptySet(); + // 如需记录日志,可在此处接入项目日志系统;为保持通用性,此处不抛出异常 + } } /** diff --git a/modules/apk-parser/src/main/java/net/dongliu/apk/parser/struct/resource/Type.java b/modules/apk-parser/src/main/java/net/dongliu/apk/parser/struct/resource/Type.java index ab7dbcdf..678c2f40 100644 --- a/modules/apk-parser/src/main/java/net/dongliu/apk/parser/struct/resource/Type.java +++ b/modules/apk-parser/src/main/java/net/dongliu/apk/parser/struct/resource/Type.java @@ -2,7 +2,6 @@ package net.dongliu.apk.parser.struct.resource; import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import net.dongliu.apk.parser.struct.ResourceValue; import net.dongliu.apk.parser.struct.StringPool; import net.dongliu.apk.parser.utils.Buffers; @@ -15,32 +14,90 @@ import java.util.Locale; * @author dongliu */ public class Type { - private String name; public final short id; - @NonNull public final Locale locale; - + /** + * see Densities.java for values + */ + public final int density; + private String name; private StringPool keyStringPool; private ByteBuffer buffer; private long[] offsets; private StringPool stringPool; - /** - * see Densities.java for values - */ - public final int density; - public Type(final @NonNull TypeHeader header) { this.id = header.getId(); final ResTableConfig config = header.config; - this.locale = new Locale.Builder() - .setLanguage(config.getLanguage()) - .setRegion(config.getCountry()) - .build(); + + // Normalize and handle language/region safely to avoid IllformedLocaleException. + // zh-CN: 规范化并容错处理 language/region, 避免 IllformedLocaleException. + final String rawLang = config.getLanguage(); + final String rawRegion = config.getCountry(); + + final String lang = sanitizeLanguage(rawLang); + final String region = sanitizeRegion(rawRegion); + + Locale tmpLocale; + try { + final Locale.Builder builder = new Locale.Builder(); + if (lang != null) { + builder.setLanguage(lang); + } + if (region != null) { + builder.setRegion(region); + } + tmpLocale = builder.build(); + } catch (final RuntimeException e) { + // Handle all invalid data including IllformedLocaleException. + // zh-CN: 包含 IllformedLocaleException 在内的所有非法数据的兜底. + if (lang != null) { + // At least keep valid language. + // zh-CN: 至少保留合法 language. + tmpLocale = new Locale(lang); + } else { + tmpLocale = Locale.ROOT; + } + } + this.locale = tmpLocale; + this.density = config.getDensity(); } + // Only allows 2 or 3 letter characters (BCP-47 common language subtags). + // zh-CN: 仅允许 2 或 3 位字母 (BCP-47 常见语言子标签). + private static String sanitizeLanguage(@Nullable String lang) { + if (lang == null) return null; + lang = lang.trim(); + if (lang.isEmpty()) return null; + // Filter out non-letter characters. + // zh-CN: 过滤非字母字符. + final String cleaned = lang.replaceAll("[^A-Za-z]", ""); + if (cleaned.length() == 2 || cleaned.length() == 3) { + return cleaned.toLowerCase(Locale.ROOT); + } + return null; + } + + // Only allows 2 letter or 3 digit characters (e.g. CN/US/419). + // zh-CN: 仅允许 2 位字母或 3 位数字 (如 CN/US/419). + private static String sanitizeRegion(@Nullable String region) { + if (region == null) return null; + region = region.trim(); + if (region.isEmpty()) return null; + + final String letters = region.replaceAll("[^A-Za-z]", ""); + if (letters.length() == 2) { + return letters.toUpperCase(Locale.ROOT); + } + final String digits = region.replaceAll("[^0-9]", ""); + if (digits.length() == 3) { + return digits; + } + return null; + } + @Nullable public ResourceEntry getResourceEntry(final int resId) { if (resId >= this.offsets.length) { @@ -49,8 +106,8 @@ public class Type { if (this.offsets[resId] == TypeHeader.NO_ENTRY) { return null; } - if( offsets[resId] >= buffer.limit() ) { - //System.out.println( "invalid offset: " + offsets[resId] ); + if (offsets[resId] >= buffer.limit()) { + // System.out.println( "invalid offset: " + offsets[resId] ); return null; } // read Resource Entries @@ -60,7 +117,7 @@ public class Type { private ResourceEntry readResourceEntry() { long beginPos = buffer.position(); -// ResourceEntry resourceEntry = new ResourceEntry(); + // ResourceEntry resourceEntry = new ResourceEntry(); // size is always 8(simple), or 16(complex) final int size = Buffers.readUShort(buffer); final int flags = Buffers.readUShort(buffer); @@ -75,22 +132,22 @@ public class Type { Buffers.position(buffer, beginPos + size); - //An individual complex Resource entry comprises an entry immediately followed by one or more fields. + // An individual complex Resource entry comprises an entry immediately followed by one or more fields. ResourceTableMap[] resourceTableMaps = new ResourceTableMap[(int) count]; for (int i = 0; i < count; i++) { resourceTableMaps[i] = readResourceTableMap(); } -// ResourceEntry resourceEntry = new ResourceEntry(size,flags,key,resourceTableMaps); - ResourceMapEntry resourceMapEntry = new ResourceMapEntry(size,flags,key,parent,count,resourceTableMaps); + // ResourceEntry resourceEntry = new ResourceEntry(size, flags, key, resourceTableMaps); + ResourceMapEntry resourceMapEntry = new ResourceMapEntry(size, flags, key, parent, count, resourceTableMaps); return resourceMapEntry; } else if ((flags & ResourceEntry.FLAG_COMPACT) != 0) { final ResourceValue value = ResourceValue.string((int) keyRef, stringPool); - return new ResourceEntry(size,flags,null,value); + return new ResourceEntry(size, flags, null, value); } else { String key = keyStringPool.get((int) keyRef); Buffers.position(buffer, beginPos + size); final ResourceValue value = ParseUtils.readResValue(buffer, stringPool); - return new ResourceEntry(size,flags,key,value); + return new ResourceEntry(size, flags, key, value); } } @@ -98,10 +155,10 @@ public class Type { final ResourceTableMap resourceTableMap = new ResourceTableMap(); resourceTableMap.setNameRef(Buffers.readUInt(this.buffer)); resourceTableMap.setResValue(ParseUtils.readResValue(this.buffer, this.stringPool)); - //noinspection StatementWithEmptyBody + // noinspection StatementWithEmptyBody if ((resourceTableMap.getNameRef() & 0x02000000) != 0) { - //read arrays - } else //noinspection StatementWithEmptyBody + // read arrays + } else // noinspection StatementWithEmptyBody if ((resourceTableMap.getNameRef() & 0x01000000) != 0) { // read attrs } else { @@ -145,9 +202,9 @@ public class Type { @Override public String toString() { return "Type{" + - "name='" + this.name + '\'' + - ", id=" + this.id + - ", locale=" + this.locale + - '}'; + "name='" + this.name + '\'' + + ", id=" + this.id + + ", locale=" + this.locale + + '}'; } } diff --git a/version.properties b/version.properties index cd739cd7..63465c60 100644 --- a/version.properties +++ b/version.properties @@ -1,5 +1,5 @@ -#Tue Dec 09 11:33:43 CST 2025 -BUILD_TIME=1765251223946 +#Tue Dec 09 12:09:37 CST 2025 +BUILD_TIME=1765253377144 COMPILE_SDK_VERSION=36 IMAGE_QUANT_CMAKE_VERSION=3.22.1 IMAGE_QUANT_NDK_VERSION=26.1.10909125