2019-12-16 02:28:34 +08:00
|
|
|
package com.unciv.app
|
|
|
|
|
2020-09-20 13:22:07 -07:00
|
|
|
import android.content.Intent
|
2022-06-22 08:32:20 +02:00
|
|
|
import android.graphics.Rect
|
2023-02-23 21:40:58 +01:00
|
|
|
import android.hardware.display.DisplayManager
|
2022-03-05 19:00:56 +01:00
|
|
|
import android.net.Uri
|
2023-02-23 21:40:58 +01:00
|
|
|
import android.opengl.GLSurfaceView
|
|
|
|
import android.os.Build
|
2019-12-16 02:28:34 +08:00
|
|
|
import android.os.Bundle
|
2023-02-23 21:40:58 +01:00
|
|
|
import android.view.Surface
|
|
|
|
import android.view.SurfaceHolder
|
2022-06-22 08:32:20 +02:00
|
|
|
import android.view.View
|
|
|
|
import android.view.ViewTreeObserver
|
2023-02-23 21:40:58 +01:00
|
|
|
import androidx.annotation.RequiresApi
|
2020-02-17 17:34:46 +01:00
|
|
|
import androidx.core.app.NotificationManagerCompat
|
|
|
|
import androidx.work.WorkManager
|
2022-06-22 08:32:20 +02:00
|
|
|
import com.badlogic.gdx.Gdx
|
2019-12-16 02:28:34 +08:00
|
|
|
import com.badlogic.gdx.backends.android.AndroidApplication
|
|
|
|
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration
|
2022-06-22 08:32:20 +02:00
|
|
|
import com.badlogic.gdx.backends.android.AndroidGraphics
|
|
|
|
import com.badlogic.gdx.math.Rectangle
|
2019-12-16 02:28:34 +08:00
|
|
|
import com.unciv.UncivGame
|
2020-06-29 19:05:58 +02:00
|
|
|
import com.unciv.UncivGameParameters
|
2023-01-18 19:28:16 +02:00
|
|
|
import com.unciv.logic.files.UncivFiles
|
2022-06-22 08:32:20 +02:00
|
|
|
import com.unciv.logic.event.EventBus
|
2023-02-19 16:35:20 +02:00
|
|
|
import com.unciv.ui.screens.basescreen.UncivStage
|
|
|
|
import com.unciv.ui.screens.basescreen.BaseScreen
|
2022-05-27 12:45:13 +02:00
|
|
|
import com.unciv.utils.Log
|
2022-06-22 08:32:20 +02:00
|
|
|
import com.unciv.utils.concurrency.Concurrency
|
2020-02-09 02:28:31 -06:00
|
|
|
import java.io.File
|
2019-12-16 02:28:34 +08:00
|
|
|
|
2020-12-01 23:26:01 +02:00
|
|
|
open class AndroidLauncher : AndroidApplication() {
|
2022-05-27 15:53:18 +02:00
|
|
|
private var customFileLocationHelper: CustomFileLocationHelperAndroid? = null
|
2022-03-05 19:00:56 +01:00
|
|
|
private var game: UncivGame? = null
|
|
|
|
private var deepLinkedMultiplayerGame: String? = null
|
2019-12-16 02:28:34 +08:00
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
|
super.onCreate(savedInstanceState)
|
2022-05-27 12:45:13 +02:00
|
|
|
Log.backend = AndroidLogBackend()
|
2022-05-27 15:53:18 +02:00
|
|
|
customFileLocationHelper = CustomFileLocationHelperAndroid(this)
|
2020-02-23 19:45:25 +01:00
|
|
|
MultiplayerTurnCheckWorker.createNotificationChannels(applicationContext)
|
2020-02-09 02:28:31 -06:00
|
|
|
|
2022-03-05 19:00:56 +01:00
|
|
|
copyMods()
|
2021-05-19 22:27:23 +02:00
|
|
|
|
|
|
|
val config = AndroidApplicationConfiguration().apply {
|
2022-03-31 22:03:57 +02:00
|
|
|
useImmersiveMode = true
|
2021-05-19 22:27:23 +02:00
|
|
|
}
|
2022-03-21 14:12:16 -05:00
|
|
|
|
2022-06-25 21:30:27 +02:00
|
|
|
val settings = UncivFiles.getSettingsForPlatformLaunchers(filesDir.path)
|
2022-03-31 22:03:57 +02:00
|
|
|
|
2022-06-14 20:34:30 +03:00
|
|
|
// Manage orientation lock and display cutout
|
2022-05-03 00:41:08 +03:00
|
|
|
val platformSpecificHelper = PlatformSpecificHelpersAndroid(this)
|
|
|
|
platformSpecificHelper.allowPortrait(settings.allowAndroidPortrait)
|
2022-03-21 14:12:16 -05:00
|
|
|
|
2022-06-15 15:59:58 +02:00
|
|
|
platformSpecificHelper.toggleDisplayCutout(settings.androidCutout)
|
2022-06-14 20:34:30 +03:00
|
|
|
|
2020-06-29 19:05:58 +02:00
|
|
|
val androidParameters = UncivGameParameters(
|
2022-03-21 14:12:16 -05:00
|
|
|
crashReportSysInfo = CrashReportSysInfoAndroid,
|
2023-02-21 22:09:11 +01:00
|
|
|
fontImplementation = FontAndroid(),
|
2022-05-27 15:53:18 +02:00
|
|
|
customFileLocationHelper = customFileLocationHelper,
|
2022-05-03 00:41:08 +03:00
|
|
|
platformSpecificHelper = platformSpecificHelper
|
2020-06-29 19:05:58 +02:00
|
|
|
)
|
2022-03-05 19:00:56 +01:00
|
|
|
|
|
|
|
game = UncivGame(androidParameters)
|
2019-12-23 23:12:35 +03:00
|
|
|
initialize(game, config)
|
2022-03-05 19:00:56 +01:00
|
|
|
|
2022-03-31 22:03:57 +02:00
|
|
|
setDeepLinkedGame(intent)
|
2022-06-22 08:32:20 +02:00
|
|
|
|
2023-02-23 21:40:58 +01:00
|
|
|
val glView = (Gdx.graphics as AndroidGraphics).view as GLSurfaceView
|
|
|
|
|
|
|
|
addScreenObscuredListener(glView)
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
|
|
|
addScreenRefreshRateListener(glView)
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Request the best available device frame rate for
|
|
|
|
* the game, as soon as OpenGL surface is created */
|
|
|
|
private fun addScreenRefreshRateListener(surfaceView: GLSurfaceView) {
|
|
|
|
surfaceView.holder.addCallback(object: SurfaceHolder.Callback {
|
|
|
|
override fun surfaceCreated(holder: SurfaceHolder) {
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
|
|
val modes = display?.supportedModes ?: return
|
|
|
|
val bestRefreshRate = modes.maxOf { it.refreshRate }
|
|
|
|
holder.surface.setFrameRate(bestRefreshRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT)
|
|
|
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
|
|
val display = windowManager.defaultDisplay
|
|
|
|
val modes = display?.supportedModes ?: return
|
|
|
|
val bestMode = modes.maxBy { it.refreshRate }
|
|
|
|
val params = window.attributes
|
|
|
|
params.preferredDisplayModeId = bestMode.modeId
|
|
|
|
window.attributes = params
|
|
|
|
}
|
|
|
|
}
|
|
|
|
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}
|
|
|
|
override fun surfaceDestroyed(holder: SurfaceHolder) {}
|
|
|
|
})
|
2022-06-22 08:32:20 +02:00
|
|
|
}
|
|
|
|
|
2023-02-23 21:40:58 +01:00
|
|
|
private fun addScreenObscuredListener(surfaceView: GLSurfaceView) {
|
|
|
|
surfaceView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
|
2022-06-22 08:32:20 +02:00
|
|
|
/** [onGlobalLayout] gets triggered not only when the [windowVisibleDisplayFrame][View.getWindowVisibleDisplayFrame] changes, but also on other things.
|
|
|
|
* So we need to check if that was actually the thing that changed. */
|
|
|
|
private var lastVisibleDisplayFrame: Rect? = null
|
|
|
|
|
|
|
|
override fun onGlobalLayout() {
|
|
|
|
if (!UncivGame.isCurrentInitialized() || UncivGame.Current.screen == null) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
val r = Rect()
|
2023-02-23 21:40:58 +01:00
|
|
|
surfaceView.getWindowVisibleDisplayFrame(r)
|
2022-06-22 08:32:20 +02:00
|
|
|
if (r.equals(lastVisibleDisplayFrame)) return
|
|
|
|
lastVisibleDisplayFrame = r
|
|
|
|
|
|
|
|
val stage = (UncivGame.Current.screen as BaseScreen).stage
|
|
|
|
|
2023-02-23 21:40:58 +01:00
|
|
|
val horizontalRatio = stage.width / surfaceView.width
|
|
|
|
val verticalRatio = stage.height / surfaceView.height
|
2022-06-22 08:32:20 +02:00
|
|
|
|
|
|
|
val visibleStage = Rectangle(
|
|
|
|
r.left * horizontalRatio,
|
2023-02-23 21:40:58 +01:00
|
|
|
(surfaceView.height - r.bottom) * verticalRatio, // Android coordinate system has the origin in the top left, while GDX uses bottom left
|
2022-06-22 08:32:20 +02:00
|
|
|
r.width() * horizontalRatio,
|
|
|
|
r.height() * verticalRatio
|
|
|
|
)
|
|
|
|
Concurrency.runOnGLThread {
|
|
|
|
EventBus.send(UncivStage.VisibleAreaChanged(visibleStage))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2019-12-16 02:28:34 +08:00
|
|
|
}
|
2020-02-09 02:28:31 -06:00
|
|
|
|
2021-04-21 22:12:46 +03:00
|
|
|
/**
|
|
|
|
* Copies mods from external data directory (where users can access) to the private one (where
|
|
|
|
* libGDX reads from). Note: deletes all files currently in the private mod directory and
|
|
|
|
* replaces them with the ones in the external folder!)
|
|
|
|
*/
|
|
|
|
private fun copyMods() {
|
2020-10-07 14:02:38 +03:00
|
|
|
// Mod directory in the internal app data (where Gdx.files.local looks)
|
|
|
|
val internalModsDir = File("${filesDir.path}/mods")
|
2020-02-09 02:28:31 -06:00
|
|
|
|
2020-10-07 14:02:38 +03:00
|
|
|
// Mod directory in the shared app data (where the user can see and modify)
|
|
|
|
val externalModsDir = File("${getExternalFilesDir(null)?.path}/mods")
|
2020-02-09 02:28:31 -06:00
|
|
|
|
2020-10-07 14:02:38 +03:00
|
|
|
// Copy external mod directory (with data user put in it) to internal (where it can be read)
|
|
|
|
if (!externalModsDir.exists()) externalModsDir.mkdirs() // this can fail sometimes, which is why we check if it exists again in the next line
|
|
|
|
if (externalModsDir.exists()) externalModsDir.copyRecursively(internalModsDir, true)
|
|
|
|
}
|
2020-02-17 17:34:46 +01:00
|
|
|
|
|
|
|
override fun onPause() {
|
2022-05-18 06:35:00 +02:00
|
|
|
if (UncivGame.isCurrentInitialized()
|
2022-06-11 21:14:44 +02:00
|
|
|
&& UncivGame.Current.gameInfo != null
|
2022-05-25 22:22:58 +02:00
|
|
|
&& UncivGame.Current.settings.multiplayer.turnCheckerEnabled
|
2022-06-25 21:30:27 +02:00
|
|
|
&& UncivGame.Current.files.getMultiplayerSaves().any()
|
2022-05-27 15:53:18 +02:00
|
|
|
) {
|
|
|
|
MultiplayerTurnCheckWorker.startTurnChecker(
|
2022-06-25 21:30:27 +02:00
|
|
|
applicationContext, UncivGame.Current.files,
|
2022-06-11 21:14:44 +02:00
|
|
|
UncivGame.Current.gameInfo!!, UncivGame.Current.settings.multiplayer
|
2022-05-27 15:53:18 +02:00
|
|
|
)
|
2020-02-17 17:34:46 +01:00
|
|
|
}
|
|
|
|
super.onPause()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onResume() {
|
2020-06-27 23:57:22 +03:00
|
|
|
try { // Sometimes this fails for no apparent reason - the multiplayer checker failing to cancel should not be enough of a reason for the game to crash!
|
|
|
|
WorkManager.getInstance(applicationContext).cancelAllWorkByTag(MultiplayerTurnCheckWorker.WORK_TAG)
|
|
|
|
with(NotificationManagerCompat.from(this)) {
|
|
|
|
cancel(MultiplayerTurnCheckWorker.NOTIFICATION_ID_INFO)
|
|
|
|
cancel(MultiplayerTurnCheckWorker.NOTIFICATION_ID_SERVICE)
|
|
|
|
}
|
2021-04-21 22:12:46 +03:00
|
|
|
} catch (ex: Exception) {
|
2020-02-17 17:34:46 +01:00
|
|
|
}
|
2022-03-05 19:00:56 +01:00
|
|
|
|
|
|
|
if (deepLinkedMultiplayerGame != null) {
|
2022-03-31 22:03:57 +02:00
|
|
|
game?.deepLinkedMultiplayerGame = deepLinkedMultiplayerGame
|
2022-03-05 19:00:56 +01:00
|
|
|
deepLinkedMultiplayerGame = null
|
|
|
|
}
|
|
|
|
|
2020-02-17 17:34:46 +01:00
|
|
|
super.onResume()
|
|
|
|
}
|
2020-09-20 13:22:07 -07:00
|
|
|
|
2022-03-05 19:00:56 +01:00
|
|
|
override fun onNewIntent(intent: Intent?) {
|
|
|
|
super.onNewIntent(intent)
|
|
|
|
if (intent == null)
|
|
|
|
return
|
|
|
|
|
2022-03-31 22:03:57 +02:00
|
|
|
setDeepLinkedGame(intent)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun setDeepLinkedGame(intent: Intent) {
|
|
|
|
// This is needed in onCreate _and_ onNewIntent to open links and notifications
|
|
|
|
// correctly even if the app was not running
|
|
|
|
deepLinkedMultiplayerGame = if (intent.action != Intent.ACTION_VIEW) null else {
|
2022-03-05 19:00:56 +01:00
|
|
|
val uri: Uri? = intent.data
|
2022-03-31 22:03:57 +02:00
|
|
|
uri?.getQueryParameter("id")
|
2020-09-20 13:22:07 -07:00
|
|
|
}
|
2022-03-05 19:00:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
2022-05-27 15:53:18 +02:00
|
|
|
customFileLocationHelper?.onActivityResult(requestCode, data)
|
2020-09-20 13:22:07 -07:00
|
|
|
super.onActivityResult(requestCode, resultCode, data)
|
|
|
|
}
|
2020-12-01 23:26:01 +02:00
|
|
|
}
|
|
|
|
|
2021-05-19 22:27:23 +02:00
|
|
|
class AndroidTvLauncher:AndroidLauncher()
|