Skip to content

Commit 17d37c3

Browse files
committed
Use MAC address from settings if it is unavailable any other way
This change will use the Bluetooth MAC address from settings if needed and prompt for it if it does not exist. To make this possible, it also cleans up the flow of checking if BluetoothSyncService can run. References #4
1 parent 56011b8 commit 17d37c3

File tree

3 files changed

+82
-46
lines changed

3 files changed

+82
-46
lines changed

app/src/main/java/com/alternativeinfrastructures/noise/sync/bluetooth/BluetoothSyncService.java

Lines changed: 77 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,32 @@
1717
import android.bluetooth.le.ScanSettings;
1818
import android.content.Context;
1919
import android.content.Intent;
20+
import android.content.SharedPreferences;
2021
import android.content.pm.PackageManager;
2122
import android.os.Build;
2223
import android.os.IBinder;
2324
import android.os.ParcelUuid;
25+
import android.preference.PreferenceManager;
2426
import android.util.Log;
2527
import android.widget.Toast;
2628

2729
import net.vidageek.mirror.dsl.Mirror;
2830

2931
import com.alternativeinfrastructures.noise.R;
3032
import com.alternativeinfrastructures.noise.sync.StreamSync;
33+
import com.alternativeinfrastructures.noise.views.SettingsActivity;
3134

3235
import java.io.IOException;
3336
import java.util.UUID;
3437
import java.util.concurrent.ConcurrentHashMap;
38+
import java.util.regex.Pattern;
3539

3640
public class BluetoothSyncService extends Service {
3741
public static final String TAG = "BluetoothSyncService";
3842
public static final UUID SERVICE_UUID_HALF = UUID.fromString("5ac825f4-6084-42a6-0000-000000000000");
3943

4044
private static final String FAKE_MAC_ADDRESS = "02:00:00:00:00:00";
45+
private static final Pattern MAC_PATTERN = Pattern.compile("\\w\\w:\\w\\w:\\w\\w:\\w\\w:\\w\\w:\\w\\w");
4146

4247
private boolean started = false;
4348
private UUID serviceUuidAndAddress;
@@ -56,42 +61,60 @@ public IBinder onBind(Intent intent) {
5661
throw new UnsupportedOperationException("Not yet implemented");
5762
}
5863

59-
// TODO: On some phones, this incorrectly returns false when the Bluetooth radio is off even though BLE advertise is supported
60-
public static boolean isSupported(Context context) {
61-
PackageManager packageManager = context.getPackageManager();
62-
if (!packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || !packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH))
63-
return false;
64-
65-
BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
66-
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
67-
return bluetoothAdapter != null && getBluetoothAdapterAddress(bluetoothAdapter) != null && bluetoothAdapter.isMultipleAdvertisementSupported();
64+
public enum CanStartResult {
65+
CAN_START,
66+
BLUETOOTH_OR_BLE_UNSUPPORTED,
67+
BLUETOOTH_OFF,
68+
BLUETOOTH_ADDRESS_UNAVAILABLE;
6869
}
6970

70-
public static boolean isStartable(Context context) {
71-
if (!isSupported(context))
72-
return false;
71+
public static CanStartResult canStart(Context context) {
72+
PackageManager packageManager = context.getPackageManager();
73+
BluetoothAdapter bluetoothAdapter = getBluetoothAdapter(context);
74+
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
75+
return CanStartResult.BLUETOOTH_OFF;
76+
} else if (!packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) ||
77+
!packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH) ||
78+
!bluetoothAdapter.isMultipleAdvertisementSupported()) {
79+
return CanStartResult.BLUETOOTH_OR_BLE_UNSUPPORTED;
80+
} else if (getBluetoothAdapterAddress(bluetoothAdapter, context) == null) {
81+
return CanStartResult.BLUETOOTH_ADDRESS_UNAVAILABLE;
82+
}
7383

74-
BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
75-
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
76-
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
84+
return CanStartResult.CAN_START;
7785
}
7886

