Skip to content

Instantly share code, notes, and snippets.

@panreel
Last active November 17, 2025 10:53
Show Gist options
  • Select an option

  • Save panreel/3fb125360a7eed5cd2409eda9a3aefbc to your computer and use it in GitHub Desktop.

Select an option

Save panreel/3fb125360a7eed5cd2409eda9a3aefbc to your computer and use it in GitHub Desktop.
Unity/C# button callback utility to initiate APK download and installation on Meta Quest devices.
using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
public class ApkDownloaderInstaller : MonoBehaviour
{
// Replace this with your real APK link (must be direct download)
private const string ApkUrl = "https://github.com/Igalia/wolvic/releases/download/v1.8.2/Wolvic-oculusvr-arm64-gecko-metaStore-release.apk";
private string localPath;
// Called by your UI Button
public void OnInstallButton()
{
StartCoroutine(DownloadAndInstall());
}
private IEnumerator DownloadAndInstall()
{
localPath = Path.Combine(Application.persistentDataPath, "MyApp.apk");
Debug.Log($"[Installer] Downloading to: {localPath}");
if (File.Exists(localPath))
File.Delete(localPath);
using (UnityWebRequest www = UnityWebRequest.Get(ApkUrl))
{
www.downloadHandler = new DownloadHandlerFile(localPath);
yield return www.SendWebRequest();
if (www.result != UnityWebRequest.Result.Success)
{
Debug.LogError("[Installer] Download failed: " + www.error);
yield break;
}
}
Debug.Log("[Installer] Download complete. Launching installer...");
#if UNITY_ANDROID && !UNITY_EDITOR
TryInstallApk(localPath);
#else
Debug.LogWarning("[Installer] Only works on Android device.");
#endif
}
#if UNITY_ANDROID && !UNITY_EDITOR
private void TryInstallApk(string apkPath)
{
using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
using (var currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"))
{
try
{
// ---- STEP 1: Get File URI via FileProvider ----
var file = new AndroidJavaObject("java.io.File", apkPath);
var fileProvider = new AndroidJavaClass("androidx.core.content.FileProvider");
var uri = fileProvider.CallStatic<AndroidJavaObject>(
"getUriForFile",
currentActivity,
currentActivity.Call<string>("getPackageName") + ".provider",
file
);
// ---- STEP 2: Build install Intent ----
var intent = new AndroidJavaObject("android.content.Intent", "android.intent.action.VIEW");
intent.Call<AndroidJavaObject>("setDataAndType", uri, "application/vnd.android.package-archive");
int FLAG_GRANT_READ_URI_PERMISSION = 1;
int FLAG_ACTIVITY_NEW_TASK = 0x10000000;
intent.Call<AndroidJavaObject>("addFlags", FLAG_GRANT_READ_URI_PERMISSION);
intent.Call<AndroidJavaObject>("addFlags", FLAG_ACTIVITY_NEW_TASK);
// ---- STEP 3: Request permission if needed ----
bool canInstall = false;
int sdkInt = new AndroidJavaClass("android.os.Build$VERSION").GetStatic<int>("SDK_INT");
if (sdkInt >= 26)
{
var pkgManager = currentActivity.Call<AndroidJavaObject>("getPackageManager");
canInstall = pkgManager.Call<bool>("canRequestPackageInstalls");
if (!canInstall)
{
// open settings to grant permission
var settingsIntent = new AndroidJavaObject(
"android.content.Intent",
"android.settings.MANAGE_UNKNOWN_APP_SOURCES"
);
var uriClass = new AndroidJavaClass("android.net.Uri");
var pkgUri = uriClass.CallStatic<AndroidJavaObject>(
"parse",
"package:" + currentActivity.Call<string>("getPackageName")
);
settingsIntent.Call<AndroidJavaObject>("setData", pkgUri);
settingsIntent.Call<AndroidJavaObject>("addFlags", FLAG_ACTIVITY_NEW_TASK);
currentActivity.Call("startActivity", settingsIntent);
Debug.LogWarning("[Installer] Please enable 'Install unknown apps' and press the button again.");
return;
}
}
// ---- STEP 4: Launch the installer ----
currentActivity.Call("startActivity", intent);
Debug.Log("[Installer] Installer launched.");
}
catch (AndroidJavaException e)
{
Debug.LogError("[Installer] Exception: " + e.Message);
}
}
}
#endif
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment