From 63dac3fb708e27aaac0b5b17ede9f2d48f877970 Mon Sep 17 00:00:00 2001 From: William Brawner Date: Sat, 19 Sep 2020 10:38:32 -0700 Subject: [PATCH] Attempt to implement custom save locations for Android It seems that the ACTION_OPEN_DOCUMENT_TREE intent action doesn't work for Google Drive nor OneDrive at least, which makes it a lot less valuable than I initially thought it would. It should be noted that it did work for Nextcloud, however. --- android/AndroidManifest.xml | 1 + android/src/com/unciv/app/AndroidLauncher.kt | 32 ++++++++++++++++--- .../com/unciv/app/SaveFolderHelperAndroid.kt | 19 +++++++---- core/src/com/unciv/UncivGame.kt | 15 ++++++--- core/src/com/unciv/logic/GameSaver.kt | 8 ++--- core/src/com/unciv/logic/SaveFolderHelper.kt | 15 +++++++++ .../app/desktop/SaveFolderHelperDesktop.kt | 15 ++++----- 7 files changed, 77 insertions(+), 28 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index d3d8fc51..c11a0735 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -6,6 +6,7 @@ + , grantResults: IntArray) { + if (requestCode == REQ_PERMISSION_EXTERNAL_STORAGE && grantResults[0] == PERMISSION_GRANTED) { + saveFolderHelper.chooseFolder() + } + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode == REQ_SAVE_FOLDER && resultCode == Activity.RESULT_OK) { + Log.d("Unciv", "Intent data: ${intent?.data}") + } + super.onActivityResult(requestCode, resultCode, data) + } } \ No newline at end of file diff --git a/android/src/com/unciv/app/SaveFolderHelperAndroid.kt b/android/src/com/unciv/app/SaveFolderHelperAndroid.kt index 1d3a59b8..d845b9ad 100644 --- a/android/src/com/unciv/app/SaveFolderHelperAndroid.kt +++ b/android/src/com/unciv/app/SaveFolderHelperAndroid.kt @@ -1,25 +1,27 @@ package com.unciv.app +import android.Manifest.permission.WRITE_EXTERNAL_STORAGE import android.app.Activity import android.content.Intent import android.content.Intent.ACTION_OPEN_DOCUMENT_TREE import android.os.Build import androidx.annotation.RequiresApi +import androidx.core.content.PermissionChecker +import androidx.core.content.PermissionChecker.PERMISSION_GRANTED import com.badlogic.gdx.Gdx import com.badlogic.gdx.files.FileHandle -import com.unciv.logic.GameSaver -import com.unciv.logic.GameSaver.getSubfolder -import com.unciv.logic.SaveFolderHelper +import com.unciv.logic.SaveFolderHelperInternal import com.unciv.models.metadata.GameSettings import java.io.File const val REQ_SAVE_FOLDER = 1 +const val REQ_PERMISSION_EXTERNAL_STORAGE = 2 -class SaveFolderHelperAndroid(private val activity: Activity) : SaveFolderHelper { +class SaveFolderHelperAndroid(private val activity: Activity) : SaveFolderHelperInternal() { /** When set, we know we're on Android and can save to the app's personal external file directory * Only allow mods on KK+, to avoid READ_EXTERNAL_STORAGE permission earlier versions need * See https://developer.android.com/training/data-storage/app-specific#external-access-files */ - val externalFilesDir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + private val externalFilesDir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { copyMods(activity) activity.getExternalFilesDir(null)?.path ?: "" } else "" @@ -27,7 +29,7 @@ class SaveFolderHelperAndroid(private val activity: Activity) : SaveFolderHelper override fun canChooseFolder(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP override fun getSave(settings: GameSettings, GameName: String, multiplayer: Boolean): FileHandle { - val localfile = Gdx.files.local("${getSubfolder(multiplayer)}/$GameName") + val localfile = super.getSave(settings, GameName, multiplayer) if (externalFilesDir == "" || !Gdx.files.isExternalStorageAvailable) return localfile val externalFile = Gdx.files.absolute(externalFilesDir + "/${getSubfolder(multiplayer)}/$GameName") if (localfile.exists() && !externalFile.exists()) return localfile @@ -42,6 +44,11 @@ class SaveFolderHelperAndroid(private val activity: Activity) : SaveFolderHelper @RequiresApi(Build.VERSION_CODES.LOLLIPOP) override fun chooseFolder(): FileHandle? { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && PermissionChecker.checkSelfPermission(activity, WRITE_EXTERNAL_STORAGE) != PERMISSION_GRANTED) { + activity.requestPermissions(arrayOf(WRITE_EXTERNAL_STORAGE), REQ_PERMISSION_EXTERNAL_STORAGE) + return null + } activity.startActivityForResult(Intent(ACTION_OPEN_DOCUMENT_TREE), REQ_SAVE_FOLDER) return null } diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index ce73f44b..fd00de51 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -13,7 +13,10 @@ import com.unciv.models.metadata.GameSettings import com.unciv.models.ruleset.RulesetCache import com.unciv.models.translations.Translations import com.unciv.ui.LanguagePickerScreen -import com.unciv.ui.utils.* +import com.unciv.ui.utils.CameraStageBaseScreen +import com.unciv.ui.utils.CrashController +import com.unciv.ui.utils.ImageGetter +import com.unciv.ui.utils.center import com.unciv.ui.worldscreen.WorldScreen import java.util.* import kotlin.concurrent.thread @@ -91,17 +94,21 @@ class UncivGame(parameters: UncivGameParameters) : Game() { // This stuff needs to run on the main thread because it needs the GL context Gdx.app.postRunnable { ImageGetter.ruleset = RulesetCache.getBaseRuleset() // so that we can enter the map editor without having to load a game first - thread(name="Music") { startMusic() } + thread(name = "Music") { startMusic() } restoreSize() if (settings.isFreshlyCreated) { setScreen(LanguagePickerScreen()) - } else { setScreen(MainMenuScreen()) } + } else { + setScreen(MainMenuScreen()) + } isInitialized = true } } crashController = CrashController.Impl(crashReportSender) - GameSaver.saveFolderHelper = saveFolderHelper + saveFolderHelper?.let { + GameSaver.saveFolderHelper = it + } } fun restoreSize() { diff --git a/core/src/com/unciv/logic/GameSaver.kt b/core/src/com/unciv/logic/GameSaver.kt index 1be5c6c0..6b22701a 100644 --- a/core/src/com/unciv/logic/GameSaver.kt +++ b/core/src/com/unciv/logic/GameSaver.kt @@ -11,14 +11,14 @@ import java.io.File import kotlin.concurrent.thread object GameSaver { - private const val saveFilesFolder = "SaveFiles" - private const val multiplayerFilesFolder = "MultiplayerGames" + const val saveFilesFolder = "SaveFiles" + const val multiplayerFilesFolder = "MultiplayerGames" private const val settingsFileName = "GameSettings.json" - lateinit var saveFolderHelper: SaveFolderHelper + + var saveFolderHelper: SaveFolderHelper = SaveFolderHelperInternal() fun json() = Json().apply { setIgnoreDeprecated(true); ignoreUnknownFields = true } // Json() is NOT THREAD SAFE so we need to create a new one for each function - fun getSubfolder(multiplayer: Boolean = false) = if (multiplayer) multiplayerFilesFolder else saveFilesFolder fun getSave(GameName: String, multiplayer: Boolean = false): FileHandle = saveFolderHelper.getSave(getGeneralSettings(), GameName, multiplayer) diff --git a/core/src/com/unciv/logic/SaveFolderHelper.kt b/core/src/com/unciv/logic/SaveFolderHelper.kt index 100df0b6..6fef88a1 100644 --- a/core/src/com/unciv/logic/SaveFolderHelper.kt +++ b/core/src/com/unciv/logic/SaveFolderHelper.kt @@ -1,6 +1,9 @@ package com.unciv.logic +import com.badlogic.gdx.Gdx import com.badlogic.gdx.files.FileHandle +import com.unciv.logic.GameSaver.multiplayerFilesFolder +import com.unciv.logic.GameSaver.saveFilesFolder import com.unciv.models.metadata.GameSettings interface SaveFolderHelper { @@ -8,4 +11,16 @@ interface SaveFolderHelper { fun chooseFolder(): FileHandle? fun getSave(settings: GameSettings, GameName: String, multiplayer: Boolean = false): FileHandle fun getSaves(settings: GameSettings, multiplayer: Boolean = false): Sequence +} + +open class SaveFolderHelperInternal : SaveFolderHelper { + override fun canChooseFolder(): Boolean = false + + override fun chooseFolder(): FileHandle? = null + + override fun getSave(settings: GameSettings, GameName: String, multiplayer: Boolean): FileHandle = Gdx.files.local("${getSubfolder(multiplayer)}/$GameName") + + override fun getSaves(settings: GameSettings, multiplayer: Boolean): Sequence = Gdx.files.local(getSubfolder(multiplayer)).list().asSequence() + + fun getSubfolder(multiplayer: Boolean = false) = if (multiplayer) multiplayerFilesFolder else saveFilesFolder } \ No newline at end of file diff --git a/desktop/src/com/unciv/app/desktop/SaveFolderHelperDesktop.kt b/desktop/src/com/unciv/app/desktop/SaveFolderHelperDesktop.kt index db274d5a..b8664195 100644 --- a/desktop/src/com/unciv/app/desktop/SaveFolderHelperDesktop.kt +++ b/desktop/src/com/unciv/app/desktop/SaveFolderHelperDesktop.kt @@ -2,15 +2,14 @@ package com.unciv.app.desktop import com.badlogic.gdx.Gdx import com.badlogic.gdx.files.FileHandle -import com.unciv.logic.GameSaver -import com.unciv.logic.SaveFolderHelper +import com.unciv.logic.SaveFolderHelperInternal import com.unciv.models.metadata.GameSettings import java.awt.event.WindowEvent import java.io.File import javax.swing.JFileChooser import javax.swing.JFrame -class SaveFolderHelperDesktop: SaveFolderHelper { +class SaveFolderHelperDesktop : SaveFolderHelperInternal() { override fun chooseFolder(): FileHandle? { val fileChooser = JFileChooser().apply fileChooser@{ fileSelectionMode = JFileChooser.DIRECTORIES_ONLY @@ -30,19 +29,17 @@ class SaveFolderHelperDesktop: SaveFolderHelper { override fun canChooseFolder(): Boolean = true override fun getSave(settings: GameSettings, GameName: String, multiplayer: Boolean): FileHandle { - val gamePath = "${GameSaver.getSubfolder(multiplayer)}/$GameName" return settings.savesFolder?.let { - Gdx.files.absolute("$it/$gamePath") - }?: Gdx.files.local(gamePath) + Gdx.files.absolute("$it/${getSubfolder(multiplayer)}/$GameName") + } ?: super.getSave(settings, GameName, multiplayer) } override fun getSaves(settings: GameSettings, multiplayer: Boolean): Sequence { - val savesPath = GameSaver.getSubfolder(multiplayer) return settings.savesFolder?.let { - val file = Gdx.files.absolute("$it/$savesPath") + val file = Gdx.files.absolute("$it/${getSubfolder(multiplayer)}") file.mkdirs() return file.list().asSequence() - }?: Gdx.files.local(savesPath).list().asSequence() + } ?: super.getSaves(settings, multiplayer) } } \ No newline at end of file