From e3ee332fb2ba4fa72f7f5228384c0faa71fe51b2 Mon Sep 17 00:00:00 2001 From: Dmitriy Krivoruchko Date: Sat, 20 Aug 2022 12:59:05 +0300 Subject: [PATCH] Bug fixes --- .../screenstream/service/ForegroundService.kt | 40 +++++++++++++------ .../screenstream/service/TileActionService.kt | 7 +--- .../ui/activity/ServiceActivity.kt | 21 ++++------ .../ui/fragment/SettingsSecurityFragment.kt | 2 - .../ui/fragment/StreamFragment.kt | 9 +---- .../data/httpserver/KtorApplicationModule.kt | 5 +-- .../data/state/AppStateMachineImpl.kt | 21 +++++----- .../cio/internals/CIOHeadersResearch.java | 22 ---------- 8 files changed, 48 insertions(+), 79 deletions(-) delete mode 100644 data/src/main/kotlin/io/ktor/http/cio/internals/CIOHeadersResearch.java diff --git a/app/src/main/kotlin/info/dvkr/screenstream/service/ForegroundService.kt b/app/src/main/kotlin/info/dvkr/screenstream/service/ForegroundService.kt index d706c127..c3a25c91 100644 --- a/app/src/main/kotlin/info/dvkr/screenstream/service/ForegroundService.kt +++ b/app/src/main/kotlin/info/dvkr/screenstream/service/ForegroundService.kt @@ -4,11 +4,14 @@ import android.annotation.SuppressLint import android.app.Service import android.content.Context import android.content.Intent +import android.hardware.display.DisplayManager import android.os.Binder import android.os.Build import android.os.IBinder import android.os.RemoteException +import android.view.Display import android.view.LayoutInflater +import android.view.WindowManager import android.widget.Toast import androidx.appcompat.content.res.AppCompatResources import androidx.core.content.ContextCompat @@ -43,12 +46,12 @@ class ForegroundService : Service() { } internal object ForegroundServiceBinder : Binder() { - private val serviceMessageStateFlow = MutableStateFlow(null) + private val serviceMessageSharedFlow = MutableSharedFlow() - internal val serviceMessageFlow: StateFlow = serviceMessageStateFlow.asStateFlow() + internal val serviceMessageFlow: SharedFlow = serviceMessageSharedFlow.asSharedFlow() - internal fun sendMessage(serviceMessage: ServiceMessage) = try { - serviceMessageStateFlow.tryEmit(serviceMessage) + internal suspend fun sendMessage(serviceMessage: ServiceMessage) = try { + serviceMessageSharedFlow.emit(serviceMessage) } catch (cause: RemoteException) { XLog.d(getLog("sendMessage", "Failed to send message: $serviceMessage: $cause")) XLog.e(getLog("sendMessage", "Failed to send message: $serviceMessage"), cause) @@ -56,7 +59,7 @@ class ForegroundService : Service() { } private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) - private val effectFlow = MutableStateFlow(null) + private val effectFlow = MutableSharedFlow(extraBufferCapacity = 8) private val settings: Settings by inject() private val notificationHelper: NotificationHelper by inject() @@ -81,7 +84,7 @@ class ForegroundService : Service() { notificationHelper.createNotificationChannel() notificationHelper.showForegroundNotification(this, NotificationHelper.NotificationType.START) - effectFlow.filterNotNull().onEach { effect -> + effectFlow.onEach { effect -> if (effect !is AppStateMachine.Effect.Statistic) XLog.d(this@ForegroundService.getLog("onEffect", "Effect: $effect")) @@ -150,7 +153,7 @@ class ForegroundService : Service() { notificationHelper.hideErrorNotification() stopForeground(true) - ForegroundServiceBinder.sendMessage(ServiceMessage.FinishActivity) + coroutineScope.launch { ForegroundServiceBinder.sendMessage(ServiceMessage.FinishActivity) } this@ForegroundService.stopSelf() } @@ -184,11 +187,11 @@ class ForegroundService : Service() { isRunning = false - coroutineScope.cancel() - appStateMachine?.destroy() appStateMachine = null + coroutineScope.cancel() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { stopForeground(STOP_FOREGROUND_REMOVE) } else { @@ -212,14 +215,27 @@ class ForegroundService : Service() { } } + @Suppress("DEPRECATION") + private val windowContext: Context by lazy(LazyThreadSafetyMode.NONE) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + this + } else { + val display = ContextCompat.getSystemService(this, DisplayManager::class.java)!! + .getDisplay(Display.DEFAULT_DISPLAY) + + this.createDisplayContext(display) + .createWindowContext(WindowManager.LayoutParams.TYPE_TOAST, null) + } + } + @Suppress("DEPRECATION") private fun showSlowConnectionToast() { coroutineScope.launch { - val layoutInflater = ContextCompat.getSystemService(this@ForegroundService, LayoutInflater::class.java)!! + val layoutInflater = ContextCompat.getSystemService(windowContext, LayoutInflater::class.java)!! val binding = ToastSlowConnectionBinding.inflate(layoutInflater) - val drawable = AppCompatResources.getDrawable(this@ForegroundService, R.drawable.ic_notification_small_24dp) + val drawable = AppCompatResources.getDrawable(windowContext, R.drawable.ic_notification_small_24dp) binding.ivToastSlowConnection.setImageDrawable(drawable) - Toast(this@ForegroundService).apply { view = binding.root; duration = Toast.LENGTH_LONG }.show() + Toast(windowContext).apply { view = binding.root; duration = Toast.LENGTH_LONG }.show() } } } \ No newline at end of file diff --git a/app/src/main/kotlin/info/dvkr/screenstream/service/TileActionService.kt b/app/src/main/kotlin/info/dvkr/screenstream/service/TileActionService.kt index f61c26a5..c52ccb3a 100644 --- a/app/src/main/kotlin/info/dvkr/screenstream/service/TileActionService.kt +++ b/app/src/main/kotlin/info/dvkr/screenstream/service/TileActionService.kt @@ -17,15 +17,10 @@ import com.elvishew.xlog.XLog import info.dvkr.screenstream.R import info.dvkr.screenstream.data.other.getLog import info.dvkr.screenstream.service.helper.IntentAction -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.cancel +import kotlinx.coroutines.* import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch @TargetApi(Build.VERSION_CODES.N) class TileActionService : TileService() { diff --git a/app/src/main/kotlin/info/dvkr/screenstream/ui/activity/ServiceActivity.kt b/app/src/main/kotlin/info/dvkr/screenstream/ui/activity/ServiceActivity.kt index 1905442b..fef7ae2a 100644 --- a/app/src/main/kotlin/info/dvkr/screenstream/ui/activity/ServiceActivity.kt +++ b/app/src/main/kotlin/info/dvkr/screenstream/ui/activity/ServiceActivity.kt @@ -18,14 +18,7 @@ import info.dvkr.screenstream.service.ForegroundService import info.dvkr.screenstream.service.ServiceMessage import info.dvkr.screenstream.service.helper.IntentAction import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch abstract class ServiceActivity(@LayoutRes contentLayoutId: Int) : AppUpdateActivity(contentLayoutId) { @@ -33,8 +26,8 @@ abstract class ServiceActivity(@LayoutRes contentLayoutId: Int) : AppUpdateActiv private var isBound: Boolean = false private var serviceMessageFlowJob: Job? = null - private val _serviceMessageFlow = MutableStateFlow(null) - internal val serviceMessageFlow: StateFlow = _serviceMessageFlow.asStateFlow() + private val _serviceMessageFlow = MutableSharedFlow() + internal val serviceMessageFlow: SharedFlow = _serviceMessageFlow.asSharedFlow() private val serviceConnection = object : ServiceConnection { override fun onServiceConnected(name: ComponentName?, binder: IBinder) { @@ -45,8 +38,10 @@ abstract class ServiceActivity(@LayoutRes contentLayoutId: Int) : AppUpdateActiv serviceMessageFlowJob = lifecycleScope.launch { foregroundServiceBinder.serviceMessageFlow - .filterNotNull() - .onEach { serviceMessage -> onServiceMessage(serviceMessage) } + .onEach { serviceMessage -> + _serviceMessageFlow.emit(serviceMessage) + onServiceMessage(serviceMessage) + } .catch { cause -> XLog.e(this@ServiceActivity.getLog("onServiceConnected.serviceMessageFlow: $cause")) XLog.e(this@ServiceActivity.getLog("onServiceConnected.serviceMessageFlow"), cause) @@ -104,8 +99,6 @@ abstract class ServiceActivity(@LayoutRes contentLayoutId: Int) : AppUpdateActiv open fun onServiceMessage(serviceMessage: ServiceMessage) { XLog.v(getLog("onServiceMessage", "$serviceMessage")) - _serviceMessageFlow.tryEmit(serviceMessage) - if (serviceMessage is ServiceMessage.FinishActivity) { finishAndRemoveTask() } diff --git a/app/src/main/kotlin/info/dvkr/screenstream/ui/fragment/SettingsSecurityFragment.kt b/app/src/main/kotlin/info/dvkr/screenstream/ui/fragment/SettingsSecurityFragment.kt index bb6df49f..e3d70006 100644 --- a/app/src/main/kotlin/info/dvkr/screenstream/ui/fragment/SettingsSecurityFragment.kt +++ b/app/src/main/kotlin/info/dvkr/screenstream/ui/fragment/SettingsSecurityFragment.kt @@ -27,7 +27,6 @@ import info.dvkr.screenstream.service.helper.IntentAction import info.dvkr.screenstream.ui.activity.ServiceActivity import info.dvkr.screenstream.ui.enableDisableViewWithChildren import info.dvkr.screenstream.ui.viewBinding -import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -44,7 +43,6 @@ class SettingsSecurityFragment : Fragment(R.layout.fragment_settings_security) { (requireActivity() as ServiceActivity).serviceMessageFlow .flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED) - .filterNotNull() .onEach { serviceMessage -> if (serviceMessage is ServiceMessage.ServiceState) { isStreaming = serviceMessage.isStreaming diff --git a/app/src/main/kotlin/info/dvkr/screenstream/ui/fragment/StreamFragment.kt b/app/src/main/kotlin/info/dvkr/screenstream/ui/fragment/StreamFragment.kt index 6529b656..b8790ff3 100644 --- a/app/src/main/kotlin/info/dvkr/screenstream/ui/fragment/StreamFragment.kt +++ b/app/src/main/kotlin/info/dvkr/screenstream/ui/fragment/StreamFragment.kt @@ -29,12 +29,7 @@ import info.dvkr.screenstream.data.model.AppError import info.dvkr.screenstream.data.model.FatalError import info.dvkr.screenstream.data.model.FixableError import info.dvkr.screenstream.data.model.HttpClient -import info.dvkr.screenstream.data.other.asString -import info.dvkr.screenstream.data.other.bytesToMbit -import info.dvkr.screenstream.data.other.getLog -import info.dvkr.screenstream.data.other.getQRBitmap -import info.dvkr.screenstream.data.other.setColorSpan -import info.dvkr.screenstream.data.other.setUnderlineSpan +import info.dvkr.screenstream.data.other.* import info.dvkr.screenstream.data.settings.SettingsReadOnly import info.dvkr.screenstream.databinding.FragmentStreamBinding import info.dvkr.screenstream.databinding.ItemClientBinding @@ -43,7 +38,6 @@ import info.dvkr.screenstream.service.ServiceMessage import info.dvkr.screenstream.service.helper.IntentAction import info.dvkr.screenstream.ui.activity.ServiceActivity import info.dvkr.screenstream.ui.viewBinding -import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -83,7 +77,6 @@ class StreamFragment : AdFragment(R.layout.fragment_stream) { (requireActivity() as ServiceActivity).serviceMessageFlow .flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED) - .filterNotNull() .onEach { serviceMessage -> when (serviceMessage) { is ServiceMessage.ServiceState -> onServiceStateMessage(serviceMessage) diff --git a/data/src/main/kotlin/info/dvkr/screenstream/data/httpserver/KtorApplicationModule.kt b/data/src/main/kotlin/info/dvkr/screenstream/data/httpserver/KtorApplicationModule.kt index a98e4486..a4282ba3 100644 --- a/data/src/main/kotlin/info/dvkr/screenstream/data/httpserver/KtorApplicationModule.kt +++ b/data/src/main/kotlin/info/dvkr/screenstream/data/httpserver/KtorApplicationModule.kt @@ -5,8 +5,6 @@ import info.dvkr.screenstream.data.model.FatalError import info.dvkr.screenstream.data.other.getLog import info.dvkr.screenstream.data.other.randomString import io.ktor.http.* -import io.ktor.http.cio.* -import io.ktor.http.cio.internals.* import io.ktor.http.content.* import io.ktor.server.application.* import io.ktor.server.cio.* @@ -85,8 +83,7 @@ internal fun Application.appModule( if (cause is IOException) return@exception if (cause is CancellationException) return@exception if (cause is IllegalArgumentException) return@exception - val headers = CIOHeadersResearch.getHeadersAsString(call.request.headers as CIOHeaders) - XLog.e(this@appModule.getLog("exception", headers)) + XLog.e(this@appModule.getLog("exception", cause.toString())) XLog.e(this@appModule.getLog("exception"), cause) sendEvent(HttpServer.Event.Error(FatalError.HttpServerException)) call.respond(HttpStatusCode.InternalServerError) diff --git a/data/src/main/kotlin/info/dvkr/screenstream/data/state/AppStateMachineImpl.kt b/data/src/main/kotlin/info/dvkr/screenstream/data/state/AppStateMachineImpl.kt index 987d8402..72703e3d 100644 --- a/data/src/main/kotlin/info/dvkr/screenstream/data/state/AppStateMachineImpl.kt +++ b/data/src/main/kotlin/info/dvkr/screenstream/data/state/AppStateMachineImpl.kt @@ -7,7 +7,6 @@ import android.content.Intent import android.graphics.Bitmap import android.media.projection.MediaProjection import android.media.projection.MediaProjectionManager -import android.os.Build import android.os.Handler import android.os.Looper import android.os.PowerManager @@ -32,7 +31,7 @@ import java.util.concurrent.LinkedBlockingDeque class AppStateMachineImpl( private val serviceContext: Context, private val settings: Settings, - private val effectSharedFlow: MutableStateFlow, + private val effectSharedFlow: MutableSharedFlow, private val onSlowConnectionDetected: () -> Unit ) : AppStateMachine { @@ -99,7 +98,7 @@ class AppStateMachineImpl( XLog.e(getLog("sendEvent", "Pending events => $eventDeque"), cause) coroutineScope.launch(NonCancellable) { streamState = componentError(streamState, FatalError.ChannelException, true) - effectSharedFlow.tryEmit(streamState.toPublicState()) + effectSharedFlow.emit(streamState.toPublicState()) } } } @@ -140,7 +139,7 @@ class AppStateMachineImpl( else -> throw IllegalArgumentException("Unknown AppStateMachine.Event: $event") } - if (streamState.isPublicStatePublishRequired(previousStreamState)) effectSharedFlow.tryEmit(streamState.toPublicState()) + if (streamState.isPublicStatePublishRequired(previousStreamState)) effectSharedFlow.emit(streamState.toPublicState()) XLog.i(this@AppStateMachineImpl.getLog("eventSharedFlow.onEach", "New state:${streamState.state}")) } @@ -150,7 +149,7 @@ class AppStateMachineImpl( .catch { cause -> XLog.e(this@AppStateMachineImpl.getLog("eventSharedFlow.catch"), cause) streamState = componentError(streamState, FatalError.CoroutineException, true) - effectSharedFlow.tryEmit(streamState.toPublicState()) + effectSharedFlow.emit(streamState.toPublicState()) } .collect() } @@ -209,13 +208,13 @@ class AppStateMachineImpl( is HttpServer.Event.Statistic -> when (event) { is HttpServer.Event.Statistic.Clients -> { + effectSharedFlow.emit(AppStateMachine.Effect.Statistic.Clients(event.clients)) if (settings.autoStartStopFlow.first()) checkAutoStartStop(event.clients) if (settings.notifySlowConnectionsFlow.first()) checkForSlowClients(event.clients) - effectSharedFlow.tryEmit(AppStateMachine.Effect.Statistic.Clients(event.clients)) } is HttpServer.Event.Statistic.Traffic -> - effectSharedFlow.tryEmit(AppStateMachine.Effect.Statistic.Traffic(event.traffic)) + effectSharedFlow.emit(AppStateMachine.Effect.Statistic.Traffic(event.traffic)) } is HttpServer.Event.Error -> onError(event.error) @@ -423,13 +422,13 @@ class AppStateMachineImpl( when (reason) { is RestartReason.ConnectionChanged -> - effectSharedFlow.tryEmit(AppStateMachine.Effect.ConnectionChanged) + effectSharedFlow.emit(AppStateMachine.Effect.ConnectionChanged) is RestartReason.SettingsChanged -> - bitmapStateFlow.tryEmit(notificationBitmap.getNotificationBitmap(NotificationBitmap.Type.RELOAD_PAGE)) + bitmapStateFlow.emit(notificationBitmap.getNotificationBitmap(NotificationBitmap.Type.RELOAD_PAGE)) is RestartReason.NetworkSettingsChanged -> - bitmapStateFlow.tryEmit(notificationBitmap.getNotificationBitmap(NotificationBitmap.Type.NEW_ADDRESS)) + bitmapStateFlow.emit(notificationBitmap.getNotificationBitmap(NotificationBitmap.Type.NEW_ADDRESS)) } withTimeoutOrNull(300) { httpServer.stop().await() } @@ -463,7 +462,7 @@ class AppStateMachineImpl( private suspend fun requestPublicState(streamState: StreamState): StreamState { XLog.d(getLog("requestPublicState")) - effectSharedFlow.tryEmit(streamState.toPublicState()) + effectSharedFlow.emit(streamState.toPublicState()) return streamState } diff --git a/data/src/main/kotlin/io/ktor/http/cio/internals/CIOHeadersResearch.java b/data/src/main/kotlin/io/ktor/http/cio/internals/CIOHeadersResearch.java deleted file mode 100644 index 9e059f6f..00000000 --- a/data/src/main/kotlin/io/ktor/http/cio/internals/CIOHeadersResearch.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.ktor.http.cio.internals; - -import java.lang.reflect.Field; - -import io.ktor.http.cio.CIOHeaders; -import io.ktor.http.cio.HttpHeadersMap; - -public class CIOHeadersResearch { - public static String getHeadersAsString(CIOHeaders headers) { - try { - Field privateHeadersField = CIOHeaders.class.getDeclaredField("headers"); - privateHeadersField.setAccessible(true); - HttpHeadersMap headersMap = (HttpHeadersMap) privateHeadersField.get(headers); - Field privateBuilderField = HttpHeadersMap.class.getDeclaredField("builder"); - privateBuilderField.setAccessible(true); - CharArrayBuilder charArrayBuilder = (CharArrayBuilder) privateBuilderField.get(headersMap); - return charArrayBuilder.toString(); - } catch (Throwable ignore) { - } - return null; - } -}