beninabox_uk
beninabox_uk

Reputation: 684

Bluetooth Location Permissions on Android 6.0

First of all, I'd just like to point out that I've been learning Android for only a few weeks now so please excuse any shabby code. I'm trying to make an app which will simply list nearby bluetooth low energy devices (in the logs for now) and eventually connect to them.

Several weeks after starting this project, I have hit a brick wall with Android 6.0 location permissions that are required for a successful bluetooth scan. The following code is what I have at the moment:

package com.benstechtips.bluetoothletest4;

import android.annotation.TargetApi;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

@TargetApi(21)
public class MainActivity extends AppCompatActivity {

private BluetoothAdapter mBluetoothAdapter;
private int REQUEST_ENABLE_BT = 1;
private Handler mHandler;
private static final long SCAN_PERIOD = 10000;
private BluetoothLeScanner mLEScanner;
private ScanSettings settings;
private List<ScanFilter> filters;
private BluetoothGatt mGatt;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mHandler = new Handler();
    if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
        Toast.makeText(this, "BLE Not Supported", Toast.LENGTH_SHORT).show();
        finish();
    }
    final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
    mBluetoothAdapter = bluetoothManager.getAdapter();


}

@Override
protected void onResume() {
    super.onResume();

    if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
    } else {
        if (Build.VERSION.SDK_INT >= 21) {
            mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
            settings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
            filters = new ArrayList<ScanFilter>();
        }
        scanLeDevice(true);
    }
}

@Override
protected void onPause() {
    super.onPause();
    if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {
        scanLeDevice(false);
    }
}

@Override
protected void onDestroy() {
    if (mGatt == null) {
        return;
    }
    mGatt.close();
    mGatt = null;
    super.onDestroy();
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_ENABLE_BT) {
        if (resultCode == Activity.RESULT_CANCELED) {
            //Bluetooth not enabled.
            finish();
            return;
        }
    }
    super.onActivityResult(requestCode, resultCode, data);
}

private void scanLeDevice(final boolean enable) {
    if (enable) {
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.i("Scan Status", "Stopping Scan...");
                if (Build.VERSION.SDK_INT < 21) {
                    mBluetoothAdapter.stopLeScan(mLeScanCallback);
                } else {
                    mLEScanner.stopScan(mScanCallback);

                }
            }
        }, SCAN_PERIOD);
        if (Build.VERSION.SDK_INT < 21) {
            Log.i("Scan Status", "Starting Scan...");
            mBluetoothAdapter.startLeScan(mLeScanCallback);
        } else {
            mLEScanner.startScan(filters, settings, mScanCallback);
        }
    } else {
        Log.i("Scan Status", "Stopping Scan...");
        if (Build.VERSION.SDK_INT < 21) {
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
        } else {
            mLEScanner.stopScan(mScanCallback);
        }
    }
}

private ScanCallback mScanCallback = new ScanCallback() {
    @Override
    public void onScanResult(int callbackType, ScanResult result) {
        Log.i("callbackType", String.valueOf(callbackType));
        Log.i("result", result.toString());
        BluetoothDevice btDevice = result.getDevice();
        //connectToDevice(btDevice);
    }

    @Override
    public void onBatchScanResults(List<ScanResult> results) {
        for (ScanResult sr : results) {
            Log.i("ScanResult - Results", sr.toString());
        }
    }

    @Override
    public void onScanFailed(int errorCode) {
        Log.e("Scan Failed", "Error Code: " + errorCode);
    }
};

private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
            @Override
            public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Log.i("onLeScan", device.toString());
                        //connectToDevice(device);
                    }
                });
            }
};

public void connectToDevice(BluetoothDevice device) {
    if (mGatt == null) {
        mGatt = device.connectGatt(this, false, gattCallback);
        scanLeDevice(false);// will stop after first device detection
    }
}

private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        Log.i("onConnectionStateChange", "Status: " + status);
        switch (newState) {
            case BluetoothProfile.STATE_CONNECTED:
                Log.i("gattCallback", "STATE_CONNECTED");
                gatt.discoverServices();
                break;
            case BluetoothProfile.STATE_DISCONNECTED:
                Log.e("gattCallback", "STATE_DISCONNECTED");
                break;
            default:
                Log.e("gattCallback", "STATE_OTHER");
        }

    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        List<BluetoothGattService> services = gatt.getServices();
        Log.i("onServicesDiscovered", services.toString());
        gatt.readCharacteristic(services.get(1).getCharacteristics().get
                (0));
    }

    @Override
    public void onCharacteristicRead(BluetoothGatt gatt,
                                     BluetoothGattCharacteristic
                                             characteristic, int status) {
        Log.i("onCharacteristicRead", characteristic.toString());
        gatt.disconnect();
    }
};

Now, as far as I can tell, on a version of Android prior to version 6.0, this code would work and display the names of discovered devices (I have disabled the ability to connect to the device for the moment). However, on Android 6.0, I get the following error:

java.lang.SecurityException: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission to get scan results

I understand that this is due to my app not having required location permissions. Although I have the following in my AndroidManifest.xml file, I know I need to request permission from the user.

<?xml version="1.0" encoding="utf-8"?>

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

But my question is, how do I request permission from the user?

Upvotes: 0

Views: 3259

Answers (1)

spurrkins
spurrkins

Reputation: 126

I encountered this same problem! This was a recently added security feature in Marshmallow. Here is what requesting the permission looks like:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (this.checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{android.Manifest.permission.ACCESS_COARSE_LOCATION},
                    PERMISSION_REQUEST_COARSE_LOCATION);
        }
}

such that

private static final int PERMISSION_REQUEST_COARSE_LOCATION = 1;

I just chose coarse location. As far as I know, it doesn't matter which one you pick. You can put this in the onCreate for your main activity. Hope this helps!

Upvotes: 2

Related Questions