How to launch another app from my first app (outside the store)
For context:
My team is trying to create a delivery system for VR EdTech applications at Imperial College London. For our desktop version, each individual learning app is built to an AssetBundle and DLL and uploaded to our server. Our main executable ("launcher app") can then browse those files, download them, import them directly into the program and launch them as a separate scene. This works great, since we only need to install that one main application to all our PCs, as that application can then download/install all the others.
Now, we want something similar to work on a standalone quest.
My understanding is that this isn't possible on Android (I hear you can't load new code into an app at runtime like you can with our DLL method on Windows... please correct me if I'm wrong, I'd love to know how other games on the Quest with mod support work... my mind goes to Contractors, it comes with a mod browser letting you import different game modes, there certainly seems to be actual logic that comes along with these mods rather than just assets)
The solution we're trying is to instead build each individual learning application as a standalone APK, and have our central application download those APKs and launch them.
To clarify, I need this work entirely outside the regular Quest store system. I want to sideload our launcher app onto the headsets, enable unknown sources/dev mode etc, and give the headsets to our students, who can then use the launcher app to browse and open the learning apps we have on our server. That rules our the whole "Deep Linking" thing. Furthermore, the launcher app can't have any knowledge of the learning apps it's going to launch at compile time. It can however be fed the names of the packages to launch programmatically.
Building, uploading and downloading the APK - all fine. The problem is launching that APK from inside the main program.
Here's the code from my main "launcher" application
public void LaunchApp()
{
Debug.Log("Try launch app 1");
string packageName = "com.ImperialCollegeLondon.VE2Hub24"; //This is definitely the name of the package I want to launch, works fine launching from side quest or from the headset's menu
var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
var currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
var packageManager = currentActivity.Call<AndroidJavaObject>("getPackageManager");
var launchIntent = packageManager.Call<AndroidJavaObject>("getLaunchIntentForPackage", packageName);
currentActivity.Call("startActivity", launchIntent);
}
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="0.1.0"
android:installLocation="0"
android:compileSdkVersion="35"
android:compileSdkVersionCodename="15"
package="com.ImperialCollegeLondon.VE2Hub24"
platformBuildVersionCode="35"
platformBuildVersionName="15">
<uses-sdk
android:minSdkVersion="32"
android:targetSdkVersion="35" />
<supports-screens
android:anyDensity="true"
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:xlargeScreens="true" />
<supports-gl-texture
android:name="GL_KHR_texture_compression_astc_ldr" />
<uses-permission
android:name="android.permission.INTERNET" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature
android:name="android.hardware.vr.headtracking"
android:required="true"
android:version="1" />
<uses-feature
android:glEsVersion="0x30000" />
<uses-feature
android:name="android.hardware.vulkan.version"
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen.multitouch"
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen.multitouch.distinct"
android:required="false" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE" />
<permission
android:name="com.ImperialCollegeLondon.VE2Hub24.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
android:protectionLevel="0x2" />
<uses-permission
android:name="com.ImperialCollegeLondon.VE2Hub24.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" />
<application
android:label="@ref/0x7f0d0021"
android:icon="@ref/0x7f0c0000"
android:debuggable="true"
android:allowBackup="false"
android:extractNativeLibs="true"
android:networkSecurityConfig="@ref/0x7f100000"
android:appComponentFactory="androidx.core.app.CoreComponentFactory"
android:enableOnBackInvokedCallback="false">
<meta-data
android:name="unityplayer.SkipPermissionsDialog"
android:value="false" />
<meta-data
android:name="com.samsung.android.vr.application.mode"
android:value="vr_only" />
<meta-data
android:name="com.oculus.ossplash.background"
android:value="black" />
<meta-data
android:name="com.oculus.telemetry.project_guid"
android:value="07bc858d-06e3-41e0-b5ab-5449f36054fd" />
<meta-data
android:name="com.oculus.supportedDevices"
android:value="quest2|questpro|quest3|quest3s" />
<meta-data
android:name="unity.splash-mode"
android:value="0" />
<meta-data
android:name="unity.splash-enable"
android:value="true" />
<meta-data
android:name="unity.launch-fullscreen"
android:value="true" />
<meta-data
android:name="unity.render-outside-safearea"
android:value="true" />
<meta-data
android:name="notch.config"
android:value="portrait|landscape" />
<meta-data
android:name="unity.auto-report-fully-drawn"
android:value="true" />
<meta-data
android:name="unity.auto-set-game-state"
android:value="true" />
<meta-data
android:name="unity.strip-engine-code"
android:value="true" />
<activity
android:theme="@ref/0x7f0e00a1"
android:name="com.unity3d.player.UnityPlayerGameActivity"
android:enabled="true"
android:exported="true"
android:excludeFromRecents="true"
android:launchMode="2"
android:screenOrientation="0"
android:configChanges="0x17f0"
android:hardwareAccelerated="false"
android:resizeableActivity="false">
<intent-filter>
<category
android:name="android.intent.category.LAUNCHER" />
<category
android:name="com.oculus.intent.category.VR" />
<action
android:name="android.intent.action.MAIN" />
</intent-filter>
<meta-data
android:name="com.oculus.vr.focusaware"
android:value="true" />
<meta-data
android:name="notch_support"
android:value="true" />
</activity>
<meta-data
android:name="com.unity.xr.oculus.LowOverheadMode"
android:value="false" />
<meta-data
android:name="com.unity.xr.oculus.LateLatching"
android:value="false" />
<meta-data
android:name="com.unity.xr.oculus.LateLatchingDebug"
android:value="false" />
<provider
android:name="androidx.startup.InitializationProvider"
android:exported="false"
android:authorities="com.ImperialCollegeLondon.VE2Hub24.androidx-startup">
<meta-data
android:name="androidx.emoji2.text.EmojiCompatInitializer"
android:value="androidx.startup" />
<meta-data
android:name="androidx.lifecycle.ProcessLifecycleInitializer"
android:value="androidx.startup" />
</provider>
</application>
</manifest>
From what I can tell this manifest is all in order. interestingly, it lists the main activity as "com.unity3d.player.UnityPlayerGameActivity", rather than
var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
- Found the fix, manifest needs the "query all packages" permission, like so<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.yourapp"><uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /><!-- Other permissions and application components --></manifest>