closestBeaconId, String fallbackUrl) {
43 | stopAdvertising();
44 | advertiser.startAdvertising(
45 | new AdvertiseSettings.Builder().setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED).build(),
46 | new AdvertiseData.Builder().addServiceUuid(ParcelUuid.fromString(uuid)).build(),
47 | callback
48 | );
49 | }
50 |
51 | @Override
52 | public void stopAdvertising() {
53 | try {
54 | advertiser.stopAdvertising(callback);
55 | } catch (Exception e) {
56 |
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/application/ReelyAwareActivity.java:
--------------------------------------------------------------------------------
1 | package com.reelyactive.blesdk.application;
2 |
3 | import com.reelyactive.blesdk.support.ble.ScanResult;
4 |
5 | /**
6 | * Implement this interface if you want your current activity to be notified when Reelceiver are detected.
7 | */
8 | public interface ReelyAwareActivity {
9 | public void onScanStarted();
10 |
11 | public void onScanStopped();
12 |
13 | public void onEnterRegion(ScanResult beacon);
14 |
15 | public void onLeaveRegion(ScanResult beacon);
16 | }
17 |
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/application/ReelyAwareApplicationCallback.java:
--------------------------------------------------------------------------------
1 | package com.reelyactive.blesdk.application;
2 |
3 | import android.Manifest;
4 | import android.app.Activity;
5 | import android.app.Application;
6 | import android.content.ComponentName;
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import android.content.ServiceConnection;
10 | import android.content.pm.PackageManager;
11 | import android.os.Build;
12 | import android.os.IBinder;
13 | import android.os.ParcelUuid;
14 | import android.support.v4.content.ContextCompat;
15 |
16 | import com.reelyactive.blesdk.service.BleService;
17 | import com.reelyactive.blesdk.service.BleServiceCallback;
18 | import com.reelyactive.blesdk.support.ble.ScanFilter;
19 | import com.reelyactive.blesdk.support.ble.ScanResult;
20 | import com.reelyactive.blesdk.support.ble.util.Logger;
21 |
22 | import java.util.concurrent.atomic.AtomicInteger;
23 |
24 | /**
25 | * This class provides a convenient way to make your application aware of any Reelceivers.
26 | *
27 | * Register it using {@link android.app.Application#registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks)}
28 | *
29 | * Extend it to customize the behaviour of your application.
30 | *
31 | * The default behaviour is to bind to the {@link BleService} as soon as the app is created.
32 | *
33 | * @see android.app.Application.ActivityLifecycleCallbacks
34 | */
35 | @SuppressWarnings("unused")
36 | public abstract class ReelyAwareApplicationCallback implements Application.ActivityLifecycleCallbacks, BleServiceCallback {
37 | private static final String TAG = ReelyAwareApplicationCallback.class.getSimpleName();
38 | private final Context context;
39 | private final ServiceConnection serviceConnection;
40 | private final AtomicInteger activityCount = new AtomicInteger();
41 | private boolean bound = false;
42 | private Activity current;
43 | private BleService service;
44 |
45 | /**
46 | * As soon as the component is created, we bindBleService to the {@link BleService}
47 | *
48 | * @param context The application's {@link Context}
49 | */
50 | public ReelyAwareApplicationCallback(Context context) {
51 | this.context = context;
52 | this.serviceConnection = new BleServiceConnection();
53 | bindBleService();
54 | }
55 |
56 | /**
57 | * Find out if an {@link Activity} implements {@link ReelyAwareActivity}
58 | *
59 | * @param activity The {@link Activity}
60 | * @return true if the {@link Activity} implements {@link ReelyAwareActivity}, false if not.
61 | */
62 | protected static boolean isReelyAware(Activity activity) {
63 | return activity != null && ReelyAwareActivity.class.isInstance(activity);
64 | }
65 |
66 | /**
67 | * The default behaviour is to check if a {@link ReelyAwareActivity} is running, and call a scan if so.
68 | * See {@link #startScan()} and {@link #getScanType()}
69 | *
70 | * @param activity The resumed {@link Activity}
71 | */
72 | @Override
73 | public void onActivityResumed(Activity activity) {
74 | current = activity;
75 | activityCount.incrementAndGet();
76 | if (!startScan()) {
77 | stopScan();
78 | }
79 | }
80 |
81 | /**
82 | * The default behaviour is to check if any {@link ReelyAwareActivity} is still running, and call a scan if so.
83 | * See {@link #startScan()} and {@link #getScanType()}
84 | *
85 | * @param activity The resumed {@link Activity}.
86 | */
87 | @Override
88 | public void onActivityPaused(Activity activity) {
89 | current = null;
90 | activityCount.decrementAndGet();
91 | if (!startScan()) {
92 | stopScan();
93 | }
94 | }
95 |
96 | /**
97 | * This method sends a scan request to the {@link BleService}.
98 | *
99 | * @return True if the service has started, false otherwise.
100 | */
101 | protected boolean startScan() {
102 | if (isBound() && hasScanPermissions() && shouldStartScan()) {
103 | updateScanType(getScanType());
104 | updateScanFilter(getScanFilter());
105 | getBleService().startScan();
106 | return true;
107 | }
108 | return false;
109 | }
110 |
111 | /**
112 | * This method requests the {@link BleService} to stop scanning.
113 | */
114 | protected void stopScan() {
115 | if (isBound()) {
116 | getBleService().stopScan();
117 | }
118 | }
119 |
120 | /**
121 | * This method sets the scan type of the {@link BleService}.
122 | *
123 | * @param scanType The {@link BleService.ScanType scan type}
124 | */
125 | protected void updateScanType(BleService.ScanType scanType) {
126 | Logger.logInfo("Updating scan type to " + scanType);
127 | getBleService().setScanType(scanType);
128 | }
129 |
130 | /**
131 | * This method sets the scan filter of the {@link BleService}.
132 | *
133 | * @param scanFilter The {@link ScanFilter scan filter}
134 | */
135 | protected void updateScanFilter(ScanFilter scanFilter) {
136 | getBleService().setScanFilter(scanFilter);
137 | }
138 |
139 | /**
140 | * Override this in order to chose which scan filter is to be used before {@link #startScan} is called when the service starts.
141 | *
142 | * @return The {@link ScanFilter} to be used in the current application state.
143 | */
144 | protected ScanFilter getScanFilter() {
145 | return new ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString("7265656C-7941-6374-6976-652055554944")).build();
146 | }
147 |
148 | /**
149 | * Override this in order to chose which kind of scan is to be used before {@link #startScan} is called when the service starts.
150 | *
151 | * @return The {@link BleService.ScanType} to be used in the current application state.
152 | */
153 | protected BleService.ScanType getScanType() {
154 | return getActivityCount() == 0 ? BleService.ScanType.LOW_POWER : BleService.ScanType.ACTIVE;
155 | }
156 |
157 | /**
158 | * Called by the class in order to check if a scan should be started.
159 | * Override this method if you need to change the behaviour of the scan.
160 | *
161 | * @return true if the conditions for a scan are present, false otherwise.
162 | */
163 | protected boolean shouldStartScan() {
164 | return isReelyAware(getCurrentActivity());
165 | }
166 |
167 | protected boolean hasScanPermissions() {
168 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
169 | if (
170 | ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
171 | ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
172 | ) {
173 | return false; // Don't start scanning if we are not allowed to use the location.
174 | }
175 | }
176 | return true;
177 | }
178 |
179 | /**
180 | * This method is called when a {@link BleService.Event} is received.
181 | * Its default behaviour is to notify the currently running {@link ReelyAwareActivity} (if any).
182 | * Override this and you can customize the behaviour of the application.
183 | *
184 | * @param event The {@link BleService.Event} received from the {@link BleService}.
185 | * @return true if the event was processed, false otherwise;
186 | */
187 | @Override
188 | public boolean onBleEvent(BleService.Event event, Object data) {
189 | boolean processed = isReelyAware(getCurrentActivity());
190 | switch (event) {
191 | case IN_REGION:
192 | if (processed) {
193 | ((ReelyAwareActivity) getCurrentActivity()).onEnterRegion((ScanResult) data);
194 | }
195 | break;
196 | case OUT_REGION:
197 | if (processed) {
198 | ((ReelyAwareActivity) getCurrentActivity()).onLeaveRegion((ScanResult) data);
199 | }
200 | break;
201 | case SCAN_STARTED:
202 | if (processed) {
203 | ((ReelyAwareActivity) getCurrentActivity()).onScanStarted();
204 | }
205 | break;
206 | case SCAN_STOPPED:
207 | if (processed) {
208 | ((ReelyAwareActivity) getCurrentActivity()).onScanStopped();
209 | }
210 | break;
211 | default:
212 | processed = false;
213 | break;
214 | }
215 | return processed;
216 | }
217 |
218 | /**
219 | * Access the application {@link Context}.
220 | *
221 | * @return The application {@link Context}.
222 | */
223 | protected Context getContext() {
224 | return context;
225 | }
226 |
227 | /**
228 | * Get currently running Activity.
229 | *
230 | * @return The currently running Activity.
231 | */
232 | protected Activity getCurrentActivity() {
233 | return current;
234 | }
235 |
236 | /**
237 | * Get access to the underlying service.
238 | *
239 | * @return The {@link com.reelyactive.blesdk.service.BleService} instance running.
240 | */
241 | protected BleService getBleService() {
242 | return service;
243 | }
244 |
245 | /**
246 | * Get the number of {@link ReelyAwareActivity ReelyAwareActivities} currently running (0 or 1 basically)
247 | *
248 | * @return The number of {@link ReelyAwareActivity ReelyAwareActivities} running
249 | */
250 | protected int getActivityCount() {
251 | return activityCount.get();
252 | }
253 |
254 | /**
255 | * Get the status of the connection to the {@link BleService}
256 | *
257 | * @return true if the {@link BleService} is bound, false otherwise.
258 | */
259 | protected boolean isBound() {
260 | return bound;
261 | }
262 |
263 | /*
264 | * ************* PRIVATE STUFF ******************
265 | */
266 |
267 | protected boolean bindBleService() {
268 | return context.bindService(new Intent(context, BleService.class), serviceConnection, Context.BIND_AUTO_CREATE);
269 | }
270 |
271 |
272 | protected void unbindBleService() {
273 | if (isBound()) {
274 | service.unregisterClient(this);
275 | context.unbindService(serviceConnection);
276 | }
277 | }
278 |
279 | /**
280 | * This method is called when the {@link BleService is available}.
281 | * The default behaviour is to start a scan.
282 | */
283 | protected void onBleServiceBound() {
284 | startScan();
285 | }
286 |
287 | final class BleServiceConnection implements ServiceConnection {
288 | @Override
289 | public void onServiceConnected(ComponentName name, IBinder remoteService) {
290 | bound = true;
291 | service = ((BleService.LocalBinder) remoteService).getService();
292 | service.registerClient(ReelyAwareApplicationCallback.this);
293 | onBleServiceBound();
294 | }
295 |
296 | @Override
297 | public void onServiceDisconnected(ComponentName name) {
298 | bound = false;
299 | }
300 | }
301 |
302 | }
303 |
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/service/BleService.java:
--------------------------------------------------------------------------------
1 | package com.reelyactive.blesdk.service;
2 |
3 | import android.app.Service;
4 | import android.content.Intent;
5 | import android.os.Binder;
6 | import android.os.IBinder;
7 | import android.os.Parcelable;
8 |
9 | import com.reelyactive.blesdk.support.ble.BluetoothLeScannerCompat;
10 | import com.reelyactive.blesdk.support.ble.BluetoothLeScannerCompatProvider;
11 | import com.reelyactive.blesdk.support.ble.ScanFilter;
12 | import com.reelyactive.blesdk.support.ble.ScanResult;
13 | import com.reelyactive.blesdk.support.ble.ScanSettings;
14 |
15 | import java.util.ArrayList;
16 | import java.util.Collections;
17 | import java.util.List;
18 |
19 | public class BleService extends Service {
20 | public static final String KEY_FILTER = "filter";
21 | public static final String KEY_EVENT_DATA = "event_data";
22 | final ScanCallback callback = new ScanCallback();
23 | final ScanSettings lowPowerScan = new ScanSettings.Builder() //
24 | .setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH | ScanSettings.CALLBACK_TYPE_MATCH_LOST) //
25 | .setScanMode(ScanSettings.SCAN_MODE_BALANCED) //
26 | .setScanResultType(ScanSettings.SCAN_RESULT_TYPE_FULL) //
27 | .build();
28 | final ScanSettings higPowerScan = new ScanSettings.Builder() //
29 | .setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH | ScanSettings.CALLBACK_TYPE_MATCH_LOST) //
30 | .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) //
31 | .setScanResultType(ScanSettings.SCAN_RESULT_TYPE_FULL) //
32 | .build();
33 | private final IBinder binder = new LocalBinder();
34 | /**
35 | * Keeps track of all current registered clients.
36 | */
37 | ArrayList mClients = new ArrayList();
38 | private BluetoothLeScannerCompat scanner;
39 | private ScanSettings currentSettings;
40 | private ScanSettings nextSettings;
41 | private ScanFilter currentFilter;
42 | private ScanFilter nextFilter;
43 |
44 | @Override
45 | public void onCreate() {
46 | scanner = BluetoothLeScannerCompatProvider.getBluetoothLeScannerCompat(getApplicationContext());
47 | }
48 |
49 | @Override
50 | public void onDestroy() {
51 | // TODO make it possible to kill the scanner.
52 | }
53 |
54 | /**
55 | * When binding to the service, we return an interface to our messenger
56 | * for sending messages to the service.
57 | */
58 | @Override
59 | public IBinder onBind(Intent intent) {
60 | return binder;
61 | }
62 |
63 | public void registerClient(BleServiceCallback client) {
64 | mClients.add(client);
65 | }
66 |
67 | public void unregisterClient(BleServiceCallback client) {
68 | mClients.remove(client);
69 | }
70 |
71 | public void startScan() {
72 | nextSettings = nextSettings == null ? lowPowerScan : nextSettings;
73 | nextFilter = nextFilter == null ? (currentFilter == null ? new ScanFilter.Builder().build() : currentFilter) : nextFilter;
74 | if (currentSettings != nextSettings || nextFilter != currentFilter) {
75 | stopScan();
76 | notifyEvent(Event.SCAN_STARTED);
77 | scanner.startScan(Collections.singletonList(nextFilter), nextSettings, callback); // TODO make it possible to scan using more filters
78 | }
79 | currentSettings = nextSettings;
80 | currentFilter = nextFilter;
81 | }
82 |
83 | public void stopScan() {
84 | currentFilter = null;
85 | currentSettings = null;
86 | scanner.stopScan(callback);
87 | notifyEvent(Event.SCAN_STOPPED);
88 | }
89 |
90 | public void setScanType(ScanType scanType) {
91 | nextSettings = ScanType.ACTIVE == scanType ? higPowerScan : lowPowerScan;
92 | }
93 |
94 | public void setScanFilter(ScanFilter scanFilter) {
95 | nextFilter = scanFilter;
96 | }
97 |
98 | public List getMatchingRecentResults(List filters) {
99 | return scanner.getMatchingRecords(filters);
100 | }
101 |
102 | private void notifyEvent(Event event, Parcelable... data) {
103 | ScanResult result = null;
104 | if (data != null && data.length == 1) {
105 | result = (ScanResult) data[0];
106 | }
107 | for (int i = mClients.size() - 1; i >= 0; i--) {
108 | mClients.get(i).onBleEvent(event, result);
109 | }
110 | }
111 |
112 | public static enum Event {
113 | SCAN_STARTED,
114 | SCAN_STOPPED,
115 | IN_REGION,
116 | OUT_REGION,
117 | CYCLE_COMPLETED,
118 | UNKNOWN;
119 | private static Event[] allValues = values();
120 |
121 | public static Event fromOrdinal(int n) {
122 | if (n >= 0 || n < UNKNOWN.ordinal()) {
123 | return allValues[n];
124 | }
125 | return UNKNOWN;
126 | }
127 | }
128 |
129 | public static enum ScanType {
130 | LOW_POWER,
131 | ACTIVE;
132 | private static ScanType[] allValues = values();
133 |
134 | public static ScanType fromOrdinal(int n) throws IllegalArgumentException {
135 | if (n < 0 || n >= allValues.length) {
136 | return LOW_POWER;
137 | }
138 | return allValues[n];
139 | }
140 | }
141 |
142 | /**
143 | * Class used for the client Binder. Because we know this service always
144 | * runs in the same process as its clients, we don't need to deal with IPC.
145 | */
146 | public class LocalBinder extends Binder {
147 | public BleService getService() {
148 | // Return this instance of LocalService so clients can call public methods
149 | return BleService.this;
150 | }
151 | }
152 |
153 | class ScanCallback extends com.reelyactive.blesdk.support.ble.ScanCallback {
154 | @Override
155 | public void onScanResult(int callbackType, ScanResult result) {
156 | notifyEvent(callbackType != ScanSettings.CALLBACK_TYPE_MATCH_LOST ? Event.IN_REGION : Event.OUT_REGION, result);
157 | }
158 |
159 | @Override
160 | public void onScanCycleCompleted() {
161 | notifyEvent(Event.CYCLE_COMPLETED);
162 | }
163 | }
164 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/service/BleServiceCallback.java:
--------------------------------------------------------------------------------
1 | package com.reelyactive.blesdk.service;
2 |
3 | /**
4 | * Created by saiimons on 15-03-30.
5 | */
6 | public interface BleServiceCallback {
7 | public boolean onBleEvent(BleService.Event event, Object data);
8 | }
9 |
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/support/ble/BluetoothLeScannerCompat.java:
--------------------------------------------------------------------------------
1 | package com.reelyactive.blesdk.support.ble;/*
2 | * Copyright 2014 Google Inc. All rights reserved.
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 | // THIS IS MODIFIED COPY OF THE "L" PLATFORM CLASS. BE CAREFUL ABOUT EDITS.
18 | // THIS CODE SHOULD FOLLOW ANDROID STYLE.
19 | //
20 | // Changes:
21 | // Change to abstract class
22 | // Remove implementations
23 | // Define setCustomScanTiming for ULR
24 | // Slight updates to javadoc
25 |
26 | import android.app.AlarmManager;
27 | import android.app.PendingIntent;
28 | import android.os.Build;
29 |
30 | import com.reelyactive.blesdk.support.ble.util.Clock;
31 | import com.reelyactive.blesdk.support.ble.util.Logger;
32 |
33 | import java.util.ArrayList;
34 | import java.util.Collection;
35 | import java.util.List;
36 |
37 | /**
38 | * Represents the public entry into the Bluetooth LE compatibility scanner that efficiently captures
39 | * advertising packets broadcast from Bluetooth LE devices.
40 | *
41 | * API declarations in this class are the same as the new LE scanning API that is being introduced
42 | * in Android "L" platform. Declarations contained here will eventually be replaced by the platform
43 | * versions. Refer to the "L" release API design for further
44 | * information.
45 | *
46 | * The API implemented here is for compatibility when used on
47 | * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} and later.
48 | *
49 | * @see Bluetooth
50 | * Adopted Specifications
51 | * @see Core
52 | * Specification Supplement (CSS) v4
53 | */
54 | public abstract class BluetoothLeScannerCompat {
55 |
56 | // Number of cycles before a sighted device is considered lost.
57 | /* @VisibleForTesting */ static final int SCAN_LOST_CYCLES = 4;
58 |
59 | // Constants for Scan Cycle
60 | // Low Power: 2.5 minute period with 1.5 seconds active (1% duty cycle)
61 | /* @VisibleForTesting */ static final int LOW_POWER_IDLE_MILLIS = 148500;
62 | /* @VisibleForTesting */ static final int LOW_POWER_ACTIVE_MILLIS = 1500;
63 |
64 | // Balanced: 15 second period with 1.5 second active (10% duty cycle)
65 | /* @VisibleForTesting */ static final int BALANCED_IDLE_MILLIS = 13500;
66 | /* @VisibleForTesting */ static final int BALANCED_ACTIVE_MILLIS = 1500;
67 |
68 | // Low Latency: 1.67 second period with 1.5 seconds active (90% duty cycle)
69 | /* @VisibleForTesting */ static final int LOW_LATENCY_IDLE_MILLIS = 167;
70 | /* @VisibleForTesting */ static final int LOW_LATENCY_ACTIVE_MILLIS = 1500;
71 | // Alarm Scan variables
72 | private final Clock clock;
73 | private final AlarmManager alarmManager;
74 | private final PendingIntent alarmIntent;
75 | // Milliseconds to wait before considering a device lost. If set to a negative number
76 | // SCAN_LOST_CYCLES is used to determine when to inform clients about lost events.
77 | protected long scanLostOverrideMillis = -1;
78 | // Default Scan Constants = Balanced
79 | private int scanIdleMillis = BALANCED_IDLE_MILLIS;
80 | private int scanActiveMillis = BALANCED_ACTIVE_MILLIS;
81 | // Override values for scan window
82 | private int overrideScanActiveMillis = -1;
83 | private int overrideScanIdleMillis;
84 |
85 | protected BluetoothLeScannerCompat(Clock clock, AlarmManager alarmManager, PendingIntent alarmIntent) {
86 | this.clock = clock;
87 | this.alarmManager = alarmManager;
88 | this.alarmIntent = alarmIntent;
89 | }
90 |
91 | protected static boolean matchesAnyFilter(List filters, ScanResult result) {
92 | if (filters == null || filters.isEmpty()) {
93 | return true;
94 | }
95 | for (ScanFilter filter : filters) {
96 | if (filter.matches(result)) {
97 | return true;
98 | }
99 | }
100 | return false;
101 | }
102 |
103 | /**
104 | * Start Bluetooth LE scan with default parameters and no filters.
105 | * The scan results will be delivered through {@code callback}.
106 | *
107 | * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
108 | *
109 | * @param callback Callback used to deliver scan results.
110 | * @throws IllegalArgumentException If {@code callback} is null.
111 | */
112 | public void startScan(final ScanCallback callback) {
113 | if (callback == null) {
114 | throw new IllegalArgumentException("callback is null");
115 | }
116 | this.startScan(new ArrayList(), new ScanSettings.Builder().build(), callback);
117 | }
118 |
119 | /**
120 | * Starts a scan for Bluetooth LE devices, looking for device advertisements that match the
121 | * given filters. Attempts to start scans more than once with the same {@link ScanCallback} instance
122 | * will fail and return {@code false}.
123 | *
124 | * Due to the resource limitations in BLE chipset, an app cannot add more than N(real number
125 | * TBD) filters.
126 | *
127 | * Once the controller storage reaches its limitation on scan filters, the framework will try to
128 | * merge the existing scan filters and set the merged filters to chipset. The framework will
129 | * need to keep a copy of the original scan filters to make sure each app gets only its desired
130 | * results.
131 | *
132 | * The callback will be invoked when LE scan finds a matching BLE advertising packet. A BLE
133 | * advertising record is considered matching the filters if it matches any of the
134 | * BluetoothLeScanFilter in the list.
135 | *
136 | * Results of the scan are reported using the {@link ScanCallback#onScanResult(int, ScanResult)} callback.
137 | *
138 | * Requires BLUETOOTH_ADMIN permission.
139 | *
140 | *
141 | * @return true if the scan starts successfully, false otherwise.
142 | */
143 | public abstract boolean startScan(List filters, ScanSettings settings,
144 | ScanCallback callback);
145 |
146 | /**
147 | * Stops an ongoing Bluetooth LE device scan.
148 | *
149 | * Requires BLUETOOTH_ADMIN permission.
150 | *
151 | *
152 | * @param callback
153 | */
154 | public abstract void stopScan(ScanCallback callback);
155 |
156 | /**
157 | * Request matching records in the scanner's list.
158 | *
159 | * @param filters Filters which will apply
160 | * @return The list of matching {@link ScanResult}
161 | */
162 | public List getMatchingRecords(List filters) {
163 | ArrayList results = new ArrayList();
164 | for (Object res : getRecentScanResults().toArray()) {
165 | if (matchesAnyFilter(filters, (ScanResult) res)) {
166 | results.add((ScanResult) res);
167 | }
168 | }
169 | return results;
170 | }
171 |
172 | /**
173 | * Sets the Bluetooth LE scan cycle overriding values set on individual scans from
174 | * {@link ScanSettings}.
175 | *
176 | * This is an extension of the "L" Platform API.
177 | *
178 | *
179 | * @param scanMillis duration in milliseconds for the scan to be active, or -1 to remove, 0 to
180 | * pause. Ignored by hardware and truncated batch scanners.
181 | * @param idleMillis duration in milliseconds for the scan to be idle. Ignored by hardware
182 | * and truncated batch scanners.
183 | * @param serialScanDurationMillis duration in milliseconds of the on-demand serial scan
184 | * launched opportunistically by the truncated batch mode scanner. Ignored by
185 | * non-truncated scanners.
186 | */
187 | public void setCustomScanTiming(
188 | int scanMillis, int idleMillis, long serialScanDurationMillis) {
189 | overrideScanActiveMillis = scanMillis;
190 | overrideScanIdleMillis = idleMillis;
191 | // reset scanner so it picks up new scan window values
192 | updateRepeatingAlarm();
193 | }
194 |
195 | /**
196 | * Sets the delay after which a device will be marked as lost if it hasn't been sighted
197 | * within the given time. Set to a negative value to allow default behaviour.
198 | */
199 | public void setScanLostOverride(long lostOverrideMillis) {
200 | scanLostOverrideMillis = lostOverrideMillis;
201 | }
202 |
203 | /**
204 | * Compute the timestamp in the past which is the earliest that a sighting can have been
205 | * seen; sightings last seen before this timestamp will be deemed to be too old.
206 | * Then the Sandmen come.
207 | *
208 | * @VisibleForTesting
209 | */
210 | long getLostTimestampMillis() {
211 | if (scanLostOverrideMillis >= 0) {
212 | return getClock().currentTimeMillis() - scanLostOverrideMillis;
213 | }
214 | return getClock().currentTimeMillis() - (SCAN_LOST_CYCLES * getScanCycleMillis());
215 | }
216 |
217 | /**
218 | * Returns the length of a single scan cycle, comprising both active and idle time.
219 | *
220 | * @VisibleForTesting
221 | */
222 | long getScanCycleMillis() {
223 | return getScanActiveMillis() + getScanIdleMillis();
224 | }
225 |
226 | /**
227 | * Get the current active ble scan time that has been set
228 | *
229 | * @VisibleForTesting
230 | */
231 | int getScanActiveMillis() {
232 | return (overrideScanActiveMillis != -1) ? overrideScanActiveMillis : scanActiveMillis;
233 | }
234 |
235 | /**
236 | * Get the current idle ble scan time that has been set
237 | *
238 | * @VisibleForTesting
239 | */
240 | int getScanIdleMillis() {
241 | return (overrideScanActiveMillis != -1) ? overrideScanIdleMillis : scanIdleMillis;
242 | }
243 |
244 | /**
245 | * Update the repeating alarm wake-up based on the period defined for the scanner If there are
246 | * no clients, or a batch scan running, it will cancel the alarm.
247 | */
248 | protected void updateRepeatingAlarm() {
249 | // Apply Scan Mode (Cycle Parameters)
250 | setScanMode(getMaxPriorityScanMode());
251 | if (!hasClients()) {
252 | // No listeners. Remove the repeating alarm, if there is one.
253 | getAlarmManager().cancel(getAlarmIntent());
254 | Logger.logInfo("Scan : No clients left, canceling alarm.");
255 | } else {
256 | long alarmIntervalMillis = getScanIdleMillis();
257 | // Specifies an alarm at the scanPeriod, starting immediately.
258 | if (Build.VERSION.SDK_INT > 22) {
259 | getAlarmManager().setExact(
260 | AlarmManager.RTC_WAKEUP,
261 | System.currentTimeMillis() + alarmIntervalMillis,
262 | getAlarmIntent()
263 | );
264 | } else if (Build.VERSION.SDK_INT > 18) {
265 | getAlarmManager().setExact(
266 | AlarmManager.RTC_WAKEUP,
267 | System.currentTimeMillis() + alarmIntervalMillis,
268 | getAlarmIntent()
269 | );
270 | } else {
271 | getAlarmManager().set(
272 | AlarmManager.RTC_WAKEUP,
273 | System.currentTimeMillis() + alarmIntervalMillis,
274 | getAlarmIntent()
275 | );
276 | }
277 | }
278 | }
279 |
280 | /**
281 | * Sets parameters for the various scan modes
282 | *
283 | * @param scanMode the ScanMode in BluetoothLeScanner Settings
284 | */
285 | protected void setScanMode(int scanMode) {
286 | switch (scanMode) {
287 | case ScanSettings.SCAN_MODE_LOW_LATENCY:
288 | scanIdleMillis = LOW_LATENCY_IDLE_MILLIS;
289 | scanActiveMillis = LOW_LATENCY_ACTIVE_MILLIS;
290 | break;
291 | case ScanSettings.SCAN_MODE_LOW_POWER:
292 | scanIdleMillis = LOW_POWER_IDLE_MILLIS;
293 | scanActiveMillis = LOW_POWER_ACTIVE_MILLIS;
294 | break;
295 |
296 | // Fall through and be balanced when there's nothing saying not to.
297 | default:
298 | case ScanSettings.SCAN_MODE_BALANCED:
299 | scanIdleMillis = BALANCED_IDLE_MILLIS;
300 | scanActiveMillis = BALANCED_ACTIVE_MILLIS;
301 | break;
302 | }
303 | }
304 |
305 | protected int getScanModePriority(int mode) {
306 | switch (mode) {
307 | case ScanSettings.SCAN_MODE_LOW_LATENCY:
308 | case ScanSettings.SCAN_MODE_BALANCED:
309 | case ScanSettings.SCAN_MODE_LOW_POWER:
310 | return mode;
311 | default:
312 | Logger.logError("Unknown scan mode " + mode);
313 | return 0;
314 | }
315 | }
316 |
317 | protected Clock getClock() {
318 | return clock;
319 | }
320 |
321 | protected AlarmManager getAlarmManager() {
322 | return alarmManager;
323 | }
324 |
325 | protected PendingIntent getAlarmIntent() {
326 | return alarmIntent;
327 | }
328 |
329 | protected abstract int getMaxPriorityScanMode();
330 |
331 | protected abstract boolean hasClients();
332 |
333 | protected abstract void onNewScanCycle();
334 |
335 | protected abstract Collection getRecentScanResults();
336 |
337 | }
338 |
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/support/ble/BluetoothLeScannerCompatProvider.java:
--------------------------------------------------------------------------------
1 | package com.reelyactive.blesdk.support.ble;/*
2 | * Copyright 2014 Google Inc. All rights reserved.
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 | import android.annotation.TargetApi;
18 | import android.app.AlarmManager;
19 | import android.bluetooth.BluetoothAdapter;
20 | import android.bluetooth.BluetoothManager;
21 | import android.content.Context;
22 | import android.os.Build;
23 | import android.support.annotation.Nullable;
24 |
25 | /**
26 | * A compatibility layer for low-energy bluetooth, providing access to an implementation of
27 | * the {@link BluetoothLeScannerCompat} which will use the Android "L" APIs if they are present,
28 | * which can leverage the newest BLE hardware; or if running on an older version of the OS,
29 | * this provider falls back to providing a CPU-bound BLE scanner which has the same feature set.
30 | *
31 | * A {@link BluetoothLeScannerCompat} allows the application to register for callback events for
32 | * advertising packets broadcast from Bluetooth LE devices.
33 | */
34 | public class BluetoothLeScannerCompatProvider {
35 |
36 | private static BluetoothLeScannerCompat scannerInstance;
37 |
38 | private BluetoothLeScannerCompatProvider() {
39 | }
40 |
41 | /**
42 | * Creates a {@link BluetoothLeScannerCompat} that works with the version of Android being used.
43 | *
44 | * For Android versions between Jelly Bean MR2 and "L", a compatibility layer will be used to
45 | * provide functionality that will be available in "L". For Android versions "L" and later, the
46 | * native functionality will be used.
47 | *
48 | * @param context The Android context of the application.
49 | * @return A {@link BluetoothLeScannerCompat} best fitting the version of Android. If no scanner
50 | * can be found, null will be returned.
51 | */
52 | @Nullable
53 | public static synchronized BluetoothLeScannerCompat getBluetoothLeScannerCompat(Context context) {
54 | return getBluetoothLeScannerCompat(context, true);
55 | }
56 |
57 | /**
58 | * Creates a {@link BluetoothLeScannerCompat}, providing either a compatibility layer or
59 | * access to native functionality available in "L".
60 | *
61 | *
62 | * @param context The Android context of the application.
63 | * @param canUseNativeApi Whether or not to enable "L" hardware support, if available.
64 | * @return A {@link BluetoothLeScannerCompat}. If no scanner can be found, null will be returned.
65 | */
66 | @Nullable
67 | public static synchronized BluetoothLeScannerCompat getBluetoothLeScannerCompat(
68 | Context context, boolean canUseNativeApi) {
69 | if (scannerInstance == null) {
70 |
71 | AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
72 |
73 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
74 | BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
75 | if (bluetoothManager != null) {
76 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
77 | && canUseNativeApi
78 | && areHardwareFeaturesSupported(bluetoothManager)) {
79 | scannerInstance = new LBluetoothLeScannerCompat(context, bluetoothManager, alarmManager);
80 | } else if (alarmManager != null
81 | && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
82 | scannerInstance = new JbBluetoothLeScannerCompat(context, bluetoothManager, alarmManager);
83 | }
84 | }
85 | }
86 | if (scannerInstance == null) {
87 | scannerInstance = new NoBluetoothLeScannerCompat(context, alarmManager);
88 | }
89 | }
90 | return scannerInstance;
91 | }
92 |
93 | /**
94 | * Check that the hardware has indeed the features used by the L-specific implementation.
95 | */
96 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
97 | private static boolean areHardwareFeaturesSupported(BluetoothManager bluetoothManager) {
98 | BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
99 | return bluetoothAdapter != null
100 | && bluetoothAdapter.isOffloadedFilteringSupported()
101 | && bluetoothAdapter.isOffloadedScanBatchingSupported();
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/support/ble/BluetoothUuid.java:
--------------------------------------------------------------------------------
1 | package com.reelyactive.blesdk.support.ble;/*
2 | * Copyright 2014 Google Inc. All rights reserved.
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 | // THIS IS MODIFIED COPY OF THE "L" PLATFORM CLASS. BE CAREFUL ABOUT EDITS.
18 | // THIS CODE SHOULD FOLLOW ANDROID STYLE.
19 |
20 | import android.os.ParcelUuid;
21 |
22 | import java.nio.ByteBuffer;
23 | import java.nio.ByteOrder;
24 | import java.util.Arrays;
25 | import java.util.HashSet;
26 | import java.util.UUID;
27 |
28 | /**
29 | * Static helper methods and constants to decode the ParcelUuid of remote devices.
30 | *
31 | * @hide
32 | */
33 | public final class BluetoothUuid {
34 | /*
35 | * See Bluetooth Assigned Numbers document - SDP section, to get the values of UUIDs for the
36 | * various services. The following 128 bit values are calculated as: uuid * 2^96 + BASE_UUID
37 | */
38 | public static final ParcelUuid AudioSink =
39 | ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB");
40 | public static final ParcelUuid AudioSource =
41 | ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB");
42 | public static final ParcelUuid AdvAudioDist =
43 | ParcelUuid.fromString("0000110D-0000-1000-8000-00805F9B34FB");
44 | public static final ParcelUuid HSP =
45 | ParcelUuid.fromString("00001108-0000-1000-8000-00805F9B34FB");
46 | public static final ParcelUuid HSP_AG =
47 | ParcelUuid.fromString("00001112-0000-1000-8000-00805F9B34FB");
48 | public static final ParcelUuid Handsfree =
49 | ParcelUuid.fromString("0000111E-0000-1000-8000-00805F9B34FB");
50 | public static final ParcelUuid Handsfree_AG =
51 | ParcelUuid.fromString("0000111F-0000-1000-8000-00805F9B34FB");
52 | public static final ParcelUuid AvrcpController =
53 | ParcelUuid.fromString("0000110E-0000-1000-8000-00805F9B34FB");
54 | public static final ParcelUuid AvrcpTarget =
55 | ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB");
56 | public static final ParcelUuid ObexObjectPush =
57 | ParcelUuid.fromString("00001105-0000-1000-8000-00805f9b34fb");
58 | public static final ParcelUuid Hid =
59 | ParcelUuid.fromString("00001124-0000-1000-8000-00805f9b34fb");
60 | public static final ParcelUuid Hogp =
61 | ParcelUuid.fromString("00001812-0000-1000-8000-00805f9b34fb");
62 | public static final ParcelUuid PANU =
63 | ParcelUuid.fromString("00001115-0000-1000-8000-00805F9B34FB");
64 | public static final ParcelUuid NAP =
65 | ParcelUuid.fromString("00001116-0000-1000-8000-00805F9B34FB");
66 | public static final ParcelUuid BNEP =
67 | ParcelUuid.fromString("0000000f-0000-1000-8000-00805F9B34FB");
68 | public static final ParcelUuid PBAP_PSE =
69 | ParcelUuid.fromString("0000112f-0000-1000-8000-00805F9B34FB");
70 | public static final ParcelUuid MAP =
71 | ParcelUuid.fromString("00001134-0000-1000-8000-00805F9B34FB");
72 | public static final ParcelUuid MNS =
73 | ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB");
74 | public static final ParcelUuid MAS =
75 | ParcelUuid.fromString("00001132-0000-1000-8000-00805F9B34FB");
76 | public static final ParcelUuid BASE_UUID =
77 | ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB");
78 | /** Length of bytes for 16 bit UUID */
79 | public static final int UUID_BYTES_16_BIT = 2;
80 | /** Length of bytes for 32 bit UUID */
81 | public static final int UUID_BYTES_32_BIT = 4;
82 | /** Length of bytes for 128 bit UUID */
83 | public static final int UUID_BYTES_128_BIT = 16;
84 | public static final ParcelUuid[] RESERVED_UUIDS = {
85 | AudioSink, AudioSource, AdvAudioDist, HSP, Handsfree, AvrcpController, AvrcpTarget,
86 | ObexObjectPush, PANU, NAP, MAP, MNS, MAS };
87 |
88 | public static boolean isAudioSource(ParcelUuid uuid) {
89 | return uuid.equals(AudioSource);
90 | }
91 |
92 | public static boolean isAudioSink(ParcelUuid uuid) {
93 | return uuid.equals(AudioSink);
94 | }
95 |
96 | public static boolean isAdvAudioDist(ParcelUuid uuid) {
97 | return uuid.equals(AdvAudioDist);
98 | }
99 |
100 | public static boolean isHandsfree(ParcelUuid uuid) {
101 | return uuid.equals(Handsfree);
102 | }
103 |
104 | public static boolean isHeadset(ParcelUuid uuid) {
105 | return uuid.equals(HSP);
106 | }
107 |
108 | public static boolean isAvrcpController(ParcelUuid uuid) {
109 | return uuid.equals(AvrcpController);
110 | }
111 |
112 | public static boolean isAvrcpTarget(ParcelUuid uuid) {
113 | return uuid.equals(AvrcpTarget);
114 | }
115 |
116 | public static boolean isInputDevice(ParcelUuid uuid) {
117 | return uuid.equals(Hid);
118 | }
119 |
120 | public static boolean isPanu(ParcelUuid uuid) {
121 | return uuid.equals(PANU);
122 | }
123 |
124 | public static boolean isNap(ParcelUuid uuid) {
125 | return uuid.equals(NAP);
126 | }
127 |
128 | public static boolean isBnep(ParcelUuid uuid) {
129 | return uuid.equals(BNEP);
130 | }
131 |
132 | public static boolean isMap(ParcelUuid uuid) {
133 | return uuid.equals(MAP);
134 | }
135 |
136 | public static boolean isMns(ParcelUuid uuid) {
137 | return uuid.equals(MNS);
138 | }
139 |
140 | public static boolean isMas(ParcelUuid uuid) {
141 | return uuid.equals(MAS);
142 | }
143 |
144 | /**
145 | * Returns true if ParcelUuid is present in uuidArray
146 | *
147 | * @param uuidArray - Array of ParcelUuids
148 | * @param uuid
149 | */
150 | public static boolean isUuidPresent(ParcelUuid[] uuidArray, ParcelUuid uuid) {
151 | if ((uuidArray == null || uuidArray.length == 0) && uuid == null) {
152 | return true;
153 | }
154 | if (uuidArray == null) {
155 | return false;
156 | }
157 | for (ParcelUuid element : uuidArray) {
158 | if (element.equals(uuid)) {
159 | return true;
160 | }
161 | }
162 | return false;
163 | }
164 |
165 | /**
166 | * Returns true if there any common ParcelUuids in uuidA and uuidB.
167 | *
168 | * @param uuidA - List of ParcelUuids
169 | * @param uuidB - List of ParcelUuids
170 | */
171 | public static boolean containsAnyUuid(ParcelUuid[] uuidA, ParcelUuid[] uuidB) {
172 | if (uuidA == null && uuidB == null) {
173 | return true;
174 | }
175 | if (uuidA == null) {
176 | return uuidB.length == 0 ? true : false;
177 | }
178 | if (uuidB == null) {
179 | return uuidA.length == 0 ? true : false;
180 | }
181 | HashSet uuidSet = new HashSet(Arrays.asList(uuidA));
182 | for (ParcelUuid uuid : uuidB) {
183 | if (uuidSet.contains(uuid)) {
184 | return true;
185 | }
186 | }
187 | return false;
188 | }
189 |
190 | /**
191 | * Returns true if all the ParcelUuids in ParcelUuidB are present in ParcelUuidA
192 | *
193 | * @param uuidA - Array of ParcelUuidsA
194 | * @param uuidB - Array of ParcelUuidsB
195 | */
196 | public static boolean containsAllUuids(ParcelUuid[] uuidA, ParcelUuid[] uuidB) {
197 | if (uuidA == null && uuidB == null) {
198 | return true;
199 | }
200 | if (uuidA == null) {
201 | return uuidB.length == 0 ? true : false;
202 | }
203 | if (uuidB == null) {
204 | return true;
205 | }
206 | HashSet uuidSet = new HashSet(Arrays.asList(uuidA));
207 | for (ParcelUuid uuid : uuidB) {
208 | if (!uuidSet.contains(uuid)) {
209 | return false;
210 | }
211 | }
212 | return true;
213 | }
214 |
215 | /**
216 | * Extract the Service Identifier or the actual uuid from the Parcel Uuid. For example, if
217 | * 0000110B-0000-1000-8000-00805F9B34FB is the parcel Uuid, this function will return 110B
218 | *
219 | * @param parcelUuid
220 | * @return the service identifier.
221 | */
222 | public static int getServiceIdentifierFromParcelUuid(ParcelUuid parcelUuid) {
223 | UUID uuid = parcelUuid.getUuid();
224 | long value = (uuid.getMostSignificantBits() & 0x0000FFFF00000000L) >>> 32;
225 | return (int) value;
226 | }
227 |
228 | /**
229 | * Parse UUID from bytes. The {@code uuidBytes} can represent a 16-bit, 32-bit or 128-bit UUID,
230 | * but the returned UUID is always in 128-bit format. Note UUID is little endian in Bluetooth.
231 | *
232 | * @param uuidBytes Byte representation of uuid.
233 | * @return {@link android.os.ParcelUuid} parsed from bytes.
234 | * @throws IllegalArgumentException If the {@code uuidBytes} cannot be parsed.
235 | */
236 | public static ParcelUuid parseUuidFrom(byte[] uuidBytes) {
237 | if (uuidBytes == null) {
238 | throw new IllegalArgumentException("uuidBytes cannot be null");
239 | }
240 | int length = uuidBytes.length;
241 | if (length != UUID_BYTES_16_BIT && length != UUID_BYTES_32_BIT &&
242 | length != UUID_BYTES_128_BIT) {
243 | throw new IllegalArgumentException("uuidBytes length invalid - " + length);
244 | }
245 | // Construct a 128 bit UUID.
246 | if (length == UUID_BYTES_128_BIT) {
247 | ByteBuffer buf = ByteBuffer.wrap(uuidBytes).order(ByteOrder.LITTLE_ENDIAN);
248 | long msb = buf.getLong(8);
249 | long lsb = buf.getLong(0);
250 | return new ParcelUuid(new UUID(msb, lsb));
251 | }
252 | // For 16 bit and 32 bit UUID we need to convert them to 128 bit value.
253 | // 128_bit_value = uuid * 2^96 + BASE_UUID
254 | long shortUuid;
255 | if (length == UUID_BYTES_16_BIT) {
256 | shortUuid = uuidBytes[0] & 0xFF;
257 | shortUuid += (uuidBytes[1] & 0xFF) << 8;
258 | } else {
259 | shortUuid = uuidBytes[0] & 0xFF;
260 | shortUuid += (uuidBytes[1] & 0xFF) << 8;
261 | shortUuid += (uuidBytes[2] & 0xFF) << 16;
262 | shortUuid += (uuidBytes[3] & 0xFF) << 24;
263 | }
264 | long msb = BASE_UUID.getUuid().getMostSignificantBits() + (shortUuid << 32);
265 | long lsb = BASE_UUID.getUuid().getLeastSignificantBits();
266 | return new ParcelUuid(new UUID(msb, lsb));
267 | }
268 |
269 | /**
270 | * Check whether the given parcelUuid can be converted to 16 bit bluetooth uuid.
271 | *
272 | * @param parcelUuid
273 | * @return true if the parcelUuid can be converted to 16 bit uuid, false otherwise.
274 | */
275 | public static boolean is16BitUuid(ParcelUuid parcelUuid) {
276 | UUID uuid = parcelUuid.getUuid();
277 | if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) {
278 | return false;
279 | }
280 | return ((uuid.getMostSignificantBits() & 0xFFFF0000FFFFFFFFL) == 0x1000L);
281 | }
282 |
283 | /**
284 | * Check whether the given parcelUuid can be converted to 32 bit bluetooth uuid.
285 | *
286 | * @param parcelUuid
287 | * @return true if the parcelUuid can be converted to 32 bit uuid, false otherwise.
288 | */
289 | public static boolean is32BitUuid(ParcelUuid parcelUuid) {
290 | UUID uuid = parcelUuid.getUuid();
291 | if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) {
292 | return false;
293 | }
294 | if (is16BitUuid(parcelUuid)) {
295 | return false;
296 | }
297 | return ((uuid.getMostSignificantBits() & 0xFFFFFFFFL) == 0x1000L);
298 | }
299 | }
300 |
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/support/ble/JbBluetoothLeScannerCompat.java:
--------------------------------------------------------------------------------
1 | package com.reelyactive.blesdk.support.ble;/*
2 | * Copyright 2014 Google Inc. All rights reserved.
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 | import android.annotation.TargetApi;
18 | import android.app.AlarmManager;
19 | import android.app.PendingIntent;
20 | import android.bluetooth.BluetoothAdapter;
21 | import android.bluetooth.BluetoothDevice;
22 | import android.bluetooth.BluetoothManager;
23 | import android.content.Context;
24 | import android.content.Intent;
25 | import android.os.Build;
26 | import android.os.Handler;
27 | import android.os.Looper;
28 |
29 | import com.reelyactive.blesdk.support.ble.util.Clock;
30 | import com.reelyactive.blesdk.support.ble.util.Logger;
31 | import com.reelyactive.blesdk.support.ble.util.SystemClock;
32 |
33 | import java.util.Collection;
34 | import java.util.HashSet;
35 | import java.util.Iterator;
36 | import java.util.List;
37 | import java.util.Map;
38 | import java.util.Map.Entry;
39 | import java.util.Set;
40 | import java.util.concurrent.ConcurrentHashMap;
41 | import java.util.concurrent.TimeUnit;
42 |
43 | /**
44 | * Implements Bluetooth LE scan related API on top of
45 | * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} and later.
46 | *
47 | * This class delivers a callback on found, updated, and lost for devices matching a
48 | * {@link ScanFilter} filter during scan cycles.
49 | *
50 | * A scan cycle comprises a period when the Bluetooth Adapter is active and a period when the
51 | * Bluetooth adapter is idle. Having an idle period is energy efficient for long lived scans.
52 | *
53 | * This class can be accessed on multiple threads:
54 | *
55 | * - main thread (user) can call any of the BluetoothLeScanner APIs
56 | *
- IntentService worker thread can call {@link #onNewScanCycle()}
57 | *
- AIDL binder thread can call {@link #leScanCallback.onLeScan()}
58 | *
59 | *
60 | * @see BLE Glossary
61 | */
62 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
63 | class JbBluetoothLeScannerCompat extends BluetoothLeScannerCompat {
64 |
65 | // Map of BD_ADDR->com.reelyactive.blesdk.support.ble.ScanResult for replay to new registrations.
66 | // Entries are evicted after SCAN_LOST_CYCLES cycles.
67 | /* @VisibleForTesting */ final Map recentScanResults = new ConcurrentHashMap<>();
68 | /* @VisibleForTesting */ final Map serialClients = new ConcurrentHashMap<>();
69 | private final BluetoothAdapter bluetoothAdapter;
70 | private BluetoothCrashResolver crashResolver;
71 | private Handler mainHandler;
72 | /**
73 | * The Bluetooth LE callback which will be registered with the OS,
74 | * to be fired on device discovery.
75 | */
76 | private final BluetoothAdapter.LeScanCallback leScanCallback =
77 | new BluetoothAdapter.LeScanCallback() {
78 | /**
79 | * Callback method called from the OS on each BLE device sighting.
80 | * This method is invoked on the AIDL handler thread, so all methods
81 | * called here must be synchronized.
82 | *
83 | * @param device The device discovered
84 | * @param rssi The signal strength in dBm it was received at
85 | * @param scanRecordBytes The raw byte payload buffer
86 | */
87 | @Override
88 | public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecordBytes) {
89 | long currentTimeInNanos = TimeUnit.MILLISECONDS.toNanos(getClock().currentTimeMillis());
90 | ScanResult result = new ScanResult(device, ScanRecord.parseFromBytes(scanRecordBytes), rssi,
91 | currentTimeInNanos);
92 | onScanResult(device.getAddress(), result);
93 | if (crashResolver != null)
94 | crashResolver.notifyScannedDevice(device, this);
95 | }
96 | };
97 |
98 | /**
99 | * Package constructor, called from {@link BluetoothLeScannerCompatProvider}.
100 | */
101 | JbBluetoothLeScannerCompat(
102 | Context context, BluetoothManager manager, AlarmManager alarmManager) {
103 | this(manager, alarmManager, new SystemClock(),
104 | PendingIntent.getBroadcast(context, 0 /* requestCode */,
105 | new Intent(context, ScanWakefulBroadcastReceiver.class).putExtra(ScanWakefulService.EXTRA_USE_LOLLIPOP_API, false), 0 /* flags */));
106 | this.crashResolver = new BluetoothCrashResolver(context);
107 | this.crashResolver.start();
108 | this.mainHandler = new Handler(Looper.getMainLooper());
109 | }
110 |
111 | /**
112 | * Testing constructor for the scanner.
113 | *
114 | * @VisibleForTesting
115 | */
116 | JbBluetoothLeScannerCompat(BluetoothManager manager, AlarmManager alarmManager,
117 | Clock clock, PendingIntent alarmIntent) {
118 | super(clock, alarmManager, alarmIntent);
119 | Logger.logDebug("BLE 'JB' hardware access layer activated");
120 | this.bluetoothAdapter = manager.getAdapter();
121 | }
122 |
123 | /**
124 | * The entry point blockingScanCycle executes a BLE Scan cycle and is called from the
125 | * com.reelyactive.blesdk.support.ble.ScanWakefulService. When this method ends, the service will signal the ScanWakefulBroadcast
126 | * receiver to release its wakelock and the phone will enter a sleep phase for the remainder of
127 | * the BLE scan cycle.
128 | *
129 | * This is called on the IntentService handler thread and hence is synchronized.
130 | *
131 | * Suppresses the experimental 'wait not in loop' warning because we don't mind exiting early.
132 | * Suppresses deprecation because this is the compatibility support.
133 | */
134 | @SuppressWarnings({"WaitNotInLoop", "deprecation"})
135 | @Override
136 | protected synchronized void onNewScanCycle() {
137 | Logger.logDebug("Starting BLE Active Scan Cycle.");
138 | int activeMillis = getScanActiveMillis();
139 | if (activeMillis > 0) {
140 | try {
141 | if (bluetoothAdapter != null) {
142 | bluetoothAdapter.startLeScan(leScanCallback);
143 | }
144 | } catch (IllegalStateException e) {
145 | Logger.logError("Failed to start the scan", e);
146 | }
147 |
148 | // Sleep for the duration of the scan. No wakeups are expected, but catch is required.
149 | try {
150 | wait(activeMillis);
151 | } catch (InterruptedException e) {
152 | Logger.logError("Exception in ScanCycle Sleep", e);
153 | } finally {
154 | try {
155 | if (bluetoothAdapter != null) {
156 | bluetoothAdapter.stopLeScan(leScanCallback);
157 | }
158 | } catch (NullPointerException e) {
159 | // An NPE is thrown if Bluetooth has been reset since this blocking scan began.
160 | Logger.logDebug("NPE thrown in BlockingScanCycle");
161 | } catch (IllegalStateException e) {
162 | Logger.logError("Failed to stop the scan", e);
163 | }
164 | // Active BLE scan ends
165 | // Execute cycle complete to 1) detect lost devices
166 | new Handler(Looper.getMainLooper()).post(new Runnable() {
167 | @Override
168 | public void run() {
169 | onScanCycleComplete();
170 | callbackCycleCompleted();
171 | }
172 | });
173 | }
174 | updateRepeatingAlarm();
175 | }
176 | Logger.logDebug("Stopping BLE Active Scan Cycle.");
177 | }
178 |
179 | private void callbackLostLeScanClients(String address, ScanResult result) {
180 | for (ScanClient client : serialClients.values()) {
181 | int wantAny = client.settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
182 | int wantLost = client.settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_MATCH_LOST;
183 |
184 | if (client.addressesSeen.remove(address) && (wantAny | wantLost) != 0) {
185 |
186 | // Catch any exceptions and log them but continue processing other scan results.
187 | try {
188 | client.callback.onScanResult(ScanSettings.CALLBACK_TYPE_MATCH_LOST, result);
189 | } catch (Exception e) {
190 | Logger.logError("Failure while sending 'lost' scan result to listener", e);
191 | }
192 | }
193 | }
194 | }
195 |
196 | private void callbackCycleCompleted() {
197 | for (ScanClient client : serialClients.values()) {
198 | client.callback.onScanCycleCompleted();
199 | }
200 | }
201 |
202 | /**
203 | * Process a single scan result, sending it directly
204 | * to any active listeners who want to know.
205 | */
206 | void onScanResult(final String address, final ScanResult result) {
207 | mainHandler.post(new Runnable() {
208 | @Override
209 | public void run() {
210 | callbackLeScanClients(address, result);
211 | }
212 | });
213 | }
214 |
215 | /**
216 | * Distribute each scan record to registered clients. When a "found" event occurs record the
217 | * address in the client filter so we can later send the "lost" event to that same client.
218 | *
219 | * This method will be called by the AIDL handler thread from onLeScan.
220 | */
221 | private synchronized void callbackLeScanClients(String address, ScanResult result) {
222 | recentScanResults.put(address, result);
223 | for (ScanClient client : serialClients.values()) {
224 | if (matchesAnyFilter(client.filtersList, result)) {
225 | boolean seenItBefore = client.addressesSeen.contains(address);
226 | int clientFlags = client.settings.getCallbackType();
227 | int firstMatchBit = clientFlags & ScanSettings.CALLBACK_TYPE_FIRST_MATCH;
228 | int allMatchesBit = clientFlags & ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
229 |
230 | // Catch any exceptions and log them but continue processing other listeners.
231 | if ((firstMatchBit | allMatchesBit) != 0) {
232 | try {
233 | if (!seenItBefore) {
234 | client.callback.onScanResult(ScanSettings.CALLBACK_TYPE_FIRST_MATCH, result);
235 | } else if (allMatchesBit != 0) {
236 | client.callback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result);
237 | }
238 | } catch (Exception e) {
239 | Logger.logError("Failure while handling scan result", e);
240 | }
241 | }
242 | if (!seenItBefore) {
243 | client.addressesSeen.add(address);
244 | }
245 | }
246 | }
247 | }
248 |
249 | @Override
250 | public synchronized boolean startScan(List filterList, ScanSettings settings,
251 | ScanCallback callback) {
252 | ScanClient client = new ScanClient(settings, filterList, callback);
253 | serialClients.put(callback, client);
254 |
255 | int clientFlags = client.settings.getCallbackType();
256 | int firstMatchBit = clientFlags & ScanSettings.CALLBACK_TYPE_FIRST_MATCH;
257 | int allMatchesBit = clientFlags & ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
258 |
259 | // Process new registrations by immediately invoking the "found" callback
260 | // with all previously sighted devices.
261 | if ((firstMatchBit | allMatchesBit) != 0) {
262 | for (Entry entry : recentScanResults.entrySet()) {
263 | String address = entry.getKey();
264 | ScanResult savedResult = entry.getValue();
265 | if (matchesAnyFilter(filterList, savedResult)) {
266 |
267 | // Catch any exceptions and log them but continue processing other scan results.
268 | try {
269 | client.callback.onScanResult(ScanSettings.CALLBACK_TYPE_FIRST_MATCH, savedResult);
270 | } catch (Exception e) {
271 | Logger.logError("Failure while handling scan result for new listener", e);
272 | }
273 | client.addressesSeen.add(address);
274 | }
275 | }
276 | }
277 | updateRepeatingAlarm();
278 | return true;
279 | }
280 |
281 | /**
282 | * Stop scanning.
283 | *
284 | * @see JbBluetoothLeScannerCompat#startScan
285 | */
286 | @Override
287 | public synchronized void stopScan(ScanCallback callback) {
288 | serialClients.remove(callback);
289 | updateRepeatingAlarm();
290 | }
291 |
292 | /**
293 | * Test for lost tags by periodically checking the found devices
294 | * for any that haven't been seen recently.
295 | */
296 | protected void onScanCycleComplete() {
297 | Iterator> iter = recentScanResults.entrySet().iterator();
298 | long lostTimestampMillis = getLostTimestampMillis();
299 |
300 | // Clear out any expired notifications from the "old sightings" record.
301 | while (iter.hasNext()) {
302 | Entry entry = iter.next();
303 | String address = entry.getKey();
304 | ScanResult savedResult = entry.getValue();
305 | if (TimeUnit.NANOSECONDS.toMillis(savedResult.getTimestampNanos()) < lostTimestampMillis) {
306 | iter.remove();
307 | callbackLostLeScanClients(address, savedResult);
308 | }
309 | }
310 | }
311 |
312 | protected int getMaxPriorityScanMode() {
313 | int maxPriority = -1;
314 |
315 | for (ScanClient scanClient : serialClients.values()) {
316 | ScanSettings settings = scanClient.settings;
317 | if (maxPriority == -1
318 | || getScanModePriority(settings.getScanMode()) > getScanModePriority(maxPriority)) {
319 | maxPriority = settings.getScanMode();
320 | }
321 | }
322 | return maxPriority;
323 | }
324 |
325 | @Override
326 | protected boolean hasClients() {
327 | return !serialClients.isEmpty();
328 | }
329 |
330 |
331 | @Override
332 | protected Collection getRecentScanResults() {
333 | return recentScanResults.values();
334 | }
335 |
336 | /**
337 | * Wraps user requests and stores the list of filters and callbacks. Also saves a set of
338 | * addresses for which any of the filters have matched in order to do lost processing.
339 | */
340 | private static class ScanClient {
341 | final List filtersList;
342 | final Set addressesSeen;
343 | final ScanCallback callback;
344 | final ScanSettings settings;
345 |
346 | ScanClient(ScanSettings settings, List filters, ScanCallback callback) {
347 | this.settings = settings;
348 | this.filtersList = filters;
349 | this.addressesSeen = new HashSet<>();
350 | this.callback = callback;
351 | }
352 | }
353 | }
354 |
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/support/ble/LBluetoothLeScannerCompat.java:
--------------------------------------------------------------------------------
1 | package com.reelyactive.blesdk.support.ble;
2 | /*
3 | * Copyright 2014 Google Inc. All rights reserved.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | import android.annotation.TargetApi;
19 | import android.app.AlarmManager;
20 | import android.app.PendingIntent;
21 | import android.bluetooth.BluetoothAdapter;
22 | import android.bluetooth.BluetoothManager;
23 | import android.content.Context;
24 | import android.content.Intent;
25 | import android.os.Build;
26 | import android.os.Handler;
27 | import android.os.Looper;
28 | import android.text.TextUtils;
29 |
30 | import com.reelyactive.blesdk.support.ble.util.Clock;
31 | import com.reelyactive.blesdk.support.ble.util.Logger;
32 | import com.reelyactive.blesdk.support.ble.util.SystemClock;
33 |
34 | import java.util.ArrayList;
35 | import java.util.Collection;
36 | import java.util.HashSet;
37 | import java.util.Iterator;
38 | import java.util.List;
39 | import java.util.Map;
40 | import java.util.Set;
41 | import java.util.concurrent.ConcurrentHashMap;
42 | import java.util.concurrent.TimeUnit;
43 |
44 | /**
45 | * Implements Bluetooth LE scan related API on top of {@link android.os.Build.VERSION_CODES#LOLLIPOP}
46 | * and later.
47 | */
48 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
49 | class LBluetoothLeScannerCompat extends BluetoothLeScannerCompat {
50 |
51 | private final Map recentScanResults = new ConcurrentHashMap<>();
52 | private final Map callbacksMap = new ConcurrentHashMap<>();
53 | private final android.bluetooth.le.BluetoothLeScanner osScanner;
54 |
55 | /**
56 | * Package-protected constructor, used by {@link BluetoothLeScannerCompatProvider}.
57 | *
58 | * Cannot be called from emulated devices that don't implement a BluetoothAdapter.
59 | */
60 | LBluetoothLeScannerCompat(Context context, BluetoothManager manager, AlarmManager alarmManager) {
61 | this(
62 | manager,
63 | alarmManager,
64 | new SystemClock(),
65 | PendingIntent.getBroadcast(
66 | context,
67 | 0 /* requestCode */,
68 | new Intent(context, ScanWakefulBroadcastReceiver.class).putExtra(ScanWakefulService.EXTRA_USE_LOLLIPOP_API, true),
69 | 0 /* flags */
70 | )
71 | );
72 | }
73 |
74 | LBluetoothLeScannerCompat(BluetoothManager manager, AlarmManager alarmManager,
75 | Clock clock, PendingIntent alarmIntent) {
76 | super(clock, alarmManager, alarmIntent);
77 | Logger.logDebug("BLE 'L' hardware access layer activated");
78 | BluetoothAdapter adapter = manager.getAdapter();
79 | if (adapter != null) {
80 | this.osScanner = adapter.getBluetoothLeScanner();
81 | } else {
82 | this.osScanner = null;
83 | }
84 | }
85 |
86 | private static android.bluetooth.le.ScanSettings toOs(ScanSettings settings) {
87 | return new android.bluetooth.le.ScanSettings.Builder()
88 | .setReportDelay(settings.getReportDelayMillis())
89 | .setScanMode(settings.getScanMode())
90 | .build();
91 | }
92 |
93 | private static List toOs(List filters) {
94 | List osFilters =
95 | new ArrayList(filters.size());
96 | for (ScanFilter filter : filters) {
97 | osFilters.add(toOs(filter));
98 | }
99 | return osFilters;
100 | }
101 |
102 | private static android.bluetooth.le.ScanFilter toOs(ScanFilter filter) {
103 | android.bluetooth.le.ScanFilter.Builder builder = new android.bluetooth.le.ScanFilter.Builder();
104 | if (!TextUtils.isEmpty(filter.getDeviceAddress())) {
105 | builder.setDeviceAddress(filter.getDeviceAddress());
106 | }
107 | if (!TextUtils.isEmpty(filter.getDeviceName())) {
108 | builder.setDeviceName(filter.getDeviceName());
109 | }
110 | if (filter.getManufacturerId() != -1 && filter.getManufacturerData() != null) {
111 | if (filter.getManufacturerDataMask() != null) {
112 | builder.setManufacturerData(filter.getManufacturerId(), filter.getManufacturerData(),
113 | filter.getManufacturerDataMask());
114 | } else {
115 | builder.setManufacturerData(filter.getManufacturerId(), filter.getManufacturerData());
116 | }
117 | }
118 | if (filter.getServiceDataUuid() != null && filter.getServiceData() != null) {
119 | if (filter.getServiceDataMask() != null) {
120 | builder.setServiceData(
121 | filter.getServiceDataUuid(), filter.getServiceData(), filter.getServiceDataMask());
122 | } else {
123 | builder.setServiceData(filter.getServiceDataUuid(), filter.getServiceData());
124 | }
125 | }
126 | if (filter.getServiceUuid() != null) {
127 | if (filter.getServiceUuidMask() != null) {
128 | builder.setServiceUuid(filter.getServiceUuid(), filter.getServiceUuidMask());
129 | } else {
130 | builder.setServiceUuid(filter.getServiceUuid());
131 | }
132 | }
133 | return builder.build();
134 | }
135 |
136 | private static List fromOs(List osResults) {
137 | List results = new ArrayList(osResults.size());
138 | for (android.bluetooth.le.ScanResult result : osResults) {
139 | results.add(fromOs(result));
140 | }
141 | return results;
142 | }
143 |
144 | private static ScanResult fromOs(android.bluetooth.le.ScanResult osResult) {
145 | return new ScanResult(
146 | osResult.getDevice(),
147 | fromOs(osResult.getScanRecord()),
148 | osResult.getRssi(),
149 | // Convert the osResult timestamp from 'nanos since boot' to 'nanos since epoch'.
150 | osResult.getTimestampNanos() + getActualBootTimeNanos());
151 | }
152 |
153 | private static long getActualBootTimeNanos() {
154 | long currentTimeNanos = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis());
155 | long elapsedRealtimeNanos = android.os.SystemClock.elapsedRealtimeNanos();
156 | return currentTimeNanos - elapsedRealtimeNanos;
157 | }
158 |
159 | private static ScanRecord fromOs(android.bluetooth.le.ScanRecord osRecord) {
160 | return ScanRecord.parseFromBytes(osRecord.getBytes());
161 | }
162 |
163 | /////////////////////////////////////////////////////////////////////////////
164 | // Conversion methods
165 |
166 | @Override
167 | public boolean startScan(List filters, ScanSettings settings, ScanCallback callback) {
168 |
169 | if (callbacksMap.containsKey(callback)) {
170 | Logger.logInfo("StartScan(): BLE 'L' hardware scan already in progress...");
171 | stopScan(callback);
172 | }
173 |
174 | android.bluetooth.le.ScanSettings osSettings = toOs(settings);
175 | ScanClient osCallback = toOs(callback, settings);
176 | List osFilters = toOs(filters);
177 |
178 | callbacksMap.put(callback, osCallback);
179 | try {
180 | Logger.logInfo("Starting BLE 'L' hardware scan ");
181 | for (ScanFilter filter : filters) {
182 | Logger.logInfo("\tFilter " + filter);
183 | }
184 | if (osScanner != null) {
185 | osScanner.startScan(osFilters, osSettings, osCallback);
186 | }
187 | updateRepeatingAlarm();
188 | return true;
189 | } catch (Exception e) {
190 | Logger.logError("Exception caught calling 'L' BluetoothLeScanner.startScan()", e);
191 | return false;
192 | }
193 | }
194 |
195 | @Override
196 | public void stopScan(ScanCallback callback) {
197 | android.bluetooth.le.ScanCallback osCallback = callbacksMap.get(callback);
198 |
199 | if (osCallback != null) {
200 | try {
201 | Logger.logInfo("Stopping BLE 'L' hardware scan");
202 | if (osScanner != null) {
203 | osScanner.stopScan(osCallback);
204 | }
205 | } catch (Exception e) {
206 | Logger.logError("Exception caught calling 'L' BluetoothLeScanner.stopScan()", e);
207 | }
208 | callbacksMap.remove(callback);
209 | updateRepeatingAlarm();
210 | }
211 | }
212 |
213 | protected int getMaxPriorityScanMode() {
214 | int maxPriority = -1;
215 |
216 | for (ScanClient scanClient : callbacksMap.values()) {
217 | ScanSettings settings = scanClient.settings;
218 | if (maxPriority == -1
219 | || getScanModePriority(settings.getScanMode()) > getScanModePriority(maxPriority)) {
220 | maxPriority = settings.getScanMode();
221 | }
222 | }
223 | return maxPriority;
224 | }
225 |
226 | @Override
227 | protected boolean hasClients() {
228 | return !callbacksMap.isEmpty();
229 | }
230 |
231 | private ScanClient toOs(final ScanCallback callback, ScanSettings settings) {
232 | return new ScanClient(callback, settings);
233 | }
234 |
235 | @Override
236 | protected void onNewScanCycle() {
237 | int activeMillis = getScanActiveMillis();
238 | if (activeMillis > 0) {
239 | synchronized (this) {
240 | try {
241 | wait(activeMillis);
242 | } catch (InterruptedException e) {
243 | Logger.logError("Exception in ScanCycle Sleep", e);
244 | }
245 | }
246 | }
247 | long lostTimestampMillis = getLostTimestampMillis();
248 | Iterator> iter = recentScanResults.entrySet().iterator();
249 | // Clear out any expired notifications from the "old sightings" record.
250 | while (iter.hasNext()) {
251 | Map.Entry entry = iter.next();
252 | final String address = entry.getKey();
253 | final ScanResult savedResult = entry.getValue();
254 | if (TimeUnit.NANOSECONDS.toMillis(savedResult.getTimestampNanos()) < lostTimestampMillis) {
255 | iter.remove();
256 | new Handler(Looper.getMainLooper()).post(new Runnable() {
257 | @Override
258 | public void run() {
259 | callbackLostLeScanClients(address, savedResult);
260 | }
261 | });
262 | }
263 | }
264 | callbackCycleCompleted();
265 | updateRepeatingAlarm();
266 | }
267 |
268 | private void callbackCycleCompleted() {
269 | for (ScanClient client : callbacksMap.values()) {
270 | client.callback.onScanCycleCompleted();
271 | }
272 | }
273 |
274 | @Override
275 | protected Collection getRecentScanResults() {
276 | return recentScanResults.values();
277 | }
278 |
279 | private void callbackLostLeScanClients(String address, ScanResult result) {
280 | for (ScanClient client : callbacksMap.values()) {
281 | int wantAny = client.settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
282 | int wantLost = client.settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_MATCH_LOST;
283 |
284 | if (client.addressesSeen.remove(address) && (wantAny | wantLost) != 0) {
285 |
286 | // Catch any exceptions and log them but continue processing other scan results.
287 | try {
288 | client.callback.onScanResult(ScanSettings.CALLBACK_TYPE_MATCH_LOST, result);
289 | } catch (Exception e) {
290 | Logger.logError("Failure while sending 'lost' scan result to listener", e);
291 | }
292 | }
293 | }
294 | }
295 |
296 | private class ScanClient extends android.bluetooth.le.ScanCallback {
297 | final Set addressesSeen;
298 | final ScanCallback callback;
299 | final ScanSettings settings;
300 |
301 | ScanClient(ScanCallback callback, ScanSettings settings) {
302 | this.settings = settings;
303 | this.addressesSeen = new HashSet<>();
304 | this.callback = callback;
305 | }
306 |
307 | @Override
308 | public void onScanResult(int callbackType, android.bluetooth.le.ScanResult osResult) {
309 | String address = osResult.getDevice().getAddress();
310 | boolean seenItBefore = addressesSeen.contains(address);
311 | int clientFlags = settings.getCallbackType();
312 |
313 | int firstMatchBit = clientFlags & ScanSettings.CALLBACK_TYPE_FIRST_MATCH;
314 | int allMatchesBit = clientFlags & ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
315 |
316 | ScanResult result = fromOs(osResult);
317 | recentScanResults.put(address, result);
318 |
319 | // Catch any exceptions and log them but continue processing other listeners.
320 | if ((firstMatchBit | allMatchesBit) != 0) {
321 | try {
322 | if (!seenItBefore) {
323 | callback.onScanResult(ScanSettings.CALLBACK_TYPE_FIRST_MATCH, result);
324 | } else if (allMatchesBit != 0) {
325 | callback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result);
326 | }
327 | } catch (Exception e) {
328 | Logger.logError("Failure while handling scan result", e);
329 | }
330 | }
331 | addressesSeen.add(address);
332 | }
333 |
334 | @Override
335 | public void onScanFailed(int errorCode) {
336 | Logger.logInfo("LBluetoothLeScannerCompat::onScanFailed(" + errorCode + ")");
337 | callback.onScanFailed(errorCode);
338 | }
339 | }
340 | }
341 |
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/support/ble/NoBluetoothLeScannerCompat.java:
--------------------------------------------------------------------------------
1 | package com.reelyactive.blesdk.support.ble;
2 |
3 | import android.app.AlarmManager;
4 | import android.app.PendingIntent;
5 | import android.content.Context;
6 | import android.content.Intent;
7 |
8 | import com.reelyactive.blesdk.support.ble.util.Logger;
9 | import com.reelyactive.blesdk.support.ble.util.SystemClock;
10 |
11 | import java.util.Collection;
12 | import java.util.Collections;
13 | import java.util.List;
14 | import java.util.Map;
15 | import java.util.concurrent.ConcurrentHashMap;
16 |
17 | /**
18 | * Created by saiimons on 16-03-17.
19 | */
20 | public class NoBluetoothLeScannerCompat extends BluetoothLeScannerCompat {
21 |
22 | private final Map callbacksMap = new ConcurrentHashMap<>();
23 |
24 | public NoBluetoothLeScannerCompat(Context context, AlarmManager alarmManager) {
25 | super(
26 | new SystemClock(),
27 | alarmManager,
28 | PendingIntent.getBroadcast(
29 | context,
30 | 0,
31 | new Intent(context, ScanWakefulBroadcastReceiver.class).putExtra(ScanWakefulService.EXTRA_USE_LOLLIPOP_API, false), 0)
32 | );
33 | }
34 |
35 | @Override
36 | public boolean startScan(List filters, ScanSettings settings, ScanCallback callback) {
37 | if (callback != null) {
38 | try {
39 | callbacksMap.put(callback, settings);
40 | updateRepeatingAlarm();
41 | return true;
42 | } catch (Exception e) {
43 | Logger.logError(e.getMessage(), e);
44 | }
45 | }
46 | return false;
47 | }
48 |
49 | @Override
50 | public void stopScan(ScanCallback callback) {
51 | callbacksMap.remove(callback);
52 | updateRepeatingAlarm();
53 | }
54 |
55 | @Override
56 | protected int getMaxPriorityScanMode() {
57 | int maxPriority = -1;
58 |
59 | for (ScanSettings settings : callbacksMap.values()) {
60 | if (maxPriority == -1
61 | || getScanModePriority(settings.getScanMode()) > getScanModePriority(maxPriority)) {
62 | maxPriority = settings.getScanMode();
63 | }
64 | }
65 | return maxPriority;
66 | }
67 |
68 | @Override
69 | protected boolean hasClients() {
70 | return !callbacksMap.isEmpty();
71 | }
72 |
73 | @Override
74 | protected void onNewScanCycle() {
75 | for (ScanCallback callback : callbacksMap.keySet()) {
76 | callback.onScanCycleCompleted();
77 | }
78 | updateRepeatingAlarm();
79 | }
80 |
81 | @Override
82 | protected Collection getRecentScanResults() {
83 | return Collections.emptyList();
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/support/ble/Objects.java:
--------------------------------------------------------------------------------
1 | package com.reelyactive.blesdk.support.ble;/*
2 | * Copyright 2014 Google Inc. All rights reserved.
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 | import java.util.Arrays;
18 |
19 | import android.support.annotation.Nullable;
20 |
21 | /**
22 | * Small class to contain local copies of the JavaSE 7 java.util.com.reelyactive.blesdk.support.ble.Objects methods
23 | * used in the compat package. Several compat classes are built by copying them
24 | * almost verbatim from the Android 'L' release API; however, not all projects
25 | * support JavaSE 7 yet. By implementing these methods locally, we avoid
26 | * having to depend on java.util.com.reelyactive.blesdk.support.ble.Objects.
27 | *
28 | * TODO: remove this class once projects using this library support Java 7.
29 | */
30 | class Objects {
31 |
32 | public static boolean equals(@Nullable Object a, @Nullable Object b) {
33 | return (a == b) || ((a != null) && a.equals(b));
34 | }
35 |
36 | // This is a byte[]-specific implementation.
37 | public static boolean deepEquals(byte[] a, byte[] b) {
38 | return ((a == null) && (b == null))
39 | || ((a != null) && (b != null) && Arrays.equals(a, b));
40 | }
41 |
42 | public static int hash(@Nullable Object... objects) {
43 | return Arrays.hashCode(objects);
44 | }
45 |
46 | public static String toString(@Nullable Object a) {
47 | return (a == null) ? "[null]" : a.toString();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/support/ble/ScanCallback.java:
--------------------------------------------------------------------------------
1 | package com.reelyactive.blesdk.support.ble;/*
2 | * Copyright 2014 Google Inc. All rights reserved.
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 | // THIS IS MODIFIED COPY OF THE "L" PLATFORM CLASS. BE CAREFUL ABOUT EDITS.
18 | // THIS CODE SHOULD FOLLOW ANDROID STYLE.
19 | //
20 | // Changes:
21 | // Added an '@Suppress unused' to the inputs of onScanFailed().
22 | // Added a bit more javadoc to the onScanResult() method.
23 |
24 | import java.util.List;
25 |
26 | /**
27 | * Bluetooth LE scan callbacks. Scan results are reported using these callbacks.
28 | *
29 | * @see BluetoothLeScannerCompat#startScan
30 | */
31 | public abstract class ScanCallback {
32 | /**
33 | * Fails to start scan as BLE scan with the same settings is already started by the app.
34 | */
35 | public static final int SCAN_FAILED_ALREADY_STARTED = 1;
36 |
37 | /**
38 | * Fails to start scan as app cannot be registered.
39 | */
40 | public static final int SCAN_FAILED_APPLICATION_REGISTRATION_FAILED = 2;
41 |
42 | /**
43 | * Fails to start scan due an internal error
44 | */
45 | public static final int SCAN_FAILED_INTERNAL_ERROR = 3;
46 |
47 | /**
48 | * Fails to start power optimized scan as this feature is not supported.
49 | */
50 | public static final int SCAN_FAILED_FEATURE_UNSUPPORTED = 4;
51 |
52 | /**
53 | * Callback when a BLE advertisement has been found.
54 | *
55 | * @param callbackType Determines why this callback was triggered:
56 | * com.reelyactive.blesdk.support.ble.ScanSettings.CALLBACK_TYPE_FIRST_MATCH: the first (recent) scan result for this beacon
57 | * com.reelyactive.blesdk.support.ble.ScanSettings.CALLBACK_TYPE_ALL_MATCHES: a regular scan result
58 | * com.reelyactive.blesdk.support.ble.ScanSettings.CALLBACK_TYPE_MATCH_LOST: a lost match indication
59 | * @param result A Bluetooth LE scan result.
60 | */
61 | public void onScanResult(int callbackType, ScanResult result) {
62 | }
63 |
64 | /**
65 | * Callback when batch results are delivered.
66 | *
67 | * @param results List of scan results that are previously scanned.
68 | */
69 | public void onBatchScanResults(List results) {
70 | }
71 |
72 | /**
73 | * Callback when scan could not be started.
74 | * @param errorCode Error code (one of SCAN_FAILED_*) for scan failure.
75 | */
76 | public void onScanFailed(@SuppressWarnings("unused") int errorCode) {
77 | }
78 |
79 | public void onScanCycleCompleted(){
80 |
81 | }
82 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/support/ble/ScanRecord.java:
--------------------------------------------------------------------------------
1 | package com.reelyactive.blesdk.support.ble;/*
2 | * Copyright 2014 Google Inc. All rights reserved.
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 | // THIS IS MODIFIED COPY OF THE "L" PLATFORM CLASS. BE CAREFUL ABOUT EDITS.
18 | // THIS CODE SHOULD FOLLOW ANDROID STYLE.
19 | //
20 | // Changes:
21 | // Use package Logger class.
22 | // Replace ArrayMap (new in Android L) with HashMap
23 |
24 | import android.os.ParcelUuid;
25 | import android.support.annotation.Nullable;
26 | import android.util.SparseArray;
27 |
28 | import com.reelyactive.blesdk.support.ble.util.Logger;
29 |
30 | import java.util.ArrayList;
31 | import java.util.Arrays;
32 | import java.util.HashMap;
33 | import java.util.List;
34 | import java.util.Map;
35 |
36 | /**
37 | * Represents a scan record from Bluetooth LE scan.
38 | */
39 | public final class ScanRecord {
40 |
41 | // The following data type values are assigned by Bluetooth SIG.
42 | // For more details refer to Bluetooth 4.1 specification, Volume 3, Part C, Section 18.
43 | private static final int DATA_TYPE_FLAGS = 0x01;
44 | private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02;
45 | private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03;
46 | private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04;
47 | private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05;
48 | private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06;
49 | private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07;
50 | private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08;
51 | private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09;
52 | private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A;
53 | private static final int DATA_TYPE_SERVICE_DATA = 0x16;
54 | private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF;
55 |
56 | // Flags of the advertising data.
57 | private final int mAdvertiseFlags;
58 |
59 | @Nullable
60 | private final List mServiceUuids;
61 |
62 | private final SparseArray mManufacturerSpecificData;
63 |
64 | private final Map mServiceData;
65 |
66 | // Transmission power level(in dB).
67 | private final int mTxPowerLevel;
68 |
69 | // Local name of the Bluetooth LE device.
70 | private final String mDeviceName;
71 |
72 | // Raw bytes of scan record.
73 | private final byte[] mBytes;
74 |
75 | /**
76 | * Returns the advertising flags indicating the discoverable mode and capability of the device.
77 | * Returns -1 if the flag field is not set.
78 | */
79 | public int getAdvertiseFlags() {
80 | return mAdvertiseFlags;
81 | }
82 |
83 | /**
84 | * Returns a list of service UUIDs within the advertisement that are used to identify the
85 | * bluetooth GATT services.
86 | */
87 | public List getServiceUuids() {
88 | return mServiceUuids;
89 | }
90 |
91 | /**
92 | * Returns a sparse array of manufacturer identifier and its corresponding manufacturer specific
93 | * data.
94 | */
95 | public SparseArray getManufacturerSpecificData() {
96 | return mManufacturerSpecificData;
97 | }
98 |
99 | /**
100 | * Returns the manufacturer specific data associated with the manufacturer id. Returns
101 | * {@code null} if the {@code manufacturerId} is not found.
102 | */
103 | @Nullable
104 | public byte[] getManufacturerSpecificData(int manufacturerId) {
105 | return mManufacturerSpecificData.get(manufacturerId);
106 | }
107 |
108 | /**
109 | * Returns a map of service UUID and its corresponding service data.
110 | */
111 | public Map getServiceData() {
112 | return mServiceData;
113 | }
114 |
115 | /**
116 | * Returns the service data byte array associated with the {@code serviceUuid}. Returns
117 | * {@code null} if the {@code serviceDataUuid} is not found.
118 | */
119 | @Nullable
120 | public byte[] getServiceData(ParcelUuid serviceDataUuid) {
121 | if (serviceDataUuid == null) {
122 | return null;
123 | }
124 | return mServiceData.get(serviceDataUuid);
125 | }
126 |
127 | /**
128 | * Returns the transmission power level of the packet in dBm. Returns {@link Integer#MIN_VALUE}
129 | * if the field is not set. This value can be used to calculate the path loss of a received
130 | * packet using the following equation:
131 | *
132 | * pathloss = txPowerLevel - rssi
133 | */
134 | public int getTxPowerLevel() {
135 | return mTxPowerLevel;
136 | }
137 |
138 | /**
139 | * Returns the local name of the BLE device. The is a UTF-8 encoded string.
140 | */
141 | @Nullable
142 | public String getDeviceName() {
143 | return mDeviceName;
144 | }
145 |
146 | /**
147 | * Returns raw bytes of scan record.
148 | */
149 | public byte[] getBytes() {
150 | return mBytes;
151 | }
152 |
153 | private ScanRecord(List serviceUuids,
154 | SparseArray manufacturerData,
155 | Map serviceData,
156 | int advertiseFlags, int txPowerLevel,
157 | String localName, byte[] bytes) {
158 | mServiceUuids = serviceUuids;
159 | mManufacturerSpecificData = manufacturerData;
160 | mServiceData = serviceData;
161 | mDeviceName = localName;
162 | mAdvertiseFlags = advertiseFlags;
163 | mTxPowerLevel = txPowerLevel;
164 | mBytes = bytes;
165 | }
166 |
167 | /**
168 | * Parse scan record bytes to {@link ScanRecord}.
169 | *
170 | * The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18.
171 | *
172 | * All numerical multi-byte entities and values shall use little-endian byte
173 | * order.
174 | *
175 | * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response.
176 | * @hide
177 | */
178 | public static ScanRecord parseFromBytes(byte[] scanRecord) {
179 | if (scanRecord == null) {
180 | return null;
181 | }
182 |
183 | int currentPos = 0;
184 | int advertiseFlag = -1;
185 | List serviceUuids = new ArrayList();
186 | String localName = null;
187 | int txPowerLevel = Integer.MIN_VALUE;
188 |
189 | SparseArray manufacturerData = new SparseArray();
190 | Map serviceData = new HashMap();
191 |
192 | try {
193 | while (currentPos < scanRecord.length) {
194 | // length is unsigned int.
195 | int length = scanRecord[currentPos++] & 0xFF;
196 | if (length == 0) {
197 | break;
198 | }
199 | // Note the length includes the length of the field type itself.
200 | int dataLength = length - 1;
201 | // fieldType is unsigned int.
202 | int fieldType = scanRecord[currentPos++] & 0xFF;
203 | switch (fieldType) {
204 | case DATA_TYPE_FLAGS:
205 | advertiseFlag = scanRecord[currentPos] & 0xFF;
206 | break;
207 | case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL:
208 | case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE:
209 | parseServiceUuid(scanRecord, currentPos,
210 | dataLength, BluetoothUuid.UUID_BYTES_16_BIT, serviceUuids);
211 | break;
212 | case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL:
213 | case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE:
214 | parseServiceUuid(scanRecord, currentPos, dataLength,
215 | BluetoothUuid.UUID_BYTES_32_BIT, serviceUuids);
216 | break;
217 | case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL:
218 | case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE:
219 | parseServiceUuid(scanRecord, currentPos, dataLength,
220 | BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids);
221 | break;
222 | case DATA_TYPE_LOCAL_NAME_SHORT:
223 | case DATA_TYPE_LOCAL_NAME_COMPLETE:
224 | localName = new String(
225 | extractBytes(scanRecord, currentPos, dataLength));
226 | break;
227 | case DATA_TYPE_TX_POWER_LEVEL:
228 | txPowerLevel = scanRecord[currentPos];
229 | break;
230 | case DATA_TYPE_SERVICE_DATA:
231 | // The first two bytes of the service data are service data UUID in little
232 | // endian. The rest bytes are service data.
233 | int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT;
234 | byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos,
235 | serviceUuidLength);
236 | ParcelUuid serviceDataUuid = BluetoothUuid.parseUuidFrom(
237 | serviceDataUuidBytes);
238 | byte[] serviceDataArray = extractBytes(scanRecord,
239 | currentPos + serviceUuidLength, dataLength - serviceUuidLength);
240 | serviceData.put(serviceDataUuid, serviceDataArray);
241 | break;
242 | case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
243 | // The first two bytes of the manufacturer specific data are
244 | // manufacturer ids in little endian.
245 | int manufacturerId = ((scanRecord[currentPos + 1] & 0xFF) << 8)
246 | + (scanRecord[currentPos] & 0xFF);
247 | byte[] manufacturerDataBytes = extractBytes(scanRecord, currentPos + 2,
248 | dataLength - 2);
249 | manufacturerData.put(manufacturerId, manufacturerDataBytes);
250 | break;
251 | default:
252 | // Just ignore, we don't handle such data type.
253 | break;
254 | }
255 | currentPos += dataLength;
256 | }
257 |
258 | if (serviceUuids.isEmpty()) {
259 | serviceUuids = null;
260 | }
261 | return new ScanRecord(serviceUuids, manufacturerData, serviceData,
262 | advertiseFlag, txPowerLevel, localName, scanRecord);
263 | } catch (Exception e) {
264 | Logger.logError("unable to parse scan record: " + Arrays.toString(scanRecord), e);
265 | // As the record is invalid, ignore all the parsed results for this packet
266 | // and return an empty record with raw scanRecord bytes in results
267 | return new ScanRecord(null, null, null, -1, Integer.MIN_VALUE, null, scanRecord);
268 | }
269 | }
270 |
271 | @Override
272 | public String toString() {
273 | return "com.reelyactive.blesdk.support.ble.ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids
274 | + ", mManufacturerSpecificData=" + Utils.toString(mManufacturerSpecificData)
275 | + ", mServiceData=" + Utils.toString(mServiceData)
276 | + ", mTxPowerLevel=" + mTxPowerLevel + ", mDeviceName=" + mDeviceName + "]";
277 | }
278 |
279 | // Parse service UUIDs.
280 | private static int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength,
281 | int uuidLength, List serviceUuids) {
282 | while (dataLength > 0) {
283 | byte[] uuidBytes = extractBytes(scanRecord, currentPos,
284 | uuidLength);
285 | serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes));
286 | dataLength -= uuidLength;
287 | currentPos += uuidLength;
288 | }
289 | return currentPos;
290 | }
291 |
292 | // Helper method to extract bytes from byte array.
293 | private static byte[] extractBytes(byte[] scanRecord, int start, int length) {
294 | byte[] bytes = new byte[length];
295 | System.arraycopy(scanRecord, start, bytes, 0, length);
296 | return bytes;
297 | }
298 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/support/ble/ScanResult.java:
--------------------------------------------------------------------------------
1 | package com.reelyactive.blesdk.support.ble;/*
2 | * Copyright 2014 Google Inc. All rights reserved.
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 | // THIS IS MODIFIED COPY OF THE "L" PLATFORM CLASS. BE CAREFUL ABOUT EDITS.
18 | // THIS CODE SHOULD FOLLOW ANDROID STYLE.
19 | //
20 | // Changes: Removed the reference to 'java.util.com.reelyactive.blesdk.support.ble.Objects'.
21 |
22 | import android.bluetooth.BluetoothDevice;
23 | import android.os.Parcel;
24 | import android.os.Parcelable;
25 |
26 | import android.support.annotation.Nullable;
27 |
28 | /**
29 | * com.reelyactive.blesdk.support.ble.ScanResult for Bluetooth LE scan.
30 | */
31 | public final class ScanResult implements Parcelable {
32 | // Remote bluetooth device.
33 | private BluetoothDevice mDevice;
34 |
35 | // Scan record, including advertising data and scan response data.
36 | @Nullable
37 | private ScanRecord mScanRecord;
38 |
39 | // Received signal strength.
40 | private int mRssi;
41 |
42 | // Device timestamp when the result was last seen.
43 | private long mTimestampNanos;
44 |
45 | /**
46 | * Constructor of scan result.
47 | *
48 | * @param device Remote bluetooth device that is found.
49 | * @param scanRecord Scan record including both advertising data and scan response data.
50 | * @param rssi Received signal strength.
51 | * @param timestampNanos Device timestamp when the scan result was observed.
52 | */
53 | public ScanResult(BluetoothDevice device, ScanRecord scanRecord, int rssi,
54 | long timestampNanos) {
55 | mDevice = device;
56 | mScanRecord = scanRecord;
57 | mRssi = rssi;
58 | mTimestampNanos = timestampNanos;
59 | }
60 |
61 | private ScanResult(Parcel in) {
62 | readFromParcel(in);
63 | }
64 |
65 | @Override
66 | public void writeToParcel(Parcel dest, int flags) {
67 | if (mDevice != null) {
68 | dest.writeInt(1);
69 | mDevice.writeToParcel(dest, flags);
70 | } else {
71 | dest.writeInt(0);
72 | }
73 | if (mScanRecord != null) {
74 | dest.writeInt(1);
75 | dest.writeByteArray(mScanRecord.getBytes());
76 | } else {
77 | dest.writeInt(0);
78 | }
79 | dest.writeInt(mRssi);
80 | dest.writeLong(mTimestampNanos);
81 | }
82 |
83 | private void readFromParcel(Parcel in) {
84 | if (in.readInt() == 1) {
85 | mDevice = BluetoothDevice.CREATOR.createFromParcel(in);
86 | }
87 | if (in.readInt() == 1) {
88 | mScanRecord = ScanRecord.parseFromBytes(in.createByteArray());
89 | }
90 | mRssi = in.readInt();
91 | mTimestampNanos = in.readLong();
92 | }
93 |
94 | @Override
95 | public int describeContents() {
96 | return 0;
97 | }
98 |
99 | /**
100 | * Returns the remote bluetooth device identified by the bluetooth device address.
101 | */
102 | public BluetoothDevice getDevice() {
103 | return mDevice;
104 | }
105 |
106 | /**
107 | * Returns the scan record, which is a combination of advertisement and scan response.
108 | */
109 | @Nullable
110 | public ScanRecord getScanRecord() {
111 | return mScanRecord;
112 | }
113 |
114 | /**
115 | * Returns the received signal strength in dBm. The valid range is [-127, 127].
116 | */
117 | public int getRssi() {
118 | return mRssi;
119 | }
120 |
121 | /**
122 | * Returns timestamp since boot when the scan record was observed.
123 | */
124 | public long getTimestampNanos() {
125 | return mTimestampNanos;
126 | }
127 |
128 | @Override
129 | public int hashCode() {
130 | return Objects.hash(mDevice, mRssi, mScanRecord, mTimestampNanos);
131 | }
132 |
133 | @Override
134 | public boolean equals(Object obj) {
135 | if (this == obj) {
136 | return true;
137 | }
138 | if (obj == null || getClass() != obj.getClass()) {
139 | return false;
140 | }
141 | ScanResult other = (ScanResult) obj;
142 | return Objects.equals(mDevice, other.mDevice) && (mRssi == other.mRssi)
143 | && Objects.equals(mScanRecord, other.mScanRecord)
144 | && (mTimestampNanos == other.mTimestampNanos);
145 | }
146 |
147 | @Override
148 | public String toString() {
149 | return "com.reelyactive.blesdk.support.ble.ScanResult{" + "mDevice=" + mDevice + ", mScanRecord="
150 | + Objects.toString(mScanRecord) + ", mRssi=" + mRssi + ", mTimestampNanos="
151 | + mTimestampNanos + '}';
152 | }
153 |
154 | /**
155 | * @hide
156 | */
157 | public static final Creator CREATOR = new Creator() {
158 | @Override
159 | public ScanResult createFromParcel(Parcel source) {
160 | return new ScanResult(source);
161 | }
162 |
163 | @Override
164 | public ScanResult[] newArray(int size) {
165 | return new ScanResult[size];
166 | }
167 | };
168 |
169 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/support/ble/ScanResultParser.java:
--------------------------------------------------------------------------------
1 | package com.reelyactive.blesdk.support.ble;
2 |
3 | /**
4 | * Created by saiimons on 15-03-30.
5 | */
6 | @SuppressWarnings("unused")
7 | public class ScanResultParser {
8 | public static String getSystemId(ScanResult result) {
9 | byte[] data = getSystemIdBytes(result);
10 | if (data == null || data.length == 0) {
11 | return null;
12 | }
13 | StringBuilder sb = new StringBuilder(data.length * 2);
14 | for (int i = data.length - 1; i >= 0; i--) {
15 | sb.append(String.format("%02x", data[i]));
16 | }
17 | return sb.toString();
18 | }
19 |
20 | public static byte[] getSystemIdBytes(ScanResult result) {
21 | if (result == null) {
22 | return null;
23 | }
24 | ScanRecord record = result.getScanRecord();
25 | if (record == null) {
26 | return null;
27 | }
28 | return record.getServiceData(BluetoothUuid.parseUuidFrom(new byte[]{0x23, 0x2a}));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/support/ble/ScanSettings.java:
--------------------------------------------------------------------------------
1 | package com.reelyactive.blesdk.support.ble;/*
2 | * Copyright 2014 Google Inc. All rights reserved.
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 | // THIS IS MODIFIED COPY OF THE "L" PLATFORM CLASS. BE CAREFUL ABOUT EDITS.
18 | // THIS CODE SHOULD FOLLOW ANDROID STYLE.
19 |
20 |
21 | import android.os.Parcel;
22 | import android.os.Parcelable;
23 |
24 | /**
25 | * Bluetooth LE scan settings are passed to {@link BluetoothLeScannerCompat#startScan}
26 | * to define the parameters for the scan.
27 | */
28 | public final class ScanSettings implements Parcelable {
29 | /**
30 | * Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes the
31 | * least power.
32 | */
33 | public static final int SCAN_MODE_LOW_POWER = 0;
34 |
35 | /**
36 | * Perform Bluetooth LE scan in balanced power mode. Scan results are returned at a rate that
37 | * provides a good trade-off between scan frequency and power consumption.
38 | */
39 | public static final int SCAN_MODE_BALANCED = 1;
40 |
41 | /**
42 | * Scan using highest duty cycle. It's recommended to only use this mode when the application is
43 | * running in the foreground.
44 | */
45 | public static final int SCAN_MODE_LOW_LATENCY = 2;
46 |
47 | /**
48 | * Trigger a callback for every Bluetooth advertisement found that matches the filter criteria.
49 | * If no filter is active, all advertisement packets are reported.
50 | */
51 | public static final int CALLBACK_TYPE_ALL_MATCHES = 1;
52 |
53 | /**
54 | * A result callback is only triggered for the first advertisement packet received that matches
55 | * the filter criteria.
56 | */
57 | public static final int CALLBACK_TYPE_FIRST_MATCH = 2;
58 |
59 | /**
60 | * Receive a callback when advertisements are no longer received from a device that has been
61 | * previously reported by a first match callback.
62 | */
63 | public static final int CALLBACK_TYPE_MATCH_LOST = 4;
64 |
65 | /**
66 | * Request full scan results which contain the device, rssi, advertising data, scan response as
67 | * well as the scan timestamp.
68 | */
69 | public static final int SCAN_RESULT_TYPE_FULL = 0;
70 |
71 | /**
72 | * Request abbreviated scan results which contain the device, rssi and scan timestamp.
73 | *
74 | * Note: It is possible for an application to get more scan results than it asked for, if
75 | * there are multiple apps using this type.
76 | *
77 | * @hide
78 | */
79 | public static final int SCAN_RESULT_TYPE_ABBREVIATED = 1;
80 |
81 | // Bluetooth LE scan mode.
82 | private int mScanMode;
83 |
84 | // Bluetooth LE scan callback type
85 | private int mCallbackType;
86 |
87 | // Bluetooth LE scan result type
88 | private int mScanResultType;
89 |
90 | // Time of delay for reporting the scan result
91 | private long mReportDelayMillis;
92 |
93 | public int getScanMode() {
94 | return mScanMode;
95 | }
96 |
97 | public int getCallbackType() {
98 | return mCallbackType;
99 | }
100 |
101 | public int getScanResultType() {
102 | return mScanResultType;
103 | }
104 |
105 | /**
106 | * Returns report delay timestamp based on the device clock.
107 | */
108 | public long getReportDelayMillis() {
109 | return mReportDelayMillis;
110 | }
111 |
112 | private ScanSettings(int scanMode, int callbackType, int scanResultType,
113 | long reportDelayMillis) {
114 | mScanMode = scanMode;
115 | mCallbackType = callbackType;
116 | mScanResultType = scanResultType;
117 | mReportDelayMillis = reportDelayMillis;
118 | }
119 |
120 | private ScanSettings(Parcel in) {
121 | mScanMode = in.readInt();
122 | mCallbackType = in.readInt();
123 | mScanResultType = in.readInt();
124 | mReportDelayMillis = in.readLong();
125 | }
126 |
127 | @Override
128 | public void writeToParcel(Parcel dest, int flags) {
129 | dest.writeInt(mScanMode);
130 | dest.writeInt(mCallbackType);
131 | dest.writeInt(mScanResultType);
132 | dest.writeLong(mReportDelayMillis);
133 | }
134 |
135 | @Override
136 | public int describeContents() {
137 | return 0;
138 | }
139 |
140 | /**
141 | * @hide
142 | */
143 | public static final Creator
144 | CREATOR = new Creator() {
145 | @Override
146 | public ScanSettings[] newArray(int size) {
147 | return new ScanSettings[size];
148 | }
149 |
150 | @Override
151 | public ScanSettings createFromParcel(Parcel in) {
152 | return new ScanSettings(in);
153 | }
154 | };
155 |
156 | /**
157 | * Builder for {@link ScanSettings}.
158 | */
159 | public static final class Builder {
160 | private int mScanMode = SCAN_MODE_LOW_POWER;
161 | private int mCallbackType = CALLBACK_TYPE_ALL_MATCHES;
162 | private int mScanResultType = SCAN_RESULT_TYPE_FULL;
163 | private long mReportDelayMillis = 0;
164 |
165 | /**
166 | * Set scan mode for Bluetooth LE scan.
167 | *
168 | * @param scanMode The scan mode can be one of {@link ScanSettings#SCAN_MODE_LOW_POWER},
169 | * {@link ScanSettings#SCAN_MODE_BALANCED} or
170 | * {@link ScanSettings#SCAN_MODE_LOW_LATENCY}.
171 | * @throws IllegalArgumentException If the {@code scanMode} is invalid.
172 | */
173 | public Builder setScanMode(int scanMode) {
174 | if (scanMode < SCAN_MODE_LOW_POWER || scanMode > SCAN_MODE_LOW_LATENCY) {
175 | throw new IllegalArgumentException("invalid scan mode " + scanMode);
176 | }
177 | mScanMode = scanMode;
178 | return this;
179 | }
180 |
181 | /**
182 | * Set callback type for Bluetooth LE scan.
183 | *
184 | * @param callbackType The callback type flags for the scan.
185 | * @throws IllegalArgumentException If the {@code callbackType} is invalid.
186 | */
187 | public Builder setCallbackType(int callbackType) {
188 |
189 | if (!isValidCallbackType(callbackType)) {
190 | throw new IllegalArgumentException("invalid callback type - " + callbackType);
191 | }
192 | mCallbackType = callbackType;
193 | return this;
194 | }
195 |
196 | // Returns true if the callbackType is valid.
197 | private boolean isValidCallbackType(int callbackType) {
198 | if (callbackType == CALLBACK_TYPE_ALL_MATCHES
199 | || callbackType == CALLBACK_TYPE_FIRST_MATCH
200 | || callbackType == CALLBACK_TYPE_MATCH_LOST) {
201 | return true;
202 | }
203 | return callbackType == (CALLBACK_TYPE_FIRST_MATCH | CALLBACK_TYPE_MATCH_LOST);
204 | }
205 |
206 | /**
207 | * Set scan result type for Bluetooth LE scan.
208 | *
209 | * @param scanResultType Type for scan result, could be either
210 | * {@link ScanSettings#SCAN_RESULT_TYPE_FULL} or
211 | * {@link ScanSettings#SCAN_RESULT_TYPE_ABBREVIATED}.
212 | * @throws IllegalArgumentException If the {@code scanResultType} is invalid.
213 | * @hide
214 | */
215 | public Builder setScanResultType(int scanResultType) {
216 | if (scanResultType < SCAN_RESULT_TYPE_FULL
217 | || scanResultType > SCAN_RESULT_TYPE_ABBREVIATED) {
218 | throw new IllegalArgumentException(
219 | "invalid scanResultType - " + scanResultType);
220 | }
221 | mScanResultType = scanResultType;
222 | return this;
223 | }
224 |
225 | /**
226 | * Set report delay timestamp for Bluetooth LE scan.
227 | *
228 | * @param reportDelayMillis Set to 0 to be notified of results immediately. Values > 0
229 | * causes the scan results to be queued up and delivered after the requested
230 | * delay or when the internal buffers fill up.
231 | * @throws IllegalArgumentException If {@code reportDelayMillis} < 0.
232 | */
233 | public Builder setReportDelayMillis(long reportDelayMillis) {
234 | if (reportDelayMillis < 0) {
235 | throw new IllegalArgumentException("reportDelayMillis must be > 0");
236 | }
237 | mReportDelayMillis = reportDelayMillis;
238 | return this;
239 | }
240 |
241 | /**
242 | * Build {@link ScanSettings}.
243 | */
244 | public ScanSettings build() {
245 | return new ScanSettings(mScanMode, mCallbackType, mScanResultType,
246 | mReportDelayMillis);
247 | }
248 | }
249 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/support/ble/ScanWakefulBroadcastReceiver.java:
--------------------------------------------------------------------------------
1 | package com.reelyactive.blesdk.support.ble;/*
2 | * Copyright 2014 Google Inc. All rights reserved.
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 | import android.content.Context;
18 | import android.content.Intent;
19 | import android.support.v4.content.WakefulBroadcastReceiver;
20 |
21 | import com.reelyactive.blesdk.support.ble.util.Logger;
22 |
23 | /**
24 | * com.reelyactive.blesdk.support.ble.ScanWakefulBroadcastReceiver initiates the Bluetooth LE scan by calling
25 | * {@link ScanWakefulService} after acquiring a WakeLock. On completion {@link ScanWakefulService}
26 | * releases the WakeLock.
27 | *
28 | * This WakefulBroadcastReceiver is invoked by a pending intent through the alarm manager.
29 | */
30 | public class ScanWakefulBroadcastReceiver extends WakefulBroadcastReceiver {
31 |
32 | @Override
33 | public void onReceive(Context context, Intent intent) {
34 | Logger.logDebug("Alarm triggered");
35 | // This is the Intent to deliver to our scan service.
36 | Intent intentService = new Intent(context, ScanWakefulService.class)
37 | .putExtra(
38 | ScanWakefulService.EXTRA_USE_LOLLIPOP_API,
39 | intent.getBooleanExtra(ScanWakefulService.EXTRA_USE_LOLLIPOP_API, true)
40 | );
41 |
42 | // Start the scan service, keeping the device awake while it is launching. This method will
43 | // explicitly grab a wakelock until the service tells the broadcast receiver to release it.
44 | startWakefulService(context, intentService);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/support/ble/ScanWakefulService.java:
--------------------------------------------------------------------------------
1 | package com.reelyactive.blesdk.support.ble;/*
2 | * Copyright 2014 Google Inc. All rights reserved.
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 | import android.app.IntentService;
18 | import android.content.Intent;
19 |
20 | import com.reelyactive.blesdk.support.ble.util.Logger;
21 |
22 | /**
23 | * The com.reelyactive.blesdk.support.ble.ScanWakefulService Class is called with a WakeLock held, and executes a single Bluetooth LE
24 | * scan cycle. It then calls the {@link ScanWakefulBroadcastReceiver#completeWakefulIntent} with the
25 | * same intent to release the associated WakeLock.
26 | */
27 | public class ScanWakefulService extends IntentService {
28 | public static final String EXTRA_USE_LOLLIPOP_API = "use_lollipop";
29 |
30 | public ScanWakefulService() {
31 | super("com.reelyactive.blesdk.support.ble.ScanWakefulService");
32 | }
33 |
34 | @Override
35 | protected void onHandleIntent(Intent intent) {
36 | if (intent == null) {
37 | return;
38 | }
39 | // This method runs in a worker thread.
40 | // At this point com.reelyactive.blesdk.support.ble.ScanWakefulBroadcastReceiver is still holding a WakeLock.
41 | // We can do whatever we need to do in the code below.
42 | // After the call to completeWakefulIntent the WakeLock is released.
43 | Logger.logDebug("Running scancycle");
44 | try {
45 | BluetoothLeScannerCompat bleScanner =
46 | BluetoothLeScannerCompatProvider.getBluetoothLeScannerCompat(this, intent.getBooleanExtra(EXTRA_USE_LOLLIPOP_API, true));
47 | if (bleScanner != null) {
48 | bleScanner.onNewScanCycle();
49 | }
50 | } finally {
51 | ScanWakefulBroadcastReceiver.completeWakefulIntent(intent);
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/support/ble/Utils.java:
--------------------------------------------------------------------------------
1 | package com.reelyactive.blesdk.support.ble;/*
2 | * Copyright 2014 Google Inc. All rights reserved.
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 | // THIS IS MODIFIED COPY OF THE "L" PLATFORM CLASS. BE CAREFUL ABOUT EDITS.
18 | // THIS CODE SHOULD FOLLOW ANDROID STYLE.
19 | //
20 | // Changes:
21 | // Removed the last two equals() methods.
22 |
23 | import android.util.SparseArray;
24 |
25 | import java.util.Arrays;
26 | import java.util.Iterator;
27 | import java.util.Map;
28 |
29 | /**
30 | * Helper class for Bluetooth LE utils.
31 | *
32 | * @hide
33 | */
34 | public class Utils {
35 |
36 | /**
37 | * Returns a string composed from a {@link android.util.SparseArray}.
38 | */
39 | static String toString(SparseArray array) {
40 | if (array == null) {
41 | return "null";
42 | }
43 | if (array.size() == 0) {
44 | return "{}";
45 | }
46 | StringBuilder buffer = new StringBuilder();
47 | buffer.append('{');
48 | for (int i = 0; i < array.size(); ++i) {
49 | buffer.append(array.keyAt(i)).append("=").append(array.valueAt(i));
50 | }
51 | buffer.append('}');
52 | return buffer.toString();
53 | }
54 |
55 | /**
56 | * Returns a string composed from a {@link java.util.Map}.
57 | */
58 | static String toString(Map map) {
59 | if (map == null) {
60 | return "null";
61 | }
62 | if (map.isEmpty()) {
63 | return "{}";
64 | }
65 | StringBuilder buffer = new StringBuilder();
66 | buffer.append('{');
67 | Iterator> it = map.entrySet().iterator();
68 | while (it.hasNext()) {
69 | Map.Entry entry = it.next();
70 | Object key = entry.getKey();
71 | buffer.append(key).append("=").append(Arrays.toString(map.get(key)));
72 | if (it.hasNext()) {
73 | buffer.append(", ");
74 | }
75 | }
76 | buffer.append('}');
77 | return buffer.toString();
78 | }
79 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/support/ble/util/BluetoothInterface.java:
--------------------------------------------------------------------------------
1 | package com.reelyactive.blesdk.support.ble.util;
2 |
3 | import android.bluetooth.BluetoothAdapter;
4 | import android.content.Context;
5 | import android.content.SharedPreferences;
6 | import android.os.Build;
7 |
8 | import java.util.Random;
9 |
10 | /**
11 | * Created by saiimons on 15-03-02.
12 | */
13 | public class BluetoothInterface {
14 | private static final String PREF_NAME = "reelyactive";
15 | private static final String MAC_PREF = "mac_address";
16 |
17 | public static MacAddress getMacAddress(Context context) {
18 | MacAddress address = new MacAddress();
19 | BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
20 | if (adapter == null || Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
21 | SharedPreferences preferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
22 | if (!preferences.contains(MAC_PREF)) {
23 | preferences.edit().putString(MAC_PREF, randomMACAddress()).apply();
24 | }
25 | address.type = "random";
26 | address.address = preferences.getString(MAC_PREF, "00:00:00:00:00:00");
27 | } else {
28 | address.type = "public";
29 | address.address = adapter.getAddress();
30 | }
31 | return address;
32 | }
33 |
34 | private static String randomMACAddress() {
35 | Random rand = new Random(System.currentTimeMillis());
36 | byte[] macAddr = new byte[6];
37 | rand.nextBytes(macAddr);
38 |
39 | macAddr[0] = (byte) (macAddr[0] & (byte) 254); //zeroing last 2 bytes to make it unicast and locally adminstrated
40 |
41 | StringBuilder sb = new StringBuilder(18);
42 | for (byte b : macAddr) {
43 |
44 | if (sb.length() > 0)
45 | sb.append(":");
46 |
47 | sb.append(String.format("%02x", b));
48 | }
49 |
50 |
51 | return sb.toString();
52 | }
53 |
54 | public static class MacAddress {
55 | public String address;
56 | public String type;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/support/ble/util/Clock.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc. All rights reserved.
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 com.reelyactive.blesdk.support.ble.util;
18 |
19 | /**
20 | * It seems Android does not have a standard equivalent to com.google.common.time.[System]Clock.
21 | *
22 | * Darn.
23 | */
24 | public interface Clock {
25 |
26 | /**
27 | * @return current time in milliseconds
28 | */
29 | public long currentTimeMillis();
30 |
31 | /**
32 | * @return time since the device was booted, in nanoseconds
33 | */
34 | public long elapsedRealtimeNanos();
35 |
36 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/support/ble/util/Logger.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc. All rights reserved.
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 com.reelyactive.blesdk.support.ble.util;
18 |
19 | import android.util.Log;
20 |
21 | /**
22 | * Utility methods for error/warning/info/debug/verbose logging.
23 | */
24 | public class Logger {
25 |
26 | public static final String TAG = "BleScanCompatLib";
27 |
28 | public static void logVerbose(String message) {
29 | if (Log.isLoggable(TAG, Log.VERBOSE)) {
30 | Log.v(TAG, message);
31 | }
32 | }
33 |
34 | public static void logWarning(String message) {
35 | if (Log.isLoggable(TAG, Log.WARN)) {
36 | Log.w(TAG, message);
37 | }
38 | }
39 |
40 | public static void logDebug(String message) {
41 | if (Log.isLoggable(TAG, Log.DEBUG)) {
42 | Log.d(TAG, message);
43 | }
44 | }
45 |
46 | public static void logInfo(String message) {
47 | if (Log.isLoggable(TAG, Log.INFO)) {
48 | Log.i(TAG, message);
49 | }
50 | }
51 |
52 | public static void logError(String message, Exception... e) {
53 | if (Log.isLoggable(TAG, Log.ERROR)) {
54 | if (e == null || e.length == 0) {
55 | Log.e(TAG, message);
56 | } else {
57 | Log.e(TAG, message, e[0]);
58 | }
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/reelyactive/blesdk/support/ble/util/SystemClock.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc. All rights reserved.
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 com.reelyactive.blesdk.support.ble.util;
18 |
19 | import android.annotation.TargetApi;
20 | import android.os.Build.VERSION_CODES;
21 |
22 | /**
23 | * It seems Android does not have a standard equivalent to com.google.common.time.[System]Clock.
24 | *
25 | *
Darn.
26 | */
27 | public class SystemClock implements Clock {
28 |
29 | /**
30 | * @return current time in milliseconds
31 | */
32 | @Override
33 | public long currentTimeMillis() {
34 | return System.currentTimeMillis();
35 | }
36 |
37 | /**
38 | * @return time since the device was booted, in nanoseconds
39 | */
40 | @Override
41 | @TargetApi(VERSION_CODES.JELLY_BEAN_MR1)
42 | public long elapsedRealtimeNanos() {
43 | return android.os.SystemClock.elapsedRealtimeNanos();
44 | }
45 | }
--------------------------------------------------------------------------------
/library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/library/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':library'
2 | project(':library').name = 'blesdk'
3 | include ':example_app'
--------------------------------------------------------------------------------