Skip to content

Commit 629aaf2

Browse files
authored
Release 16.11.2 (#1366)
1 parent a867782 commit 629aaf2

File tree

3 files changed

+120
-130
lines changed

3 files changed

+120
-130
lines changed

urbanairship-automation/src/main/java/com/urbanairship/automation/InAppAutomation.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -535,12 +535,16 @@ private void onPrepareSchedule(final @NonNull Schedule<? extends ScheduleData> s
535535
if (!schedule.getFrequencyConstraintIds().isEmpty()) {
536536
FrequencyChecker frequencyChecker = getFrequencyChecker(schedule);
537537
if (frequencyChecker == null) {
538-
return RetryingExecutor.retryResult();
538+
remoteDataSubscriber.attemptRefresh(true, () -> {
539+
callbackWrapper.onFinish(AutomationDriver.PREPARE_RESULT_INVALIDATE);
540+
});
541+
return RetryingExecutor.cancelResult();
539542
}
540543
frequencyCheckerMap.put(schedule.getId(), frequencyChecker);
541544
if (frequencyChecker.isOverLimit()) {
542545
// The frequency constraint is exceeded, skip
543546
callbackWrapper.onFinish(AutomationDriver.PREPARE_RESULT_SKIP);
547+
return RetryingExecutor.cancelResult();
544548
}
545549
}
546550

urbanairship-automation/src/main/java/com/urbanairship/automation/limits/FrequencyLimitManager.java

+106-129
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,12 @@
1919
import java.util.Collection;
2020
import java.util.Collections;
2121
import java.util.HashMap;
22-
import java.util.HashSet;
2322
import java.util.List;
2423
import java.util.Map;
25-
import java.util.Set;
26-
import java.util.WeakHashMap;
2724
import java.util.concurrent.Executor;
2825
import java.util.concurrent.Future;
2926

3027
import androidx.annotation.NonNull;
31-
import androidx.annotation.Nullable;
3228
import androidx.annotation.RestrictTo;
3329
import androidx.annotation.VisibleForTesting;
3430

@@ -39,21 +35,12 @@
3935
*/
4036
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
4137
public class FrequencyLimitManager {
42-
43-
/*
44-
* A frequency checker will have a strong reference to the list of constraints entities. Once
45-
* the checker is cleaned up this should remove the values from the map.
46-
*/
47-
private final Map<ConstraintEntity, List<OccurrenceEntity>> occurrencesMap = new WeakHashMap<>();
48-
49-
/*
50-
* List of pending occurrences to write to the database.
51-
*/
38+
private final Map<String, List<OccurrenceEntity>> occurrencesMap = new HashMap<>();
39+
private final Map<String, ConstraintEntity> constraintEntityMap = new HashMap<>();
5240
private final List<OccurrenceEntity> pendingOccurrences = new ArrayList<>();
53-
41+
private final Clock clock;
5442
private final Object lock = new Object();
5543
private final FrequencyLimitDao dao;
56-
private final Clock clock;
5744
private final Executor executor;
5845

5946
public FrequencyLimitManager(@NonNull Context context, @NonNull AirshipRuntimeConfig config) {
@@ -77,30 +64,40 @@ public FrequencyLimitManager(@NonNull Context context, @NonNull AirshipRuntimeCo
7764
* @return A future for the checker.
7865
*/
7966
@NonNull
80-
public Future<FrequencyChecker> getFrequencyChecker(@Nullable final Collection<String> constraintIds) {
67+
public Future<FrequencyChecker> getFrequencyChecker(@NonNull final Collection<String> constraintIds) {
8168
final PendingResult<FrequencyChecker> pendingResult = new PendingResult<>();
82-
executor.execute(new Runnable() {
83-
@Override
84-
public void run() {
85-
try {
86-
final Collection<ConstraintEntity> constraints = fetchConstraints(constraintIds);
87-
FrequencyChecker checker = new FrequencyChecker() {
88-
@Override
89-
public boolean isOverLimit() {
90-
return FrequencyLimitManager.this.isOverLimit(constraints);
91-
}
69+
executor.execute(() -> {
70+
for (String constraintId : constraintIds) {
71+
synchronized (lock) {
72+
if (constraintEntityMap.containsKey(constraintId)) {
73+
continue;
74+
}
75+
}
9276

93-
@Override
94-
public boolean checkAndIncrement() {
95-
return FrequencyLimitManager.this.checkAndIncrement(constraints);
96-
}
97-
};
98-
pendingResult.setResult(checker);
99-
} catch (Exception e) {
100-
Logger.error("Failed to fetch constraints.");
77+
List<OccurrenceEntity> occurrenceEntities = dao.getOccurrences(constraintId);
78+
List<ConstraintEntity> constraintEntities = dao.getConstraints(Collections.singletonList(constraintId));
79+
if (constraintEntities.size() != 1) {
10180
pendingResult.setResult(null);
81+
return;
82+
}
83+
84+
synchronized (lock) {
85+
constraintEntityMap.put(constraintId, constraintEntities.get(0));
86+
occurrencesMap.put(constraintId, occurrenceEntities);
10287
}
10388
}
89+
90+
pendingResult.setResult(new FrequencyChecker() {
91+
@Override
92+
public boolean isOverLimit() {
93+
return FrequencyLimitManager.this.isOverLimit(constraintIds);
94+
}
95+
96+
@Override
97+
public boolean checkAndIncrement() {
98+
return FrequencyLimitManager.this.checkAndIncrement(constraintIds);
99+
}
100+
});
104101
});
105102

106103
return pendingResult;
@@ -113,130 +110,112 @@ public boolean checkAndIncrement() {
113110
*/
114111
public Future<Boolean> updateConstraints(@NonNull final Collection<FrequencyConstraint> constraints) {
115112
final PendingResult<Boolean> pendingResult = new PendingResult<>();
116-
executor.execute(new Runnable() {
117-
@Override
118-
public void run() {
119-
try {
120-
Collection<ConstraintEntity> constraintEntities = dao.getConstraints();
121-
122-
Map<String, ConstraintEntity> constraintEntityMap = new HashMap<>();
123-
for (ConstraintEntity entity : constraintEntities) {
124-
constraintEntityMap.put(entity.constraintId, entity);
125-
}
113+
executor.execute(() -> {
114+
try {
115+
Collection<ConstraintEntity> constraintEntities = dao.getConstraints();
116+
117+
Map<String, ConstraintEntity> entityMap = new HashMap<>();
118+
for (ConstraintEntity entity : constraintEntities) {
119+
entityMap.put(entity.constraintId, entity);
120+
}
126121

127-
for (FrequencyConstraint constraint : constraints) {
128-
ConstraintEntity entity = new ConstraintEntity();
129-
entity.constraintId = constraint.getId();
130-
entity.count = constraint.getCount();
131-
entity.range = constraint.getRange();
132-
133-
ConstraintEntity existing = constraintEntityMap.remove(constraint.getId());
134-
if (existing != null) {
135-
if (existing.range != entity.range) {
136-
dao.delete(existing);
137-
dao.insert(entity);
138-
} else {
139-
dao.update(entity);
122+
for (FrequencyConstraint constraint : constraints) {
123+
ConstraintEntity entity = new ConstraintEntity();
124+
entity.constraintId = constraint.getId();
125+
entity.count = constraint.getCount();
126+
entity.range = constraint.getRange();
127+
128+
ConstraintEntity existing = entityMap.remove(constraint.getId());
129+
if (existing != null) {
130+
if (existing.range != entity.range) {
131+
dao.delete(existing);
132+
dao.insert(entity);
133+
134+
synchronized(lock) {
135+
occurrencesMap.put(constraint.getId(), new ArrayList<>());
136+
137+
if (entityMap.containsKey(constraint.getId())) {
138+
constraintEntityMap.put(constraint.getId(), entity);
139+
}
140140
}
141141
} else {
142-
dao.insert(entity);
142+
dao.update(entity);
143+
144+
synchronized(lock) {
145+
if (entityMap.containsKey(constraint.getId())) {
146+
constraintEntityMap.put(constraint.getId(), entity);
147+
}
148+
}
143149
}
150+
} else {
151+
dao.insert(entity);
144152
}
145-
146-
dao.delete(constraintEntityMap.keySet());
147-
pendingResult.setResult(true);
148-
} catch (Exception e) {
149-
Logger.error(e, "Failed to update constraints");
150-
pendingResult.setResult(false);
151153
}
154+
155+
dao.delete(entityMap.keySet());
156+
pendingResult.setResult(true);
157+
} catch (Exception e) {
158+
Logger.error(e, "Failed to update constraints");
159+
pendingResult.setResult(false);
152160
}
153161
});
154162

155163
return pendingResult;
156164
}
157165

158-
private boolean checkAndIncrement(@NonNull Collection<ConstraintEntity> constraints) {
159-
if (constraints.isEmpty()) {
166+
private boolean checkAndIncrement(@NonNull Collection<String> constraintIds) {
167+
if (constraintIds.isEmpty()) {
160168
return true;
161169
}
162170

163171
synchronized (lock) {
164-
if (isOverLimit(constraints)) {
172+
if (isOverLimit(constraintIds)) {
165173
return false;
166174
}
167-
recordOccurrence(getConstraintIds(constraints));
175+
recordOccurrence(constraintIds);
168176
return true;
169177
}
170178
}
171179

172-
private boolean isOverLimit(@NonNull Collection<ConstraintEntity> constraints) {
173-
if (constraints.isEmpty()) {
180+
private boolean isOverLimit(@NonNull Collection<String> constraintIds) {
181+
if (constraintIds.isEmpty()) {
174182
return false;
175183
}
176184

177185
synchronized (lock) {
178-
for (ConstraintEntity constraint : constraints) {
179-
if (isConstraintOverLimit(constraint)) {
186+
for (String constraintId : constraintIds) {
187+
if (isConstraintOverLimit(constraintId)) {
180188
return true;
181189
}
182190
}
183191
return false;
184192
}
185193
}
186194

187-
private void recordOccurrence(@NonNull Set<String> constraintIds) {
195+
private void recordOccurrence(@NonNull Collection<String> constraintIds) {
188196
if (constraintIds.isEmpty()) {
189197
return;
190198
}
191199

192200
long timeMillis = clock.currentTimeMillis();
193201

194-
for (String id : constraintIds) {
195-
OccurrenceEntity occurrence = new OccurrenceEntity();
196-
occurrence.parentConstraintId = id;
197-
occurrence.timeStamp = timeMillis;
202+
synchronized (lock) {
203+
for (String id : constraintIds) {
204+
OccurrenceEntity occurrence = new OccurrenceEntity();
205+
occurrence.parentConstraintId = id;
206+
occurrence.timeStamp = timeMillis;
198207

199-
pendingOccurrences.add(occurrence);
208+
pendingOccurrences.add(occurrence);
200209

201-
// Update any constraints that are still active
202-
for (Map.Entry<ConstraintEntity, List<OccurrenceEntity>> entry : occurrencesMap.entrySet()) {
203-
ConstraintEntity constraint = entry.getKey();
204-
if (constraint != null && id.equals(constraint.constraintId)) {
205-
entry.getValue().add(occurrence);
210+
if (occurrencesMap.get(id) == null) {
211+
occurrencesMap.put(id, new ArrayList<>());
206212
}
213+
occurrencesMap.get(id).add(occurrence);
207214
}
208215
}
209216

210217
// Save to database
211-
executor.execute(new Runnable() {
212-
@Override
213-
public void run() {
214-
writePendingOccurrences();
215-
}
216-
});
217-
}
218-
219-
@NonNull
220-
private Collection<ConstraintEntity> fetchConstraints(@Nullable Collection<String> constraintIds) {
221-
if (constraintIds == null || constraintIds.isEmpty()) {
222-
return Collections.emptyList();
223-
}
224-
225-
Collection<ConstraintEntity> constraints = dao.getConstraints(constraintIds);
226-
227-
for (ConstraintEntity constraint : constraints) {
228-
List<OccurrenceEntity> occurrences = dao.getOccurrences(constraint.constraintId);
229-
synchronized (lock) {
230-
for (OccurrenceEntity entity : pendingOccurrences) {
231-
if (entity.parentConstraintId.equals(constraint.constraintId)) {
232-
occurrences.add(entity);
233-
}
234-
}
235-
occurrencesMap.put(constraint, occurrences);
236-
}
237-
}
238-
239-
return constraints;
218+
executor.execute(this::writePendingOccurrences);
240219
}
241220

242221
private void writePendingOccurrences() {
@@ -251,28 +230,26 @@ private void writePendingOccurrences() {
251230
dao.insert(occurrence);
252231
} catch (SQLiteException e) {
253232
Logger.verbose(e);
233+
synchronized (lock) {
234+
pendingOccurrences.add(occurrence);
235+
}
254236
}
255237
}
256238
}
257239

258-
private boolean isConstraintOverLimit(@NonNull ConstraintEntity constraint) {
259-
List<OccurrenceEntity> occurrences = occurrencesMap.get(constraint);
260-
261-
if (occurrences == null || occurrences.size() < constraint.count) {
262-
return false;
263-
}
240+
private boolean isConstraintOverLimit(@NonNull String constraintId) {
241+
synchronized (lock) {
242+
List<OccurrenceEntity> occurrences = occurrencesMap.get(constraintId);
243+
ConstraintEntity constraint = constraintEntityMap.get(constraintId);
264244

265-
long timeSinceOccurrence = clock.currentTimeMillis() - occurrences.get(occurrences.size() - constraint.count).timeStamp;
266-
return timeSinceOccurrence <= constraint.range;
267-
}
245+
if (constraint == null || occurrences == null || occurrences.size() < constraint.count) {
246+
return false;
247+
}
268248

269-
@NonNull
270-
private Set<String> getConstraintIds(@NonNull Collection<ConstraintEntity> constraints) {
271-
Set<String> constraintIds = new HashSet<>();
272-
for (ConstraintEntity constraint : constraints) {
273-
constraintIds.add(constraint.constraintId);
249+
// Sort the occurrences by timestamp
250+
Collections.sort(occurrences, new OccurrenceEntity.Comparator());
251+
long timeSinceOccurrence = clock.currentTimeMillis() - occurrences.get(occurrences.size() - constraint.count).timeStamp;
252+
return timeSinceOccurrence <= constraint.range;
274253
}
275-
return constraintIds;
276254
}
277-
278255
}

urbanairship-automation/src/main/java/com/urbanairship/automation/limits/storage/OccurrenceEntity.java

+9
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,13 @@ public class OccurrenceEntity {
2323

2424
public String parentConstraintId;
2525
public long timeStamp;
26+
27+
/** @hide **/
28+
public static class Comparator implements java.util.Comparator<OccurrenceEntity> {
29+
@Override
30+
public int compare(OccurrenceEntity self, OccurrenceEntity other) {
31+
return Long.compare(self.timeStamp, other.timeStamp);
32+
}
33+
}
34+
2635
}

0 commit comments

Comments
 (0)