update package name (even bigger one)

This commit is contained in:
2024-07-16 07:02:50 +03:00
parent 4f9e49003b
commit c8b1d72f08
367 changed files with 12 additions and 25 deletions
@@ -0,0 +1,129 @@
package dev.meloda.fast.service
import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log
import dev.meloda.fast.common.UserConfig
import dev.meloda.fast.common.extensions.createTimerFlow
import dev.meloda.fast.data.api.account.AccountUseCase
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import kotlin.coroutines.CoroutineContext
import kotlin.time.Duration.Companion.minutes
class OnlineService : Service() {
private val job = SupervisorJob()
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
Log.d(TAG, "error: $throwable")
throwable.printStackTrace()
}
private val coroutineContext: CoroutineContext
get() = Dispatchers.Default + job + exceptionHandler
private val coroutineScope = CoroutineScope(coroutineContext)
private val useCase: AccountUseCase by inject()
private var timerJob: Job? = null
private var onlineJob: Job? = null
override fun onBind(intent: Intent?): IBinder? {
Log.d(STATE_TAG, "onBind: intent: $intent")
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (startId > 1) return START_STICKY
Log.d(STATE_TAG, "onStartCommand: flags: $flags; startId: $startId\ninstance: $this")
// TODO: 05/05/2024, Danil Nikolaev: implement
// if (AppGlobal.preferences.getBoolean(
// SettingsKeys.KEY_VISIBILITY_SEND_ONLINE_STATUS,
// SettingsKeys.DEFAULT_VALUE_KEY_VISIBILITY_SEND_ONLINE_STATUS
// )
// ) {
// createTimer()
// }
return START_STICKY
}
private fun createTimer() {
timerJob = createTimerFlow(
isNeedToEndCondition = { false },
onStartAction = ::setOnline,
onTickAction = ::setOnline,
interval = 5.minutes
).launchIn(coroutineScope)
}
private fun setOnline() {
if (onlineJob != null) return
// TODO: 05/05/2024, Danil Nikolaev: implement
// if (!AppGlobal.preferences.getBoolean(
// SettingsKeys.KEY_VISIBILITY_SEND_ONLINE_STATUS,
// SettingsKeys.DEFAULT_VALUE_KEY_VISIBILITY_SEND_ONLINE_STATUS
// )
// ) return
Log.d(TAG, "setOnline()")
onlineJob = coroutineScope.launch {
val token = UserConfig.fastToken ?: UserConfig.accessToken
if (token.isBlank()) {
Log.d(TAG, "setOnline: token is empty")
return@launch
}
val response = useCase.setOnline(
voip = false,
accessToken = token
)
Log.d(TAG, "setOnline: response: $response")
}.also { coroutine -> coroutine.invokeOnCompletion { onlineJob = null } }
}
private suspend fun setOffline() {
Log.d(TAG, "setOffline()")
val response = useCase.setOffline(
accessToken = UserConfig.accessToken
)
Log.d(TAG, "setOffline: response: $response")
}
override fun onLowMemory() {
Log.d(STATE_TAG, "onLowMemory")
super.onLowMemory()
}
override fun onDestroy() {
Log.d(STATE_TAG, "onDestroy")
timerJob?.cancel("OnlineService destroyed")
onlineJob?.cancel("OnlineService destroyed")
super.onDestroy()
}
companion object {
private const val TAG = "OnlineService"
private const val STATE_TAG = "OnlineServiceState"
}
}
@@ -0,0 +1,279 @@
package dev.meloda.fast.service.longpolling
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.IBinder
import android.provider.Settings
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat
import com.conena.nanokt.android.app.stopForegroundCompat
import dev.meloda.fast.common.AppConstants
import dev.meloda.fast.common.LongPollController
import dev.meloda.fast.common.UserConfig
import dev.meloda.fast.common.VkConstants
import dev.meloda.fast.common.extensions.listenValue
import dev.meloda.fast.common.model.LongPollState
import dev.meloda.fast.data.LongPollUpdatesParser
import dev.meloda.fast.data.LongPollUseCase
import dev.meloda.fast.data.processState
import dev.meloda.fast.datastore.AppSettings
import dev.meloda.fast.model.api.data.LongPollUpdates
import dev.meloda.fast.model.api.data.VkLongPollData
import dev.meloda.fast.ui.R
import dev.meloda.fast.util.NotificationsUtils
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class LongPollingService : Service() {
private val longPollController: LongPollController by inject()
private val job = SupervisorJob()
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
Log.e(TAG, "error: $throwable")
if (throwable !is NoAccessTokenException) {
throwable.printStackTrace()
}
longPollController.updateCurrentState(LongPollState.Exception)
longPollController.setStateToApply(LongPollState.Exception)
}
private val coroutineContext: CoroutineContext
get() = Dispatchers.IO + job + exceptionHandler
private val coroutineScope = CoroutineScope(coroutineContext)
private val longPollUseCase: LongPollUseCase by inject()
private val updatesParser: LongPollUpdatesParser by inject()
private var currentJob: Job? = null
override fun onCreate() {
super.onCreate()
Log.d(STATE_TAG, "onCreate()")
}
override fun onBind(intent: Intent?): IBinder? {
Log.d(STATE_TAG, "onBind: intent: $intent")
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (startId > 1) return START_STICKY
val inBackground = AppSettings.Debug.longPollInBackground
Log.d(
STATE_TAG,
"onStartCommand: asForeground: $inBackground; flags: $flags; startId: $startId;\ninstance: $this"
)
if (currentJob != null) {
currentJob?.cancel()
currentJob = null
}
coroutineScope.launch {
currentJob = startPolling().also { it.join() }
}
val openCategorySettingsIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
.putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
.putExtra(Settings.EXTRA_CHANNEL_ID, "long_polling")
} else {
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.fromParts("package", packageName, null))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
val openCategorySettingsPendingIntent = PendingIntent.getActivity(
this,
1,
openCategorySettingsIntent,
PendingIntent.FLAG_IMMUTABLE
)
longPollController.updateCurrentState(
if (inBackground) LongPollState.Background
else LongPollState.InApp
)
if (inBackground) {
val notification =
NotificationsUtils.createNotification(
context = this,
title = getString(R.string.long_polling_service_notification_title),
contentText = getString(R.string.long_polling_service_notification_content),
notRemovable = false,
channelId = AppConstants.NOTIFICATION_CHANNEL_LONG_POLLING,
priority = NotificationsUtils.NotificationPriority.Low,
category = NotificationCompat.CATEGORY_SERVICE,
customNotificationId = NOTIFICATION_ID,
contentIntent = openCategorySettingsPendingIntent
).build()
startForeground(NOTIFICATION_ID, notification)
} else {
stopForegroundCompat(ServiceCompat.STOP_FOREGROUND_REMOVE)
}
return START_STICKY
}
private fun startPolling(): Job {
if (job.isCompleted || job.isCancelled) {
Log.d(STATE_TAG, "job is completed or cancelled")
throw Exception("Job is over")
}
Log.d(STATE_TAG, "job started")
return coroutineScope.launch {
if (UserConfig.accessToken.isEmpty()) {
throw NoAccessTokenException
}
var serverInfo = getServerInfo()
?: throw LongPollException(message = "bad VK response (server info)")
var lastUpdatesResponse: LongPollUpdates? = getUpdatesResponse(serverInfo)
?: throw LongPollException(message = "initiation error: bad VK response (last updates)")
var failCount = 0
while (job.isActive) {
if (lastUpdatesResponse == null) {
failCount++
serverInfo = getServerInfo()
?: throw LongPollException(message = "failed retrieving server info after error: bad VK response (server info #2)")
lastUpdatesResponse = getUpdatesResponse(serverInfo)
continue
}
when (lastUpdatesResponse.failed) {
1 -> {
val newTs = lastUpdatesResponse.ts ?: kotlin.run {
failCount++
serverInfo.ts
}
lastUpdatesResponse = getUpdatesResponse(serverInfo.copy(ts = newTs))
}
2, 3 -> {
serverInfo = getServerInfo()
?: throw LongPollException(
message = "failed retrieving server info after error: bad VK response (server info #3)"
)
lastUpdatesResponse = getUpdatesResponse(serverInfo)
}
else -> {
val newTs = lastUpdatesResponse.ts
if (newTs == null) {
failCount++
} else {
val updates = lastUpdatesResponse.updates
if (updates == null) {
failCount++
} else {
updates.forEach(updatesParser::parseNextUpdate)
}
lastUpdatesResponse = getUpdatesResponse(serverInfo.copy(ts = newTs))
}
}
}
}
}
}
private suspend fun getServerInfo(): VkLongPollData? = suspendCoroutine {
longPollUseCase.getLongPollServer(
needPts = true,
version = VkConstants.LP_VERSION
).listenValue(coroutineScope) { state ->
state.processState(
success = { response ->
Log.d(TAG, "getServerInfo: serverInfoResponse: $response")
it.resume(response)
},
error = { error ->
Log.e(TAG, "getServerInfo: $error")
it.resume(null)
}
)
}
}
private suspend fun getUpdatesResponse(
server: VkLongPollData
): LongPollUpdates? = suspendCoroutine {
longPollUseCase.getLongPollUpdates(
serverUrl = "https://${server.server}",
key = server.key,
ts = server.ts,
wait = 25,
mode = 2 or 8 or 32 or 64 or 128,
version = VkConstants.LP_VERSION
).listenValue(coroutineScope) { state ->
state.processState(
success = { response ->
Log.d(TAG, "lastUpdateResponse: $response")
it.resume(response)
},
error = { error ->
Log.d(TAG, "getUpdatesResponse: error: $error")
it.resume(null)
}
)
}
}
override fun onDestroy() {
Log.d(STATE_TAG, "onDestroy")
longPollController.updateCurrentState(LongPollState.Stopped)
try {
AppSettings.edit { putBoolean(KEY_LONG_POLL_WAS_DESTROYED, true) }
job.cancel()
} catch (e: Exception) {
e.printStackTrace()
}
super.onDestroy()
}
override fun onLowMemory() {
Log.d(STATE_TAG, "onLowMemory")
longPollController.updateCurrentState(LongPollState.Stopped)
super.onLowMemory()
}
companion object {
const val TAG = "LongPollTask"
private const val STATE_TAG = "LongPollServiceState"
const val KEY_LONG_POLL_WAS_DESTROYED = "long_poll_was_destroyed"
private const val NOTIFICATION_ID = 1001
}
}
private data class LongPollException(override val message: String) : Throwable()
private data object NoAccessTokenException : Throwable()
@@ -0,0 +1,13 @@
package dev.meloda.fast.service.longpolling.di
import dev.meloda.fast.data.LongPollUpdatesParser
import dev.meloda.fast.data.LongPollUseCase
import dev.meloda.fast.data.LongPollUseCaseImpl
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.bind
import org.koin.dsl.module
val longPollModule = module {
singleOf(::LongPollUseCaseImpl) bind LongPollUseCase::class
singleOf(::LongPollUpdatesParser)
}