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:
William Brawner 2020-09-19 10:38:32 -07:00
parent e0e8d2fb6f
commit 63dac3fb70
7 changed files with 77 additions and 28 deletions

View file

@ -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"

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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() {

View file

@ -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)

View file

@ -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 {
@ -9,3 +12,15 @@ interface SaveFolderHelper {
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
}

View file

@ -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)
}
}