/*
 * 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.trackers;

import android.content.Context;
import android.support.annotation.RestrictTo;

import androidx.work.Logger;
import androidx.work.impl.constraints.ConstraintListener;

import java.util.LinkedHashSet;
import java.util.Set;

/**
 * A base for tracking constraints and notifying listeners of changes.
 *
 * @param <T> the constraint data type observed by this tracker
 * @hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public abstract class ConstraintTracker<T> {

    private static final String TAG = "ConstraintTracker";

    protected final Context mAppContext;
    private final Set<ConstraintListener<T>> mListeners = new LinkedHashSet<>();
    private T mCurrentState;

    ConstraintTracker(Context context) {
        mAppContext = context.getApplicationContext();
    }

    /**
     * Add the given listener for tracking.
     * This may cause {@link #getInitialState()} and {@link #startTracking()} to be invoked.
     * If a state is set, this will immediately notify the given listener.
     *
     * @param listener The target listener to start notifying
     */
    public void addListener(ConstraintListener<T> listener) {
        if (mListeners.add(listener)) {
            if (mListeners.size() == 1) {
                mCurrentState = getInitialState();
                Logger.debug(TAG, String.format("%s: initial state = %s",
                        getClass().getSimpleName(),
                        mCurrentState));
                startTracking();
            }
            listener.onConstraintChanged(mCurrentState);
        }
    }

    /**
     * Remove the given listener from tracking.
     *
     * @param listener The listener to stop notifying.
     */
    public void removeListener(ConstraintListener<T> listener) {
        if (mListeners.remove(listener) && mListeners.isEmpty()) {
            stopTracking();
        }
    }

    /**
     * Sets the state of the constraint.
     * If state is has not changed, nothing happens.
     *
     * @param newState new state of constraint
     */
    public void setState(T newState) {
        if (mCurrentState == newState
                || (mCurrentState != null && mCurrentState.equals(newState))) {
            return;
        }
        mCurrentState = newState;
        for (ConstraintListener<T> listener : mListeners) {
            listener.onConstraintChanged(mCurrentState);
        }
    }

    /**
     * Determines the initial state of the constraint being tracked.
     */
    public abstract T getInitialState();

    /**
     * Start tracking for constraint state changes.
     */
    public abstract void startTracking();

    /**
     * Stop tracking for constraint state changes.
     */
    public abstract void stopTracking();
}

@@ -847,28 +847,79 @@
 va.util.
-LinkedHashSe
+ArrayList;%0Aimport java.util.LinkedHashSet;%0Aimport java.util.Lis
 t;%0Aimpor
@@ -1282,16 +1282,63 @@
 ontext;%0A
+    private final Object mLock = new Object();%0A
     priv
@@ -1894,32 +1894,67 @@
 %3CT%3E listener) %7B%0A
+        synchronized (mLock) %7B%0A    
         if (mLis
@@ -1981,32 +1981,36 @@
 ) %7B%0A            
+    
 if (mListeners.s
@@ -2031,32 +2031,36 @@
                 
+  
+  
 mCurrentState = 
@@ -2074,24 +2074,28 @@
 ialState();%0A
+    
             
@@ -2156,16 +2156,20 @@
  = %25s%22,%0A
+    
         
@@ -2228,32 +2228,36 @@
                 
+    
 mCurrentState));
@@ -2257,16 +2257,20 @@
 tate));%0A
+    
         
@@ -2298,34 +2298,42 @@
 ();%0A            
+  
-%7D%0A
+  %7D%0A    
             list
@@ -2365,32 +2365,46 @@
 mCurrentState);%0A
+            %7D%0A
         %7D%0A    %7D%0A
@@ -2587,32 +2587,67 @@
 %3CT%3E listener) %7B%0A
+        synchronized (mLock) %7B%0A    
         if (mLis
@@ -2709,16 +2709,20 @@
         
+    
 stopTrac
@@ -2721,32 +2721,46 @@
 stopTracking();%0A
+            %7D%0A
         %7D%0A    %7D%0A
@@ -2959,24 +2959,59 @@
 newState) %7B%0A
+        synchronized (mLock) %7B%0A    
         if (
@@ -3036,16 +3036,20 @@
 ewState%0A
+    
         
@@ -3132,16 +3132,20 @@
         
+   
+ 
 return;%0A
@@ -3144,34 +3144,42 @@
 return;%0A        
-%7D%0A
+    %7D%0A    
         mCurrent
@@ -3196,24 +3196,29 @@
 wState;%0A
+%0A    
         
 for (Con
@@ -3213,13 +3213,288 @@
     
-for (
+// onConstraintChanged may lead to calls to addListener or removeListener.  This can%0A            // potentially result in a modification to the set while it is being iterated over, so%0A            // we handle this by creating a copy and using that for iteration.%0A            List%3C
 Cons
@@ -3502,32 +3502,33 @@
 raintListener%3CT%3E
+%3E
  listener : mLis
@@ -3524,25 +3524,116 @@
 ener
- : mListeners) %7B%0A
+sList = new ArrayList%3C%3E(mListeners);%0A            for (ConstraintListener%3CT%3E listener : listenersList) %7B%0A    
     
@@ -3677,32 +3677,46 @@
 mCurrentState);%0A
+            %7D%0A
         %7D%0A    %7D%0A

Fix ConstraintTracker concurrency problems.