From adc333fd404cedce1d2ebde9227de24e3979d8cc Mon Sep 17 00:00:00 2001 From: Jonas Lochmann Date: Mon, 16 Aug 2021 02:00:00 +0200 Subject: [PATCH] Only update session duration start times after five seconds usage --- .../android/logic/BackgroundTaskLogic.kt | 10 +- .../logic/UndisturbedCategoryUsageCounter.kt | 51 +++++++++ .../android/logic/UsedTimeUpdateHelper.kt | 102 +++++++++++++----- 3 files changed, 136 insertions(+), 27 deletions(-) create mode 100644 app/src/main/java/io/timelimit/android/logic/UndisturbedCategoryUsageCounter.kt diff --git a/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt b/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt index 86e746c..a2ec7c7 100644 --- a/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt +++ b/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt @@ -128,6 +128,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { timeApi = appLogic.timeApi, longDuration = 1000 * 60 * 10 /* 10 minutes */ ) + private val undisturbedCategoryUsageCounter = UndisturbedCategoryUsageCounter() private val appTitleCache = QueryAppTitleCache(appLogic.platformIntegration) private val categoryHandlingCache = CategoryHandlingCache() @@ -194,6 +195,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { // app must be enabled if (!appLogic.enable.waitForNonNullValue()) { commitUsedTimeUpdaters() + undisturbedCategoryUsageCounter.reset() appLogic.platformIntegration.setAppStatusMessage(null) appLogic.platformIntegration.setShowBlockingOverlay(false) setShowNotificationToRevokeTemporarilyAllowedApps(false) @@ -215,6 +217,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { // device must be used by a child if (deviceRelatedData == null || userRelatedData == null || userRelatedData.user.type != UserType.Child) { commitUsedTimeUpdaters() + undisturbedCategoryUsageCounter.reset() val shouldDoAutomaticSignOut = deviceRelatedData != null && DefaultUserLogic.hasAutomaticSignOut(deviceRelatedData) && deviceRelatedData.canSwitchToDefaultUser @@ -248,6 +251,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { val nowTimestamp = realTime.timeInMillis val nowTimezone = TimeZone.getTimeZone(userRelatedData.user.timeZone) + val nowUptime = appLogic.timeApi.getCurrentUptimeInMillis() val nowDate = DateInTimezone.getLocalDate(nowTimestamp, nowTimezone) val nowMinuteOfWeek = getMinuteOfWeek(nowTimestamp, nowTimezone) @@ -323,6 +327,9 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { if (it is AppBaseHandling.UseCategories) it.categoryIds else emptySet() }.flattenToSet() + undisturbedCategoryUsageCounter.report(nowUptime, currentCategoryIds) + val recentlyStartedCategories = undisturbedCategoryUsageCounter.getRecentlyStartedCategories(nowUptime) + val needsNetworkId = allAppsBaseHandlings.find { it.needsNetworkId() } != null val networkId: NetworkId? = if (needsNetworkId) appLogic.platformIntegration.getCurrentNetworkId() else null @@ -496,7 +503,8 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { duration = timeToSubtract, dayOfEpoch = dayOfEpoch, trustedTimestamp = if (realTime.shouldTrustTimePermanently) realTime.timeInMillis else 0, - handlings = categoryHandlingsToCount + handlings = categoryHandlingsToCount, + recentlyStartedCategories = recentlyStartedCategories ) if (didAutoCommitOfUsedTimes) { diff --git a/app/src/main/java/io/timelimit/android/logic/UndisturbedCategoryUsageCounter.kt b/app/src/main/java/io/timelimit/android/logic/UndisturbedCategoryUsageCounter.kt new file mode 100644 index 0000000..23cabc1 --- /dev/null +++ b/app/src/main/java/io/timelimit/android/logic/UndisturbedCategoryUsageCounter.kt @@ -0,0 +1,51 @@ +/* + * TimeLimit Copyright 2019 - 2021 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 + * the Free Software Foundation version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package io.timelimit.android.logic + +class UndisturbedCategoryUsageCounter { + private val data = mutableMapOf() + + fun reset() { + data.clear() + } + + fun report(uptime: Long, categories: Set) { + removeUnknown(categories) + addNewCategories(categories, uptime) + } + + fun getRecentlyStartedCategories(uptime: Long): Set { + return data.filterValues { it >= uptime - 5000 }.keys + } + + private fun removeUnknown(currentCategories: Set) { + val iterator = data.iterator() + + while (iterator.hasNext()) { + if (!currentCategories.contains(iterator.next().key)) { + iterator.remove() + } + } + } + + private fun addNewCategories(categories: Set, uptime: Long) { + categories.forEach { categoryId -> + if (!data.containsKey(categoryId)) { + data[categoryId] = uptime + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/logic/UsedTimeUpdateHelper.kt b/app/src/main/java/io/timelimit/android/logic/UsedTimeUpdateHelper.kt index a935fd0..0e61480 100644 --- a/app/src/main/java/io/timelimit/android/logic/UsedTimeUpdateHelper.kt +++ b/app/src/main/java/io/timelimit/android/logic/UsedTimeUpdateHelper.kt @@ -32,6 +32,7 @@ class UsedTimeUpdateHelper (private val appLogic: AppLogic) { private var countedTime = 0 private var lastCategoryHandlings = emptyList() private var categoryIds = emptySet() + private var recentlyStartedCategories = emptySet() private var trustedTimestamp = 0L private var dayOfEpoch = 0 private var maxTimeToAdd = Long.MAX_VALUE @@ -40,7 +41,13 @@ class UsedTimeUpdateHelper (private val appLogic: AppLogic) { fun getCountedCategoryIds() = categoryIds // returns true if it made a commit - suspend fun report(duration: Int, handlings: List, trustedTimestamp: Long, dayOfEpoch: Int): Boolean { + suspend fun report( + duration: Int, + handlings: List, + recentlyStartedCategories: Set, + trustedTimestamp: Long, + dayOfEpoch: Int + ): Boolean { if (handlings.find { !it.shouldCountTime } != null || duration < 0) { throw IllegalArgumentException() } @@ -94,6 +101,7 @@ class UsedTimeUpdateHelper (private val appLogic: AppLogic) { } this.lastCategoryHandlings = handlings + this.recentlyStartedCategories = recentlyStartedCategories this.trustedTimestamp = trustedTimestamp this.dayOfEpoch = dayOfEpoch @@ -111,40 +119,82 @@ class UsedTimeUpdateHelper (private val appLogic: AppLogic) { val makeCommit = lastCategoryHandlings.isNotEmpty() && countedTime > 0 if (makeCommit) { - try { - ApplyActionUtil.applyAppLogicAction( - action = AddUsedTimeActionVersion2( - dayOfEpoch = dayOfEpoch, - items = lastCategoryHandlings.map { handling -> - AddUsedTimeActionItem( - categoryId = handling.createdWithCategoryRelatedData.category.id, - timeToAdd = countedTime, - extraTimeToSubtract = if (handling.shouldCountExtraTime) countedTime else 0, - additionalCountingSlots = handling.additionalTimeCountingSlots, - sessionDurationLimits = handling.sessionDurationSlotsToCount - ) - }, - trustedTimestamp = if (lastCategoryHandlings.find { it.sessionDurationSlotsToCount.isNotEmpty() } != null) trustedTimestamp else 0 - ), - appLogic = appLogic, - ignoreIfDeviceIsNotConfigured = true + val categoriesWithSessionDurationLimits = lastCategoryHandlings + .filter { it.sessionDurationSlotsToCount.isNotEmpty() } + .map { it.createdWithCategoryRelatedData.category.id } + .toSet() + + val categoriesWithSessionDurationLimitsWhichWereRecentlyStarted = categoriesWithSessionDurationLimits.intersect(recentlyStartedCategories) + + if (categoriesWithSessionDurationLimitsWhichWereRecentlyStarted.isEmpty()) { + commitSendAction( + items = lastCategoryHandlings, + sendTimestamp = categoriesWithSessionDurationLimits.isNotEmpty() ) - } catch (ex: CategoryNotFoundException) { + } else { if (BuildConfig.DEBUG) { - Log.d(LOG_TAG, "could not commit used times", ex) + Log.d(LOG_TAG, "skip updating the session duration last usage timestamps") } - // this is a very rare case if a category is deleted while it is used; - // in this case there could be some lost time - // changes for other categories, but it's no big problem + if (categoriesWithSessionDurationLimits == categoriesWithSessionDurationLimitsWhichWereRecentlyStarted) { + // no category needs the timestamp + commitSendAction( + items = lastCategoryHandlings, + sendTimestamp = false + ) + } else { + if (BuildConfig.DEBUG) { + Log.d(LOG_TAG, "... but only for some categories") + } + + // some categories need the timestamp and others do not + val items1 = lastCategoryHandlings.filter { + categoriesWithSessionDurationLimitsWhichWereRecentlyStarted.contains(it.createdWithCategoryRelatedData.category.id) + } + val items2 = lastCategoryHandlings.filterNot { + categoriesWithSessionDurationLimitsWhichWereRecentlyStarted.contains(it.createdWithCategoryRelatedData.category.id) + } + + commitSendAction(items = items1, sendTimestamp = false) + commitSendAction(items = items2, sendTimestamp = true) + } } } countedTime = 0 - // doing this would cause a commit very soon again - // lastCategoryHandlings = emptyList() - // categoryIds = emptySet() return makeCommit } + + private suspend fun commitSendAction(items: List, sendTimestamp: Boolean) { + try { + ApplyActionUtil.applyAppLogicAction( + action = AddUsedTimeActionVersion2( + dayOfEpoch = dayOfEpoch, + items = items.map { handling -> prepareHandling(handling) }, + trustedTimestamp = if (sendTimestamp) trustedTimestamp else 0 + ), + appLogic = appLogic, + ignoreIfDeviceIsNotConfigured = true + ) + } catch (ex: CategoryNotFoundException) { + if (BuildConfig.DEBUG) { + Log.d(LOG_TAG, "could not commit used times", ex) + } + + // this is a very rare case if a category is deleted while it is used; + // in this case there could be some lost time + // changes for other categories, but it's no big problem + } + } + + private fun prepareHandling(handling: CategoryItselfHandling): AddUsedTimeActionItem { + return AddUsedTimeActionItem( + categoryId = handling.createdWithCategoryRelatedData.category.id, + timeToAdd = countedTime, + extraTimeToSubtract = if (handling.shouldCountExtraTime) countedTime else 0, + additionalCountingSlots = handling.additionalTimeCountingSlots, + sessionDurationLimits = handling.sessionDurationSlotsToCount + ) + } } \ No newline at end of file