1 /*
2  * Copyright 2019 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 
17 package androidx.enterprise.feedback;
18 
19 import org.jspecify.annotations.NonNull;
20 import org.jspecify.annotations.Nullable;
21 
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.concurrent.atomic.AtomicInteger;
29 
30 /**
31  * A fake {@link KeyedAppStatesReporter} for testing.
32  *
33  * <p>Example usage:
34  * <pre>{@code
35  *   FakeKeyedAppStatesReporter reporter = new FakeKeyedAppStatesReporter();
36  *   // Inject the reporter to the part of your code it will be used.
37  *   assertThat(reporter.getKeyedAppStatesByKey().get("myKey").getMessage()).isEqualTo("expected");
38  * }</pre>
39  */
40 public class FakeKeyedAppStatesReporter extends KeyedAppStatesReporter {
41 
42     // States are stored in both a List and a Map (rather than using Map#values) to ensure that
43     // order and duplicates are preserved.
44     private List<KeyedAppState> mKeyedAppStates =
45             Collections.synchronizedList(new ArrayList<KeyedAppState>());
46     private List<KeyedAppState> mOnDeviceKeyedAppStates =
47             Collections.synchronizedList(new ArrayList<KeyedAppState>());
48     private List<KeyedAppState> mUploadedKeyedAppStates =
49             Collections.synchronizedList(new ArrayList<KeyedAppState>());
50     private Map<String, KeyedAppState> mKeyedAppStatesByKey =
51             Collections.synchronizedMap(new HashMap<String, KeyedAppState>());
52     private Map<String, KeyedAppState> mOnDeviceKeyedAppStatesByKey =
53             Collections.synchronizedMap(new HashMap<String, KeyedAppState>());
54     private Map<String, KeyedAppState> mUploadedKeyedAppStatesByKey =
55             Collections.synchronizedMap(new HashMap<String, KeyedAppState>());
56     private AtomicInteger mNumberOfUploads = new AtomicInteger();
57 
58     /** @deprecated see {@link #setStates(Collection, KeyedAppStatesCallback)}. */
59     @Override
60     @Deprecated
setStates(@onNull Collection<KeyedAppState> states)61     public void setStates(@NonNull Collection<KeyedAppState> states) {
62         setStates(states, /* callback= */ null);
63     }
64 
65     /**
66      * Record the states set.
67      *
68      * <p>Does not enforce any limit on total size of states collection.
69      */
70     @Override
setStates(@onNull Collection<KeyedAppState> states, @Nullable KeyedAppStatesCallback callback)71     public void setStates(@NonNull Collection<KeyedAppState> states,
72             @Nullable KeyedAppStatesCallback callback) {
73         for (KeyedAppState state : states) {
74             mOnDeviceKeyedAppStates.add(state);
75             mOnDeviceKeyedAppStatesByKey.put(state.getKey(), state);
76             mKeyedAppStates.add(state);
77             mKeyedAppStatesByKey.put(state.getKey(), state);
78         }
79         if (callback != null) {
80             callback.onResult(KeyedAppStatesCallback.STATUS_SUCCESS, /* throwable= */ null);
81         }
82     }
83 
84     /** @deprecated See {@link #setStatesImmediate(Collection, KeyedAppStatesCallback)}. */
85     @Override
86     @Deprecated
setStatesImmediate(@onNull Collection<KeyedAppState> states)87     public void setStatesImmediate(@NonNull Collection<KeyedAppState> states) {
88         setStatesImmediate(states, /* callback= */ null);
89     }
90 
91     /**
92      * Record the set states and immediately mark all states as having been uploaded.
93      *
94      * <p>Does not enforce any quota on uploading, or limit on total size of states collection.
95      */
96     @Override
setStatesImmediate(@onNull Collection<KeyedAppState> states, @Nullable KeyedAppStatesCallback callback)97     public void setStatesImmediate(@NonNull Collection<KeyedAppState> states,
98             @Nullable KeyedAppStatesCallback callback) {
99         setStates(states, callback);
100         upload();
101     }
102 
upload()103     private void upload() {
104         for (KeyedAppState state : mOnDeviceKeyedAppStates) {
105             mUploadedKeyedAppStates.add(state);
106             mUploadedKeyedAppStatesByKey.put(state.getKey(), state);
107         }
108         mOnDeviceKeyedAppStates.clear();
109         mOnDeviceKeyedAppStatesByKey.clear();
110         mNumberOfUploads.addAndGet(1);
111     }
112 
113     /**
114      * Get a list of all {@link KeyedAppState} instances that have been set.
115      *
116      * <p>This is in the order that they were set, and may contain multiple with the same key, if
117      * that key has been set twice.
118      */
getKeyedAppStates()119     public @NonNull List<KeyedAppState> getKeyedAppStates() {
120         return new ArrayList<>(mKeyedAppStates);
121     }
122 
123     /**
124      * Get a map of the latest {@link KeyedAppState} set for each key.
125      */
getKeyedAppStatesByKey()126     public @NonNull Map<String, KeyedAppState> getKeyedAppStatesByKey() {
127         return new HashMap<>(mKeyedAppStatesByKey);
128     }
129 
130     /**
131      * Get a list of {@link KeyedAppState} instances that have been set but not yet uploaded.
132      *
133      * <p>This is in the order that they were set, and may contain multiple with the same key, if
134      * that key has been set twice.
135      *
136      * <p>Once uploaded (using {@link #setStatesImmediate(Collection)}) instances will no longer be
137      * returned by this method.
138      */
getOnDeviceKeyedAppStates()139     public @NonNull List<KeyedAppState> getOnDeviceKeyedAppStates() {
140         return new ArrayList<>(mOnDeviceKeyedAppStates);
141     }
142 
143     /**
144      * Get a map of the latest {@link KeyedAppState} set for each key that has not yet uploaded.
145      *
146      * <p>Once uploaded (using {@link #setStatesImmediate(Collection)}) instances will no longer be
147      * returned by this method.
148      */
getOnDeviceKeyedAppStatesByKey()149     public @NonNull Map<String, KeyedAppState> getOnDeviceKeyedAppStatesByKey() {
150         return new HashMap<>(mOnDeviceKeyedAppStatesByKey);
151     }
152 
153     /**
154      * Get a list of {@link KeyedAppState} instances that have been set and uploaded.
155      *
156      * <p>This is in the order that they were set, and may contain multiple with the same key, if
157      * that key has been set twice.
158      *
159      * <p>States will be returned by this method if they were set using
160      * {@link #setStatesImmediate(Collection)} or if {@link #setStatesImmediate(Collection)} has
161      * been called since they were set.
162      */
getUploadedKeyedAppStates()163     public @NonNull List<KeyedAppState> getUploadedKeyedAppStates() {
164         return new ArrayList<>(mUploadedKeyedAppStates);
165     }
166 
167     /**
168      * Get a list of the latest {@link KeyedAppState} set for each key that has been uploaded.
169      *
170      * <p>This is in the order that they were set, and may contain multiple with the same key, if
171      * that key has been set twice.
172      *
173      * <p>States will be returned by this method if they were set using
174      * {@link #setStatesImmediate(Collection)} or if {@link #setStatesImmediate(Collection)} has
175      * been called since they were set.
176      */
getUploadedKeyedAppStatesByKey()177     public @NonNull Map<String, KeyedAppState> getUploadedKeyedAppStatesByKey() {
178         return new HashMap<>(mUploadedKeyedAppStatesByKey);
179     }
180 
181     /**
182      * Get the number of times {@link #setStatesImmediate(Collection)} has been called.
183      */
getNumberOfUploads()184     public int getNumberOfUploads() {
185         return mNumberOfUploads.get();
186     }
187 }
188