7987
public static void startOrPromptBluetooth(Context context) {
80-
if (!BluetoothSyncService.isSupported(context)) {
81-
Log.d(TAG, "BLE not supported, not starting BLE sync service");
82-
Toast.makeText(context, R.string.bluetooth_not_supported, Toast.LENGTH_LONG).show();
83-
return;
88+
switch (canStart(context)) {
89+
case CAN_START:
90+
Log.d(TAG, "Starting BLE sync service");
91+
context.startService(new Intent(context, BluetoothSyncService.class));
92+
break;
93+
case BLUETOOTH_OR_BLE_UNSUPPORTED:
94+
Log.d(TAG, "BLE not supported, not starting BLE sync service");
95+
Toast.makeText(context, R.string.bluetooth_not_supported, Toast.LENGTH_LONG).show();
96+
break;
97+
case BLUETOOTH_OFF:
98+
Log.d(TAG, "BLE supported but Bluetooth is off; will prompt for Bluetooth and start once it's on");
99+
Toast.makeText(context, R.string.bluetooth_ask_enable, Toast.LENGTH_LONG).show();
100+
context.startActivity(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE));
101+
// BluetoothSyncServiceManager will start this service once Bluetooth is on.
102+
break;
103+
case BLUETOOTH_ADDRESS_UNAVAILABLE:
104+
Log.d(TAG, "BLE supported but MAC address is unavailable; will prompt for address and start once it's available");
105+
Toast.makeText(context, R.string.bluetooth_ask_address, Toast.LENGTH_LONG).show();
106+
// TODO: Open the app's settings? Maybe getting the address should be part of onboarding UI
107+
// BluetoothSyncServiceManager will start this (re)start this service when the address changes.
108+
break;
84109
}
110+
}
85111

86-
if (BluetoothSyncService.isStartable(context)) {
87-
Log.d(TAG, "BLE supported and Bluetooth is on; starting BLE sync service");
88-
context.startService(new Intent(context, BluetoothSyncService.class));
89-
} else {
90-
Log.d(TAG, "BLE supported but Bluetooth is off; will prompt for Bluetooth and start once it's on");
91-
Toast.makeText(context, R.string.bluetooth_ask, Toast.LENGTH_LONG).show();
92-
context.startActivity(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE));
93-
// BluetoothSyncServiceManager will start this service once Bluetooth is on.
94-
}
112+
private static BluetoothAdapter getBluetoothAdapter(Context context) {
113+
BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
114+
if (bluetoothManager == null)
115+
return null;
116+
117+
return bluetoothManager.getAdapter();
95118
}
96119

