1 /* <lambda>null2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package androidx.work.impl.constraints.trackers 17 18 import android.content.Context 19 import androidx.annotation.RestrictTo 20 import androidx.work.Logger 21 import androidx.work.impl.constraints.ConstraintListener 22 import androidx.work.impl.utils.taskexecutor.TaskExecutor 23 import java.util.LinkedHashSet 24 25 /** 26 * A base for tracking constraints and notifying listeners of changes. 27 * 28 * @param T the constraint data type observed by this tracker 29 */ 30 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 31 abstract class ConstraintTracker<T> 32 protected constructor(context: Context, private val taskExecutor: TaskExecutor) { 33 protected val appContext: Context = context.applicationContext 34 private val lock = Any() 35 private val listeners = LinkedHashSet<ConstraintListener<T>>() 36 37 private var currentState: T? = null 38 39 /** 40 * Add the given listener for tracking. This may cause [.getInitialState] and [.startTracking] 41 * to be invoked. If a state is set, this will immediately notify the given listener. 42 * 43 * @param listener The target listener to start notifying 44 */ 45 fun addListener(listener: ConstraintListener<T>) { 46 synchronized(lock) { 47 if (listeners.add(listener)) { 48 if (listeners.size == 1) { 49 currentState = readSystemState() 50 Logger.get() 51 .debug(TAG, "${javaClass.simpleName}: initial state = $currentState") 52 startTracking() 53 } 54 @Suppress("UNCHECKED_CAST") listener.onConstraintChanged(currentState as T) 55 } 56 } 57 } 58 59 /** 60 * Remove the given listener from tracking. 61 * 62 * @param listener The listener to stop notifying. 63 */ 64 fun removeListener(listener: ConstraintListener<T>) { 65 synchronized(lock) { 66 if (listeners.remove(listener) && listeners.isEmpty()) { 67 stopTracking() 68 } 69 } 70 } 71 72 var state: T 73 get() { 74 return currentState ?: readSystemState() 75 } 76 set(newState) { 77 synchronized(lock) { 78 if (currentState != null && (currentState == newState)) { 79 return 80 } 81 82 currentState = newState 83 84 // onConstraintChanged may lead to calls to addListener or removeListener. 85 // This can potentially result in a modification to the set while it is being 86 // iterated over, so we handle this by creating a copy and using that for 87 // iteration. 88 val listenersList = listeners.toList() 89 taskExecutor.mainThreadExecutor.execute { 90 listenersList.forEach { listener -> 91 // currentState was initialized by now 92 @Suppress("UNCHECKED_CAST") listener.onConstraintChanged(currentState as T) 93 } 94 } 95 } 96 } 97 98 /** 99 * Reads the state of the constraints from source of truth. (e.g. NetworkManager for 100 * NetworkTracker). It is always accurate unlike `state` that can be stale after stopTracking 101 * call. 102 */ 103 abstract fun readSystemState(): T 104 105 /** Start tracking for constraint state changes. */ 106 abstract fun startTracking() 107 108 /** Stop tracking for constraint state changes. */ 109 abstract fun stopTracking() 110 } 111 112 private val TAG = Logger.tagWithPrefix("ConstraintTracker") 113