
AndroidManifest.xml declares the following. These are the minimum Horizon OS requires to treat your activity as a VR panel and keep it usable in headset:| Area | Manifest declaration | Why it matters |
|---|---|---|
Horizon OS SDK | <horizonos:uses-horizonos-sdk horizonos:minSdkVersion="69" horizonos:targetSdkVersion="69" /> | Min and target SDK Horizon OS requires |
Supported devices | <meta-data android:name="com.oculus.supportedDevices" android:value="quest2|questpro|quest3" /> | Declares Quest 2, Pro, 3 as targets |
Default panel size | android:defaultWidth="1024dp" android:defaultHeight="640dp" inside <layout> | Launch size for your resizable panel |
Minimum panel size | android:minWidth="360dp" android:minHeight="225dp" inside <layout> | Smallest size the system may shrink you to for field-of-view; keeps you usable on gaze-and-hands |
VR intent category | <category android:name="com.oculus.intent.category.VR" /> | Marks the activity as VR-capable |
Orientation and display | android:screenOrientation and window metadata | Handles panel resizing and immersive display |
| GMS dependency | Replacement |
|---|---|
gms.auth (Sign-In) | |
gms.location | |
gms.ads.identifier (AdID) | |
billingclient.api | |
Android Notifications |
| Firebase library | Replacement |
|---|---|
App Check Play Integrity provider | |
App Check SafetyNet provider | |
App Indexing | |
Dynamic Links | |
Cloud Messaging | |
ML Vision | None |
ML Custom Model | None |
INSTALL_PACKAGES or headset-meaningless ones like CALL_PHONE. A build that requests a prohibited permission fails store upload. Other permissions are allowed after review; you explain the use case at submission.AndroidManifest.xml before you build. The Android Studio plugin underlines unsupported libraries and permissions as you type.| Approach | Use when |
|---|---|
Product flavors | You want to strip mobile-only dependencies from Quest build at compile time |
Runtime detection | You need one artifact for both platforms – it decides whether you are on Horizon OS or a phone at runtime. This avoids maintaining two APKs |
app/src/quest/, app/src/mobile/) has its own manifest and dependencies merged with main at compile time.mobileImplementation for Google Play Billing and questImplementation for Horizon Billing. The Android Studio plugin can generate this structure for you.// app/build.gradle.kts
dependencies {
"mobileImplementation"(libs.billing)
"questImplementation"(libs.horizon.billing.compatibility)
}
HorizonOsDetector:dependencies { implementation("com.meta.horizonosx:core:0.1.0") }
if (HorizonOsDetector.isOnHorizonOs(context)) {
// Horizon OS only
} else {
// Mobile only
}
questDebug or mobileDebug as needed – changing the variant re-syncs Gradle.app/src/main/ and flavor-specific code in app/src/quest/ and app/src/mobile/. For example, keep BillingUser.kt in main and provide quest/java/Billing.kt using Horizon Billing and mobile/java/Billing.kt using Google Play Billing with the same interface.questImplementation / mobileImplementation instead of implementation (see example in Option 1: Product flavors).quest/AndroidManifest.xml, mobile-only permissions in mobile/AndroidManifest.xml, and shared data in main/AndroidManifest.xml.HorizonOsDetector is not found, ensure you added com.meta.horizonosx:core from Maven Central (see Option 2: Runtime detection) and re-synced Gradle.Context to isOnHorizonOs(context) and guard the whole call chain – do not just guard the final API call, guard the import path that would load GMS classes.build.gradle contains the Meta Maven repository and correct minSdkVersion / targetSdkVersion (see Recommended manifest setup).