ConstraintController.kt
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.work.impl.constraints.controllers
import androidx.work.impl.constraints.ConstraintListener
import androidx.work.impl.constraints.trackers.ConstraintTracker
import androidx.work.impl.model.WorkSpec
/**
* A controller for a particular constraint.
*
* @param T the constraint data type managed by this controller.
*/
abstract class ConstraintController<T> internal constructor(
private val tracker: ConstraintTracker<T>
) : ConstraintListener<T> {
/**
* A callback for when a constraint changes.
*/
interface OnConstraintUpdatedCallback {
/**
* Called when a constraint is met.
*
* @param workSpecs A list of [WorkSpec] IDs that may have become eligible to run
*/
fun onConstraintMet(workSpecs: List<WorkSpec>)
/**
* Called when a constraint is not met.
*
* @param workSpecs A list of [WorkSpec] IDs that have become ineligible to run
*/
fun onConstraintNotMet(workSpecs: List<WorkSpec>)
}
private val matchingWorkSpecs = mutableListOf<WorkSpec>()
private val matchingWorkSpecIds = mutableListOf<String>()
private var currentValue: T? = null
/**
* Sets the callback to inform when constraints change. This callback is also triggered the
* first time it is set.
*/
var callback: OnConstraintUpdatedCallback? = null
set(value) {
if (field !== value) {
field = value
updateCallback(value, currentValue)
}
}
abstract fun hasConstraint(workSpec: WorkSpec): Boolean
abstract fun isConstrained(value: T): Boolean
/**
* Replaces the list of [WorkSpec]s to monitor constraints for.
*
* @param workSpecs A list of [WorkSpec]s to monitor constraints for
*/
fun replace(workSpecs: Iterable<WorkSpec>) {
matchingWorkSpecs.clear()
matchingWorkSpecIds.clear()
workSpecs.filterTo(matchingWorkSpecs) { hasConstraint(it) }
matchingWorkSpecs.mapTo(matchingWorkSpecIds) { it.id }
if (matchingWorkSpecs.isEmpty()) {
tracker.removeListener(this)
} else {
tracker.addListener(this)
}
updateCallback(callback, currentValue)
}
/**
* Clears all tracked [WorkSpec]s.
*/
fun reset() {
if (matchingWorkSpecs.isNotEmpty()) {
matchingWorkSpecs.clear()
tracker.removeListener(this)
}
}
/**
* Determines if a particular [WorkSpec] is constrained. It is constrained if it is
* tracked by this controller, and the controller constraint was set, but not satisfied.
*
* @param workSpecId The ID of the [WorkSpec] to check if it is constrained.
* @return `true` if the [WorkSpec] is considered constrained
*/
fun isWorkSpecConstrained(workSpecId: String): Boolean {
// TODO: unify `null` treatment here and in updateCallback, because
// here it is considered as not constrained and but in updateCallback as constrained.
val value = currentValue
return (value != null && isConstrained(value) && workSpecId in matchingWorkSpecIds)
}
private fun updateCallback(callback: OnConstraintUpdatedCallback?, currentValue: T?) {
// We pass copies of references (callback, currentValue) to updateCallback because public
// APIs on ConstraintController may be called from any thread, and onConstraintChanged() is
// called from the main thread.
if (matchingWorkSpecs.isEmpty() || callback == null) {
return
}
if (currentValue == null || isConstrained(currentValue)) {
callback.onConstraintNotMet(matchingWorkSpecs)
} else {
callback.onConstraintMet(matchingWorkSpecs)
}
}
override fun onConstraintChanged(newValue: T) {
currentValue = newValue
updateCallback(callback, currentValue)
}
}