diff --git a/app/src/main/java/io/timelimit/android/data/dao/UsedTimeDao.kt b/app/src/main/java/io/timelimit/android/data/dao/UsedTimeDao.kt index 97c7dc1..ccf9e96 100644 --- a/app/src/main/java/io/timelimit/android/data/dao/UsedTimeDao.kt +++ b/app/src/main/java/io/timelimit/android/data/dao/UsedTimeDao.kt @@ -69,9 +69,6 @@ abstract class UsedTimeDao { @Query("SELECT * FROM used_time WHERE category_id = :categoryId ORDER BY day_of_epoch DESC") abstract fun getUsedTimesByCategoryId(categoryId: String): DataSource.Factory - @Query("SELECT * FROM used_time WHERE category_id = :categoryId AND day_of_epoch = :dayOfEpoch") - abstract fun getUsedTimesByCategoryIdAndDayOfEpoch(categoryId: String, dayOfEpoch: Int): LiveData - @Query("SELECT * FROM used_time WHERE category_id IN (:categoryIds) AND day_of_epoch >= :startingDayOfEpoch AND day_of_epoch <= :endDayOfEpoch") abstract fun getUsedTimesByDayAndCategoryIds(categoryIds: List, startingDayOfEpoch: Int, endDayOfEpoch: Int): LiveData> 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 265f5fa..f10c013 100644 --- a/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt +++ b/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt @@ -109,8 +109,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { private val shouldDoAutomaticSignOut = cache.shouldDoAutomaticSignOut private val liveDataCaches = cache.liveDataCaches - private var usedTimeUpdateHelperForegroundApp: UsedTimeItemBatchUpdateHelper? = null - private var usedTimeUpdateHelperBackgroundPlayback: UsedTimeItemBatchUpdateHelper? = null + private var usedTimeUpdateHelper: UsedTimeUpdateHelper? = null private var previousMainLogicExecutionTime = 0 private var previousMainLoopEndTime = 0L private val dayChangeTracker = DayChangeTracker( @@ -140,8 +139,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { val audioPlaybackHandling = BackgroundTaskRestrictionLogicResult() private suspend fun commitUsedTimeUpdaters() { - usedTimeUpdateHelperForegroundApp?.commit(appLogic) - usedTimeUpdateHelperBackgroundPlayback?.commit(appLogic) + usedTimeUpdateHelper?.forceCommit(appLogic) } private suspend fun backgroundServiceLoop() { @@ -281,84 +279,55 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { result = audioPlaybackHandling ) + // update used time helper if date does not match + if (usedTimeUpdateHelper?.date != nowDate) { + usedTimeUpdateHelper?.forceCommit(appLogic) + usedTimeUpdateHelper = UsedTimeUpdateHelper(nowDate) + } + + val usedTimeUpdateHelper = usedTimeUpdateHelper!! + // check times fun buildUsedTimesSparseArray(items: SparseArray, categoryId: String): SparseLongArray { - val a = usedTimeUpdateHelperForegroundApp - val b = usedTimeUpdateHelperBackgroundPlayback - val result = SparseLongArray() for (i in 0..6) { - val usedTimesItem = items[i]?.usedMillis + val usedTimesItem = items[i]?.usedMillis ?: 0 + val timeToAddButNotCommited = usedTimeUpdateHelper.timeToAdd[categoryId] ?: 0 - if (a?.date?.dayOfWeek == i && a.childCategoryId == categoryId) { - result.put(i, a.getTotalUsedTimeChild()) - } else if (a?.date?.dayOfWeek == i && a.parentCategoryId == categoryId) { - result.put(i, a.getTotalUsedTimeParent()) - } else if (b?.date?.dayOfWeek == i && b.childCategoryId == categoryId) { - result.put(i, b.getTotalUsedTimeChild()) - } else if (b?.date?.dayOfWeek == i && b.parentCategoryId == categoryId) { - result.put(i, b.getTotalUsedTimeParent()) - } else { - result.put(i, usedTimesItem ?: 0) - } + result.put(i, usedTimesItem + timeToAddButNotCommited) } return result } - fun getCachedExtraTimeToSubstract(categoryId: String): Int { - val a = usedTimeUpdateHelperForegroundApp - val b = usedTimeUpdateHelperBackgroundPlayback - - if (a?.childCategoryId == categoryId || a?.parentCategoryId == categoryId) { - return a.getCachedExtraTimeToSubtract() - } - - if (b?.childCategoryId == categoryId || b?.parentCategoryId == categoryId) { - return b.getCachedExtraTimeToSubtract() - } - - return 0 - } - - suspend fun getRemainingTime(categoryId: String?, parentCategoryId: String?): RemainingTime? { + suspend fun getRemainingTime(categoryId: String?): RemainingTime? { categoryId ?: return null val category = categories.find { it.id == categoryId } ?: return null - val parentCategory = categories.find { it.id == parentCategoryId } - val rules = timeLimitRules.get(category.id).waitForNonNullValue() - val parentRules = parentCategory?.let { - timeLimitRules.get(it.id).waitForNonNullValue() - } ?: emptyList() + + if (rules.isEmpty()) { + return null + } val usedTimes = usedTimesOfCategoryAndWeekByFirstDayOfWeek.get(Pair(category.id, nowDate.dayOfEpoch - nowDate.dayOfWeek)).waitForNonNullValue() - val parentUsedTimes = parentCategory?.let { - usedTimesOfCategoryAndWeekByFirstDayOfWeek.get(Pair(it.id, nowDate.dayOfEpoch - nowDate.dayOfWeek)).waitForNonNullValue() - } ?: SparseArray() - val remainingChild = RemainingTime.getRemainingTime( + return RemainingTime.getRemainingTime( nowDate.dayOfWeek, buildUsedTimesSparseArray(usedTimes, categoryId), rules, - Math.max(0, category.extraTimeInMillis - getCachedExtraTimeToSubstract(category.id)) + Math.max(0, category.extraTimeInMillis - (usedTimeUpdateHelper.extraTimeToSubtract.get(categoryId) ?: 0)) ) - - val remainingParent = parentCategory?.let { - RemainingTime.getRemainingTime( - nowDate.dayOfWeek, - buildUsedTimesSparseArray(parentUsedTimes, parentCategory.id), - parentRules, - Math.max(0, parentCategory.extraTimeInMillis - getCachedExtraTimeToSubstract(parentCategory.id)) - ) - } - - return RemainingTime.min(remainingChild, remainingParent) } - val remainingTimeForegroundApp = getRemainingTime(foregroundAppHandling.categoryId, foregroundAppHandling.parentCategoryId) - val remainingTimeBackgroundApp = getRemainingTime(audioPlaybackHandling.categoryId, audioPlaybackHandling.parentCategoryId) + val remainingTimeForegroundAppChild = getRemainingTime(foregroundAppHandling.categoryId) + val remainingTimeForegroundAppParent = getRemainingTime(foregroundAppHandling.parentCategoryId) + val remainingTimeForegroundApp = RemainingTime.min(remainingTimeForegroundAppChild, remainingTimeForegroundAppParent) + + val remainingTimeBackgroundAppChild = getRemainingTime(audioPlaybackHandling.categoryId) + val remainingTimeBackgroundAppParent = getRemainingTime(audioPlaybackHandling.parentCategoryId) + val remainingTimeBackgroundApp = RemainingTime.min(remainingTimeBackgroundAppChild, remainingTimeBackgroundAppParent) // eventually block if (remainingTimeForegroundApp?.hasRemainingTime == false) { @@ -370,126 +339,75 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { } // update times - suspend fun getUsedTimeItem(categoryId: String?): UsedTimeItem? { - categoryId ?: return null - - return cache.usedTimesOfCategoryAndDayOfEpoch.get(categoryId to nowDate.dayOfEpoch).waitForNullableValue() - } + val timeToSubtract = Math.min(previousMainLogicExecutionTime, MAX_USED_TIME_PER_ROUND) val shouldCountForegroundApp = remainingTimeForegroundApp != null && isScreenOn && remainingTimeForegroundApp.hasRemainingTime val shouldCountBackgroundApp = remainingTimeBackgroundApp != null && remainingTimeBackgroundApp.hasRemainingTime - val countTwoTypes = shouldCountForegroundApp && shouldCountBackgroundApp - val doCategoriesMatch = ( - foregroundAppHandling.categoryId != null && ( - foregroundAppHandling.categoryId == audioPlaybackHandling.categoryId || - foregroundAppHandling.categoryId == audioPlaybackHandling.parentCategoryId - ) - ) || ( - foregroundAppHandling.parentCategoryId != null && ( - foregroundAppHandling.parentCategoryId == audioPlaybackHandling.categoryId || - foregroundAppHandling.parentCategoryId == audioPlaybackHandling.parentCategoryId - ) - ) - val useSingleCounter = countTwoTypes && doCategoriesMatch - if (useSingleCounter) { - val isBackgroundCategoryHigher = audioPlaybackHandling.categoryId != null && - audioPlaybackHandling.categoryId == foregroundAppHandling.parentCategoryId + val categoriesToCount = mutableSetOf() + val categoriesToCountExtraTime = mutableSetOf() - if (usedTimeUpdateHelperForegroundApp !== usedTimeUpdateHelperBackgroundPlayback) { - if (isBackgroundCategoryHigher) { - usedTimeUpdateHelperForegroundApp?.commit(appLogic) - usedTimeUpdateHelperForegroundApp = null - } else { - usedTimeUpdateHelperBackgroundPlayback?.commit(appLogic) - usedTimeUpdateHelperBackgroundPlayback = null + if (shouldCountForegroundApp) { + remainingTimeForegroundAppChild?.let { remainingTime -> + foregroundAppHandling.categoryId?.let { categoryId -> + categoriesToCount.add(categoryId) + + if (remainingTime.usingExtraTime) { + categoriesToCountExtraTime.add(categoryId) + } } } - if (isBackgroundCategoryHigher) { - usedTimeUpdateHelperBackgroundPlayback = UsedTimeItemBatchUpdateHelper.eventuallyUpdateInstance( - date = nowDate, - childCategoryId = audioPlaybackHandling.categoryId!!, - parentCategoryId = audioPlaybackHandling.parentCategoryId, - oldInstance = usedTimeUpdateHelperBackgroundPlayback, - usedTimeItemForDayChild = getUsedTimeItem(audioPlaybackHandling.categoryId), - usedTimeItemForDayParent = getUsedTimeItem(audioPlaybackHandling.parentCategoryId), - logic = appLogic - ) + remainingTimeForegroundAppParent?.let { remainingTime -> + foregroundAppHandling.parentCategoryId?.let { + categoriesToCount.add(it) - usedTimeUpdateHelperForegroundApp = usedTimeUpdateHelperBackgroundPlayback - } else { - usedTimeUpdateHelperForegroundApp = UsedTimeItemBatchUpdateHelper.eventuallyUpdateInstance( - date = nowDate, - childCategoryId = foregroundAppHandling.categoryId!!, - parentCategoryId = foregroundAppHandling.parentCategoryId, - oldInstance = usedTimeUpdateHelperForegroundApp, - usedTimeItemForDayChild = getUsedTimeItem(foregroundAppHandling.categoryId), - usedTimeItemForDayParent = getUsedTimeItem(foregroundAppHandling.parentCategoryId), - logic = appLogic - ) - - usedTimeUpdateHelperBackgroundPlayback = usedTimeUpdateHelperForegroundApp - } - } else { - if (shouldCountForegroundApp) { - usedTimeUpdateHelperForegroundApp = UsedTimeItemBatchUpdateHelper.eventuallyUpdateInstance( - date = nowDate, - childCategoryId = foregroundAppHandling.categoryId!!, - parentCategoryId = foregroundAppHandling.parentCategoryId, - oldInstance = usedTimeUpdateHelperForegroundApp, - usedTimeItemForDayChild = getUsedTimeItem(foregroundAppHandling.categoryId), - usedTimeItemForDayParent = getUsedTimeItem(foregroundAppHandling.parentCategoryId), - logic = appLogic - ) - } else { - usedTimeUpdateHelperForegroundApp?.commit(appLogic) - usedTimeUpdateHelperForegroundApp = null - } - - if (shouldCountBackgroundApp) { - usedTimeUpdateHelperBackgroundPlayback = UsedTimeItemBatchUpdateHelper.eventuallyUpdateInstance( - date = nowDate, - childCategoryId = audioPlaybackHandling.categoryId!!, - parentCategoryId = audioPlaybackHandling.parentCategoryId, - oldInstance = usedTimeUpdateHelperBackgroundPlayback, - usedTimeItemForDayChild = getUsedTimeItem(audioPlaybackHandling.categoryId), - usedTimeItemForDayParent = getUsedTimeItem(audioPlaybackHandling.parentCategoryId), - logic = appLogic - ) - } else { - usedTimeUpdateHelperBackgroundPlayback?.commit(appLogic) - usedTimeUpdateHelperBackgroundPlayback = null + if (remainingTime.usingExtraTime) { + categoriesToCountExtraTime.add(it) + } + } } } - // count times - // never save more than a second of used time - // FIXME: currently, this uses extra time if the parent or the child category needs it and it subtracts it from both - val timeToSubtract = Math.min(previousMainLogicExecutionTime, MAX_USED_TIME_PER_ROUND) + if (shouldCountBackgroundApp) { + remainingTimeBackgroundAppChild?.let { remainingTime -> + audioPlaybackHandling.categoryId?.let { + categoriesToCount.add(it) - if (usedTimeUpdateHelperForegroundApp === usedTimeUpdateHelperBackgroundPlayback) { - usedTimeUpdateHelperForegroundApp?.addUsedTime( - time = timeToSubtract, - subtractExtraTime = remainingTimeForegroundApp!!.usingExtraTime or remainingTimeBackgroundApp!!.usingExtraTime, - appLogic = appLogic - ) - } else { - usedTimeUpdateHelperForegroundApp?.addUsedTime( - time = timeToSubtract, - subtractExtraTime = remainingTimeForegroundApp!!.usingExtraTime, - appLogic = appLogic - ) + if (remainingTime.usingExtraTime) { + categoriesToCountExtraTime.add(it) + } + } + } - usedTimeUpdateHelperBackgroundPlayback?.addUsedTime( - time = timeToSubtract, - subtractExtraTime = remainingTimeBackgroundApp!!.usingExtraTime, - appLogic = appLogic - ) + remainingTimeBackgroundAppParent?.let { remainingTime -> + audioPlaybackHandling.parentCategoryId?.let { + categoriesToCount.add(it) + + if (remainingTime.usingExtraTime) { + categoriesToCountExtraTime.add(it) + } + } + } + } + + if (categoriesToCount.isNotEmpty()) { + categoriesToCount.forEach { categoryId -> + usedTimeUpdateHelper.add( + categoryId = categoryId, + time = timeToSubtract, + includingExtraTime = categoriesToCountExtraTime.contains(categoryId) + ) + } + } + + usedTimeUpdateHelper.reportCurrentCategories(categoriesToCount) + + if (usedTimeUpdateHelper.shouldDoAutoCommit) { + usedTimeUpdateHelper.forceCommit(appLogic) } // trigger time warnings - // FIXME: this uses the resulting time, not the time per category fun eventuallyTriggerTimeWarning(remaining: RemainingTime, categoryId: String?) { val category = categories.find { it.id == categoryId } ?: return val oldRemainingTime = remaining.includingExtraTime @@ -509,13 +427,10 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { } } - if (remainingTimeForegroundApp != null) { - eventuallyTriggerTimeWarning(remainingTimeForegroundApp, foregroundAppHandling.categoryId) - } - - if (remainingTimeBackgroundApp != null) { - eventuallyTriggerTimeWarning(remainingTimeBackgroundApp, foregroundAppHandling.categoryId) - } + remainingTimeForegroundAppChild?.let { eventuallyTriggerTimeWarning(it, foregroundAppHandling.categoryId) } + remainingTimeForegroundAppParent?.let { eventuallyTriggerTimeWarning(it, foregroundAppHandling.parentCategoryId) } + remainingTimeBackgroundAppChild?.let { eventuallyTriggerTimeWarning(it, audioPlaybackHandling.categoryId) } + remainingTimeBackgroundAppParent?.let { eventuallyTriggerTimeWarning(it, audioPlaybackHandling.parentCategoryId) } // show notification fun buildStatusMessageWithCurrentAppTitle( @@ -633,7 +548,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { if (foregroundAppHandling.status == BackgroundTaskLogicAppStatus.ShouldBlock) { openLockscreen(foregroundAppPackageName!!, foregroundAppActivityName) - usedTimeUpdateHelperForegroundApp?.commit(appLogic) + commitUsedTimeUpdaters() } else { appLogic.platformIntegration.setShowBlockingOverlay(false) } @@ -641,7 +556,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { if (audioPlaybackHandling.status == BackgroundTaskLogicAppStatus.ShouldBlock && audioPlaybackPackageName != null) { appLogic.platformIntegration.muteAudioIfPossible(audioPlaybackPackageName) - usedTimeUpdateHelperBackgroundPlayback?.commit(appLogic) + commitUsedTimeUpdaters() } } catch (ex: SecurityException) { // this is handled by an other main loop (with a delay) diff --git a/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogicCache.kt b/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogicCache.kt index b171850..4ca394f 100644 --- a/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogicCache.kt +++ b/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogicCache.kt @@ -54,11 +54,6 @@ class BackgroundTaskLogicCache (private val appLogic: AppLogic) { return appLogic.database.usedTimes().getUsedTimesOfWeek(key.first, key.second) } } - val usedTimesOfCategoryAndDayOfEpoch = object: MultiKeyLiveDataCache>() { - override fun createValue(key: Pair): LiveData { - return appLogic.database.usedTimes().getUsedTimesByCategoryIdAndDayOfEpoch(key.first, key.second) - } - } val shouldDoAutomaticSignOut = SingleItemLiveDataCacheWithRequery { -> appLogic.defaultUserLogic.hasAutomaticSignOut()} val liveDataCaches = LiveDataCaches(arrayOf( @@ -68,7 +63,6 @@ class BackgroundTaskLogicCache (private val appLogic: AppLogic) { appCategories, timeLimitRules, usedTimesOfCategoryAndWeekByFirstDayOfWeek, - usedTimesOfCategoryAndDayOfEpoch, shouldDoAutomaticSignOut )) } \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/logic/UsedTimeItemBatchUpdateHelper.kt b/app/src/main/java/io/timelimit/android/logic/UsedTimeItemBatchUpdateHelper.kt deleted file mode 100644 index b7b9456..0000000 --- a/app/src/main/java/io/timelimit/android/logic/UsedTimeItemBatchUpdateHelper.kt +++ /dev/null @@ -1,123 +0,0 @@ -/* - * TimeLimit Copyright 2019 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 - -import io.timelimit.android.data.Database -import io.timelimit.android.data.model.UsedTimeItem -import io.timelimit.android.date.DateInTimezone -import io.timelimit.android.livedata.waitForNullableValue -import io.timelimit.android.sync.actions.AddUsedTimeAction -import io.timelimit.android.sync.actions.apply.ApplyActionUtil - -class UsedTimeItemBatchUpdateHelper( - val date: DateInTimezone, - val childCategoryId: String, - val parentCategoryId: String?, - var cachedItemChild: UsedTimeItem?, - var cachedItemParent: UsedTimeItem? -) { - companion object { - suspend fun eventuallyUpdateInstance( - date: DateInTimezone, - childCategoryId: String, - parentCategoryId: String?, - oldInstance: UsedTimeItemBatchUpdateHelper?, - usedTimeItemForDayChild: UsedTimeItem?, - usedTimeItemForDayParent: UsedTimeItem?, - logic: AppLogic - ): UsedTimeItemBatchUpdateHelper { - if ( - oldInstance != null && - oldInstance.date == date && - oldInstance.childCategoryId == childCategoryId && - oldInstance.parentCategoryId == parentCategoryId - ) { - if (oldInstance.cachedItemChild != usedTimeItemForDayChild) { - oldInstance.cachedItemChild = usedTimeItemForDayChild - } - - if (oldInstance.cachedItemParent != usedTimeItemForDayParent) { - oldInstance.cachedItemParent = usedTimeItemForDayParent - } - - return oldInstance - } else { - if (oldInstance != null) { - oldInstance.commit(logic) - } - - return UsedTimeItemBatchUpdateHelper( - date = date, - childCategoryId = childCategoryId, - parentCategoryId = parentCategoryId, - cachedItemChild = usedTimeItemForDayChild, - cachedItemParent = usedTimeItemForDayParent - ) - } - } - } - - private var timeToAdd = 0 - private var extraTimeToSubtract = 0 - - suspend fun addUsedTime(time: Int, subtractExtraTime: Boolean, appLogic: AppLogic) { - timeToAdd += time - - if (subtractExtraTime) { - extraTimeToSubtract += time - } - - if (Math.max(timeToAdd, extraTimeToSubtract) > 1000 * 10 /* 10 seconds */) { - commit(appLogic) - } - } - - fun getTotalUsedTimeChild(): Long = (cachedItemChild?.usedMillis ?: 0) + timeToAdd - fun getTotalUsedTimeParent(): Long = (cachedItemParent?.usedMillis ?: 0) + timeToAdd - - fun getCachedExtraTimeToSubtract(): Int { - return extraTimeToSubtract - } - - suspend fun queryCurrentStatusFromDatabase(database: Database) { - cachedItemChild = database.usedTimes().getUsedTimeItem(childCategoryId, date.dayOfEpoch).waitForNullableValue() - cachedItemParent = parentCategoryId?.let { - database.usedTimes().getUsedTimeItem(parentCategoryId, date.dayOfEpoch).waitForNullableValue() - } - } - - suspend fun commit(logic: AppLogic) { - if (timeToAdd == 0) { - // do nothing - } else { - ApplyActionUtil.applyAppLogicAction( - action = AddUsedTimeAction( - categoryId = childCategoryId, - timeToAdd = timeToAdd, - dayOfEpoch = date.dayOfEpoch, - extraTimeToSubtract = extraTimeToSubtract - ), - appLogic = logic, - ignoreIfDeviceIsNotConfigured = true - ) - - timeToAdd = 0 - extraTimeToSubtract = 0 - - queryCurrentStatusFromDatabase(logic.database) - } - } -} \ 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 new file mode 100644 index 0000000..c361a3c --- /dev/null +++ b/app/src/main/java/io/timelimit/android/logic/UsedTimeUpdateHelper.kt @@ -0,0 +1,83 @@ +/* + * TimeLimit Copyright 2019 - 2020 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 + +import io.timelimit.android.date.DateInTimezone +import io.timelimit.android.sync.actions.AddUsedTimeActionItem +import io.timelimit.android.sync.actions.AddUsedTimeActionVersion2 +import io.timelimit.android.sync.actions.apply.ApplyActionUtil + +class UsedTimeUpdateHelper (val date: DateInTimezone) { + val timeToAdd = mutableMapOf() + val extraTimeToSubtract = mutableMapOf() + var shouldDoAutoCommit = false + + suspend fun add(categoryId: String, time: Int, includingExtraTime: Boolean) { + if (time < 0) { + throw IllegalArgumentException() + } + + if (time == 0) { + return + } + + timeToAdd[categoryId] = (timeToAdd[categoryId] ?: 0) + time + + if (includingExtraTime) { + extraTimeToSubtract[categoryId] = (extraTimeToSubtract[categoryId] ?: 0) + time + } + + if (timeToAdd[categoryId]!! >= 1000 * 10) { + shouldDoAutoCommit = true + } + } + + fun reportCurrentCategories(categories: Set) { + if (!categories.containsAll(timeToAdd.keys)) { + shouldDoAutoCommit = true + } + + if (!categories.containsAll(extraTimeToSubtract.keys)) { + shouldDoAutoCommit = true + } + } + + suspend fun forceCommit(appLogic: AppLogic) { + if (timeToAdd.isEmpty() && extraTimeToSubtract.isEmpty()) { + return + } + + val categoryIds = timeToAdd.keys + extraTimeToSubtract.keys + + ApplyActionUtil.applyAppLogicAction( + action = AddUsedTimeActionVersion2( + dayOfEpoch = date.dayOfEpoch, + items = categoryIds.map { categoryId -> + AddUsedTimeActionItem( + categoryId = categoryId, + timeToAdd = timeToAdd[categoryId] ?: 0, + extraTimeToSubtract = extraTimeToSubtract[categoryId] ?: 0 + ) + } + ), + appLogic = appLogic, + ignoreIfDeviceIsNotConfigured = true + ) + + timeToAdd.clear() + extraTimeToSubtract.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/sync/actions/Actions.kt b/app/src/main/java/io/timelimit/android/sync/actions/Actions.kt index 36b5565..bce66d7 100644 --- a/app/src/main/java/io/timelimit/android/sync/actions/Actions.kt +++ b/app/src/main/java/io/timelimit/android/sync/actions/Actions.kt @@ -142,6 +142,82 @@ data class AddUsedTimeAction(val categoryId: String, val dayOfEpoch: Int, val ti } } +data class AddUsedTimeActionVersion2(val dayOfEpoch: Int, val items: List): AppLogicAction() { + companion object { + const val TYPE_VALUE = "ADD_USED_TIME_V2" + private const val DAY_OF_EPOCH = "d" + private const val ITEMS = "i" + + fun parse(action: JSONObject): AddUsedTimeActionVersion2 = AddUsedTimeActionVersion2( + dayOfEpoch = action.getInt(DAY_OF_EPOCH), + items = ParseUtils.readObjectArray(action.getJSONArray(ITEMS)).map { AddUsedTimeActionItem.parse(it) } + ) + } + + init { + if (dayOfEpoch < 0) { + throw IllegalArgumentException() + } + + if (items.isEmpty()) { + throw IllegalArgumentException() + } + + if (items.distinctBy { it.categoryId }.size != items.size) { + throw IllegalArgumentException() + } + } + + override fun serialize(writer: JsonWriter) { + writer.beginObject() + + writer.name(TYPE).value(TYPE_VALUE) + writer.name(DAY_OF_EPOCH).value(dayOfEpoch) + + writer.name(ITEMS).beginArray() + items.forEach { it.serialize(writer) } + writer.endArray() + + writer.endObject() + } +} + +data class AddUsedTimeActionItem(val categoryId: String, val timeToAdd: Int, val extraTimeToSubtract: Int) { + companion object { + private const val CATEGORY_ID = "categoryId" + private const val TIME_TO_ADD = "tta" + private const val EXTRA_TIME_TO_SUBTRACT = "etts" + + fun parse(item: JSONObject): AddUsedTimeActionItem = AddUsedTimeActionItem( + categoryId = item.getString(CATEGORY_ID), + timeToAdd = item.getInt(TIME_TO_ADD), + extraTimeToSubtract = item.getInt(EXTRA_TIME_TO_SUBTRACT) + ) + } + + init { + IdGenerator.assertIdValid(categoryId) + + if (timeToAdd < 0) { + throw IllegalArgumentException() + } + + if (extraTimeToSubtract < 0) { + throw IllegalArgumentException() + } + } + + fun serialize(writer: JsonWriter) { + writer.beginObject() + + writer.name(CATEGORY_ID).value(categoryId) + writer.name(TIME_TO_ADD).value(timeToAdd) + writer.name(EXTRA_TIME_TO_SUBTRACT).value(extraTimeToSubtract) + + writer.endObject() + } +} + // data class ClearTemporarilyAllowedAppsAction(val deviceId: String): AppLogicAction(), LocalOnlyAction data class InstalledApp(val packageName: String, val title: String, val isLaunchable: Boolean, val recommendation: AppRecommendation) { diff --git a/app/src/main/java/io/timelimit/android/sync/actions/Parser.kt b/app/src/main/java/io/timelimit/android/sync/actions/Parser.kt index cf71d0a..61abb75 100644 --- a/app/src/main/java/io/timelimit/android/sync/actions/Parser.kt +++ b/app/src/main/java/io/timelimit/android/sync/actions/Parser.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 Jonas Lochmann + * TimeLimit Copyright 2019 - 2020 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 @@ -26,6 +26,7 @@ object ActionParser { SignOutAtDeviceAction.TYPE_VALUE -> SignOutAtDeviceAction UpdateAppActivitiesAction.TYPE_VALUE -> UpdateAppActivitiesAction.parse(action) UpdateDeviceStatusAction.TYPE_VALUE -> UpdateDeviceStatusAction.parse(action) + AddUsedTimeActionVersion2.TYPE_VALUE -> AddUsedTimeActionVersion2.parse(action) else -> throw IllegalStateException() } diff --git a/app/src/main/java/io/timelimit/android/sync/actions/apply/ApplyAction.kt b/app/src/main/java/io/timelimit/android/sync/actions/apply/ApplyAction.kt index 42e7ec5..cdec219 100644 --- a/app/src/main/java/io/timelimit/android/sync/actions/apply/ApplyAction.kt +++ b/app/src/main/java/io/timelimit/android/sync/actions/apply/ApplyAction.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 Jonas Lochmann + * TimeLimit Copyright 2019 - 2020 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 @@ -106,6 +106,51 @@ object ApplyActionUtil { database.setTransactionSuccessful() syncUtil.requestVeryUnimportantSync() + return@executeAndWait + } + } + } else if (action is AddUsedTimeActionVersion2) { + val previousAction = database.pendingSyncAction().getLatestUnscheduledActionSync() + + if (previousAction != null && previousAction.type == PendingSyncActionType.AppLogic) { + val parsed = ActionParser.parseAppLogicAction(JSONObject(previousAction.encodedAction)) + + if (parsed is AddUsedTimeActionVersion2 && parsed.dayOfEpoch == action.dayOfEpoch) { + var updatedAction: AddUsedTimeActionVersion2 = parsed + + action.items.forEach { newItem -> + val oldItem = updatedAction.items.find { it.categoryId == newItem.categoryId } + + if (oldItem == null) { + updatedAction = updatedAction.copy( + items = updatedAction.items + listOf(newItem) + ) + } else { + val mergedItem = AddUsedTimeActionItem( + timeToAdd = oldItem.timeToAdd + newItem.timeToAdd, + extraTimeToSubtract = oldItem.extraTimeToSubtract + newItem.extraTimeToSubtract, + categoryId = newItem.categoryId + ) + + updatedAction = updatedAction.copy( + items = updatedAction.items.filter { it.categoryId != mergedItem.categoryId } + listOf(mergedItem) + ) + } + } + + // update the previous action + database.pendingSyncAction().updateEncodedActionSync( + sequenceNumber = previousAction.sequenceNumber, + action = StringWriter().apply { + JsonWriter(this).apply { + updatedAction.serialize(this) + } + }.toString() + ) + + database.setTransactionSuccessful() + syncUtil.requestVeryUnimportantSync() + return@executeAndWait } } @@ -126,7 +171,7 @@ object ApplyActionUtil { userId = "" )) - if (action is AddUsedTimeAction) { + if (action is AddUsedTimeAction || action is AddUsedTimeActionVersion2) { syncUtil.requestVeryUnimportantSync() } else { if (BuildConfig.DEBUG) { diff --git a/app/src/main/java/io/timelimit/android/sync/actions/dispatch/AppLogicAction.kt b/app/src/main/java/io/timelimit/android/sync/actions/dispatch/AppLogicAction.kt index 309e041..1fc52c8 100644 --- a/app/src/main/java/io/timelimit/android/sync/actions/dispatch/AppLogicAction.kt +++ b/app/src/main/java/io/timelimit/android/sync/actions/dispatch/AppLogicAction.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 Jonas Lochmann + * TimeLimit Copyright 2019 - 2020 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 @@ -76,6 +76,37 @@ object LocalDatabaseAppLogicActionDispatcher { null } + is AddUsedTimeActionVersion2 -> { + action.items.forEach { item -> + database.category().getCategoryByIdSync(item.categoryId)!! + + val updatedRows = database.usedTimes().addUsedTime( + categoryId = item.categoryId, + timeToAdd = item.timeToAdd, + dayOfEpoch = action.dayOfEpoch + ) + + if (updatedRows == 0) { + // create new entry + + database.usedTimes().insertUsedTime(UsedTimeItem( + categoryId = item.categoryId, + dayOfEpoch = action.dayOfEpoch, + usedMillis = item.timeToAdd.toLong() + )) + } + + + if (item.extraTimeToSubtract != 0) { + database.category().subtractCategoryExtraTime( + categoryId = item.categoryId, + removedExtraTime = item.extraTimeToSubtract + ) + } + } + + null + } is AddInstalledAppsAction -> { database.app().addAppsSync( action.apps.map {