Forum Discussion

FredTA's avatar
FredTA
Protege
6 months ago
Solved

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);
    }

 

 

 

This gives me the following error 
<br>java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.content.Intent.migrateExtraStreamToClipData(android.content.Context)' on a null object reference<br>java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.content.Intent.migrateExtraStreamToClipData(android.content.Context)' on a null object reference<br>&nbsp;&nbsp;at android.app.Instrumentation.execStartActivity(Instrumentation.java:1756)<br>&nbsp;&nbsp;at android.app.Activity.startActivityForResult(Activity.java:5411)<br>&nbsp;&nbsp;at androidx.activity.ComponentActivity.startActivityForResult(ComponentActivity.java:728)<br>&nbsp;&nbsp;at android.app.Activity.startActivityForResult(Activity.java:5369)<br>&nbsp;&nbsp;at androidx.activity.ComponentActivity.startActivityForResult(ComponentActivity.java:709)<br>&nbsp;&nbsp;at android.app.Activity.startActivity(Activity.java:5755)<br>&nbsp;&nbsp;at android.app.Activity.startActivity(Activity.java:5708)<br>  at UnityEngine.AndroidJNISafe.CheckException ()
 
 
Here's the manifest for the app I'm trying to open 

 

 

<?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 

com.unity3d.player.UnityPlayer", when I change my code instead to 

 

 

var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");

 

 

I get a different error instead -
 //<br>java.lang.NoSuchFieldError: no "Ljava/lang/Object;" field "currentActivity" in class "Lcom/unity3d/player/UnityPlayerGameActivity;" or its superclasses<br>java.lang.NoSuchFieldError: no "Ljava/lang/Object;" field "currentActivity" in class "Lcom/unity3d/player/UnityPlayerGameActivity;" or its superclasses<br>  at UnityEngine.AndroidJNISafe.CheckException () [0x00000] in <00000000000000000000000000000000>:0 <br>  at UnityEngine.AndroidJNISafe.GetStaticFieldID (System.IntPtr clazz, System.String name, System.String sig) [0x00000] in <00000000000000000000000000000000>:0 <br>  at UnityEngine._AndroidJNIHelper.GetFieldID (System.IntPtr jclass, System.String fieldName, System.String signature, System.Boolean isStatic) [0x00000] in <00000000000000000000000000000000>:0 <br>  at UnityEngine.AndroidJavaObject._GetStatic[FieldType] (System.String fieldName) [0x00000] in <00000000000000000000000000000000>:0 <br>  at WorldLauncher.LaunchApp2 () [0x00000] in <
 
People tell me my first approach is more along the right lines...
 
So, what am I doing wrong? I've scoured the forums, but haven't found a solution that works in the modern day, on the Quest, outside the store. 
 
Is what I'm trying to even possible? Please help! 
 
Best,
Fred
  • Found the fix, manifest needs the "query all packages" permission, like so 
     
        package="com.example.yourapp">
        <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
        <!-- Other permissions and application components -->
    </manifest>

5 Replies

  • Found the fix, manifest needs the "query all packages" permission, like so 
     
        package="com.example.yourapp">
        <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
        <!-- Other permissions and application components -->
    </manifest>
  • It looks like your issue stems from the getLaunchIntentForPackage method returning null, which typically happens when the specified package name is incorrect, or the target app does not have a launchable activity. Since you confirmed the package name works when launching from SideQuest or the headset’s menu, you may need to explicitly specify the main activity in your intent. Try using new Intent() with setComponent(new ComponentName(packageName, "com.unity3d.player.UnityPlayerActivity")) instead of relying on getLaunchIntentForPackage. Additionally, ensure that the target app's manifest has <category android:name="android.intent.category.LAUNCHER"/> inside its activity declaration. If the issue persists, check the logs for further hints and confirm that the launcher website app has the necessary permissions to start external activities.

    • FredTA's avatar
      FredTA
      Protege

      Thanks for the thoughts - package name was correct and intent was launchable, what I was missing was the right permission in the manifest - see solution above 

      • vercelsmith's avatar
        vercelsmith
        Honored Guest

        Since the issue was resolved by adding the right permission in the manifest, it’s likely that the missing permission was related to launching external applications or activities. In future cases like this, make sure to check the AndroidManifest.xml for necessary permissions, such as:

        <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

        Also, ensure that the target app’s activity has the correct intent filters:

        <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter>