I am scanning for Bluetooth LE devices and running as a Peripheral (running Android 6.0 on a Moto G 2nd Gen)
The problem I am having is that sometimes (randomly it seems but often) it will not find any of my other peripheral devices, the other times it works fine.
I have a companion iOS device running similar code (both scanning for peripherals and acting as a peripheral), and when the Android scanning can't find the iOS device, my iOS finds the Android device acting as a peripheral just fine. So it seems only to be a problem with the scanning side of things.
It's not only just not finding my companion iOS device, but doesn't find any Bluetooth devices. When it works, it finds my companion iOS device as well as a bunch of other devices.
I have tried it with and without ScanFilters, and get the same issue.
I am building against SDK 26 with a minimum SDK of 23.
I am setting the permissions that are needed, as it sometimes works.
Relevant code below:
private void startScanning() {
mHandler = new Handler(mContext.getMainLooper());
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setReportDelay(0)
.build();
mBluetoothLeScanner.startScan(null, settings, mScanCallback);
}
}, 1000);
}
private ScanCallback mScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
if( result == null || result.getDevice() == null )
return;
Log.e("myTest", "Found Device");
BluetoothDevice device = result.getDevice();
final String deviceAddress = device.getAddress();
List<ParcelUuid> parcel = result.getScanRecord().getServiceUuids();
if (parcel != null) {
String parcelUUID = parcel.toString().substring(1,37);
if (parcelUUID.equalsIgnoreCase(mContext.getString(R.string.service_uuid))) {
final BluetoothDevice bleDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(deviceAddress);
if (!seenPeripherals.contains(deviceAddress)) {
stopScanning();
mHandler = new Handler(mContext.getMainLooper());
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
Log.e("AppToApp", "Trying to connect to device " + deviceAddress);
mGatt = bleDevice.connectGatt(mContext, false, mGattCallback);
}
}, 1000);
}
}
}
}
}
I face the same issue. This is because Google policy has been changed for Marshmallow API 23 and higher version, to use BLE user need to turn ON GPS. For Google Docs check this Permission # Runtime link. To fix it you have to enable "Location" in the settings of the phone as well as request location permission in the app at Runtime. Both need to be done for scanning to work properly.
To request the location permission put the following in a dialog or the likes:
myActivity.requestPermissions(new String[]{Manifest.permission.ACCESS_COURSE_LOCATION}, yourPermissionRequestCode);
and implement:
#Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] Results){
if(requestCode == yourPermissionRequestCode)
{
... //Do something based on Results
}
}
in myActivity handle whatever the user selects. You also need to do the following to turn on your device's location services:
Intent enableLocationIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
myActivity.startActivityForResult(enableLocationIntent, yourServiceRequestCode);
You can check if the user turned on the location services by implementing:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if(requestCode == yourServiceRequestCode)
{
...//Do whatever you need to
}
}
in myActivity. You can also manually turn on location services by doing:
Enter phone settings -> Select "Apps" -> Select your app -> Select the "Permissions" option -> Switch the "Location" permission on.
Once the user has enabled the permission and started location services then you could start scanning for peripherals. I've noticed that if you are already scanning while you enable the permission/turn on the location service it will still not put anything in your onScanResults
This allow companies to see your location and direct you to where they want.
while SCANNING for device, Once you are connected to the BLE device you can Turn Off the location service on your phone and you will still stay connected to your peripheral device. However, once you connected to a firmware (peripheral) you cannot then connect to any new peripheral until you disconnect the connection to the paired device, and all other scanning devices(mobile) cannot see the peripheral device name in their scanned list (when user search for near by peripheral device) when the peripheral is already connected to any other mobile device, because a peripheral can be connect to only 1 device at a time.
For BLE basic sample you can check this Truiton Ble tutorial. This snippet will fix your Issue I think. Happy coding :)
It might just simply be the case that the Android phone has a crappy Bluetooth chip. Have you looked at the hci log? Or logcat?
Bluetooth 4.0 chips (which is in your moto g) have strict limitations that you can't be a central and a peripheral at the same time, even though scanning should be allowed all the time. Therefore I wouldn't make a product that depends on the peripheral feature in Android until BT 4.0 is phased out.
You should test with a newer phone that has at least a Bluetooth 4.1 chip.
This question is related to something I posted before. Check these links
https://stackoverflow.com/a/39084810/3997720, https://stackoverflow.com/a/39597845/3997720
You need two methods: one for older api and one for newest api.
As for SDK 23 and up, You have to request run time permissions. Ask for location permission.
If you're using the app for non-commercial purposes (meaning expecting less than 5K users), you can use P2Pkit.
It uses P2P wifi connection in addition to BLE and non-BLE bluetooth, on older versions of android, and actually gets great results on discovery of nearby phones.
Upsides are it works on IOS too, and it has a fairly simple interface.
Downside is over 5K users you have to pay..
Related
I am developing a solution to check BLE devices and I used the native API that comes with Android to check BluetoothLeScanner.
Wanted to understand a little better operation, I take the location permission and bluetooth.
After the scan starts, I turn Bluetooth on my phone to off, on Moto G2 Android 6.0 Scan still keeps giving me the expected result when I test on a Samsung S8 Android 9 and Sony Xperia T2 Ultra Android 5.1 in the log I get which was bluetooth disabled and the scan was stopped.
I can only perform the test when I purchase it as follows
bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
bluetoothAdapter = bluetoothManager.adapter
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
bluetoothScanner = bluetoothAdapter.bluetoothLeScanner
}
#TargetApi(Build.VERSION_CODES.M)
class BleScanCallback(resultMap: MutableMap) : ScanCallback() {
var resultOfScan = resultMap
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
#TargetApi(Build.VERSION_CODES.M)
override fun onScanResult(callbackType: Int, result: ScanResult?) {
addScanResult(result)
Log.v("Main Activity", "I found a ble device ${result}")
Log.v("Main Activity", "I found a ble device ${result?.device?.address}")
}
override fun onBatchScanResults(results: MutableList<ScanResult>?) {
results?.forEach { result -> addScanResult(result) }
}
override fun onScanFailed(errorCode: Int) {
Log.v("Main Activity","Bluetooth LE scan failed. Error code: $errorCode")
}
fun addScanResult(scanResult: ScanResult?) {
val bleDevice = scanResult?.device
val deviceAddress = bleDevice?.address
resultOfScan.put(deviceAddress, bleDevice)
}
scanResult is bringing the necessary information when bluetooth is online, I already set it up as the image below
https://i.stack.imgur.com/o9jGRm.png
I see that this makes scanning even off
On some Android devices including Pixel phones, Android One devices, and unmodified AOSP builds, turning off bluetooth in the quick settings panel doesn't really turn off bluetooth. Instead, it merely blocks bluetooth connections and pairing in software, yet allows Bluetooth LE scans to continue unaffected. As #Jorgesys correctly notes, it is impossible to detect BLE devices if the Bluetooth radio is really turned off, so let me say again: despite what the quick settings panel says, bluetooth is not necessarily powered off.
On supported devices, this happens only if two things are true:
Bluetooth is turned on in the full settings menu (On Android 9: Settings -> Connected Devices -> Connection preferences -> Bluetooth ON)
The user has selected to "Allow apps and services to scan for nearby devices at any time, even when Bluetooth is off. This can be used, for example, to improve location-based features and services." (Settings -> Security & Location -> Location -> Advanced -> Scanning -> Bluetooth scanning ON)
There is no way to detect BLE devices with bluetooth off
Bluetooth must be enabled
Set up BLE
Before your application can communicate over BLE, you need
to verify that BLE is supported on the device, and if so, ensure that
it is enabled.
I have written an app that connects to a BLE device. The app works OK on most devices; but some devices (most noticeably Huawei P8 Lite and Nexus 6P) refuse to connect after the Bluetooth adapter has been disabled.
This is the test sequence:
Make sure the app is NOT running.
Slide down from the top, disable BT for a couple of seconds, then re-enable bluetooth.
Start the app. The app automatically connects to a bluetooth address stored in the preferences.
Wait for connect. This is where nothing happens on Huawei phones, but other phones, such as Samsung, works like a charm.
Verify from another phone the device is advertising and you can
connect to it.
This is the code I use to connect:
private final Runnable mBeginConnectRunnable = new Runnable() {
#Override
public void run() {
synchronized (GattConnection.this) {
if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {
try {
mBluetoothAdapter.cancelDiscovery();
mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(mAddress);
mGatt = mBluetoothDevice.connectGatt(mContext, mBackgroundConnect, mGattCallback);
final boolean connectSuccess = mGatt.connect();
Log.d(TAG, String.format(Locale.ENGLISH, "mGatt.connect(%s, %s) %s",
mAddress,
mBackgroundConnect ? "background[slow]" : "foreground[fast]",
connectSuccess ? "success" : "failed"));
refreshDeviceCache(mGatt);
} catch (Exception ex) {
Log.e(TAG, "Create connection failed: " + ex.getMessage());
setState(State.Closed);
}
} else {
Log.d(TAG, "Can't create connection. Adapter is disabled");
setState(State.Closed);
}
}
}
};
All calls are posted via a Handler to the main thread. I can see it waits for a connect, gives up after 30 seconds at which I call BluetoothGatt.close() on the object and nulls it. It's like nothing is out there.
After some time, later in the day, it works again.
Help is highly appreciated :-)
Update September 14, 2018: After great explanation from Emil I've updated our app and as such don't have this problem on the Nexus. I've noticed the Huawei P8 Lite continues to scan in the background and it seems there is nothing you can do to stop it.
To demonstrate the problems I've made a very simple and clean app that exercise the Bluetooth LE functionality on a phone and used it to demonstrate this problem and also the P8 is broken. The app is available here: https://play.google.com/store/apps/details?id=eu.millibit.bluetootherror
Source is available here: https://bitbucket.org/millibit/eu.millibit.bluetootherror/src/master/
I hope I over time can extend this app to make it a test vehicle for Android documenting all the stange behavior from Android and collect it in a database. In case you are interested in contributing, don't hesitate to drop me a mail on bt.error#millibit.dk
The Android Bluetooth stack has a design flaw in its API. When you connect to a specific device by Bluetooth Device Address, there is no way to tell if you mean a Public address or Random address.
If you start to connect to a device with autoConnect=false which is not bonded and has not recently been seen in a Scan, it will assume you mean a Public address. So if you try to connect to a device having a static random address, it will fail.
To be sure you connect with the correct address type if the device is not bonded, you MUST perform a scan first, find the device and THEN start the connection attempt.
I want to establish a WiFi-Direct Connection with another device peered through NFC. My steps are as follows:
First, Device A gets its own WiFiP2P address and transmits it to Device B via NFC.
Then, Device B tries to establish a connection with Device A using the provided address.
As you can see I didn't involve discovering peers in the process. But when Device B is trying to connect, the result is always failed (reason 0, this should be ERROR).
I think this might be related to device visibility, but I don't know and can't find any code to make a device visible.
Here's my code:
//NOTE: These code should be executed on Device B
//Starting WiFi Direct Transmission
//First we should establish a connection
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = remoteWifiP2pDevice;
//remoteWifiP2pDevice is the address of device A obtained from NFC
config.wps.setup = WpsInfo.PBC;
mManager.connect(mChannel, config, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
//success logic
Toast.makeText(MainActivity.this, "success", Toast.LENGTH_SHORT).show();
if (!FILE_RECV)
{
new SendFilesTask().execute("");
}
}
#Override
public void onFailure(int reason) {
//failure logic
Toast.makeText(MainActivity.this, "failed" + reason, Toast.LENGTH_SHORT).show();
}
});
In OnCreate() I have
intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
mChannel = mManager.initialize(this, getMainLooper(), null);
mReceiver = new WiFiDirectBroadcastReceiver(mManager, mChannel, this);
The WifiDirectBroadcastReceiver has code only related to getting Device A's address and can be considered empty.
So what's wrong with these and how can I fix it?
Thanks in advance.
P.S. If I connect Device A and B manually, and run my code again, it returns success.
WIfi Direct assumptions:
All the devices should be in discoverable (scanning mode)
simultaneously.
Devices scan for a 30 sec and after that
it stops by default.So we need to initiate scan programatically
using discoverpeers method.
Most important is displaying
nearby devices is device specific.ie sometimes devices wont show
nearby ones eventhough they are available.Thats why wifi direct
is not reliable and because of these, there wont be much wifi
direct apps in play store.
I have discovered that delaying several seconds will make the connection succeed. I don't know the reason, but this can be used as a workaround.
So after all is there a better solution to this? And why does the delay work?
With Android 4.3, Android implemented the idea of always-on WiFi where, even if you had Wi-Fi toggled off, the device and apps could still scan for WiFi networks to improve the location's accuracy. Along with using network triangulation, it's another way of getting your current position as quickly as possible without having to rely too much on GPS signals.
Android M is taking the idea further, adding Bluetooth scanning to the equation. Under the Location settings on M, you'll find a Scanning option in the menu, where both Wifi and Bluetooth scanning can be toggled on and off. When enabled, Bluetooth scanning will presumably look for BLE devices like beacons to get a quicker location fix.
Image resized. Click to view in full size
This may be very useful in the future inside malls, airports, and various indoor or underground locations where the reach and dispersion of Bluetooth beacons can outweigh a slow or impossible GPS signal lock. And the fact that it's always on, accessible whenever apps need a location fix, will make it even handier than if you had to remember to manually turn on Bluetooth.
Can anyone help in providing some insights or sample code for scanning for beacons with BLE without the main Bluetooth settings turned on?
I figured it out.
We have to write a system application and use the
BluetoothAdapter.enableBLE()
method.
This method is for special/system apps which use Bluetooth Low energy to scan for nearby devices which is mostly used for location accuracy.Even if the Bluetooth is turned off in the device settings.
Then we can use
BluetoothAdapter.LeScanCallback
callback to get the device details.
Sample:
for calling the method:
mBluetoothAdapter.enableBLE())
for callback:
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
#Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
if( device == null ){
System.out.println("-------onLeScan "+device);
}
runOnUiThread(new Runnable() {
#Override
public void run() {
mLeDeviceListAdapter.addDevice(device);
mLeDeviceListAdapter.notifyDataSetChanged();
}
});
}
};
Thanks
Alright, I've got a bit of a weird question here. I'm working on an Android game where I'd like to be able to have Android phones detect the presence of each other.
The device searching for other players will know the bluetooth mac addresses of the other players' devices (from a game DB), however the devices will not be paired and the devices will not be in discoverable mode. Also, there will only be a handful of devices that could possibly be found - so it's not a big deal to scan through mac addresses.
I don't need to connect to the devices, I just need to be able to answer one simple question: is this device with this mac address nearby?
It is permissible to have a pairing dialog appear on the other user's screen...I don't care what the outcome of their choice is...I just need to know if their device is there.
Any help would be greatly appreciated!
This use-case may be a good fit for the recently released Nearby API. See the Nearby Messages developer overview
Nearby has its own runtime permission saving you from adding BLUETOOTH_ADMIN or similar to your manifest. It works across iOS and Android by utilizing multiple technologies (Classic Bluetooth, BLE, ultrasound). There's an option to use only the ultrasonic modem which reduces the range to about 5 feet.
I've included a partial example below, you can find a more complete sample on github
// Call this when the user clicks "find players" or similar
// In the ResultCallback you'll want to trigger the permission
// dialog
Nearby.Messages.getPermissionStatus(client)
.setResultCallback(new ResultCallback<Status>() {
public void onResult(Status status) {
// Request Nearby runtime permission if missing
// ... see github sample for details
// If you already have the Nearby permission,
// call publishAndSubscribe()
}
});
void publishAndSubscribe() {
// You can put whatever you want in the message up to a modest
// size limit (currently 100KB). Smaller will be faster, though.
Message msg = "your device identifier/MAC/etc.".getBytes();
Nearby.Messages.publish(googleApiClient, msg)
.setResultCallback(...);
MessageListener listener = new MessageListener() {
public void onFound(Message msg) {
Log.i(TAG, "You found another device " + new String(msg));
}
});
Nearby.Messages.subscribe(googleApiClient, listener)
.setResultCallback(...);
}
Disclaimer I work on the Nearby API