android - implement firmware installation

This commit is contained in:
Emmanuel Hansen 2023-12-17 19:09:52 +00:00
parent 27059ded86
commit 2ef28525be
9 changed files with 669 additions and 283 deletions

View File

@ -250,6 +250,59 @@ namespace LibRyujinx
return LoadApplication(stream, (FileType)(int)type); return LoadApplication(stream, (FileType)(int)type);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceVerifyFirmware")]
public static JLong JniVerifyFirmware(JEnvRef jEnv, JObjectLocalRef jObj, JInt descriptor, JBoolean isXci)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var stream = OpenFile(descriptor);
long stringHandle = -1;
try
{
var version = VerifyFirmware(stream, isXci);
if (version != null)
{
stringHandle = storeString(version.VersionString);
}
}
catch(Exception _)
{
}
return stringHandle;
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceInstallFirmware")]
public static void JniInstallFirmware(JEnvRef jEnv, JObjectLocalRef jObj, JInt descriptor, JBoolean isXci)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var stream = OpenFile(descriptor);
InstallFirmware(stream, isXci);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetInstalledFirmwareVersion")]
public static JLong JniGetInstalledFirmwareVersion(JEnvRef jEnv, JObjectLocalRef jObj)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var version = SwitchDevice?.ContentManager.GetCurrentFirmwareVersion();
long stringHandle = -1;
if (version != null)
{
stringHandle = storeString(version.VersionString);
}
return stringHandle;
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsInitialize")] [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsInitialize")]
public static JBoolean JniInitializeGraphicsNative(JEnvRef jEnv, JObjectLocalRef jObj, JObjectLocalRef graphicObject) public static JBoolean JniInitializeGraphicsNative(JEnvRef jEnv, JObjectLocalRef jObj, JObjectLocalRef graphicObject)
{ {

View File

@ -2,6 +2,7 @@ using ARMeilleure.Translation;
using LibHac.Ncm; using LibHac.Ncm;
using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.Input.HLE; using Ryujinx.Input.HLE;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
@ -66,6 +67,16 @@ namespace LibRyujinx
return LoadApplication(path); return LoadApplication(path);
} }
public static void InstallFirmware(Stream stream, bool isXci)
{
SwitchDevice?.ContentManager.InstallFirmware(stream, isXci);
}
public static SystemVersion? VerifyFirmware(Stream stream, bool isXci)
{
return SwitchDevice?.ContentManager?.VerifyFirmwarePackage(stream, isXci) ?? null;
}
public static bool LoadApplication(Stream stream, FileType type) public static bool LoadApplication(Stream stream, FileType type)
{ {
var emulationContext = SwitchDevice.EmulationContext; var emulationContext = SwitchDevice.EmulationContext;

View File

@ -525,6 +525,27 @@ namespace Ryujinx.HLE.FileSystem
FinishInstallation(temporaryDirectory, registeredDirectory); FinishInstallation(temporaryDirectory, registeredDirectory);
} }
public void InstallFirmware(Stream stream, bool isXci)
{
string contentPathString = ContentPath.GetContentPath(StorageId.BuiltInSystem);
string contentDirectory = ContentPath.GetRealPath(contentPathString);
string registeredDirectory = Path.Combine(contentDirectory, "registered");
string temporaryDirectory = Path.Combine(contentDirectory, "temp");
if (!isXci)
{
using ZipArchive archive = new ZipArchive(stream);
InstallFromZip(archive, temporaryDirectory);
}
else
{
Xci xci = new(_virtualFileSystem.KeySet, stream.AsStorage());
InstallFromCart(xci, temporaryDirectory);
}
FinishInstallation(temporaryDirectory, registeredDirectory);
}
private void FinishInstallation(string temporaryDirectory, string registeredDirectory) private void FinishInstallation(string temporaryDirectory, string registeredDirectory)
{ {
if (Directory.Exists(registeredDirectory)) if (Directory.Exists(registeredDirectory))
@ -643,13 +664,16 @@ namespace Ryujinx.HLE.FileSystem
throw new MissingKeyException("HeaderKey is empty. Cannot decrypt NCA headers."); throw new MissingKeyException("HeaderKey is empty. Cannot decrypt NCA headers.");
} }
Dictionary<ulong, List<(NcaContentType type, string path)>> updateNcas = new();
if (Directory.Exists(firmwarePackage)) if (Directory.Exists(firmwarePackage))
{ {
return VerifyAndGetVersionDirectory(firmwarePackage); return VerifyAndGetVersionDirectory(firmwarePackage);
} }
SystemVersion VerifyAndGetVersionDirectory(string firmwareDirectory)
{
return VerifyAndGetVersion(new LocalFileSystem(firmwareDirectory));
}
if (!File.Exists(firmwarePackage)) if (!File.Exists(firmwarePackage))
{ {
throw new FileNotFoundException("Firmware file does not exist."); throw new FileNotFoundException("Firmware file does not exist.");
@ -657,16 +681,28 @@ namespace Ryujinx.HLE.FileSystem
FileInfo info = new(firmwarePackage); FileInfo info = new(firmwarePackage);
if (info.Extension == ".zip" || info.Extension == ".xci")
{
using FileStream file = File.OpenRead(firmwarePackage); using FileStream file = File.OpenRead(firmwarePackage);
switch (info.Extension) var isXci = info.Extension == ".xci";
return VerifyFirmwarePackage(file, isXci);
}
return null;
}
public SystemVersion VerifyFirmwarePackage(Stream file, bool isXci)
{ {
case ".zip": if (!isXci)
using (ZipArchive archive = ZipFile.OpenRead(firmwarePackage))
{ {
using ZipArchive archive = new ZipArchive(file, ZipArchiveMode.Read);
return VerifyAndGetVersionZip(archive); return VerifyAndGetVersionZip(archive);
} }
case ".xci": else
{
Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage()); Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
if (xci.HasPartition(XciPartitionType.Update)) if (xci.HasPartition(XciPartitionType.Update))
@ -679,17 +715,13 @@ namespace Ryujinx.HLE.FileSystem
{ {
throw new InvalidFirmwarePackageException("Update not found in xci file."); throw new InvalidFirmwarePackageException("Update not found in xci file.");
} }
default: }
break;
} }
SystemVersion VerifyAndGetVersionDirectory(string firmwareDirectory) private SystemVersion VerifyAndGetVersionZip(ZipArchive archive)
{ {
return VerifyAndGetVersion(new LocalFileSystem(firmwareDirectory)); Dictionary<ulong, List<(NcaContentType type, string path)>> updateNcas = new();
}
SystemVersion VerifyAndGetVersionZip(ZipArchive archive)
{
SystemVersion systemVersion = null; SystemVersion systemVersion = null;
foreach (var entry in archive.Entries) foreach (var entry in archive.Entries)
@ -845,8 +877,10 @@ namespace Ryujinx.HLE.FileSystem
return systemVersion; return systemVersion;
} }
SystemVersion VerifyAndGetVersion(IFileSystem filesystem) private SystemVersion VerifyAndGetVersion(IFileSystem filesystem)
{ {
Dictionary<ulong, List<(NcaContentType type, string path)>> updateNcas = new();
SystemVersion systemVersion = null; SystemVersion systemVersion = null;
CnmtContentMetaEntry[] metaEntries = null; CnmtContentMetaEntry[] metaEntries = null;
@ -977,9 +1011,6 @@ namespace Ryujinx.HLE.FileSystem
return systemVersion; return systemVersion;
} }
return null;
}
public SystemVersion GetCurrentFirmwareVersion() public SystemVersion GetCurrentFirmwareVersion()
{ {
LoadEntries(); LoadEntries();

View File

@ -11,8 +11,8 @@ android {
applicationId "org.ryujinx.android" applicationId "org.ryujinx.android"
minSdk 30 minSdk 30
targetSdk 33 targetSdk 33
versionCode 10008 versionCode 10010
versionName '1.0.8' versionName '1.0.10'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
@ -49,6 +49,7 @@ android {
buildFeatures { buildFeatures {
compose true compose true
prefab true prefab true
buildConfig true
} }
composeOptions { composeOptions {
kotlinCompilerExtensionVersion '1.3.2' kotlinCompilerExtensionVersion '1.3.2'

View File

@ -102,6 +102,8 @@ class MainActivity : BaseActivity() {
mainViewModel!!.physicalControllerManager = physicalControllerManager mainViewModel!!.physicalControllerManager = physicalControllerManager
mainViewModel!!.motionSensorManager = motionSensorManager mainViewModel!!.motionSensorManager = motionSensorManager
mainViewModel!!.refreshFirmwareVersion()
mainViewModel?.apply { mainViewModel?.apply {
setContent { setContent {
RyujinxAndroidTheme { RyujinxAndroidTheme {

View File

@ -8,12 +8,14 @@ class RyujinxNative {
companion object { companion object {
val instance: RyujinxNative = RyujinxNative() val instance: RyujinxNative = RyujinxNative()
init { init {
System.loadLibrary("ryujinx") System.loadLibrary("ryujinx")
} }
} }
external fun deviceInitialize(isHostMapped: Boolean, useNce: Boolean, external fun deviceInitialize(
isHostMapped: Boolean, useNce: Boolean,
systemLanguage: Int, systemLanguage: Int,
regionCode: Int, regionCode: Int,
enableVsync: Boolean, enableVsync: Boolean,
@ -21,7 +23,9 @@ class RyujinxNative {
enablePtc: Boolean, enablePtc: Boolean,
enableInternetAccess: Boolean, enableInternetAccess: Boolean,
timeZone: Long, timeZone: Long,
ignoreMissingServices : Boolean): Boolean ignoreMissingServices: Boolean
): Boolean
external fun graphicsInitialize(configuration: GraphicsConfiguration): Boolean external fun graphicsInitialize(configuration: GraphicsConfiguration): Boolean
external fun graphicsInitializeRenderer( external fun graphicsInitializeRenderer(
extensions: Array<String>, extensions: Array<String>,
@ -67,4 +71,7 @@ class RyujinxNative {
external fun userOpenUser(userId: Long) external fun userOpenUser(userId: Long)
external fun userCloseUser(userId: String) external fun userCloseUser(userId: String)
external fun loggingSetEnabled(logLevel: Int, enabled: Boolean) external fun loggingSetEnabled(logLevel: Int, enabled: Boolean)
external fun deviceVerifyFirmware(fileDescriptor: Int, isXci: Boolean): Long
external fun deviceInstallFirmware(fileDescriptor: Int, isXci: Boolean)
external fun deviceGetInstalledFirmwareVersion() : Long
} }

View File

@ -35,6 +35,7 @@ class MainViewModel(val activity: MainActivity) {
var isMiiEditorLaunched = false var isMiiEditorLaunched = false
val userViewModel = UserViewModel() val userViewModel = UserViewModel()
val logging = Logging(this) val logging = Logging(this)
var firmwareVersion = ""
private var gameTimeState: MutableState<Double>? = null private var gameTimeState: MutableState<Double>? = null
private var gameFpsState: MutableState<Double>? = null private var gameFpsState: MutableState<Double>? = null
private var fifoState: MutableState<Double>? = null private var fifoState: MutableState<Double>? = null
@ -69,6 +70,13 @@ class MainViewModel(val activity: MainActivity) {
motionSensorManager?.setControllerId(-1) motionSensorManager?.setControllerId(-1)
} }
fun refreshFirmwareVersion(){
var handle = RyujinxNative.instance.deviceGetInstalledFirmwareVersion()
if(handle != -1L) {
firmwareVersion = NativeHelpers.instance.getStringJava(handle)
}
}
fun loadGame(game:GameModel) : Boolean { fun loadGame(game:GameModel) : Boolean {
val nativeRyujinx = RyujinxNative.instance val nativeRyujinx = RyujinxNative.instance
@ -178,8 +186,6 @@ class MainViewModel(val activity: MainActivity) {
return true return true
} }
fun loadMiiEditor() : Boolean { fun loadMiiEditor() : Boolean {
val nativeRyujinx = RyujinxNative.instance val nativeRyujinx = RyujinxNative.instance

View File

@ -5,23 +5,33 @@ import androidx.compose.runtime.MutableState
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.anggrayudi.storage.callback.FileCallback
import com.anggrayudi.storage.file.FileFullPath import com.anggrayudi.storage.file.FileFullPath
import com.anggrayudi.storage.file.copyFileTo
import com.anggrayudi.storage.file.extension
import com.anggrayudi.storage.file.getAbsolutePath import com.anggrayudi.storage.file.getAbsolutePath
import org.ryujinx.android.LogLevel import org.ryujinx.android.LogLevel
import org.ryujinx.android.MainActivity import org.ryujinx.android.MainActivity
import org.ryujinx.android.NativeHelpers
import org.ryujinx.android.RyujinxNative import org.ryujinx.android.RyujinxNative
import java.io.File
import kotlin.concurrent.thread
class SettingsViewModel(var navController: NavHostController, val activity: MainActivity) { class SettingsViewModel(var navController: NavHostController, val activity: MainActivity) {
private var previousCallback: ((requestCode: Int, folder: DocumentFile) -> Unit)? var selectedFirmwareVersion: String = ""
private var previousFileCallback: ((requestCode: Int, files: List<DocumentFile>) -> Unit)?
private var previousFolderCallback: ((requestCode: Int, folder: DocumentFile) -> Unit)?
private var sharedPref: SharedPreferences private var sharedPref: SharedPreferences
var selectedFirmwareFile: DocumentFile? = null
init { init {
sharedPref = getPreferences() sharedPref = getPreferences()
previousCallback = activity.storageHelper!!.onFolderSelected previousFolderCallback = activity.storageHelper!!.onFolderSelected
previousFileCallback = activity.storageHelper!!.onFileSelected
activity.storageHelper!!.onFolderSelected = { requestCode, folder -> activity.storageHelper!!.onFolderSelected = { requestCode, folder ->
run { run {
val p = folder.getAbsolutePath(activity!!) val p = folder.getAbsolutePath(activity)
val editor = sharedPref?.edit() val editor = sharedPref.edit()
editor?.putString("gameFolder", p) editor?.putString("gameFolder", p)
editor?.apply() editor?.apply()
} }
@ -52,8 +62,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
enableGuestLogs: MutableState<Boolean>, enableGuestLogs: MutableState<Boolean>,
enableAccessLogs: MutableState<Boolean>, enableAccessLogs: MutableState<Boolean>,
enableTraceLogs: MutableState<Boolean> enableTraceLogs: MutableState<Boolean>
) ) {
{
isHostMapped.value = sharedPref.getBoolean("isHostMapped", true) isHostMapped.value = sharedPref.getBoolean("isHostMapped", true)
useNce.value = sharedPref.getBoolean("useNce", true) useNce.value = sharedPref.getBoolean("useNce", true)
@ -62,7 +71,8 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
enablePtc.value = sharedPref.getBoolean("enablePtc", true) enablePtc.value = sharedPref.getBoolean("enablePtc", true)
ignoreMissingServices.value = sharedPref.getBoolean("ignoreMissingServices", false) ignoreMissingServices.value = sharedPref.getBoolean("ignoreMissingServices", false)
enableShaderCache.value = sharedPref.getBoolean("enableShaderCache", true) enableShaderCache.value = sharedPref.getBoolean("enableShaderCache", true)
enableTextureRecompression.value = sharedPref.getBoolean("enableTextureRecompression", false) enableTextureRecompression.value =
sharedPref.getBoolean("enableTextureRecompression", false)
resScale.value = sharedPref.getFloat("resScale", 1f) resScale.value = sharedPref.getFloat("resScale", 1f)
useVirtualController.value = sharedPref.getBoolean("useVirtualController", true) useVirtualController.value = sharedPref.getBoolean("useVirtualController", true)
isGrid.value = sharedPref.getBoolean("isGrid", true) isGrid.value = sharedPref.getBoolean("isGrid", true)
@ -123,7 +133,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
editor.putBoolean("enableTraceLogs", enableTraceLogs.value) editor.putBoolean("enableTraceLogs", enableTraceLogs.value)
editor.apply() editor.apply()
activity.storageHelper!!.onFolderSelected = previousCallback activity.storageHelper!!.onFolderSelected = previousFolderCallback
RyujinxNative.instance.loggingSetEnabled(LogLevel.Debug.ordinal, enableDebugLogs.value) RyujinxNative.instance.loggingSetEnabled(LogLevel.Debug.ordinal, enableDebugLogs.value)
RyujinxNative.instance.loggingSetEnabled(LogLevel.Info.ordinal, enableInfoLogs.value) RyujinxNative.instance.loggingSetEnabled(LogLevel.Info.ordinal, enableInfoLogs.value)
@ -135,17 +145,122 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
RyujinxNative.instance.loggingSetEnabled(LogLevel.Trace.ordinal, enableTraceLogs.value) RyujinxNative.instance.loggingSetEnabled(LogLevel.Trace.ordinal, enableTraceLogs.value)
} }
fun openGameFolder() { fun openGameFolder() {
val path = sharedPref?.getString("gameFolder", "") ?: "" val path = sharedPref?.getString("gameFolder", "") ?: ""
if (path.isEmpty()) if (path.isEmpty())
activity?.storageHelper?.storage?.openFolderPicker() activity.storageHelper?.storage?.openFolderPicker()
else else
activity?.storageHelper?.storage?.openFolderPicker( activity.storageHelper?.storage?.openFolderPicker(
activity.storageHelper!!.storage.requestCodeFolderPicker, activity.storageHelper!!.storage.requestCodeFolderPicker,
FileFullPath(activity, path) FileFullPath(activity, path)
) )
} }
fun importProdKeys() {
activity.storageHelper!!.onFileSelected = { requestCode, files ->
run {
activity.storageHelper!!.onFileSelected = previousFileCallback
val file = files.firstOrNull()
file?.apply {
if (name == "prod.keys") {
val outputFile = File(MainActivity.AppPath + "/system");
outputFile.delete()
thread {
file.copyFileTo(
activity,
outputFile,
callback = object : FileCallback() {
override fun onCompleted(result: Any) {
super.onCompleted(result)
}
})
}
}
}
}
}
activity.storageHelper?.storage?.openFilePicker()
}
fun selectFirmware(installState: MutableState<FirmwareInstallState>) {
if (installState.value != FirmwareInstallState.None)
return
activity.storageHelper!!.onFileSelected = { _, files ->
run {
activity.storageHelper!!.onFileSelected = previousFileCallback
val file = files.firstOrNull()
file?.apply {
if (extension == "xci" || extension == "zip") {
installState.value = FirmwareInstallState.Verifying
thread {
val descriptor =
activity.contentResolver.openFileDescriptor(file.uri, "rw")
descriptor?.use { d ->
val version = RyujinxNative.instance.deviceVerifyFirmware(
d.fd,
extension == "xci"
)
selectedFirmwareFile = file
if (version != -1L) {
selectedFirmwareVersion =
NativeHelpers.instance.getStringJava(version)
installState.value = FirmwareInstallState.Query
} else {
installState.value = FirmwareInstallState.Cancelled
}
}
}
} else {
installState.value = FirmwareInstallState.Cancelled
}
}
}
}
activity.storageHelper?.storage?.openFilePicker()
}
fun installFirmware(installState: MutableState<FirmwareInstallState>) {
if (installState.value != FirmwareInstallState.Query)
return
if (selectedFirmwareFile == null) {
installState.value = FirmwareInstallState.None
return
}
selectedFirmwareFile?.apply {
val descriptor =
activity.contentResolver.openFileDescriptor(uri, "rw")
descriptor?.use { d ->
installState.value = FirmwareInstallState.Install
thread {
try {
RyujinxNative.instance.deviceInstallFirmware(
d.fd,
extension == "xci"
)
} finally {
MainActivity.mainViewModel?.refreshFirmwareVersion()
installState.value = FirmwareInstallState.Done
}
}
}
}
}
fun clearFirmwareSelection(installState: MutableState<FirmwareInstallState>){
selectedFirmwareFile = null
selectedFirmwareVersion = ""
installState.value = FirmwareInstallState.None
}
}
enum class FirmwareInstallState{
None,
Cancelled,
Verifying,
Query,
Install,
Done
} }

View File

@ -60,6 +60,7 @@ import com.anggrayudi.storage.file.extension
import org.ryujinx.android.Helpers import org.ryujinx.android.Helpers
import org.ryujinx.android.MainActivity import org.ryujinx.android.MainActivity
import org.ryujinx.android.providers.DocumentProvider import org.ryujinx.android.providers.DocumentProvider
import org.ryujinx.android.viewmodels.FirmwareInstallState
import org.ryujinx.android.viewmodels.MainViewModel import org.ryujinx.android.viewmodels.MainViewModel
import org.ryujinx.android.viewmodels.SettingsViewModel import org.ryujinx.android.viewmodels.SettingsViewModel
import org.ryujinx.android.viewmodels.VulkanDriverViewModel import org.ryujinx.android.viewmodels.VulkanDriverViewModel
@ -107,6 +108,15 @@ class SettingViews {
val useVirtualController = remember { val useVirtualController = remember {
mutableStateOf(true) mutableStateOf(true)
} }
val showFirwmareDialog = remember {
mutableStateOf(false)
}
val firmwareInstallState = remember {
mutableStateOf(FirmwareInstallState.None)
}
val firmwareVersion = remember {
mutableStateOf(mainViewModel.firmwareVersion)
}
val isGrid = remember { mutableStateOf(true) } val isGrid = remember { mutableStateOf(true) }
val enableDebugLogs = remember { mutableStateOf(true) } val enableDebugLogs = remember { mutableStateOf(true) }
@ -211,37 +221,187 @@ class SettingViews {
Text(text = "Choose Folder") Text(text = "Choose Folder")
} }
} }
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "System Firmware",
modifier = Modifier.align(Alignment.CenterVertically)
)
Text(
text = firmwareVersion.value,
modifier = Modifier.align(Alignment.CenterVertically)
)
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Button(onClick = { Button(onClick = {
fun createIntent(action: String): Intent { fun createIntent(action: String): Intent {
val intent = Intent(action) val intent = Intent(action)
intent.addCategory(Intent.CATEGORY_DEFAULT) intent.addCategory(Intent.CATEGORY_DEFAULT)
intent.data = DocumentsContract.buildRootUri(DocumentProvider.AUTHORITY, DocumentProvider.ROOT_ID) intent.data = DocumentsContract.buildRootUri(
DocumentProvider.AUTHORITY,
DocumentProvider.ROOT_ID
)
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
return intent return intent
} }
try { try {
mainViewModel.activity.startActivity(createIntent(Intent.ACTION_VIEW)) mainViewModel.activity.startActivity(createIntent(Intent.ACTION_VIEW))
return@Button return@Button
} catch (_: ActivityNotFoundException) {
} }
catch (_: ActivityNotFoundException){}
try { try {
mainViewModel.activity.startActivity(createIntent("android.provider.action.BROWSE")) mainViewModel.activity.startActivity(createIntent("android.provider.action.BROWSE"))
return@Button return@Button
} catch (_: ActivityNotFoundException) {
} }
catch (_: ActivityNotFoundException){}
try { try {
mainViewModel.activity.startActivity(createIntent("com.google.android.documentsui")) mainViewModel.activity.startActivity(createIntent("com.google.android.documentsui"))
return@Button return@Button
} catch (_: ActivityNotFoundException) {
} }
catch (_: ActivityNotFoundException){}
try { try {
mainViewModel.activity.startActivity(createIntent("com.android.documentsui")) mainViewModel.activity.startActivity(createIntent("com.android.documentsui"))
return@Button return@Button
} catch (_: ActivityNotFoundException) {
} }
catch (_: ActivityNotFoundException){}
}) { }) {
Text(text = "Open App Folder") Text(text = "Open App Folder")
} }
Button(onClick = {
settingsViewModel.importProdKeys()
}) {
Text(text = "Import prod Keys")
}
Button(onClick = {
showFirwmareDialog.value = true
}) {
Text(text = "Install Firmware")
}
}
}
}
if(showFirwmareDialog.value) {
AlertDialog(onDismissRequest = {
if(firmwareInstallState.value != FirmwareInstallState.Install) {
showFirwmareDialog.value = false
settingsViewModel.clearFirmwareSelection(firmwareInstallState)
}
}) {
Card(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
shape = MaterialTheme.shapes.medium
) {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
.align(Alignment.CenterHorizontally),
verticalArrangement = Arrangement.SpaceBetween
) {
if (firmwareInstallState.value == FirmwareInstallState.None) {
Text(text = "Select a zip or XCI file to install from.")
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier.fillMaxWidth()
.padding(top = 4.dp)
) {
Button(onClick = {
settingsViewModel.selectFirmware(
firmwareInstallState
)
}, modifier = Modifier.padding(horizontal = 8.dp)) {
Text(text = "Select File")
}
Button(onClick = {
showFirwmareDialog.value = false
settingsViewModel.clearFirmwareSelection(
firmwareInstallState
)
}, modifier = Modifier.padding(horizontal = 8.dp)) {
Text(text = "Cancel")
}
}
} else if (firmwareInstallState.value == FirmwareInstallState.Query) {
Text(text = "Firmware ${settingsViewModel.selectedFirmwareVersion} will be installed. Do you want to continue?")
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier.fillMaxWidth()
.padding(top = 4.dp)
) {
Button(onClick = {
settingsViewModel.installFirmware(
firmwareInstallState
)
if(firmwareInstallState.value == FirmwareInstallState.None){
showFirwmareDialog.value = false
settingsViewModel.clearFirmwareSelection(firmwareInstallState)
}
}, modifier = Modifier.padding(horizontal = 8.dp)) {
Text(text = "Yes")
}
Button(onClick = {
showFirwmareDialog.value = false
settingsViewModel.clearFirmwareSelection(
firmwareInstallState
)
}, modifier = Modifier.padding(horizontal = 8.dp)) {
Text(text = "No")
}
}
} else if (firmwareInstallState.value == FirmwareInstallState.Install) {
Text(text = "Installing Firmware ${settingsViewModel.selectedFirmwareVersion}...")
LinearProgressIndicator(modifier = Modifier
.padding(top = 4.dp))
} else if (firmwareInstallState.value == FirmwareInstallState.Verifying) {
Text(text = "Verifying selected file...")
LinearProgressIndicator(modifier = Modifier
.fillMaxWidth()
)
}
else if (firmwareInstallState.value == FirmwareInstallState.Done) {
Text(text = "Installed Firmware ${settingsViewModel.selectedFirmwareVersion}")
firmwareVersion.value = mainViewModel.firmwareVersion
}
else if(firmwareInstallState.value == FirmwareInstallState.Cancelled){
val file = settingsViewModel.selectedFirmwareFile
if(file != null){
if(file.extension == "xci" || file.extension == "zip"){
if(settingsViewModel.selectedFirmwareVersion.isEmpty()) {
Text(text = "Unable to find version in selected file")
}
else {
Text(text = "Unknown Error has occurred. Please check logs")
}
}
else {
Text(text = "File type is not supported")
}
}
else {
Text(text = "File type is not supported")
}
}
}
}
} }
} }
ExpandableView(onCardArrowClick = { }, title = "System") { ExpandableView(onCardArrowClick = { }, title = "System") {