diff --git a/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.bin b/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.bin index 83bf4f1..9336693 100644 Binary files a/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.bin and b/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.bin differ diff --git a/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.lock b/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.lock index d137783..fd1f4ec 100644 Binary files a/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.lock and b/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.lock differ diff --git a/packages/client-android/android/.gradle/8.10.2/fileHashes/resourceHashesCache.bin b/packages/client-android/android/.gradle/8.10.2/fileHashes/resourceHashesCache.bin index 577e9c0..079ba66 100644 Binary files a/packages/client-android/android/.gradle/8.10.2/fileHashes/resourceHashesCache.bin and b/packages/client-android/android/.gradle/8.10.2/fileHashes/resourceHashesCache.bin differ diff --git a/packages/client-android/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/packages/client-android/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock index df7b6a1..2bf0c79 100644 Binary files a/packages/client-android/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/packages/client-android/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/packages/client-android/android/.gradle/file-system.probe b/packages/client-android/android/.gradle/file-system.probe index a803afb..b43c857 100644 Binary files a/packages/client-android/android/.gradle/file-system.probe and b/packages/client-android/android/.gradle/file-system.probe differ diff --git a/packages/client-android/android/app/src/main/java/dev/yuzu/gca/MainApplication.kt b/packages/client-android/android/app/src/main/java/dev/yuzu/gca/MainApplication.kt index e1fa547..9155a5d 100644 --- a/packages/client-android/android/app/src/main/java/dev/yuzu/gca/MainApplication.kt +++ b/packages/client-android/android/app/src/main/java/dev/yuzu/gca/MainApplication.kt @@ -1,5 +1,24 @@ package dev.yuzu.gca import android.app.Application +import com.facebook.react.PackageList +import com.facebook.react.ReactApplication +import com.facebook.react.ReactNativeHost +import com.facebook.react.ReactPackage +import com.facebook.react.defaults.DefaultReactNativeHost +import com.facebook.soloader.SoLoader -class MainApplication : Application() +class MainApplication : Application(), ReactApplication { + override val reactNativeHost: ReactNativeHost = + object : DefaultReactNativeHost(this) { + override fun getPackages(): List = + PackageList(this).packages.apply { add(PackageInstallerPackage()) } + override fun getJSMainModuleName() = "index" + override fun getUseDeveloperSupport() = false + } + + override fun onCreate() { + super.onCreate() + SoLoader.init(this, false) + } +} diff --git a/packages/client-android/android/app/src/main/java/dev/yuzu/gca/PackageInstallerModule.kt b/packages/client-android/android/app/src/main/java/dev/yuzu/gca/PackageInstallerModule.kt new file mode 100644 index 0000000..3237fd7 --- /dev/null +++ b/packages/client-android/android/app/src/main/java/dev/yuzu/gca/PackageInstallerModule.kt @@ -0,0 +1,51 @@ +package dev.yuzu.gca + +import android.app.PendingIntent +import android.content.Intent +import android.content.pm.PackageInstaller +import com.facebook.react.bridge.* +import java.io.File +import java.security.MessageDigest + +class PackageInstallerModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { + override fun getName() = "PackageInstallerModule" + + @ReactMethod + fun installApk(filePath: String, expectedSha256: String, promise: Promise) { + try { + val context = reactApplicationContext + val apkFile = File(filePath) + if (!apkFile.exists()) { promise.reject("NOT_FOUND", "APK not found"); return } + + if (!context.packageManager.canRequestPackageInstalls()) { + promise.reject("PERMISSION", "Install permission not granted"); return + } + + // SHA256 verify + val sha256 = MessageDigest.getInstance("SHA-256") + apkFile.inputStream().use { input -> + val buf = ByteArray(8192); var read: Int + while (input.read(buf).also { read = it } != -1) sha256.update(buf, 0, read) + } + if (sha256.digest().joinToString("") { "%02x".format(it) } != expectedSha256) { + promise.reject("SHA256", "SHA256 mismatch"); return + } + + val pi = context.packageManager.packageInstaller + val sessionId = pi.createSession(PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)) + val session = pi.openSession(sessionId) + try { + apkFile.inputStream().use { input -> + session.openWrite("package", 0, apkFile.length()).use { output -> + input.copyTo(output); session.fsync(output) + } + } + val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName) + ?: run { promise.reject("NO_INTENT", "No launch intent"); return } + session.commit(PendingIntent.getActivity(context, 0, launchIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE).intentSender) + promise.resolve("installed") + } finally { session.close() } + } catch (e: Exception) { promise.reject("ERROR", e.message) } + } +} diff --git a/packages/client-android/android/app/src/main/java/dev/yuzu/gca/PackageInstallerPackage.kt b/packages/client-android/android/app/src/main/java/dev/yuzu/gca/PackageInstallerPackage.kt new file mode 100644 index 0000000..eb4c2f1 --- /dev/null +++ b/packages/client-android/android/app/src/main/java/dev/yuzu/gca/PackageInstallerPackage.kt @@ -0,0 +1,12 @@ +package dev.yuzu.gca + +import com.facebook.react.ReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ViewManager + +class PackageInstallerPackage : ReactPackage { + override fun createNativeModules(reactContext: ReactApplicationContext): List = + listOf(PackageInstallerModule(reactContext)) + override fun createViewManagers(reactContext: ReactApplicationContext): List> = emptyList() +}