diff --git a/app/build.gradle b/app/build.gradle index c56d7db6..0c58eb4b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,8 +12,8 @@ android { applicationId = "info.dvkr.screenstream" minSdkVersion(21) targetSdkVersion(31) - versionCode = 30800 - versionName = "3.8.0" + versionCode = 30801 + versionName = "3.8.1" resConfigs("en", "ru", "pt-rBR", "zh-rTW", "fr-rFR", "fa", "it", "pl", "hi", "de", "sk", "es", "ar", "ja", "gl", "ca", "uk", "nl") vectorDrawables.useSupportLibrary = true @@ -92,17 +92,17 @@ dependencies { implementation("androidx.core:core-ktx:1.6.0") implementation("androidx.activity:activity-ktx:1.3.1") - implementation("androidx.fragment:fragment-ktx:1.4.0-alpha08") + implementation("androidx.fragment:fragment-ktx:1.4.0-alpha09") implementation("androidx.appcompat:appcompat:1.3.1") implementation("androidx.constraintlayout:constraintlayout:2.1.0") implementation("androidx.recyclerview:recyclerview:1.2.1") implementation("com.google.android.material:material:1.4.0") implementation("androidx.window:window:1.0.0-beta02") - implementation("androidx.navigation:navigation-fragment-ktx:2.4.0-alpha08") - implementation("androidx.navigation:navigation-ui-ktx:2.4.0-alpha08") - implementation("androidx.lifecycle:lifecycle-livedata:2.3.1") - implementation("androidx.lifecycle:lifecycle-common-java8:2.3.1") + implementation("androidx.navigation:navigation-fragment-ktx:2.4.0-alpha09") + implementation("androidx.navigation:navigation-ui-ktx:2.4.0-alpha09") + implementation("androidx.lifecycle:lifecycle-livedata:2.4.0-beta01") + implementation("androidx.lifecycle:lifecycle-common:2.4.0-beta01") implementation("com.afollestad.material-dialogs:core:3.3.0") implementation("com.afollestad.material-dialogs:color:3.3.0") @@ -116,7 +116,7 @@ dependencies { implementation("com.github.iamironz:binaryprefs:1.0.1") implementation("com.elvishew:xlog:1.11.0") - firebaseImplementation("com.google.android.play:core:1.10.1") + firebaseImplementation("com.google.android.play:core:1.10.2") firebaseImplementation("com.google.android.play:core-ktx:1.8.1") { exclude group: "org.jetbrains.kotlin" exclude group: "org.jetbrains.kotlinx" @@ -125,7 +125,7 @@ dependencies { } firebaseImplementation("com.google.firebase:firebase-analytics:19.0.1") firebaseImplementation("com.google.firebase:firebase-crashlytics:18.2.1") - firebaseImplementation("com.google.android.gms:play-services-ads:20.3.0") + firebaseImplementation("com.google.android.gms:play-services-ads:20.4.0") firebaseImplementation("androidx.work:work-runtime:2.7.0-beta01") // Temp fix for AdMob Android 12 debugImplementation("com.squareup.leakcanary:leakcanary-android:2.7") diff --git a/app/src/main/kotlin/info/dvkr/screenstream/ui/view/ColorCircleView.kt b/app/src/main/kotlin/info/dvkr/screenstream/ui/view/ColorCircleView.kt index 76b17d0f..3dd6d4e5 100644 --- a/app/src/main/kotlin/info/dvkr/screenstream/ui/view/ColorCircleView.kt +++ b/app/src/main/kotlin/info/dvkr/screenstream/ui/view/ColorCircleView.kt @@ -15,7 +15,7 @@ class ColorCircleView @JvmOverloads constructor( private val fillPaint = Paint() private val strokePaint = Paint() - private val borderWidth = resources.getDimension(R.dimen.color_circle_view_border) + private val borderWidth = resources.getDimension(com.afollestad.materialdialogs.color.R.dimen.color_circle_view_border) init { setWillNotDraw(false) diff --git a/build.gradle b/build.gradle index febae010..1fd4b7ec 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { dependencies { classpath("com.android.tools.build:gradle:7.0.2") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.30") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31") classpath("com.google.gms:google-services:4.3.10") classpath("com.google.firebase:firebase-crashlytics-gradle:2.7.1") } diff --git a/data/build.gradle b/data/build.gradle index 863cfc63..69f4db2f 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -26,7 +26,7 @@ android { dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2") - implementation("org.jetbrains.kotlin:kotlin-reflect:1.5.30") + implementation("org.jetbrains.kotlin:kotlin-reflect:1.5.31") implementation("androidx.core:core:1.6.0") implementation("androidx.window:window:1.0.0-beta02") diff --git a/data/src/main/kotlin/info/dvkr/screenstream/data/image/BitmapCapture.kt b/data/src/main/kotlin/info/dvkr/screenstream/data/image/BitmapCapture.kt index 372e84f6..4f8c784c 100644 --- a/data/src/main/kotlin/info/dvkr/screenstream/data/image/BitmapCapture.kt +++ b/data/src/main/kotlin/info/dvkr/screenstream/data/image/BitmapCapture.kt @@ -1,7 +1,9 @@ package info.dvkr.screenstream.data.image import android.annotation.SuppressLint +import android.content.ComponentCallbacks import android.content.Context +import android.content.res.Configuration import android.graphics.* import android.hardware.display.DisplayManager import android.hardware.display.VirtualDisplay @@ -38,20 +40,12 @@ import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicReference class BitmapCapture( - private val applicationContext: Context, + private val context: Context, private val settingsReadOnly: SettingsReadOnly, private val mediaProjection: MediaProjection, private val bitmapStateFlow: MutableStateFlow, private val onError: (AppError) -> Unit ) { - - private val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable -> - XLog.e(getLog("onCoroutineException"), throwable) - onError(FatalError.CoroutineException) - } - - private val coroutineScope = CoroutineScope(Job() + Dispatchers.Default + coroutineExceptionHandler) - private enum class State { INIT, STARTED, DESTROYED, ERROR } @Volatile @@ -63,12 +57,12 @@ class BitmapCapture( private val imageThreadHandler: Handler by lazy { Handler(imageThread.looper) } private val display: Display by lazy { - ContextCompat.getSystemService(applicationContext, DisplayManager::class.java)!! + ContextCompat.getSystemService(context, DisplayManager::class.java)!! .getDisplay(Display.DEFAULT_DISPLAY) } @RequiresApi(Build.VERSION_CODES.S) - private fun windowContext(): Context = applicationContext.createDisplayContext(display) + private fun windowContext(): Context = context.createDisplayContext(display) .createWindowContext(WindowManager.LayoutParams.TYPE_APPLICATION, null) private fun getDensityDpiCompat(): Int = @@ -117,6 +111,15 @@ class BitmapCapture( } } + private val componentCallback = object : ComponentCallbacks { + override fun onConfigurationChanged(newConfig: Configuration) { + XLog.d(this@BitmapCapture.getLog("ComponentCallbacks", "Configuration changed")) + if (state == State.STARTED) restart() + } + + override fun onLowMemory() = Unit + } + init { XLog.d(getLog("init")) settingsReadOnly.registerChangeListener(settingsListener) @@ -151,27 +154,7 @@ class BitmapCapture( fun start() { XLog.d(getLog("start")) requireState(State.INIT) - startDisplayCapture() - - if (state == State.STARTED) - coroutineScope.launch { - val rotation = display.rotation - var previous = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 - var current: Boolean - - XLog.d(this@BitmapCapture.getLog("Start", "Rotation detector started")) - while (isActive) { - delay(250) - val newRotation = display.rotation - current = newRotation == Surface.ROTATION_0 || newRotation == Surface.ROTATION_180 - if (previous != current) { - XLog.d(this@BitmapCapture.getLog("Start", "Rotation detected")) - previous = current - if (state == State.STARTED) restart() - } - } - } } @Synchronized @@ -182,9 +165,7 @@ class BitmapCapture( return } requireState(State.STARTED, State.ERROR) - coroutineScope.cancel(CancellationException("BitmapCapture.destroy")) state = State.DESTROYED - settingsReadOnly.unregisterChangeListener(settingsListener) stopDisplayCapture() imageThread.quit() } @@ -251,9 +232,14 @@ class BitmapCapture( XLog.w(getLog("startDisplayCapture", ex.toString())) onError(FixableError.CastSecurityException) } + + if (state == State.STARTED) + context.registerComponentCallbacks(componentCallback) } private fun stopDisplayCapture() { + context.unregisterComponentCallbacks(componentCallback) + virtualDisplay?.release() imageReader?.close() 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 256fc846..90b49ba9 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 @@ -24,12 +24,11 @@ import kotlinx.coroutines.flow.* import java.util.concurrent.LinkedBlockingDeque class AppStateMachineImpl( - context: Context, + private val context: Context, private val settingsReadOnly: SettingsReadOnly, private val onEffect: suspend (AppStateMachine.Effect) -> Unit ) : AppStateMachine { - private val appContext = context.applicationContext private val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable -> XLog.e(getLog("onCoroutineException"), throwable) onError(FatalError.CoroutineException) @@ -51,7 +50,7 @@ class AppStateMachineImpl( private val networkHelper = NetworkHelper(context) private val notificationBitmap = NotificationBitmap(context) private val httpServer = HttpServer( - appContext, coroutineScope, settingsReadOnly, bitmapStateFlow.asStateFlow(), + context, coroutineScope, settingsReadOnly, bitmapStateFlow.asStateFlow(), notificationBitmap.getNotificationBitmap(NotificationBitmap.Type.ADDRESS_BLOCKED) ) @@ -103,6 +102,7 @@ class AppStateMachineImpl( try { _eventDeque.addLast(event) _eventSharedFlow.tryEmit(event) || throw IllegalStateException("_eventSharedFlow IsFull") + XLog.d(getLog("sendEvent", _eventDeque.toString())) } catch (th: Throwable) { XLog.e(getLog("sendEvent", _eventDeque.toString())) XLog.e(getLog("sendEvent"), th) @@ -117,7 +117,7 @@ class AppStateMachineImpl( private var streamState = StreamState() private var previousStreamState = StreamState() - private val _eventSharedFlow = MutableSharedFlow(extraBufferCapacity = 8) + private val _eventSharedFlow = MutableSharedFlow(replay = 5, extraBufferCapacity = 8) private val _eventDeque = LinkedBlockingDeque() init { @@ -148,9 +148,10 @@ class AppStateMachineImpl( if (streamState.isPublicStatePublishRequired(previousStreamState)) onEffect(streamState.toPublicState()) - _eventDeque.pollFirst() XLog.i(this@AppStateMachineImpl.getLog("eventSharedFlow.onEach", "New state:${streamState.state}")) } + _eventDeque.pollFirst() + XLog.d(getLog("eventSharedFlow.onEach.done", _eventDeque.toString())) } .catch { cause -> XLog.e(this@AppStateMachineImpl.getLog("eventSharedFlow.catch"), cause) @@ -290,7 +291,7 @@ class AppStateMachineImpl( val mediaProjection = projectionManager.getMediaProjection(Activity.RESULT_OK, intent) mediaProjection.registerCallback(projectionCallback, Handler(Looper.getMainLooper())) - val bitmapCapture = BitmapCapture(appContext, settingsReadOnly, mediaProjection, bitmapStateFlow, ::onError) + val bitmapCapture = BitmapCapture(context, settingsReadOnly, mediaProjection, bitmapStateFlow, ::onError) bitmapCapture.start() return streamState.copy( diff --git a/gradle.properties b/gradle.properties index 24596653..3dea1cdf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,4 +23,5 @@ kotlin.code.style=official android.defaults.buildfeatures.aidl=false android.defaults.buildfeatures.dataBinding=false android.defaults.buildfeatures.renderscript=false -android.defaults.buildfeatures.shaders=false \ No newline at end of file +android.defaults.buildfeatures.shaders=false +android.nonTransitiveRClass=true \ No newline at end of file