6

I'm trying to fetch the UUIDS from a remote bluetooth device like this:

        device.fetchUuidsWithSdp();

This will work silently and without user interaction on all devices except those with Android 6.0 which visibly ask with a pairing dialog to connect with the remote device to fetch the UUID. Is that an expected behaviour? Where is this documented? Is there a way to trigger UUID discovery without explicitly having to allow it from the other end?

Roberto Betancourt
  • 2,375
  • 3
  • 27
  • 35
  • Looked at the source code of the BluetoothDevice for [lollipop](https://android.googlesource.com/platform/frameworks/base/+/lollipop-release/core/java/android/bluetooth/BluetoothDevice.java#1013) and [marshmallow](https://android.googlesource.com/platform/frameworks/base/+/marshmallow-release/core/java/android/bluetooth/BluetoothDevice.java#1081) but only a runtime permission annotation was added. So I cannot explain why a "pairing dialog" pops up. Maybe to make the user aware that your are communicating with a device? – Jeroen Mols Nov 25 '15 at 14:25
  • @jmols maybe. But that's why i would like an official response from Google. Fetching UUIDs doesn't necessarily mean accessing personal data. I'd much prefer showing a dialog when a secure socket connection is established and not before. – Roberto Betancourt Nov 25 '15 at 17:42

2 Answers2

3

I've found a workaround to this by using the hidden sdpSearch method instead of fetchUuidsWithSdp. This requires a bit of reflection. This worked for me on android 6.0 and 5.1.1, without the devices trying to pair. Hope this helps, and feel free to improve the rather poor exception handling.

public class DeviceFinder{

public interface Callback{
    void onDeviceFound(BluetoothDevice bd);
    void onFinishedCallback();
    void onStartCallback();
}

private ArrayList<BluetoothDevice> tempDevices = new ArrayList<>();
private Callback mCallback;
private Context mContext;
private String ACTION_SDP_RECORD;
private String EXTRA_SDP_SEARCH_RESULT;

private BroadcastReceiver mReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (BluetoothDevice.ACTION_FOUND.equals(action)){
            // Aggregating found devices
            BluetoothDevice bd = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            tempDevices.add(bd);
        }else if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)){
            // Prepare for new search
            tempDevices = new ArrayList<>();
            mCallback.onStartCallback();
        }else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)){
            // Do a sdpSearch for all found devices
            for (BluetoothDevice bd : tempDevices){
                try {
                    Method m = bd.getClass().getDeclaredMethod("sdpSearch", ParcelUuid.class);
                    m.invoke(bd, new ParcelUuid(/* your uuid here */));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            mCallback.onFinishedCallback();
        }else if( ACTION_SDP_RECORD.equals(action)){
            // check if the device has the specified uuid
            BluetoothDevice bd = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            if (intent.getIntExtra(EXTRA_SDP_SEARCH_RESULT, 1) == 0){
                mCallback.onDeviceFound(bd);
            }
        }
    }
};


public DeviceFinder(Context context, Callback mCallback){
    this.mCallback = mCallback;
    this.mContext = context;

    try {
        Field f = BluetoothDevice.class.getDeclaredField("ACTION_SDP_RECORD");
        ACTION_SDP_RECORD = ((String)f.get(null));
        f = BluetoothDevice.class.getDeclaredField("EXTRA_SDP_SEARCH_STATUS");
        EXTRA_SDP_SEARCH_RESULT = ((String)f.get(null));
    } catch (Exception e) {
        e.printStackTrace();
    }


    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(BluetoothDevice.ACTION_FOUND);
    intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
    intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
    intentFilter.addAction(ACTION_SDP_RECORD);
    context.registerReceiver(mReceiver, intentFilter);
    startScan();
}

public void startScan(){
    BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (!bluetoothAdapter.isDiscovering()) {
        bluetoothAdapter.startDiscovery();
    }
}

public void unregisterReciever(){
    mContext.unregisterReceiver(mReceiver);
}
}

edit: sdpSearch was added in android 6.0, so it does not work for earlier versions

Alexander K
  • 164
  • 1
  • 7
2

update

Google's official response to your question:

Hi, We understand you have query on particular usage which can be looked up here in stackoverflow.

https://stackoverflow.com/questions/14812326/android-bluetooth-get-uuids-of-discovered-devices

Thanks


In Android 6.0 there are changes to permissions. Introducing runtime permissions, rather than a global acceptance of all app required permission upon installation of the app.

In a nutshell, permissions types are loosely put into two types, normal or dangerous. Any permission that encroaches upon a user's privacy is regarded as dangerous.

Permissions are also placed into groups of similar permissions, example access fine location and coarse location.

Users are given a choice whilst using the app to accept the permission of a particular group that is regarded as dangerous. Once the permission for a particular group is given for that app run, it is not asked again for permissions from within the same group, but will be asked for permissions from other groups as required.

See details below:

Runtime permisssions.

This release introduces a new permissions model, where users can now directly manage app permissions at runtime. This model gives users improved visibility and control over permissions, while streamlining the installation and auto-update processes for app developers. Users can grant or revoke permissions individually for installed apps.

On your apps that target Android 6.0 (API level 23) or higher, make sure to check for and request permissions at runtime. To determine if your app has been granted a permission, call the new checkSelfPermission() method. To request a permission, call the new requestPermissions() method. Even if your app is not targeting Android 6.0 (API level 23), you should test your app under the new permissions model.

.../

Beginning with Android 6.0 (API level 23), users grant and revoke app permissions at run time, instead of doing so when they install the app. As a result, you'll have to test your app under a wider range of conditions. Prior to Android 6.0, you could reasonably assume that if your app is running at all, it has all the permissions it declares in the app manifest. Under the new permissions model, you can no longer make that assumption.

Permissions are classed as normal or dangerous

Bluetooth access is regarded as a normal permission, whilst pairing or reading details of an app is regarded as dangerous.

Dangerous permissions cover areas where the app wants data or resources that involve the user's private information, or could potentially affect the user's stored data or the operation of other apps. For example, the ability to read the user's contacts is a dangerous permission. If an app declares that it needs a dangerous permission, the user has to explicitly grant the permission to the app.

So when requesting information from a device:

If an app requests a dangerous permission listed in its manifest, and the app does not currently have any permissions in the permission group, the system shows a dialog box to the user describing the permission group that the app wants access to. The dialog box does not describe the specific permission within that group. For example, if an app requests the READ_CONTACTS permission, the system dialog box just says the app needs access to the device's contacts. If the user grants approval, the system gives the app just the permission it requested.

See Working With System Permissions for details of implementation.


edit In response to your- comment

enter image description here

There are multiple issues with bluetooth bugs and the release of android 6.0. as there was with 5.0, it is anticipated these will be fixed with the next patch. However, I don't see your issue as a bug.

And after seeing your post here on google, my answer directly answers what your are perceiving as a bug.

Your screen shot:

enter image description here

And from your code here on github:

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "me.bluetoothuuidsample"
        minSdkVersion 18
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }

The marshmallow devices are behaving exactly as they are intended to with runtime permissions, but I am sure google will confirm this for you.

To prevent having to deal with the changeover at this stage, use the target sdk as less than 23.

targetSdkVersion 22

This Cheesefactory blog goes into detail well. Everything every Android Developer must know about new Android's Runtime Permission

edit 2

Fetching UUIDs requires location permission, there's a post here that's goes into more details: https://stackoverflow.com/a/33045489/3956566

Community
  • 1
  • 1
  • Sorry, this has nothing to do with the issue at hand. Im aware that Bluetooth scanning requires Location permissions but i already added it. – Roberto Betancourt Nov 23 '15 at 16:20