Skip to content

Commit cb53932

Browse files
fix(java2spec): populate missing descriptors in service mappings
Scan all Java files under 3rdparty/ for IFoo.Stub.asInterface(ServiceManager.getService(...)) patterns and @SystemService(Context.XXX) annotations to infer AIDL interface descriptors for services that are not registered in SystemServiceRegistry.java (e.g., telephony services using TelephonyFrameworkInitializer). This fills 62 previously-empty descriptors in the servicemanager spec, including "phone" -> com.android.internal.telephony.ITelephony.
1 parent fe6a6ac commit cb53932

2 files changed

Lines changed: 320 additions & 1 deletion

File tree

tools/cmd/java2spec/main.go

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func run(
6161
}
6262
fmt.Fprintf(os.Stderr, "java2spec: loaded %d existing package specs\n", len(specs))
6363

64-
if err := mergeServiceMappings(frameworksBase, specs); err != nil {
64+
if err := mergeServiceMappings(absThirdparty, frameworksBase, specs); err != nil {
6565
return fmt.Errorf("service mappings: %w", err)
6666
}
6767

@@ -91,7 +91,13 @@ func run(
9191
// servicemanager package spec. It also includes all _SERVICE constants from
9292
// Context.java that lack a SystemServiceRegistry match (with empty descriptor)
9393
// so that the generated ServiceName constants cover every well-known service.
94+
//
95+
// After the initial extraction, it scans all Java files for
96+
// IFoo.Stub.asInterface(ServiceManager.getService(...)) patterns to fill in
97+
// descriptors that SystemServiceRegistry does not cover (e.g., telephony
98+
// services registered via TelephonyFrameworkInitializer).
9499
func mergeServiceMappings(
100+
thirdpartyDir string,
95101
frameworksBase string,
96102
specs map[string]*spec.PackageSpec,
97103
) error {
@@ -132,6 +138,10 @@ func mergeServiceMappings(
132138
})
133139
}
134140

141+
// Scan all Java files for Stub.asInterface(ServiceManager.getService(...))
142+
// patterns to fill in descriptors that SystemServiceRegistry does not cover.
143+
fillDescriptorsFromBinderUsages(thirdpartyDir, allConstants, mappings)
144+
135145
sort.Slice(mappings, func(i, j int) bool {
136146
return mappings[i].ServiceName < mappings[j].ServiceName
137147
})
@@ -151,6 +161,64 @@ func mergeServiceMappings(
151161
return nil
152162
}
153163

164+
// fillDescriptorsFromBinderUsages scans Java source files for
165+
// IFoo.Stub.asInterface(ServiceManager.getService(...)) patterns and uses
166+
// them to populate empty descriptors in service mappings.
167+
func fillDescriptorsFromBinderUsages(
168+
thirdpartyDir string,
169+
allConstants map[string]string,
170+
mappings []spec.ServiceMapping,
171+
) {
172+
usages, err := servicemap.ScanBinderUsages(thirdpartyDir)
173+
if err != nil {
174+
fmt.Fprintf(os.Stderr, "java2spec: warning: scanning binder usages: %v\n", err)
175+
return
176+
}
177+
178+
// Build lookup tables for resolving usages to service mappings.
179+
// constantToIdx: Context constant name -> index in mappings slice.
180+
// serviceToIdx: service name string -> index in mappings slice.
181+
constantToIdx := make(map[string]int, len(mappings))
182+
serviceToIdx := make(map[string]int, len(mappings))
183+
for i, m := range mappings {
184+
if m.ConstantName != "" {
185+
constantToIdx[m.ConstantName] = i
186+
}
187+
if m.ServiceName != "" {
188+
serviceToIdx[m.ServiceName] = i
189+
}
190+
}
191+
192+
filled := 0
193+
for _, usage := range usages {
194+
idx := -1
195+
196+
switch {
197+
case usage.ServiceConstant != "":
198+
if i, ok := constantToIdx[usage.ServiceConstant]; ok {
199+
idx = i
200+
}
201+
case usage.ServiceLiteral != "":
202+
if i, ok := serviceToIdx[usage.ServiceLiteral]; ok {
203+
idx = i
204+
}
205+
}
206+
207+
if idx < 0 {
208+
continue
209+
}
210+
if mappings[idx].Descriptor != "" {
211+
// Already has a descriptor from SystemServiceRegistry.
212+
continue
213+
}
214+
215+
mappings[idx].Descriptor = usage.AIDLInterface
216+
filled++
217+
}
218+
219+
fmt.Fprintf(os.Stderr, "java2spec: filled %d descriptors from binder usage scan\n", filled)
220+
}
221+
154222
// mergeJavaWireFormats walks Java sources in the 3rdparty directory tree,
155223
// extracts writeToParcel wire format specs, and merges them into matching
156224
// parcelables in the existing AIDL specs.
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
package servicemap
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"regexp"
8+
"strings"
9+
)
10+
11+
// BinderUsage records a single occurrence of an AIDL interface being obtained
12+
// via ServiceManager.getService for a specific service.
13+
type BinderUsage struct {
14+
// AIDLInterface is the fully qualified interface name when resolvable
15+
// via imports, otherwise the simple name (e.g., "ITelephony").
16+
AIDLInterface string
17+
18+
// ServiceConstant is the Context constant name when the call uses
19+
// Context.XXX_SERVICE (e.g., "TELEPHONY_SERVICE"). Empty if the
20+
// call uses a string literal instead.
21+
ServiceConstant string
22+
23+
// ServiceLiteral is the service name string literal when the call
24+
// uses ServiceManager.getService("name") directly. Empty if the
25+
// call uses a Context constant.
26+
ServiceLiteral string
27+
}
28+
29+
// asInterfaceGetServiceRe matches patterns like:
30+
//
31+
// IFoo.Stub.asInterface(ServiceManager.getService(Context.BAR_SERVICE))
32+
// IFoo.Stub.asInterface(ServiceManager.getServiceOrThrow(Context.BAR_SERVICE))
33+
// IFoo.Stub.asInterface(\n ServiceManager.getService("bar"))
34+
//
35+
// It captures the interface name, and optionally the Context constant or string literal.
36+
var asInterfaceGetServiceRe = regexp.MustCompile(
37+
`(\w+)\.Stub\.asInterface\s*\(\s*` +
38+
`(?:\w+\s*\.\s*)*` + // optional qualifier chain before ServiceManager
39+
`ServiceManager\s*\.\s*getService(?:OrThrow)?\s*\(\s*` +
40+
`(?:` +
41+
`Context\s*\.\s*([A-Z_]+)` + // group 2: Context constant
42+
`|` +
43+
`"([^"]+)"` + // group 3: string literal
44+
`)`,
45+
)
46+
47+
// systemServiceAnnotationRe matches @SystemService(Context.XXX_SERVICE).
48+
var systemServiceAnnotationRe = regexp.MustCompile(
49+
`@SystemService\s*\(\s*Context\s*\.\s*([A-Z_]+)\s*\)`,
50+
)
51+
52+
// scannerStubAsInterfaceRe matches IFoo.Stub.asInterface(...) and captures IFoo.
53+
var scannerStubAsInterfaceRe = regexp.MustCompile(
54+
`(\w+)\.Stub\.asInterface\s*\(`,
55+
)
56+
57+
// classNameRe extracts the public class name from a Java source file.
58+
var classNameRe = regexp.MustCompile(
59+
`(?m)^public\s+(?:final\s+)?class\s+(\w+)`,
60+
)
61+
62+
// ScanBinderUsages walks all Java files under root and extracts
63+
// service-to-AIDL-interface associations using two strategies:
64+
//
65+
// 1. Direct: IFoo.Stub.asInterface(ServiceManager.getService(...)) in the same expression.
66+
// 2. Annotated: @SystemService(Context.XXX) on a class that calls IFoo.Stub.asInterface(),
67+
// where the interface name relates to the class name (e.g., ActivityManager -> IActivityManager).
68+
//
69+
// Multiple usages of the same interface are deduplicated.
70+
func ScanBinderUsages(
71+
root string,
72+
) ([]BinderUsage, error) {
73+
var usages []BinderUsage
74+
seen := map[string]bool{}
75+
76+
addUsage := func(
77+
fqn string,
78+
constName string,
79+
literalName string,
80+
) {
81+
key := fqn + ":" + constName + ":" + literalName
82+
if seen[key] {
83+
return
84+
}
85+
seen[key] = true
86+
87+
usages = append(usages, BinderUsage{
88+
AIDLInterface: fqn,
89+
ServiceConstant: constName,
90+
ServiceLiteral: literalName,
91+
})
92+
}
93+
94+
err := filepath.Walk(root, func(
95+
path string,
96+
info os.FileInfo,
97+
walkErr error,
98+
) error {
99+
if walkErr != nil {
100+
return walkErr
101+
}
102+
if info.IsDir() || !strings.HasSuffix(path, ".java") {
103+
return nil
104+
}
105+
106+
src, readErr := os.ReadFile(path)
107+
if readErr != nil {
108+
return fmt.Errorf("reading %s: %w", path, readErr)
109+
}
110+
111+
content := string(src)
112+
if !strings.Contains(content, "Stub.asInterface") {
113+
return nil
114+
}
115+
116+
imports := extractImports(content)
117+
118+
// Strategy 1: direct ServiceManager.getService calls inside asInterface.
119+
if strings.Contains(content, "ServiceManager") {
120+
for _, m := range asInterfaceGetServiceRe.FindAllStringSubmatch(content, -1) {
121+
fqn := resolveInterfaceName(m[1], imports)
122+
addUsage(fqn, m[2], m[3])
123+
}
124+
}
125+
126+
// Strategy 2: @SystemService(Context.XXX) annotation on a class
127+
// that calls IFoo.Stub.asInterface(). To avoid false positives
128+
// when a class uses multiple AIDL interfaces, only accept interfaces
129+
// whose name relates to the annotated class name.
130+
annotationMatch := systemServiceAnnotationRe.FindStringSubmatch(content)
131+
if annotationMatch == nil {
132+
return nil
133+
}
134+
constName := annotationMatch[1]
135+
136+
className := extractPublicClassName(content)
137+
candidates := scannerStubAsInterfaceRe.FindAllStringSubmatch(content, -1)
138+
best := selectBestInterfaceForClass(className, candidates)
139+
if best != "" {
140+
fqn := resolveInterfaceName(best, imports)
141+
addUsage(fqn, constName, "")
142+
}
143+
144+
return nil
145+
})
146+
if err != nil {
147+
return nil, fmt.Errorf("scanning binder usages: %w", err)
148+
}
149+
150+
return usages, nil
151+
}
152+
153+
// resolveInterfaceName resolves a simple Java class name to its fully
154+
// qualified name using the file's import table. Returns the simple name
155+
// unchanged if no import is found.
156+
func resolveInterfaceName(
157+
simpleName string,
158+
imports map[string]string,
159+
) string {
160+
if fqn, ok := imports[simpleName]; ok {
161+
return fqn
162+
}
163+
return simpleName
164+
}
165+
166+
// extractPublicClassName extracts the public class name from a Java source.
167+
func extractPublicClassName(content string) string {
168+
m := classNameRe.FindStringSubmatch(content)
169+
if m == nil {
170+
return ""
171+
}
172+
return m[1]
173+
}
174+
175+
// selectBestInterfaceForClass picks the interface from candidates whose
176+
// name best matches the given class name. For a class named "FooManager"
177+
// or "FooService", the ideal interface is "IFoo", "IFooManager", or
178+
// "IFooService". If only one candidate exists, it is returned directly.
179+
// Returns empty string if no suitable match is found.
180+
func selectBestInterfaceForClass(
181+
className string,
182+
candidates [][]string,
183+
) string {
184+
// Deduplicate candidate interface names.
185+
unique := make(map[string]struct{}, len(candidates))
186+
for _, c := range candidates {
187+
unique[c[1]] = struct{}{}
188+
}
189+
190+
if len(unique) == 0 {
191+
return ""
192+
}
193+
194+
// Single candidate — no ambiguity.
195+
if len(unique) == 1 {
196+
for name := range unique {
197+
return name
198+
}
199+
}
200+
201+
if className == "" {
202+
return ""
203+
}
204+
205+
// Strip common suffixes to derive the core concept.
206+
// "ActivityManager" -> "Activity", "TelephonyManager" -> "Telephony".
207+
core := className
208+
for _, suffix := range []string{"Manager", "Service"} {
209+
core = strings.TrimSuffix(core, suffix)
210+
}
211+
coreLower := strings.ToLower(core)
212+
213+
// Score each candidate: prefer interfaces whose name (minus the "I"
214+
// prefix) matches or contains the class's core concept.
215+
type scored struct {
216+
name string
217+
score int
218+
}
219+
var best scored
220+
for name := range unique {
221+
ifaceCore := strings.TrimPrefix(name, "I")
222+
ifaceCoreLower := strings.ToLower(ifaceCore)
223+
224+
var s int
225+
switch {
226+
case ifaceCoreLower == coreLower:
227+
// IActivity matches ActivityManager.
228+
s = 4
229+
case ifaceCoreLower == coreLower+"manager":
230+
// IActivityManager matches ActivityManager.
231+
s = 3
232+
case ifaceCoreLower == coreLower+"service":
233+
// IActivityService matches ActivityManager.
234+
s = 3
235+
case strings.Contains(ifaceCoreLower, coreLower):
236+
// Partial match.
237+
s = 2
238+
case strings.Contains(coreLower, ifaceCoreLower):
239+
// Reverse partial match.
240+
s = 1
241+
default:
242+
continue
243+
}
244+
245+
if s > best.score {
246+
best = scored{name: name, score: s}
247+
}
248+
}
249+
250+
return best.name
251+
}

0 commit comments

Comments
 (0)