108
108
public class BeaconManager {
109
109
private static final String TAG = "BeaconManager" ;
110
110
private Context mContext ;
111
- protected static BeaconManager client = null ;
111
+ protected static volatile BeaconManager client = null ;
112
112
private final ConcurrentMap <BeaconConsumer , ConsumerInfo > consumers = new ConcurrentHashMap <BeaconConsumer ,ConsumerInfo >();
113
113
private Messenger serviceMessenger = null ;
114
114
protected final Set <RangeNotifier > rangeNotifiers = new CopyOnWriteArraySet <>();
@@ -123,6 +123,11 @@ public class BeaconManager {
123
123
private static boolean sAndroidLScanningDisabled = false ;
124
124
private static boolean sManifestCheckingDisabled = false ;
125
125
126
+ /**
127
+ * Private lock object for singleton initialization protecting against denial-of-service attack.
128
+ */
129
+ private static final Object SINGLETON_LOCK = new Object ();
130
+
126
131
/**
127
132
* Set to true if you want to show library debugging.
128
133
*
@@ -239,11 +244,29 @@ public static long getRegionExitPeriod(){
239
244
* or non-Service class, you can attach it to another singleton or a subclass of the Android Application class.
240
245
*/
241
246
public static BeaconManager getInstanceForApplication (Context context ) {
242
- if (client == null ) {
243
- LogManager .d (TAG , "BeaconManager instance creation" );
244
- client = new BeaconManager (context );
247
+ /*
248
+ * Follow double check pattern from Effective Java v2 Item 71.
249
+ *
250
+ * Bloch recommends using the local variable for this for performance reasons:
251
+ *
252
+ * > What this variable does is ensure that `field` is read only once in the common case
253
+ * > where it's already initialized. While not strictly necessary, this may improve
254
+ * > performance and is more elegant by the standards applied to low-level concurrent
255
+ * > programming. On my machine, [this] is about 25 percent faster than the obvious
256
+ * > version without a local variable.
257
+ *
258
+ * Joshua Bloch. Effective Java, Second Edition. Addison-Wesley, 2008. pages 283-284
259
+ */
260
+ BeaconManager instance = client ;
261
+ if (instance == null ) {
262
+ synchronized (SINGLETON_LOCK ) {
263
+ instance = client ;
264
+ if (instance == null ) {
265
+ client = instance = new BeaconManager (context );
266
+ }
267
+ }
245
268
}
246
- return client ;
269
+ return instance ;
247
270
}
248
271
249
272
protected BeaconManager (Context context ) {
@@ -271,17 +294,10 @@ public List<BeaconParser> getBeaconParsers() {
271
294
*/
272
295
@ TargetApi (18 )
273
296
public boolean checkAvailability () throws BleNotAvailableException {
274
- if (android . os . Build . VERSION . SDK_INT < 18 ) {
297
+ if (! isBleAvailable () ) {
275
298
throw new BleNotAvailableException ("Bluetooth LE not supported by this device" );
276
299
}
277
- if (!mContext .getPackageManager ().hasSystemFeature (PackageManager .FEATURE_BLUETOOTH_LE )) {
278
- throw new BleNotAvailableException ("Bluetooth LE not supported by this device" );
279
- } else {
280
- if (((BluetoothManager ) mContext .getSystemService (Context .BLUETOOTH_SERVICE )).getAdapter ().isEnabled ()) {
281
- return true ;
282
- }
283
- }
284
- return false ;
300
+ return ((BluetoothManager ) mContext .getSystemService (Context .BLUETOOTH_SERVICE )).getAdapter ().isEnabled ();
285
301
}
286
302
287
303
/**
@@ -292,8 +308,12 @@ public boolean checkAvailability() throws BleNotAvailableException {
292
308
* @param consumer the <code>Activity</code> or <code>Service</code> that will receive the callback when the service is ready.
293
309
*/
294
310
public void bind (BeaconConsumer consumer ) {
295
- if (android .os .Build .VERSION .SDK_INT < 18 ) {
296
- LogManager .w (TAG , "Not supported prior to API 18. Method invocation will be ignored" );
311
+ if (!isBleAvailable ()) {
312
+ LogManager .w (TAG , "Method invocation will be ignored." );
313
+ return ;
314
+ }
315
+ if (!mContext .getPackageManager ().hasSystemFeature (PackageManager .FEATURE_BLUETOOTH_LE )) {
316
+ LogManager .w (TAG , "This device does not support bluetooth LE. Will not start beacon scanning." );
297
317
return ;
298
318
}
299
319
synchronized (consumers ) {
@@ -318,8 +338,8 @@ public void bind(BeaconConsumer consumer) {
318
338
* @param consumer the <code>Activity</code> or <code>Service</code> that no longer needs to use the service.
319
339
*/
320
340
public void unbind (BeaconConsumer consumer ) {
321
- if (android . os . Build . VERSION . SDK_INT < 18 ) {
322
- LogManager .w (TAG , "Not supported prior to API 18. Method invocation will be ignored" );
341
+ if (! isBleAvailable () ) {
342
+ LogManager .w (TAG , "Method invocation will be ignored. " );
323
343
return ;
324
344
}
325
345
synchronized (consumers ) {
@@ -391,8 +411,9 @@ public boolean isAnyConsumerBound() {
391
411
* @see #setBackgroundBetweenScanPeriod(long p)
392
412
*/
393
413
public void setBackgroundMode (boolean backgroundMode ) {
394
- if (android .os .Build .VERSION .SDK_INT < 18 ) {
395
- LogManager .w (TAG , "Not supported prior to API 18. Method invocation will be ignored" );
414
+ if (!isBleAvailable ()) {
415
+ LogManager .w (TAG , "Method invocation will be ignored." );
416
+ return ;
396
417
}
397
418
mBackgroundModeUninitialized = false ;
398
419
if (backgroundMode != mBackgroundMode ) {
@@ -608,8 +629,8 @@ public void requestStateForRegion(Region region) {
608
629
*/
609
630
@ TargetApi (18 )
610
631
public void startRangingBeaconsInRegion (Region region ) throws RemoteException {
611
- if (android . os . Build . VERSION . SDK_INT < 18 ) {
612
- LogManager .w (TAG , "Not supported prior to API 18. Method invocation will be ignored" );
632
+ if (! isBleAvailable () ) {
633
+ LogManager .w (TAG , "Method invocation will be ignored. " );
613
634
return ;
614
635
}
615
636
if (serviceMessenger == null ) {
@@ -636,8 +657,8 @@ public void startRangingBeaconsInRegion(Region region) throws RemoteException {
636
657
*/
637
658
@ TargetApi (18 )
638
659
public void stopRangingBeaconsInRegion (Region region ) throws RemoteException {
639
- if (android . os . Build . VERSION . SDK_INT < 18 ) {
640
- LogManager .w (TAG , "Not supported prior to API 18. Method invocation will be ignored" );
660
+ if (! isBleAvailable () ) {
661
+ LogManager .w (TAG , "Method invocation will be ignored. " );
641
662
return ;
642
663
}
643
664
if (serviceMessenger == null ) {
@@ -671,8 +692,8 @@ public void stopRangingBeaconsInRegion(Region region) throws RemoteException {
671
692
*/
672
693
@ TargetApi (18 )
673
694
public void startMonitoringBeaconsInRegion (Region region ) throws RemoteException {
674
- if (android . os . Build . VERSION . SDK_INT < 18 ) {
675
- LogManager .w (TAG , "Not supported prior to API 18. Method invocation will be ignored" );
695
+ if (! isBleAvailable () ) {
696
+ LogManager .w (TAG , "Method invocation will be ignored. " );
676
697
return ;
677
698
}
678
699
if (serviceMessenger == null ) {
@@ -699,8 +720,8 @@ public void startMonitoringBeaconsInRegion(Region region) throws RemoteException
699
720
*/
700
721
@ TargetApi (18 )
701
722
public void stopMonitoringBeaconsInRegion (Region region ) throws RemoteException {
702
- if (android . os . Build . VERSION . SDK_INT < 18 ) {
703
- LogManager .w (TAG , "Not supported prior to API 18. Method invocation will be ignored" );
723
+ if (! isBleAvailable () ) {
724
+ LogManager .w (TAG , "Method invocation will be ignored. " );
704
725
return ;
705
726
}
706
727
if (serviceMessenger == null ) {
@@ -721,8 +742,8 @@ public void stopMonitoringBeaconsInRegion(Region region) throws RemoteException
721
742
*/
722
743
@ TargetApi (18 )
723
744
public void updateScanPeriods () throws RemoteException {
724
- if (android . os . Build . VERSION . SDK_INT < 18 ) {
725
- LogManager .w (TAG , "Not supported prior to API 18. Method invocation will be ignored" );
745
+ if (! isBleAvailable () ) {
746
+ LogManager .w (TAG , "Method invocation will be ignored. " );
726
747
return ;
727
748
}
728
749
if (serviceMessenger == null ) {
@@ -895,6 +916,18 @@ public void setNonBeaconLeScanCallback(NonBeaconLeScanCallback callback) {
895
916
mNonBeaconLeScanCallback = callback ;
896
917
}
897
918
919
+ private boolean isBleAvailable () {
920
+ boolean available = false ;
921
+ if (android .os .Build .VERSION .SDK_INT < android .os .Build .VERSION_CODES .JELLY_BEAN_MR2 ) {
922
+ LogManager .w (TAG , "Bluetooth LE not supported prior to API 18." );
923
+ } else if (!mContext .getPackageManager ().hasSystemFeature (PackageManager .FEATURE_BLUETOOTH_LE )) {
924
+ LogManager .w (TAG , "This device does not support bluetooth LE." );
925
+ } else {
926
+ available = true ;
927
+ }
928
+ return available ;
929
+ }
930
+
898
931
private long getScanPeriod () {
899
932
if (mBackgroundMode ) {
900
933
return backgroundScanPeriod ;
0 commit comments