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.
This commit is contained in:
parent
e0e8d2fb6f
commit
63dac3fb70
7 changed files with 77 additions and 28 deletions
|
@ -6,6 +6,7 @@
|
|||
<uses-sdk/>
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
|
|
|
@ -1,18 +1,25 @@
|
|||
package com.unciv.app
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.work.WorkManager
|
||||
import com.badlogic.gdx.backends.android.AndroidApplication
|
||||
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.UncivGameParameters
|
||||
import com.unciv.logic.GameSaver
|
||||
import com.unciv.ui.utils.ORIGINAL_FONT_SIZE
|
||||
import java.io.File
|
||||
|
||||
class AndroidLauncher : AndroidApplication() {
|
||||
private val saveFolderHelper: SaveFolderHelperAndroid by lazy {
|
||||
SaveFolderHelperAndroid(this)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
MultiplayerTurnCheckWorker.createNotificationChannels(applicationContext)
|
||||
|
@ -24,9 +31,9 @@ class AndroidLauncher : AndroidApplication() {
|
|||
crashReportSender = CrashReportSenderAndroid(this),
|
||||
exitEvent = this::finish,
|
||||
fontImplementation = NativeFontAndroid(ORIGINAL_FONT_SIZE.toInt()),
|
||||
saveFolderHelper = SaveFolderHelperAndroid(this)
|
||||
saveFolderHelper = saveFolderHelper
|
||||
)
|
||||
val game = UncivGame ( androidParameters )
|
||||
val game = UncivGame(androidParameters)
|
||||
initialize(game, config)
|
||||
}
|
||||
|
||||
|
@ -47,8 +54,23 @@ class AndroidLauncher : AndroidApplication() {
|
|||
cancel(MultiplayerTurnCheckWorker.NOTIFICATION_ID_INFO)
|
||||
cancel(MultiplayerTurnCheckWorker.NOTIFICATION_ID_SERVICE)
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
}
|
||||
catch (ex:Exception){}
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, 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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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<FileHandle>
|
||||
}
|
||||
|
||||
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<FileHandle> = Gdx.files.local(getSubfolder(multiplayer)).list().asSequence()
|
||||
|
||||
fun getSubfolder(multiplayer: Boolean = false) = if (multiplayer) multiplayerFilesFolder else saveFilesFolder
|
||||
}
|
|
@ -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<FileHandle> {
|
||||
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)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue