Browse Source
[android] Automatic update fetcher and APK installer (#2987)
[android] Automatic update fetcher and APK installer (#2987)
This might need a test run before merging. Just to make sure. Co-authored-by: crueter <crueter@eden-emu.dev> Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2987 Reviewed-by: crueter <crueter@eden-emu.dev> Reviewed-by: Lizzie <lizzie@eden-emu.dev> Co-authored-by: kleidis <kleidis1@protonmail.com> Co-committed-by: kleidis <kleidis1@protonmail.com>xbzk-saf-recursive-write-with-permission-request
committed by
crueter
No known key found for this signature in database
GPG Key ID: 425ACD2D4830EBC6
11 changed files with 315 additions and 6 deletions
-
13src/android/app/src/main/AndroidManifest.xml
-
5src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
-
1src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
-
93src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
-
61src/android/app/src/main/java/org/yuzu/yuzu_emu/updater/APKDownloader.kt
-
41src/android/app/src/main/java/org/yuzu/yuzu_emu/updater/APKInstaller.kt
-
30src/android/app/src/main/java/org/yuzu/yuzu_emu/updater/AppInstallReceiver.kt
-
31src/android/app/src/main/jni/native.cpp
-
31src/android/app/src/main/res/layout/dialog_download_progress.xml
-
7src/android/app/src/main/res/values/strings.xml
-
8src/android/app/src/main/res/xml/file_paths.xml
@ -0,0 +1,61 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
package org.yuzu.yuzu_emu.updater |
|||
|
|||
import okhttp3.Call |
|||
import okhttp3.Callback |
|||
import okhttp3.OkHttpClient |
|||
import okhttp3.Request |
|||
import okhttp3.Response |
|||
import java.io.File |
|||
import java.io.FileOutputStream |
|||
import java.io.IOException |
|||
|
|||
class APKDownloader(private val url: String, private val outputFile: File) { |
|||
|
|||
fun download(onProgress: (Int) -> Unit, onComplete: (Boolean) -> Unit) { |
|||
val client = OkHttpClient() |
|||
val request = Request.Builder().url(url).build() |
|||
|
|||
client.newCall(request).enqueue(object : Callback { |
|||
override fun onFailure(call: Call, e: IOException) { |
|||
e.printStackTrace() |
|||
onComplete(false) |
|||
} |
|||
|
|||
override fun onResponse(call: Call, response: Response) { |
|||
if (response.isSuccessful) { |
|||
response.body?.let { body -> |
|||
val contentLength = body.contentLength() |
|||
try { |
|||
val inputStream = body.byteStream() |
|||
val outputStream = FileOutputStream(outputFile) |
|||
val buffer = ByteArray(4096) |
|||
var bytesRead: Int |
|||
var totalBytesRead: Long = 0 |
|||
|
|||
while (inputStream.read(buffer).also { bytesRead = it } != -1) { |
|||
outputStream.write(buffer, 0, bytesRead) |
|||
totalBytesRead += bytesRead |
|||
val progress = (totalBytesRead * 100 / contentLength).toInt() |
|||
onProgress(progress) |
|||
} |
|||
outputStream.flush() |
|||
outputStream.close() |
|||
inputStream.close() |
|||
onComplete(true) |
|||
} catch (e: IOException) { |
|||
e.printStackTrace() |
|||
onComplete(false) |
|||
} |
|||
} ?: run { |
|||
onComplete(false) |
|||
} |
|||
} else { |
|||
onComplete(false) |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
package org.yuzu.yuzu_emu.updater |
|||
|
|||
import android.content.Context |
|||
import android.content.Intent |
|||
import android.content.IntentFilter |
|||
import android.net.Uri |
|||
import androidx.core.content.FileProvider |
|||
import kotlinx.coroutines.GlobalScope |
|||
import kotlinx.coroutines.launch |
|||
import java.io.File |
|||
|
|||
class APKInstaller(private val context: Context) { |
|||
|
|||
fun install(apkFile: File, onComplete: () -> Unit, onFailure: (Exception) -> Unit) { |
|||
try { |
|||
val apkUri: Uri = FileProvider.getUriForFile( |
|||
context, |
|||
context.applicationContext.packageName + ".provider", |
|||
apkFile |
|||
) |
|||
val intent = Intent(Intent.ACTION_VIEW) |
|||
intent.setDataAndType(apkUri, "application/vnd.android.package-archive") |
|||
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK |
|||
context.startActivity(intent) |
|||
|
|||
GlobalScope.launch { |
|||
val receiver = AppInstallReceiver(onComplete, onFailure) |
|||
context.registerReceiver(receiver, IntentFilter().apply { |
|||
addAction(Intent.ACTION_PACKAGE_ADDED) |
|||
addAction(Intent.ACTION_PACKAGE_REPLACED) |
|||
addDataScheme("package") |
|||
}) |
|||
} |
|||
} catch (e: Exception) { |
|||
onFailure(e) |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
package org.yuzu.yuzu_emu.updater |
|||
|
|||
import android.content.BroadcastReceiver |
|||
import android.content.Context |
|||
import android.content.Intent |
|||
import android.util.Log |
|||
|
|||
class AppInstallReceiver( |
|||
private val onComplete: () -> Unit, |
|||
private val onFailure: (Exception) -> Unit |
|||
) : BroadcastReceiver() { |
|||
|
|||
override fun onReceive(context: Context, intent: Intent) { |
|||
val packageName = intent.data?.schemeSpecificPart |
|||
when (intent.action) { |
|||
Intent.ACTION_PACKAGE_ADDED, Intent.ACTION_PACKAGE_REPLACED -> { |
|||
Log.i("AppInstallReceiver", "Package installed or updated: $packageName") |
|||
onComplete() |
|||
context.unregisterReceiver(this) |
|||
} |
|||
else -> { |
|||
onFailure(Exception("Installation failed for package: $packageName")) |
|||
context.unregisterReceiver(this) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:app="http://schemas.android.com/apk/res-auto" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:orientation="vertical" |
|||
android:padding="24dp"> |
|||
|
|||
<com.google.android.material.textview.MaterialTextView |
|||
android:id="@+id/download_progress_message" |
|||
style="@style/TextAppearance.Material3.BodyMedium" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginBottom="16dp" |
|||
android:text="0%" |
|||
android:textAlignment="center" |
|||
android:textSize="18sp" |
|||
android:textStyle="bold" /> |
|||
|
|||
<com.google.android.material.progressindicator.LinearProgressIndicator |
|||
android:id="@+id/download_progress_bar" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:max="100" |
|||
android:progress="0" |
|||
app:indicatorColor="?attr/colorPrimary" |
|||
app:trackColor="?attr/colorSurfaceVariant" |
|||
app:trackCornerRadius="4dp" |
|||
app:trackThickness="8dp" /> |
|||
|
|||
</LinearLayout> |
|||
@ -0,0 +1,8 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<paths xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
<!-- this is required to share files in the app's internal storage --> |
|||
<cache-path name="apk_cache" path="." /> |
|||
<external-cache-path name="external_apk_cache" path="." /> |
|||
<files-path name="files" path="." /> |
|||
<external-files-path name="external_files" path="." /> |
|||
</paths> |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue