-
Notifications
You must be signed in to change notification settings - Fork 832
Expand file tree
/
Copy pathScanState.java
More file actions
334 lines (289 loc) · 13.3 KB
/
ScanState.java
File metadata and controls
334 lines (289 loc) · 13.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
package org.altbeacon.beacon.service;
import android.content.Context;
import android.util.Log;
import org.altbeacon.beacon.BeaconManager;
import org.altbeacon.beacon.BeaconParser;
import org.altbeacon.beacon.Region;
import org.altbeacon.beacon.logging.LogManager;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InvalidClassException;
import java.io.FileNotFoundException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static android.content.Context.MODE_PRIVATE;
/**
* Stores the full state of scanning for the library, including all settings so it can be ressurrected easily
* for running from a scheduled job
*
* Created by dyoung on 3/26/17.
* @hide
*/
public class ScanState implements Serializable {
private static final String TAG = ScanState.class.getSimpleName();
private static final String STATUS_PRESERVATION_FILE_NAME = "android-beacon-library-scan-state";
private static final String TEMP_STATUS_PRESERVATION_FILE_NAME = "android-beacon-library-scan-state-temp";
public static int MIN_SCAN_JOB_INTERVAL_MILLIS = 300000; // 5 minutes
private static long SANITY_FILE_SIZE_LIMIT = 100000; // ~100k
private static long SANITY_REGION_LIMIT = 1000;
private static long SANITY_PARSER_LIMIT = 1000;
private Map<Region, RangeState> mRangedRegionState = new HashMap<Region, RangeState>();
private transient MonitoringStatus mMonitoringStatus;
private Set<BeaconParser> mBeaconParsers = new HashSet<BeaconParser>();
// Don't persist extra beacon tracker because this could grow to be very very large if a huge
// number of beacons are in the vicinity, or if a smaller number rotate MACs or identifiers
// and appear to be a large number
private transient ExtraDataBeaconTracker mExtraBeaconDataTracker = new ExtraDataBeaconTracker();
private long mForegroundBetweenScanPeriod;
private long mBackgroundBetweenScanPeriod;
private long mForegroundScanPeriod;
private long mBackgroundScanPeriod;
private boolean mBackgroundMode;
private long mLastScanStartTimeMillis = 0l;
private transient Context mContext;
public Boolean getBackgroundMode() {
return mBackgroundMode;
}
public void setBackgroundMode(Boolean backgroundMode) {
mBackgroundMode = backgroundMode;
}
public Long getBackgroundBetweenScanPeriod() {
return mBackgroundBetweenScanPeriod;
}
public void setBackgroundBetweenScanPeriod(Long backgroundBetweenScanPeriod) {
mBackgroundBetweenScanPeriod = backgroundBetweenScanPeriod;
}
public Long getBackgroundScanPeriod() {
return mBackgroundScanPeriod;
}
public void setBackgroundScanPeriod(Long backgroundScanPeriod) {
mBackgroundScanPeriod = backgroundScanPeriod;
}
public Long getForegroundBetweenScanPeriod() {
return mForegroundBetweenScanPeriod;
}
public void setForegroundBetweenScanPeriod(Long foregroundBetweenScanPeriod) {
mForegroundBetweenScanPeriod = foregroundBetweenScanPeriod;
}
public Long getForegroundScanPeriod() {
return mForegroundScanPeriod;
}
public void setForegroundScanPeriod(Long foregroundScanPeriod) {
mForegroundScanPeriod = foregroundScanPeriod;
}
public ScanState(Context context) {
mContext = context;
}
public MonitoringStatus getMonitoringStatus() {
return mMonitoringStatus;
}
public void setMonitoringStatus(MonitoringStatus monitoringStatus) {
mMonitoringStatus = monitoringStatus;
}
public Map<Region, RangeState> getRangedRegionState() {
return mRangedRegionState;
}
public void setRangedRegionState(Map<Region, RangeState> rangedRegionState) {
mRangedRegionState = rangedRegionState;
}
public ExtraDataBeaconTracker getExtraBeaconDataTracker() {
return mExtraBeaconDataTracker;
}
public void setExtraBeaconDataTracker(ExtraDataBeaconTracker extraDataBeaconTracker) {
mExtraBeaconDataTracker = extraDataBeaconTracker;
}
public Set<BeaconParser> getBeaconParsers() {
return mBeaconParsers;
}
public void setBeaconParsers(Set<BeaconParser> beaconParsers) {
mBeaconParsers = beaconParsers;
}
public long getLastScanStartTimeMillis() {
return mLastScanStartTimeMillis;
}
public void setLastScanStartTimeMillis(long time) {
mLastScanStartTimeMillis = time;
}
public static ScanState restore(Context context) {
ScanState scanState = null;
synchronized (ScanState.class) {
FileInputStream inputStream = null;
ObjectInputStream objectInputStream = null;
try {
File file = context.getFileStreamPath(STATUS_PRESERVATION_FILE_NAME);
if (file.length() > SANITY_FILE_SIZE_LIMIT) {
// make sure file size is reasonable. If over 100k, do not restore
// See issue #1129
LogManager.e(TAG, "Refusing to restore file of size "+file.length());
}
else {
inputStream = context.openFileInput(STATUS_PRESERVATION_FILE_NAME);
objectInputStream = new ObjectInputStream(inputStream);
scanState = (ScanState) objectInputStream.readObject();
scanState.mContext = context;
}
}
catch (FileNotFoundException fnfe) {
LogManager.w(TAG, "Serialized ScanState does not exist. This may be normal on first run.");
}
catch (IllegalStateException ise) {
LogManager.e(TAG, "Exception deserializing", ise);
}
catch (IOException | ClassNotFoundException | ClassCastException e) {
if (e instanceof InvalidClassException) {
LogManager.d(TAG, "Serialized ScanState has wrong class. Just ignoring saved state...");
}
else {
LogManager.e(TAG, "Deserialization exception");
Log.e(TAG, "error: ", e);
}
} finally {
if (null != inputStream) {
try {
inputStream.close();
} catch (IOException ignored) {
}
}
if (objectInputStream != null) {
try {
objectInputStream.close();
} catch (IOException ignored) {
}
}
}
if (scanState == null) {
scanState = new ScanState(context);
}
if (scanState.mExtraBeaconDataTracker == null) {
scanState.mExtraBeaconDataTracker = new ExtraDataBeaconTracker();
}
scanState.mMonitoringStatus = MonitoringStatus.getInstanceForApplication(context);
LogManager.d(TAG, "Scan state restore regions: monitored="+scanState.getMonitoringStatus().regions().size()+" ranged="+scanState.getRangedRegionState().keySet().size());
return scanState;
}
}
public void save() {
synchronized (ScanState.class) {
if (mRangedRegionState.size() > SANITY_REGION_LIMIT) {
LogManager.e(TAG, "Refusing to save scan state with excessive region count: "+mRangedRegionState.size());
return;
}
if (mMonitoringStatus.regions().size() > SANITY_REGION_LIMIT) {
LogManager.e(TAG, "Refusing to save scan state with excessive region count: "+mMonitoringStatus.regions().size());
return;
}
if (mBeaconParsers.size() > SANITY_PARSER_LIMIT) {
LogManager.e(TAG, "Refusing to save scan state with excessive parser count: "+mBeaconParsers.size());
return;
}
FileOutputStream outputStream = null;
ObjectOutputStream objectOutputStream = null;
try {
outputStream = mContext.openFileOutput(TEMP_STATUS_PRESERVATION_FILE_NAME, MODE_PRIVATE);
objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(this);
} catch (IOException e) {
LogManager.e(TAG, "Error while saving scan status to file: ", e.getMessage());
} finally {
if (null != outputStream) {
try {
outputStream.close();
} catch (IOException ignored) {
}
}
if (objectOutputStream != null) {
try {
objectOutputStream.close();
} catch (IOException ignored) {
}
}
}
File file = new File(mContext.getFilesDir(), STATUS_PRESERVATION_FILE_NAME);
File tempFile = new File(mContext.getFilesDir(), TEMP_STATUS_PRESERVATION_FILE_NAME);
LogManager.d(TAG, "Temp file is "+tempFile.getAbsolutePath());
LogManager.d(TAG, "Perm file is "+file.getAbsolutePath());
if (!file.delete()) {
LogManager.e(TAG, "Error while saving scan status to file: Cannot delete existing file.");
}
if (!tempFile.renameTo(file)) {
LogManager.e(TAG, "Error while saving scan status to file: Cannot rename temp file.");
}
mMonitoringStatus.saveMonitoringStatusIfOn();
}
}
public int getScanJobIntervalMillis() {
long cyclePeriodMillis;
if (getBackgroundMode()) {
cyclePeriodMillis = getBackgroundScanPeriod()+getBackgroundBetweenScanPeriod();
}
else {
cyclePeriodMillis = getForegroundScanPeriod()+getForegroundBetweenScanPeriod();
}
int scanJobIntervalMillis = MIN_SCAN_JOB_INTERVAL_MILLIS;
if (cyclePeriodMillis > MIN_SCAN_JOB_INTERVAL_MILLIS) {
scanJobIntervalMillis = (int) cyclePeriodMillis;
}
return scanJobIntervalMillis;
}
public int getScanJobRuntimeMillis() {
long scanPeriodMillis;
LogManager.d(TAG, "ScanState says background mode for ScanJob is "+getBackgroundMode());
if (getBackgroundMode()) {
scanPeriodMillis = getBackgroundScanPeriod();
}
else {
scanPeriodMillis = getForegroundScanPeriod();
}
if (!getBackgroundMode()) {
// if we are in the foreground, we keep the scan job going for the minimum interval
if (scanPeriodMillis < MIN_SCAN_JOB_INTERVAL_MILLIS) {
return MIN_SCAN_JOB_INTERVAL_MILLIS;
}
}
return (int) scanPeriodMillis;
}
public void applyChanges(BeaconManager beaconManager) {
mBeaconParsers = new HashSet<>(beaconManager.getBeaconParsers());
mForegroundScanPeriod = beaconManager.getForegroundScanPeriod();
mForegroundBetweenScanPeriod = beaconManager.getForegroundBetweenScanPeriod();
mBackgroundScanPeriod = beaconManager.getBackgroundScanPeriod();
mBackgroundBetweenScanPeriod = beaconManager.getBackgroundBetweenScanPeriod();
mBackgroundMode = beaconManager.getBackgroundMode();
ArrayList<Region> existingMonitoredRegions = new ArrayList<>(mMonitoringStatus.regions());
ArrayList<Region> existingRangedRegions = new ArrayList<>(mRangedRegionState.keySet());
ArrayList<Region> newMonitoredRegions = new ArrayList<>(beaconManager.getMonitoredRegions());
ArrayList<Region> newRangedRegions = new ArrayList<>(beaconManager.getRangedRegions());
LogManager.d(TAG, "ranged regions: old="+existingRangedRegions.size()+" new="+newRangedRegions.size());
LogManager.d(TAG, "monitored regions: old="+existingMonitoredRegions.size()+" new="+newMonitoredRegions.size());
for (Region newRangedRegion: newRangedRegions) {
if (!existingRangedRegions.contains(newRangedRegion)) {
LogManager.d(TAG, "Starting ranging region: "+newRangedRegion);
mRangedRegionState.put(newRangedRegion, new RangeState(new Callback(mContext.getPackageName())));
}
else {
// In case the user has changed the definition, update it.
Region existingRegion = existingRangedRegions.get(existingRangedRegions.indexOf(newRangedRegion));
if (newRangedRegion.hasSameIdentifiers(existingRegion)) {
mRangedRegionState.remove(existingRegion);
mRangedRegionState.put(newRangedRegion, new RangeState(new Callback(mContext.getPackageName())));
}
}
}
for (Region existingRangedRegion: existingRangedRegions) {
if (!newRangedRegions.contains(existingRangedRegion)) {
LogManager.d(TAG, "Stopping ranging region: "+existingRangedRegion);
mRangedRegionState.remove(existingRangedRegion);
}
}
LogManager.d(TAG, "Updated state with "+newRangedRegions.size()+" ranging regions and "+newMonitoredRegions.size()+" monitoring regions.");
this.save();
}
}