Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions internal/playscan/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,29 @@ const (

// featureFormFactors maps the <uses-feature> declaration that puts an app on a
// non-phone Play track to that form factor.
//
// Every name here was read back from PackageManager's constant pool in
// android.jar on android-34, -35 and -36, which all agree.
var featureFormFactors = map[string]FormFactor{
"android.hardware.type.watch": FormFactorWear,
"android.hardware.type.television": FormFactorTV,
"android.software.leanback": FormFactorTV,
"android.hardware.type.automotive": FormFactorAutomotive,
"android.software.xr.immersive": FormFactorXR,
"android.hardware.xr.head_tracking": FormFactorXR,
"android.hardware.type.watch": FormFactorWear,
"android.hardware.type.television": FormFactorTV,
"android.software.leanback": FormFactorTV,
"android.hardware.type.automotive": FormFactorAutomotive,
}

// xrFeaturePrefixes cover Android XR, which is matched by namespace rather than
// by exact name.
//
// No XR feature constant appears in any android.jar shipped for API 34 to 36,
// so an exact name could not be verified the way the ones above were. Guessing
// one is how this rule broke before: two invented names meant no XR app ever
// matched, and XR packages kept getting the phone track's blocking finding.
// Matching the namespace is correct for every current XR feature
// (android.software.xr.api.spatial, android.software.xr.api.openxr, the
// android.hardware.xr.* inputs) and for ones added later.
var xrFeaturePrefixes = []string{
"android.software.xr.",
"android.hardware.xr.",
}

// FormFactor reports the non-phone form factor this manifest declares, or
Expand All @@ -86,6 +102,11 @@ func (m *Manifest) FormFactor() FormFactor {
if ff, ok := featureFormFactors[f.Name]; ok {
return ff
}
for _, prefix := range xrFeaturePrefixes {
if strings.HasPrefix(f.Name, prefix) {
return FormFactorXR
}
}
}
return FormFactorPhone
}
Expand Down
46 changes: 45 additions & 1 deletion internal/playscan/rules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ func TestTargetAPILevelRespectsFormFactor(t *testing.T) {
{"wear", "android.hardware.type.watch", FormFactorWear},
{"tv", "android.software.leanback", FormFactorTV},
{"automotive", "android.hardware.type.automotive", FormFactorAutomotive},
{"xr", "android.software.xr.immersive", FormFactorXR},
{"xr spatial", "android.software.xr.api.spatial", FormFactorXR},
{"xr openxr", "android.software.xr.api.openxr", FormFactorXR},
{"xr hardware input", "android.hardware.xr.input.controller", FormFactorXR},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
Expand Down Expand Up @@ -71,3 +73,45 @@ func TestTargetAPILevelFormFactorCleanWhenCurrent(t *testing.T) {
t.Errorf("want no findings, got %d: %s", len(findings), findings[0].Title)
}
}

// XR is matched by namespace, not by an exact constant name. The rule shipped
// with two invented names (android.software.xr.immersive,
// android.hardware.xr.head_tracking) that no real XR app declares, so no XR
// package ever matched and they kept getting the phone track's blocking
// finding. No XR constant appears in android.jar for API 34-36, so the
// namespace is what can actually be relied on.
func TestFormFactorMatchesXRByNamespace(t *testing.T) {
xr := []string{
"android.software.xr.api.spatial",
"android.software.xr.api.openxr",
"android.hardware.xr.input.controller",
"android.software.xr.something.added.later",
}
for _, name := range xr {
m := &Manifest{UsesFeatures: []UsesFeature{{Name: name}}}
if got := m.FormFactor(); got != FormFactorXR {
t.Errorf("%s: FormFactor = %q, want %q", name, got, FormFactorXR)
}
}

// The namespace must not swallow unrelated features that merely contain
// "xr", nor an XR feature the app says it does not require.
notXR := []string{
"android.hardware.camera",
"android.software.xrated", // not the xr namespace: no dot after xr
"com.example.xr.custom",
}
for _, name := range notXR {
m := &Manifest{UsesFeatures: []UsesFeature{{Name: name}}}
if got := m.FormFactor(); got != FormFactorPhone {
t.Errorf("%s: FormFactor = %q, want phone", name, got)
}
}

optional := &Manifest{UsesFeatures: []UsesFeature{
{Name: "android.software.xr.api.spatial", Required: "false"},
}}
if got := optional.FormFactor(); got != FormFactorPhone {
t.Errorf("optional XR feature: FormFactor = %q, want phone", got)
}
}
Loading