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 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && requestCode == REQ_WRITE_STORAGE && grantResults[0] == PERMISSION_GRANTED) { + customSaveLocationHelper?.continuePreviousRequest() + } + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // This should only happen on API 19+ but it's wrapped in the if check to keep the diff --git a/android/src/com/unciv/app/CustomSaveLocationHelperAndroid.kt b/android/src/com/unciv/app/CustomSaveLocationHelperAndroid.kt index 146f8f87..e5fb2279 100644 --- a/android/src/com/unciv/app/CustomSaveLocationHelperAndroid.kt +++ b/android/src/com/unciv/app/CustomSaveLocationHelperAndroid.kt @@ -1,10 +1,13 @@ package com.unciv.app +import android.Manifest.permission.WRITE_EXTERNAL_STORAGE import android.app.Activity import android.content.Intent +import android.content.pm.PackageManager.PERMISSION_GRANTED import android.net.Uri import android.os.Build import androidx.annotation.RequiresApi +import androidx.core.content.ContextCompat import com.unciv.logic.CustomSaveLocationHelper import com.unciv.logic.GameInfo import com.unciv.logic.GameSaver @@ -13,12 +16,16 @@ import java.util.concurrent.atomic.AtomicReference const val REQ_SAVE_GAME = 1 const val REQ_LOAD_GAME = 2 +const val REQ_WRITE_STORAGE = 3 // The Storage Access Framework is available from API 19 and up: // https://developer.android.com/guide/topics/providers/document-provider @RequiresApi(Build.VERSION_CODES.KITKAT) class CustomSaveLocationHelperAndroid(private val activity: Activity) : CustomSaveLocationHelper { private val callback = AtomicReference Unit>?>() + private var cachedSaveRequest: SaveRequest? = null + @Synchronized get + @Synchronized set override fun saveGame(gameInfo: GameInfo, gameName: String, forcePrompt: Boolean, block: (() -> Unit)?) { callback.set(Thread.currentThread() to { uri -> @@ -33,6 +40,17 @@ class CustomSaveLocationHelperAndroid(private val activity: Activity) : CustomSa return } } + + // For some reason it seems you can save to an existing file that you open without the + // permission, but you can't write to a file that you've requested be created so if this is + // a "Save as" operation then we need to get permission to write first + if (ContextCompat.checkSelfPermission(activity, WRITE_EXTERNAL_STORAGE) != PERMISSION_GRANTED + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + cachedSaveRequest = SaveRequest(gameInfo, gameName, forcePrompt, block) + activity.requestPermissions(arrayOf(WRITE_EXTERNAL_STORAGE), REQ_WRITE_STORAGE) + return + } + Intent(Intent.ACTION_CREATE_DOCUMENT).apply { type = "application/json" putExtra(Intent.EXTRA_TITLE, gameName) @@ -40,6 +58,12 @@ class CustomSaveLocationHelperAndroid(private val activity: Activity) : CustomSa } } + fun continuePreviousRequest() { + cachedSaveRequest?.let { + saveGame(it.gameInfo, it.gameName, it.forcePrompt, it.block) + cachedSaveRequest = null + } + } // This will be called on the main thread fun handleIntentData(uri: Uri?) { @@ -63,11 +87,11 @@ class CustomSaveLocationHelperAndroid(private val activity: Activity) : CustomSa callback.set(Thread.currentThread() to { uri -> uri?.let { val game = activity.contentResolver.openInputStream(it) - ?.reader() - ?.readText() - ?.run { - GameSaver.gameInfoFromString(this) - }?: return@let + ?.reader() + ?.readText() + ?.run { + GameSaver.gameInfoFromString(this) + } ?: return@let // If the user has saved the game from another platform (like Android), // then the save location might not be right so we have to correct for that // here @@ -81,4 +105,11 @@ class CustomSaveLocationHelperAndroid(private val activity: Activity) : CustomSa activity.startActivityForResult(this, REQ_LOAD_GAME) } } -} \ No newline at end of file +} + +data class SaveRequest( + val gameInfo: GameInfo, + val gameName: String, + val forcePrompt: Boolean, + val block: (() -> Unit)? +) \ No newline at end of file