diff --git a/src/adb/AdbProvider.test.ts b/src/adb/AdbProvider.test.ts index 8615e4cdf1..77a460a94c 100644 --- a/src/adb/AdbProvider.test.ts +++ b/src/adb/AdbProvider.test.ts @@ -709,7 +709,7 @@ it("projects package details from dumpsys package output", async () => { }); expect(result).toMatchObject({ version_name: "1.2.3", - version_code: 45, + version_code: "45", first_install_time: "2026-01-02 03:04:05", last_update_time: "2026-02-03 04:05:06", installer_package_name: "com.android.vending", @@ -719,6 +719,30 @@ it("projects package details from dumpsys package output", async () => { }); }); +// PackageInfo.getLongVersionCode() combines versionCodeMajor and versionCode +// into a signed 64-bit long, which JSON numbers cannot hold exactly. +it.each(["9007199254740993", "-4294967296"])( + "keeps the long version code %s exactly as dumpsys prints it", + async (versionCode) => { + const { binary } = await writeAdbStub( + shellStub({ + "dumpsys:package": `Packages: + Package [com.example] (abcdef): + versionCode=${versionCode} minSdk=24 targetSdk=34 +`, + }), + ); + const result = await evidenceResult(providerOver(binary), { + operation: "inspect_adb_package", + input: { serial: "emulator-5554", package: "com.example" }, + }); + expect(result).toMatchObject({ + version_code: versionCode, + coverage: "complete", + }); + }, +); + // Android 10 and 11 append ": restricted=true" to hard- and soft-restricted // entries of the requested-permissions list (Settings.dumpPackageLPr). it("counts requested permissions that the device marks as restricted", async () => { diff --git a/src/adb/AdbShellOutput.ts b/src/adb/AdbShellOutput.ts index 70a1584a22..90ed6f99d9 100644 --- a/src/adb/AdbShellOutput.ts +++ b/src/adb/AdbShellOutput.ts @@ -193,7 +193,8 @@ export const parseWindowFocusOutput = ( export interface AdbPackageDetails { readonly versionName: string | null; - readonly versionCode: number | null; + /** Signed 64-bit long version code exactly as Package Manager prints it. */ + readonly versionCode: string | null; readonly firstInstallTime: string | null; readonly lastUpdateTime: string | null; readonly installerPackageName: string | null; @@ -214,7 +215,7 @@ export const parseDumpsysPackageOutput = ( ): AdbPackageDetails => { const details: { versionName: string | null; - versionCode: number | null; + versionCode: string | null; firstInstallTime: string | null; lastUpdateTime: string | null; installerPackageName: string | null; @@ -252,8 +253,8 @@ export const parseDumpsysPackageOutput = ( if (key === "versionName" && details.versionName === null) details.versionName = value === "" ? null : value; else if (key === "versionCode" && details.versionCode === null) { - const parsed = /^(\d+)/u.exec(value); - if (parsed !== null) details.versionCode = Number(parsed[1]!); + const parsed = /^(-?\d+)/u.exec(value); + if (parsed !== null) details.versionCode = parsed[1]!; } else if (key === "firstInstallTime" && details.firstInstallTime === null) details.firstInstallTime = value === "" ? null : value; else if (key === "lastUpdateTime" && details.lastUpdateTime === null) diff --git a/src/domain/adb/adbDeviceAnalysis.ts b/src/domain/adb/adbDeviceAnalysis.ts index ca6cf78e58..e5b14b8ee1 100644 --- a/src/domain/adb/adbDeviceAnalysis.ts +++ b/src/domain/adb/adbDeviceAnalysis.ts @@ -527,7 +527,13 @@ export const adbResultSchemas = { serial: z.string().min(1), package_name: packageNameSchema, version_name: z.string().nullable(), - version_code: z.number().int().nullable(), + version_code: z + .string() + .regex(/^-?\d+$/u) + .nullable() + .describe( + "Long version code exactly as dumpsys package prints it; may exceed the JSON safe-integer range", + ), first_install_time: z.string().nullable(), last_update_time: z.string().nullable(), installer_package_name: z.string().nullable(), diff --git a/tests/boundary/mcp/adbMcp.test.ts b/tests/boundary/mcp/adbMcp.test.ts index 656855dd0e..61af5378fe 100644 --- a/tests/boundary/mcp/adbMcp.test.ts +++ b/tests/boundary/mcp/adbMcp.test.ts @@ -94,7 +94,7 @@ const results: Readonly> = { serial: "emulator-5554", package_name: "com.example", version_name: "1.2.3", - version_code: 45, + version_code: "45", first_install_time: "2026-01-02 03:04:05", last_update_time: "2026-02-03 04:05:06", installer_package_name: null,