fix(adb)!: keep the exact Android long version code (#3485)

inspect_adb_package converted the dumpsys versionCode, a signed 64-bit long
(versionCodeMajor << 32 | versionCode), to a JavaScript number. Values above
2^53 were rounded, so 9007199254740993 was reported as 9007199254740992 with
the same Evidence ID, and negative values became null.

Return version_code as the exact decimal string dumpsys prints, matching the
apktool and JADX version_code contracts.

BREAKING CHANGE: inspect_adb_package version_code is now a decimal string
("45") instead of a JSON number (45). Read it as a string, or parse it with
BigInt when numeric comparison is needed.

Fixes #3368

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kaoru0822andClaude Opus 5.5 authored and GitHub committed 2026-10-14 17:01:25 -07:00
1 parent fa318995f5
commit 7666a482b6
4 files changed
+38 -7

No files matched your search

+25 -1
View File
@@ -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 () => {
+5 -4
View File
@@ -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)
+7 -1
View File
@@ -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(),
+1 -1
View File
@@ -94,7 +94,7 @@ const results: Readonly<Record<string, unknown>> = {
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,