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