Improve time counting with nested categories

This commit is contained in:
Jonas Lochmann 2020-01-27 01:00:00 +01:00
parent 192b871ae5
commit a81bc687fb
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
9 changed files with 325 additions and 306 deletions

View file

@ -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<Int, UsedTimeItem>
@Query("SELECT * FROM used_time WHERE category_id = :categoryId AND day_of_epoch = :dayOfEpoch")
abstract fun getUsedTimesByCategoryIdAndDayOfEpoch(categoryId: String, dayOfEpoch: Int): LiveData<UsedTimeItem?>
@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<String>, startingDayOfEpoch: Int, endDayOfEpoch: Int): LiveData<List<UsedTimeItem>>

View file

@ -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<UsedTimeItem>, 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<String>()
val categoriesToCountExtraTime = mutableSetOf<String>()
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)

View file

@ -54,11 +54,6 @@ class BackgroundTaskLogicCache (private val appLogic: AppLogic) {
return appLogic.database.usedTimes().getUsedTimesOfWeek(key.first, key.second)
}
}
val usedTimesOfCategoryAndDayOfEpoch = object: MultiKeyLiveDataCache<UsedTimeItem?, Pair<String, Int>>() {
override fun createValue(key: Pair<String, Int>): LiveData<UsedTimeItem?> {
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
))
}

View file

@ -1,123 +0,0 @@
/*
* TimeLimit Copyright <C> 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 <https://www.gnu.org/licenses/>.
*/
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)
}
}
}

View file

@ -0,0 +1,83 @@
/*
* TimeLimit Copyright <C> 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 <https://www.gnu.org/licenses/>.
*/
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<String, Int>()
val extraTimeToSubtract = mutableMapOf<String, Int>()
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<String>) {
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()
}
}

View file

@ -142,6 +142,82 @@ data class AddUsedTimeAction(val categoryId: String, val dayOfEpoch: Int, val ti
}
}
data class AddUsedTimeActionVersion2(val dayOfEpoch: Int, val items: List<AddUsedTimeActionItem>): 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) {

View file

@ -1,5 +1,5 @@
/*
* TimeLimit Copyright <C> 2019 Jonas Lochmann
* TimeLimit Copyright <C> 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()
}

View file

@ -1,5 +1,5 @@
/*
* TimeLimit Copyright <C> 2019 Jonas Lochmann
* TimeLimit Copyright <C> 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) {

View file

@ -1,5 +1,5 @@
/*
* TimeLimit Copyright <C> 2019 Jonas Lochmann
* TimeLimit Copyright <C> 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 {