97120
private AdvertiseData buildAdvertiseData() {
@@ -127,7 +150,7 @@ private ScanSettings buildScanSettings() {
127150
return builder.build();
128151
}
129152

130-
private static String getBluetoothAdapterAddress(BluetoothAdapter bluetoothAdapter) {
153+
private static String getBluetoothAdapterAddress(BluetoothAdapter bluetoothAdapter, Context context) {
131154
@SuppressLint("HardwareIds") // Pair-free peer-to-peer communication should qualify as an "advanced telephony use case".
132155
String address = bluetoothAdapter.getAddress();
133156

@@ -163,8 +186,11 @@ private static String getBluetoothAdapterAddress(BluetoothAdapter bluetoothAdapt
163186
// https://stackoverflow.com/a/35984808/702467
164187
if (address.equals(FAKE_MAC_ADDRESS)) {
165188
Log.w(TAG, "Android is actively blocking requests to get the MAC address");
166-
return null;
167-
// TODO: In this case, present UI that asks the user to manually copy the MAC address from settings
189+
190+
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
191+
address = preferences.getString(SettingsActivity.KEY_BLUETOOTH_MAC, "").toUpperCase();
192+
if (!MAC_PATTERN.matcher(address).matches())
193+
return null;
168194
}
169195

170196
return address;
@@ -208,6 +234,8 @@ public void onStartFailure(int errorCode) {
208234

209235
// Scan filters on service UUIDs were completely broken on the devices I tested (fully updated Google Pixel and Moto G4 Play as of March 2017)
210236
// https://stackoverflow.com/questions/29664316/bluetooth-le-scan-filter-not-working
237+
// TODO: Check if that's supported using bluetoothAdapter.isOffloadedFilteringSupported/isOffloadedScanBatchingSupported
238+
// https://stackoverflow.com/questions/26482611/chipsets-devices-supporting-android-5-ble-peripheral-mode
211239
bluetoothLeScanner.startScan(null /*filters*/, buildScanSettings(),
212240
new ScanCallback() {
213241
@Override
@@ -246,6 +274,9 @@ public void onScanResult(int callbackType, ScanResult result) {
246274
}
247275

248276
private void stopBluetoothLeDiscovery() {
277+
if (!bluetoothAdapter.isEnabled())
278+
return;
279+
249280
if (bluetoothLeAdvertiser != null) {
250281
bluetoothLeAdvertiser.stopAdvertising(new AdvertiseCallback() {
251282
@Override
@@ -256,13 +287,15 @@ public void onStartFailure(int errorCode) {
256287
});
257288
}
258289

259-
bluetoothLeScanner.stopScan(new ScanCallback() {
260-
@Override
261-
public void onScanFailed(int errorCode) {
262-
super.onScanFailed(errorCode);
263-
Log.e(TAG, "BLE scan failed to stop: error " + errorCode);
264-
}
265-
});
290+
if (bluetoothLeScanner != null) {
291+
bluetoothLeScanner.stopScan(new ScanCallback() {
292+
@Override
293+
public void onScanFailed(int errorCode) {
294+
super.onScanFailed(errorCode);
295+
Log.e(TAG, "BLE scan failed to stop: error " + errorCode);
296+
}
297+
});
298+
}
266299
}
267300

268301
private class BluetoothClassicServer extends Thread {
@@ -280,7 +313,7 @@ public BluetoothClassicServer(UUID uuid) {
280313
public void run() {
281314
BluetoothSocket socket = null;
282315

283-
while (started) {
316+
while (bluetoothAdapter.isEnabled() && started) {
284317
String macAddress = null;
285318
try {
286319
// This will block until there is a connection
@@ -352,7 +385,7 @@ public void run() {
352385
public int onStartCommand(Intent intent, int flags, int startId) {
353386
super.onStartCommand(intent, flags, startId);
354387

355-
if (!isStartable(this)) {
388+
if (canStart(this) != CanStartResult.CAN_START) {
356389
Log.e(TAG, "Trying to start the service even though Bluetooth is off or BLE is unsupported");
357390
stopSelf(startId);
358391
return START_NOT_STICKY;
@@ -363,13 +396,12 @@ public int onStartCommand(Intent intent, int flags, int startId) {
363396
return START_STICKY;
364397
}
365398

366-
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
367-
bluetoothAdapter = bluetoothManager.getAdapter();
399+
bluetoothAdapter = getBluetoothAdapter(this);
368400

369401
// First half identifies that the advertisement is for Noise.
370402
// Second half is the MAC address of this device's Bluetooth adapter so that clients know how to connect to it.
371403
// These are not listed separately in the advertisement because a UUID is 16 bytes and ads are limited to 31 bytes.
372-
String macAddress = getBluetoothAdapterAddress(bluetoothAdapter);
404+
String macAddress = getBluetoothAdapterAddress(bluetoothAdapter, this);
373405
if (macAddress == null) {
374406
Log.e(TAG, "Unable to get this device's Bluetooth MAC address");
375407
stopSelf(startId);
@@ -394,6 +426,8 @@ public int onStartCommand(Intent intent, int flags, int startId) {
394426

395427
@Override
396428
public void onDestroy() {
429+
started = false;
430+
397431
stopBluetoothLeDiscovery();
398432

399433
// TODO: Verify that this actually stops the thread
@@ -402,7 +436,6 @@ public void onDestroy() {
402436
// TODO: Stop all BluetoothClassicClient threads
403437

404438
Toast.makeText(this, R.string.bluetooth_sync_stopped, Toast.LENGTH_LONG).show();
405-
started = false;
406439
Log.d(TAG, "Stopped");
407440
super.onDestroy();
408441
}

app/src/main/java/com/alternativeinfrastructures/noise/views/SettingsActivity.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import com.alternativeinfrastructures.noise.R;
1111

1212
public class SettingsActivity extends AppCompatActivity {
13+
public static final String KEY_BLUETOOTH_MAC = "pref_key_bluetooth_mac";
14+
1315
@Override
1416
protected void onCreate(Bundle savedInstanceState) {
1517
super.onCreate(savedInstanceState);

app/src/main/res/values/strings.xml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
<string name="action_raw_message_list">Debug: raw messages</string>
66
<string name="settings_title">Settings</string>
77

8-
<string name="bluetooth_not_supported">Noise cannot sync over Bluetooth because Bluetooth LE is not supported on this device.</string>
9-
<string name="bluetooth_ask">Enable Bluetooth to let Noise send and receive messages.</string>
8+
<string name="bluetooth_not_supported">Noise cannot sync over Bluetooth because Bluetooth LE beacons are not supported on this device.</string>
9+
<string name="bluetooth_ask_enable">Enable Bluetooth to let Noise send and receive messages.</string>
10+
<string name="bluetooth_ask_address">Copy the Bluetooth hardware address into Noise\'s settings to send and receive messages.</string>
1011
<string name="bluetooth_sync_started">Bluetooth is on. Noise will now send and receive messages in the background.</string>
1112
<string name="bluetooth_sync_stopped">Bluetooth is off. Noise will not be able to send or receive messages.</string>
1213

0 commit comments

Comments
 (0)