"""Package-based library filtering for APK DEX code analysis.
Determines whether a method belongs to a known library/framework package
and should be filtered out of content tables. This is the Android equivalent
of is_lib_or_thunk() in the Binary Ninja pipeline.
"""
import os
from typing import Dict, List
DEFAULT_LIBRARY_PREFIXES = [
"android.",
"androidx.",
"com.google.android.",
"com.google.firebase.",
"com.google.gson.",
"com.google.protobuf.",
"kotlin.",
"kotlinx.",
"org.apache.",
"com.squareup.",
"io.reactivex.",
"org.reactivestreams.",
"com.facebook.",
"com.crashlytics.",
"io.fabric.",
"org.junit.",
"org.mockito.",
]
class LibraryFilter:
"""Filters library/framework classes from APK analysis."""
def __init__(self, prefixes: List[str] = None):
env_prefixes = os.getenv("APK_LIBRARY_PREFIXES")
if env_prefixes:
self._prefixes = [p.strip() for p in env_prefixes.split(",") if p.strip()]
elif prefixes is not None:
self._prefixes = prefixes
else:
self._prefixes = DEFAULT_LIBRARY_PREFIXES
# Convert Dalvik-style prefixes to both formats for matching
self._dot_prefixes = tuple(self._prefixes)
self._stats = {"library": 0, "user": 0}
def is_library(self, class_name: str) -> bool:
"""Check if a class belongs to a known library/framework package.
Accepts both Java dot notation (com.example.Foo) and Dalvik
descriptor notation (Lcom/example/Foo;).
"""
# Normalize Dalvik descriptor to dot notation
normalized = self._normalize_class_name(class_name)
is_lib = normalized.startswith(self._dot_prefixes)
if is_lib:
self._stats["library"] += 1
else:
self._stats["user"] += 1
return is_lib
def get_filter_stats(self) -> Dict[str, int]:
"""Return counts of filtered vs. retained classes."""
return dict(self._stats)
@staticmethod
def _normalize_class_name(class_name: str) -> str:
"""Convert Dalvik descriptor to dot notation.
Lcom/example/Foo; -> com.example.Foo
com.example.Foo -> com.example.Foo
"""
if class_name.startswith("L") and class_name.endswith(";"):
return class_name[1:-1].replace("/", ".")
return class_name.replace("/", ".")