diff --git a/app/src/main/java/io/timelimit/android/ui/login/AllowUserLoginStatus.kt b/app/src/main/java/io/timelimit/android/ui/login/AllowUserLoginStatus.kt index 3368cc4..c95afc8 100644 --- a/app/src/main/java/io/timelimit/android/ui/login/AllowUserLoginStatus.kt +++ b/app/src/main/java/io/timelimit/android/ui/login/AllowUserLoginStatus.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2020 Jonas Lochmann + * TimeLimit Copyright 2019 - 2022 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,25 +23,51 @@ import io.timelimit.android.data.extensions.getCategoryWithParentCategories import io.timelimit.android.data.model.ExperimentalFlags import io.timelimit.android.data.model.derived.CompleteUserLoginRelatedData import io.timelimit.android.integration.platform.BatteryStatus +import io.timelimit.android.integration.platform.NetworkId +import io.timelimit.android.livedata.ignoreUnchanged +import io.timelimit.android.livedata.liveDataFromFunction import io.timelimit.android.logic.AppLogic import io.timelimit.android.logic.BlockingReason import io.timelimit.android.logic.RealTime import io.timelimit.android.logic.blockingreason.CategoryHandlingCache +import java.lang.IllegalStateException import java.util.concurrent.CountDownLatch sealed class AllowUserLoginStatus { - data class Allow(val maxTime: Long): AllowUserLoginStatus() - data class ForbidByCategory(val categoryTitle: String, val blockingReason: BlockingReason, val maxTime: Long): AllowUserLoginStatus() + open val dependsOnNetworkId = false + + data class Allow( + val maxTime: Long, + override val dependsOnNetworkId: Boolean + ): AllowUserLoginStatus() + + data class ForbidByCategory( + val categoryTitle: String, + val blockingReason: BlockingReason, + val maxTime: Long, + override val dependsOnNetworkId: Boolean + ): AllowUserLoginStatus() + object ForbidByMissingSync: AllowUserLoginStatus() object ForbidUserNotFound: AllowUserLoginStatus() } object AllowUserLoginStatusUtil { - private fun calculate(data: CompleteUserLoginRelatedData, time: RealTime, cache: CategoryHandlingCache, batteryStatus: BatteryStatus, missingSyncForLimitLoginUser: Boolean): AllowUserLoginStatus = synchronized(cache) { + private fun calculate( + data: CompleteUserLoginRelatedData, + time: RealTime, + cache: CategoryHandlingCache, + batteryStatus: BatteryStatus, + missingSyncForLimitLoginUser: Boolean, + currentNetworkId: NetworkId? + ): AllowUserLoginStatus = synchronized(cache) { val hasPremium = data.deviceRelatedData.isConnectedAndHasPremium || data.deviceRelatedData.isLocalMode if (!hasPremium) { - return AllowUserLoginStatus.Allow(maxTime = Long.MAX_VALUE) + return AllowUserLoginStatus.Allow( + maxTime = Long.MAX_VALUE, + dependsOnNetworkId = false + ) } return if (data.limitLoginCategoryUserRelatedData != null && data.loginRelatedData.limitLoginCategory != null) { @@ -52,6 +78,7 @@ object AllowUserLoginStatusUtil { val preBlockDuration = data.loginRelatedData.limitLoginCategory.preBlockDuration val maxCheckedTime = time.timeInMillis + preBlockDuration val categoryIds = data.limitLoginCategoryUserRelatedData.getCategoryWithParentCategories(data.loginRelatedData.limitLoginCategory.categoryId) + var dependsOnAnyNetworkId = false while (true) { cache.reportStatus( @@ -60,18 +87,21 @@ object AllowUserLoginStatusUtil { timeInMillis = currentCheckedTime, shouldTrustTimeTemporarily = time.shouldTrustTimeTemporarily, batteryStatus = batteryStatus, - currentNetworkId = null, // only checks shouldBlockAtSystemLevel which ignores the network id + currentNetworkId = currentNetworkId, hasPremiumOrLocalMode = data.deviceRelatedData.isLocalMode || data.deviceRelatedData.isConnectedAndHasPremium ) val handlings = categoryIds.map { cache.get(it) } val remainingTimeToCheck = maxCheckedTime - currentCheckedTime + dependsOnAnyNetworkId = dependsOnAnyNetworkId or (handlings.find { it.dependsOnNetworkId } != null) + handlings.find { it.remainingSessionDuration != null && it.remainingSessionDuration < remainingTimeToCheck }?.let { blockingHandling -> return AllowUserLoginStatus.ForbidByCategory( categoryTitle = blockingHandling.createdWithCategoryRelatedData.category.title, blockingReason = BlockingReason.SessionDurationLimit, - maxTime = blockingHandling.dependsOnMaxTime + maxTime = blockingHandling.dependsOnMaxTime, + dependsOnNetworkId = dependsOnAnyNetworkId ) } @@ -79,7 +109,8 @@ object AllowUserLoginStatusUtil { return AllowUserLoginStatus.ForbidByCategory( categoryTitle = blockingHandling.createdWithCategoryRelatedData.category.title, blockingReason = BlockingReason.TimeOver, - maxTime = blockingHandling.dependsOnMaxTime + maxTime = blockingHandling.dependsOnMaxTime, + dependsOnNetworkId = dependsOnAnyNetworkId ) } @@ -87,7 +118,19 @@ object AllowUserLoginStatusUtil { return AllowUserLoginStatus.ForbidByCategory( categoryTitle = blockingHandling.createdWithCategoryRelatedData.category.title, blockingReason = blockingHandling.systemLevelBlockingReason, - maxTime = blockingHandling.dependsOnMaxTime + maxTime = blockingHandling.dependsOnMaxTime, + dependsOnNetworkId = dependsOnAnyNetworkId + ) + } + + handlings.find { !it.okByNetworkId }?.let { blockingHandling -> + if (!dependsOnAnyNetworkId) throw IllegalStateException() + + return AllowUserLoginStatus.ForbidByCategory( + categoryTitle = blockingHandling.createdWithCategoryRelatedData.category.title, + blockingReason = blockingHandling.activityBlockingReason, + maxTime = Long.MAX_VALUE, + dependsOnNetworkId = dependsOnAnyNetworkId ) } @@ -101,12 +144,14 @@ object AllowUserLoginStatusUtil { val maxTimeByCategories = categoryIds.map { cache.get(it) }.minByOrNull { it.dependsOnMaxTime }?.dependsOnMaxTime ?: Long.MAX_VALUE AllowUserLoginStatus.Allow( - maxTime = (maxTimeByCategories - preBlockDuration).coerceAtLeast(time.timeInMillis + 1000) + maxTime = (maxTimeByCategories - preBlockDuration).coerceAtLeast(time.timeInMillis + 1000), + dependsOnNetworkId = dependsOnAnyNetworkId ) } } else { AllowUserLoginStatus.Allow( - maxTime = Long.MAX_VALUE + maxTime = Long.MAX_VALUE, + dependsOnNetworkId = false ) } } @@ -118,27 +163,54 @@ object AllowUserLoginStatusUtil { val userRelatedData = logic.database.derivedDataDao().getUserLoginRelatedDataSync(userId) ?: return AllowUserLoginStatus.ForbidUserNotFound val realTime = RealTime.newInstance() val batteryStatus = logic.platformIntegration.getBatteryStatus() - val latch = CountDownLatch(1) - Threads.mainThreadHandler.post { - logic.realTimeLogic.getRealTime(realTime) - latch.countDown() + CountDownLatch(1).let { latch -> + Threads.mainThreadHandler.post { + logic.realTimeLogic.getRealTime(realTime) + latch.countDown() + } + + latch.await() } - latch.await() + val attempt1 = calculate( + data = userRelatedData, + batteryStatus = batteryStatus, + time = realTime, + cache = CategoryHandlingCache(), + missingSyncForLimitLoginUser = requireSyncOnLimitLoginUser && !didSync, + currentNetworkId = null + ) - return calculate( + return if (attempt1.dependsOnNetworkId) { + val currentNetworkId = CountDownLatch(1).let { latch -> + var currentNetworkId: NetworkId? = null + + Threads.mainThreadHandler.post { + currentNetworkId = logic.platformIntegration.getCurrentNetworkId() + latch.countDown() + } + + latch.await() + + currentNetworkId!! + } + + calculate( data = userRelatedData, batteryStatus = batteryStatus, time = realTime, cache = CategoryHandlingCache(), - missingSyncForLimitLoginUser = requireSyncOnLimitLoginUser && !didSync - ) + missingSyncForLimitLoginUser = requireSyncOnLimitLoginUser && !didSync, + currentNetworkId = currentNetworkId + ) + } else attempt1 } fun calculateLive(logic: AppLogic, userId: String, didSyncLive: LiveData): LiveData = object : MediatorLiveData() { val cache = CategoryHandlingCache() val time = RealTime.newInstance() + val currentNetworkIdLive = liveDataFromFunction { logic.platformIntegration.getCurrentNetworkId() }.ignoreUnchanged() var batteryStatus: BatteryStatus? = null var hasUserLoginRelatedData = false var userLoginRelatedData: CompleteUserLoginRelatedData? = null @@ -147,6 +219,8 @@ object AllowUserLoginStatusUtil { var isConnectedMode = false var didLoadIsConnectedMode = false var didSync = false + var isObservingNetworkId = false + var currentNetworkId: NetworkId? = null init { addSource(logic.platformIntegration.getBatteryStatusLive(), androidx.lifecycle.Observer { @@ -194,7 +268,8 @@ object AllowUserLoginStatusUtil { batteryStatus = batteryStatus, cache = cache, time = time, - missingSyncForLimitLoginUser = isConnectedMode && requireSyncForLimitLoginUsers && !didSync + missingSyncForLimitLoginUser = isConnectedMode && requireSyncForLimitLoginUsers && !didSync, + currentNetworkId = currentNetworkId ) if (result != value) { @@ -212,6 +287,20 @@ object AllowUserLoginStatusUtil { logic.timeApi.cancelScheduledAction(updateRunnable) logic.timeApi.runDelayedByUptime(updateRunnable, scheduledTime - time.timeInMillis) } + + if (result.dependsOnNetworkId != isObservingNetworkId) { + // important detail: the addSource can call update immediately + isObservingNetworkId = result.dependsOnNetworkId + + if (result.dependsOnNetworkId) { + addSource(currentNetworkIdLive) { + currentNetworkId = it; update() + } + } else { + removeSource(currentNetworkIdLive) + currentNetworkId = null + } + } } override fun onActive() {