Files
podlite/tools/scan_dalvik.py
T
XoséandClaude Opus 5.5 666a7eb8cd Podlite 1.12: a build for Android 4.1 (Xperia J) with ExoPlayer, speed and skip silences
- One build per Android level, as in Tenda and Plexamp Lite: build/Podlite.apk (Android 2.2+, versionCode 1308) and
  build/Podlite-api16.apk (Android 4.1+, 1316), from shared src/ plus platform/api8 or platform/api16; build.sh now
  runs on the local toolchain (aapt2, dx or R8, signapk.py; the JKS key converted for the run).
- Android 4.1 build: ExoPlayer 2.18.7 behind an Engine interface, with playback speed (player header) and skipping
  silences (Settings), a 3-minute buffer and MediaPlayer as fallback for audio it can't decode; a notification and
  lock screen with the cover and buttons; Holo; a menu button on phones without a Menu key; metered networks for
  "download on Wi-Fi only".
- Both builds: tlskit's Http.abort fix (a finished response's pooled connection is no longer closed under a later
  request); the lock screen's and newer headsets' play/pause keys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:56:18 +01:00

47 lines
2.3 KiB
Python

# Find invokeinterface calls to java.lang.Object methods (toString/equals/hashCode/getClass) through an
# interface that doesn't declare them itself: Dalvik before 4.0 can't resolve those (NoSuchMethodError).
import os, re, subprocess, sys, zipfile
classes = sys.argv[1]; android_jar = sys.argv[2]
OBJ = {"toString:()Ljava/lang/String;", "equals:(Ljava/lang/Object;)Z", "hashCode:()I", "getClass:()Ljava/lang/Class;"}
z = zipfile.ZipFile(android_jar)
declared = {}
def declares(iface, m):
key = (iface, m)
if key in declared: return declared[key]
path = os.path.join(classes, iface + ".class")
src = path if os.path.exists(path) else None
args = ["javap", "-p", "-s", "-cp", classes if src else android_jar, iface.replace("/", ".")]
out = subprocess.run(args, capture_output=True, text=True).stdout
name, desc = m.split(":")
found = False
lines = out.splitlines()
for i, l in enumerate(lines):
if re.search(r"\b" + name + r"\(", l) and i + 1 < len(lines) and desc in lines[i + 1]:
found = True
# superinterfaces declaring it also count
if not found:
mm = re.search(r"interface \S+ extends (.+?) \{", out)
if mm:
for sup in mm.group(1).split(","):
sup = sup.strip().split("<")[0].replace(".", "/")
if sup and declares(sup, m): found = True
declared[key] = found
return found
hits = {}
files = [os.path.join(dp, f) for dp, _, fs in os.walk(classes) for f in fs if f.endswith(".class")]
for i in range(0, len(files), 200):
out = subprocess.run(["javap", "-c", "-p"] + files[i:i+200], capture_output=True, text=True).stdout
cur = None
for line in out.splitlines():
m = re.match(r"^(?:public |final |abstract |class |interface )*.*?(?:class|interface) (\S+)", line)
if m and not line.startswith(" "): cur = m.group(1)
mi = re.search(r"invokeinterface\s+#\d+,\s*\d+\s*// InterfaceMethod (\S+)\.(\w+):(\S+)", line)
if mi:
iface, name, desc = mi.group(1), mi.group(2), mi.group(3)
sig = name + ":" + desc
if sig in OBJ and not declares(iface, sig):
hits.setdefault((iface, sig), set()).add(cur)
for (iface, sig), users in sorted(hits.items()):
print(f"{iface}.{sig} <- {len(users)} class(es): " + ", ".join(sorted(users))[:300])
print("total patterns:", len(hits))