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