I'm creating my first android app, so I'm really a beginner with (android) development.
The first fase of the app is to implement an Activity (i.e. BleActivity.java), which is called when a certain button is clicked. The BleActivity should list available BLE devices (testing with a tiSensorTag CC2650) and later on I want to read the data from the devices.
It was quite a challenge to get it to work because most of the tutorials online are written with deprecated API's. After combining several tutorials, the app is now working!
There is one bug in the app which I'm unable to fix:
When bluetooth is turned off and I trigger the BleActivity, the onResume() checks if bluetooth is enabled (which is not the case) and if it's not, a dialog screen appears requesting the user to turn bluetooth on.
I can see the dialog screen but before I'm able to use it, the app crashes.
BleActivity.java
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ListActivity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
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.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class BleActivity extends ListActivity {
public static final int MY_PERMISSIONS_REQUEST_LOCATION = 99;
// see nested class LeDeviceListAdapter
private LeDeviceListAdapter mLeDeviceListAdapter;
private BluetoothAdapter mBluetoothAdapter;
private static final int REQUEST_ENABLE_BT = 1;
private static final long SCAN_PERIOD = 10000;
private Handler mHandler;
private boolean mScanning;
private BluetoothLeScanner mLEScanner;
private ScanSettings settings;
private List<ScanFilter> filters;
private static final String INFO = "ZINFO";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Check if BLE is supported
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
finish();
} else {
Log.i(INFO, "onCreate: BLE is supported");
}
// Create a Handler to send and process Message Class and Runnable objects associated with a thread's MessageQueue.
mHandler = new Handler();
// Get BluetoothManager and BluetoothAdapter in order to conduct overall Bluetooth Management.
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
}
#Override
protected void onResume() {
super.onResume();
// Initialize list view adapter
mLeDeviceListAdapter = new LeDeviceListAdapter();
setListAdapter(mLeDeviceListAdapter);
//Check if permission for ACCESS_COARSE_LOCATION (AndroidManifest.xml) is granted.
if (checkLocationPermission()) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, R.string.grant_permission, Toast.LENGTH_LONG).show();
finish();
} else {
Log.i(INFO, "onResume: Permission for ACCESS_COARSE_LOCATION is granted");
}
}
// Check if bluetoothAdapter is successfully obtained and if BLE is enabled.
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
} else {
Log.i(INFO, "onResume: BLE is enabled");
}
// GET getBluetoothLeScanner(): This class provides methods to perform scan related operations for Bluetooth LE devices
mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
// Set scan settings
settings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_BALANCED).build();
// Set device filter (null is allowed)
filters = new ArrayList<ScanFilter>();
// START SCAN FOR BLE DEVICES!
scanLeDevice(true);
}
// When user denies prompt for enabling Bluetooth, the Activity is closed
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_ENABLE_BT && resultCode == Activity.RESULT_CANCELED) {
finish();
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
// Pause scanning for BLE devices */
#Override
protected void onPause() {
super.onPause();
scanLeDevice(false);
mLeDeviceListAdapter.clear();
}
//Methods for permission check
public boolean checkLocationPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_COARSE_LOCATION)) {
new AlertDialog.Builder(this)
.setTitle(R.string.title_location_permission)
.setMessage(R.string.text_location_permission)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
ActivityCompat.requestPermissions(BleActivity.this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, MY_PERMISSIONS_REQUEST_LOCATION);
}
})
.create()
.show();
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission. ACCESS_COARSE_LOCATION}, MY_PERMISSIONS_REQUEST_LOCATION);
}
return false;
} else {
return true;
}
}
#Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_LOCATION: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.i(INFO, "onRequestPermissionsResult: PERMISSION_GRANTED");
} else {
finish();
}
}
}
}
// Methods for START & STOP scan
private void scanLeDevice(final boolean enable) {
if (enable) {
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
mScanning = false;
mLEScanner.stopScan(mScanCallback); //STOP SCANNING
}
}, SCAN_PERIOD); // Stops scanning after a pre-defined scan period.
mScanning = true;
mLEScanner.startScan(filters, settings, mScanCallback); //START SCANNING
} else {
mScanning = false;
mLEScanner.stopScan(mScanCallback); //STOP SCANNING
}
if(mScanning == true) {
//TODO: Implement code for when the app is scanning (green stoplight, turning wheel, etc.)
} else {
//TODO: Implement code for when the app is NOT scanning (red stoplight, idle wheel, etc.)
}
}
// Bluetooth LE scan results are reported using these callbacks.
private ScanCallback mScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
BluetoothDevice btDevice = result.getDevice();
mLeDeviceListAdapter.addDevice(btDevice);
mLeDeviceListAdapter.notifyDataSetChanged(); }
#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);
}
};
// ListAdapter for holding devices found through scanning.
private class LeDeviceListAdapter extends BaseAdapter {
private ArrayList<BluetoothDevice> mLeDevices;
private LayoutInflater mInflator;
public LeDeviceListAdapter() {
super();
mLeDevices = new ArrayList<BluetoothDevice>();
mInflator = BleActivity.this.getLayoutInflater();
}
public void addDevice(BluetoothDevice device) {
if(!mLeDevices.contains(device)) {
mLeDevices.add(device);
}
}
/*
TODO: implement onListItemClick (see example code)
public BluetoothDevice getDevice(int position) {
return mLeDevices.get(position);
}*/
public void clear() {
mLeDevices.clear();
}
#Override
public int getCount() {
return mLeDevices.size();
}
#Override
public Object getItem(int i) {
return mLeDevices.get(i);
}
#Override
public long getItemId(int i) {
return i;
}
#Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder;
// General ListView optimization code.
if (view == null) {
view = mInflator.inflate(R.layout.activity_ble, null);
viewHolder = new ViewHolder();
viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address);
viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
BluetoothDevice device = mLeDevices.get(i);
final String deviceName = device.getName();
if (deviceName != null && deviceName.length() > 0)
viewHolder.deviceName.setText(deviceName);
else
viewHolder.deviceName.setText(R.string.unknown_device);
viewHolder.deviceAddress.setText(device.getAddress());
return view;
}
}
static class ViewHolder {
TextView deviceName;
TextView deviceAddress;
}
}
Your help is much appreciated and please keep in mind that this is my first app and I'm still an amateur/beginner developer.
logcat:
08-06 16:21:39.152 5489-5489/nl.cargosys.iotcloudconnect E/AndroidRuntime: FATAL EXCEPTION: main
Process: nl.cargosys.iotcloudconnect, PID: 5489
java.lang.RuntimeException: Unable to pause activity {nl.cargosys.iotcloudconnect/nl.cargosys.iotcloudconnect.BleActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.bluetooth.le.BluetoothLeScanner.stopScan(android.bluetooth.le.ScanCallback)' on a null object reference
at android.app.ActivityThread.performPauseActivityIfNeeded(ActivityThread.java:4179)
at android.app.ActivityThread.performPauseActivity(ActivityThread.java:4145)
at android.app.ActivityThread.performPauseActivity(ActivityThread.java:4119)
at android.app.ActivityThread.handlePauseActivity(ActivityThread.java:4093)
at android.app.ActivityThread.-wrap18(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1654)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6776)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1410)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.bluetooth.le.BluetoothLeScanner.stopScan(android.bluetooth.le.ScanCallback)' on a null object reference
at nl.cargosys.iotcloudconnect.BleActivity.scanLeDevice(BleActivity.java:179)
at nl.cargosys.iotcloudconnect.BleActivity.onPause(BleActivity.java:121)
at android.app.Activity.performPause(Activity.java:7148)
at android.app.Instrumentation.callActivityOnPause(Instrumentation.java:1330)
at android.app.ActivityThread.performPauseActivityIfNeeded(ActivityThread.java:4168)
at android.app.ActivityThread.performPauseActivity(ActivityThread.java:4145)
at android.app.ActivityThread.performPauseActivity(ActivityThread.java:4119)
at android.app.ActivityThread.handlePauseActivity(ActivityThread.java:4093)
at android.app.ActivityThread.-wrap18(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1654)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6776)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1410)
In your if condition below -
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled())
The main problem lies in your -
mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
// Set scan settings
settings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_BALANCED).build();
// Set device filter (null is allowed)
filters = new ArrayList<ScanFilter>();
// START SCAN FOR BLE DEVICES!
scanLeDevice(true);
You check if the adapter is available and if not you start another activity for result, but you continue to do the operation in your scanner code and scan for device after your else. Ideally you should place the above code in your else statement and only start scanning device if you are 100% sure that you have Bluetooth enabled.
Update 1
Ok so this error is coming from your onPause() method where you are calling scanLeDevice(false); method. Now inside this method you do mLEScanner.stopScan(mScanCallback); but your mLEScanner is not yet initialized as the initialization code is in your else statement or after it in the line
mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
So it is better if you do this in your method-
if(mLEScanner!=null)
{
mLEScanner.stopScan(mScanCallback);
}
Related
I want make some project where Android can scan nearby Beacon/BLE and send it using MQTT. But I want the service to work in the background if the service work in the foreground it will interrupt the scanning process when screen is off.
This is my code for scanning:
package com.example.mqtt_active;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import org.eclipse.paho.android.service.MqttAndroidClient;
import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttException;
import java.nio.charset.StandardCharsets;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
private Button turnon, changeLayout;
MqttAndroidClient client;
private boolean state=false;
private BluetoothAdapter bluetoothAdapter;
public static final int REQUEST_ACCESS_COARSE_LOCATION = 1;
public static final int REQUEST_ENABLE_BLUETOOTH = 11;
public static String mqtt_server,mqtt_port,mqtt_id;
private TextView textView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().hide();
setContentView(R.layout.activity_main);
Log.d("Logger", "On Create Android");
turnon = findViewById(R.id.turnon);
changeLayout = findViewById(R.id.mqttSet);
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
textView = findViewById(R.id.textView4);
textView.setText("id "+mqtt_id+" port "+mqtt_port+" server "+mqtt_server);
client = new MqttAndroidClient(this.getApplicationContext(), "tcp://"+mqtt_server+":"+mqtt_port,mqtt_id);
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
stateCheck();
Log.d("Logger", "State Check");
handler.postDelayed(this, 1000);
}
}, 1000);
// final Handler handlerStop = new Handler();
// handlerStop.postDelayed(new Runnable() {
// #Override
// public void run() {
// bluetoothAdapter.cancelDiscovery();
// Log.d("Logger", "Cancel Dsicovery");
// handlerStop.postDelayed(this, 2000);
// }
// }, 2000);
turnon.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (!state){
turnon.setText("Turn Off");
Log.d("Logger", "Turn On State");
// if (bluetoothAdapter!=null & bluetoothAdapter.isEnabled()) {
// if(checkCoarsePermission()){
// bluetoothAdapter.startDiscovery();
// }
// }
if(mqtt_server!=null||mqtt_id!=null||mqtt_port!=null){
try {
Log.d("Logger", "Try ");
IMqttToken token = client.connect();
token.setActionCallback(new IMqttActionListener() {
#Override
public void onSuccess(IMqttToken asyncActionToken) {
Log.d("Logger", "Connect MQTT");
Toast.makeText(MainActivity.this,"connected!!",Toast.LENGTH_LONG).show();
}
#Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
Log.d("Logger", "Connect Failed");
Toast.makeText(MainActivity.this,"connection failed!!",Toast.LENGTH_LONG).show();
}
});
} catch (MqttException e) {
e.printStackTrace();
Log.d("Logger", "error"+e);
}}
state = true;
}else{
turnon.setText("Turn On");
state = false;
// bluetoothAdapter.cancelDiscovery();
}
}
});
changeLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
startActivity(new Intent(MainActivity.this,MqttActivity.class));
}
});
}
public void stateCheck(){
if (state){
if (bluetoothAdapter!=null & bluetoothAdapter.isEnabled()) {
if(checkCoarsePermission()){
Log.d("Logger", "Discover");
bluetoothAdapter.startDiscovery();
}
}
}
// else {
// Log.d("Logger", "Cancel");
// bluetoothAdapter.cancelDiscovery();
// }
}
private boolean checkCoarsePermission(){
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.ACCESS_COARSE_LOCATION},
REQUEST_ACCESS_COARSE_LOCATION);
return false;
}else {
return true;
}
}
#Override
protected void onResume() {
super.onResume();
registerReceiver(devicesFoundReceiver, new IntentFilter(BluetoothDevice.ACTION_FOUND));
registerReceiver(devicesFoundReceiver, new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_STARTED));
registerReceiver(devicesFoundReceiver, new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED));
}
#Override
protected void onPause() {
super.onPause();
unregisterReceiver(devicesFoundReceiver);
}
private final BroadcastReceiver devicesFoundReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action= intent.getAction();
if(BluetoothDevice.ACTION_FOUND.equals(action)){
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
int rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI,Short.MIN_VALUE);
String RSSI = String.valueOf(rssi);
Toast.makeText(context.getApplicationContext(),"rssi "+RSSI+" "+device.getAddress(),Toast.LENGTH_SHORT).show();
Log.d("Logger", "Recive data "+device.getAddress());
if(mqtt_server!=null||mqtt_id!=null||mqtt_port!=null){
try {
Log.d("Logger", "Sending data");
String payload = "rssi:"+RSSI+"mac:"+device.getAddress();
client.publish("test",payload.getBytes(),0,false);
} catch ( MqttException e) {
e.printStackTrace();
Log.d("Logger", "Error Sending "+e);
}}
}else if(BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)){
}else if(BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)){
}
}
};
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode){
case REQUEST_ACCESS_COARSE_LOCATION:
if(grantResults.length>0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
Toast.makeText(this,"ALLOWED", Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(this,"Forbidden",Toast.LENGTH_SHORT).show();
} break;
}
}
}
App Flow:
Insert MQTT server, port, id, topic.
Turn on the proccess.
Android scan BLE/Beacon
Android sending MAC/RSSI to MQTT
I hope someone can help to guide me, on how to make the application run in the background?
I'm a beginner, and I don't understand how to implement background service in my application. Please help me!
You need to implement a foreground service that will handle your ble scanning and MQTT logic.
See this article with an overview of how to do it. Depending on your build/target SDK, the implementation will vary.
I've created some java function in the MainActivity.java. I then add some Toast to determine the sequence of the functions. And then, the sequence of the functions are confusing. onCreate() started first, which is totally normal, followed by askCoarsePermission(). Then the weird thing happen, onResume() is then followed and proceed to call askBluetoothPermission(). What happen in askCoarsePermission() that it's able to call onResume()?
The sequence:
1. onCreate()
2. askCoarsePermission()
3. onResume()
4. askBluetoothPermission()
5. -- stops.. no functions is then being invoked --
I tried to check what are the function that can calls onResume(), but none of the askCoarsePermission() did call that function.
Here are the code for MainActivity.java:
package com.example.bluetoothscanexample;
import android.app.ListActivity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.widget.Toast;
public class MainActivity extends ListActivity {
private static int REQUEST_CODE_COARSE_PERMISSION = 1;
private static int REQUEST_CODE_BLUETOOTH_PERMISSION = 2;
// make scan period 10 seconds before starting the new one
private static int SCAN_PERIOD = 100;
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothLeScanner mBluetoothLeScanner;
private com.example.bluetoothscanexample.adapter.BluetoothLeListAdapter mLeDeviceListAdapter;
// No idea what is this lol
private Handler mHandler;
private boolean mScanning;
private ScanCallback mBluetoothScanCallBack = new ScanCallback() {
#Override
public void onScanResult(int callbackType, final ScanResult result) {
Toast.makeText(MainActivity.this, "onScanResult here...", Toast.LENGTH_LONG).show();
super.onScanResult(callbackType, result);
runOnUiThread(new Runnable() {
#Override
public void run() {
mLeDeviceListAdapter.addDevice(result.getDevice());
mLeDeviceListAdapter.notifyDataSetChanged();
}
});
}
#Override
public void onScanFailed(int errorCode) {
Toast.makeText(MainActivity.this, "onScanFailed here...", Toast.LENGTH_LONG).show();
super.onScanFailed(errorCode);
Toast.makeText(MainActivity.this, "Scan Failed: Please try again...", Toast.LENGTH_LONG).show();
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new Handler();
mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = mBluetoothManager.getAdapter();
mLeDeviceListAdapter = new com.example.bluetoothscanexample.adapter.BluetoothLeListAdapter(this);
// Add this line to make scanning work!!!
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
// check if this phone support Bluetooth Low Energy
if(!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)){
Toast.makeText(this,"ble_not_supported",Toast.LENGTH_SHORT);
finish();
}
Toast.makeText(MainActivity.this, "onCreate here..", Toast.LENGTH_LONG).show();
askCoarsePermission();
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data){
Toast.makeText(MainActivity.this, "onActivityResult here...", Toast.LENGTH_LONG).show();
super.onActivityResult(requestCode,resultCode,data);
if(requestCode == REQUEST_CODE_BLUETOOTH_PERMISSION){
askCoarsePermission();
}
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
scanBluetoothDevices(true);
}
#Override
protected void onResume(){
Toast.makeText(MainActivity.this, "onResume here...", Toast.LENGTH_LONG).show();
super.onResume();
setListAdapter(mLeDeviceListAdapter);
askBluetoothPermission();
}
#Override
protected void onPause(){
Toast.makeText(MainActivity.this, "onPause here...", Toast.LENGTH_LONG).show();
super.onPause();
mLeDeviceListAdapter.clear();
}
private void askCoarsePermission(){
Toast.makeText(MainActivity.this, "askCoarsePermission here...", Toast.LENGTH_LONG).show();
if(this.checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)!= PackageManager.PERMISSION_GRANTED){
requestPermissions(new String[]{android.Manifest.permission.ACCESS_COARSE_LOCATION},REQUEST_CODE_COARSE_PERMISSION);
}
}
private void askBluetoothPermission() {
Toast.makeText(MainActivity.this, "askBluetoothPermission here...", Toast.LENGTH_LONG).show();
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_CODE_BLUETOOTH_PERMISSION);
}
}
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults){
Toast.makeText(MainActivity.this, "onRequestPermissionResult here...", Toast.LENGTH_LONG).show();
super.onRequestPermissionsResult(requestCode,permissions,grantResults);
if(requestCode == REQUEST_CODE_COARSE_PERMISSION){
Toast.makeText(this,"Coarse Permission Granted",Toast.LENGTH_LONG).show();
}
}
private void scanBluetoothDevices(boolean enable){
Toast.makeText(MainActivity.this, "scanBluetoothDevices here...", Toast.LENGTH_LONG).show();
if(enable){
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
mScanning = false;
mBluetoothLeScanner.stopScan(mBluetoothScanCallBack);
}
}, SCAN_PERIOD);
mScanning = true;
mBluetoothLeScanner.startScan(mBluetoothScanCallBack);
}else{
mScanning = false;
mBluetoothLeScanner.stopScan(mBluetoothScanCallBack);
}
}
}
One more issue followed. Why does the java stops at askBluetoothPermission() and no other function runs? I am so confusing here. Someone please enlighten me.
this is the regular Activity life cycle, first onCreate -> onStart -> onResume.
To understand better take a look in this docs
My current android app can turn on Bluetooth, make itself discoverable, list paired devices, and select a file from the device ( ie an image). However when I attempted to hit "send" the app seems to crash with an error. Not sure if it's refusing to send, or if it's not actually obtaining the file (yesterday I had a problem where it refused to send the selected, saying for me to select a file repeatedly. I will post the crash results and my mainActivity code. If anyone suggestions or ideas, please let me know.
Error: Debugging device
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.bluetooth_demoproject, PID: 17593
java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1, result=300, data=null} to activity {com.example.bluetooth_demoproject/com.example.bluetooth_demoproject.MainActivity}: android.os.FileUriExposedException: file:///storage/emulated/0/Pictures/Screenshots/Screenshot_20161013-215137.png exposed beyond app through ClipData.Item.getUri()
at android.app.ActivityThread.deliverResults(ActivityThread.java:4107)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4150)
at android.app.ActivityThread.-wrap20(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1517)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6120)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)
Caused by: android.os.FileUriExposedException: file:///storage/emulated/0/Pictures/Screenshots/Screenshot_20161013-215137.png exposed beyond app through ClipData.Item.getUri()
at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799)
at android.net.Uri.checkFileUriExposed(Uri.java:2346)
at android.content.ClipData.prepareToLeaveProcess(ClipData.java:832)
at android.content.Intent.prepareToLeaveProcess(Intent.java:8909)
at android.content.Intent.prepareToLeaveProcess(Intent.java:8894)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1517)
at android.app.Activity.startActivityForResult(Activity.java:4224)
at android.app.Activity.startActivityForResult(Activity.java:4183)
at android.app.Activity.startActivity(Activity.java:4507)
at android.app.Activity.startActivity(Activity.java:4475)
at com.example.bluetooth_demoproject.MainActivity.onActivityResult(MainActivity.java:350)
at android.app.Activity.dispatchActivityResult(Activity.java:6917)
at android.app.ActivityThread.deliverResults(ActivityThread.java:4103)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4150)
at android.app.ActivityThread.-wrap20(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1517)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6120)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)
Disconnected from the target VM, address: 'localhost:8601', transport: 'socket'
Here is my MainActivity
package com.example.bluetooth_demoproject;
import android.app.Activity;
import android.app.Dialog;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Environment;
import android.view.Menu;
import android.view.MenuItem;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.Set;
import java.io.File;
import java.util.List;
import java.util.ArrayList;
public class MainActivity extends Activity {
// Creating objects -----------------------------
private static final int REQUEST_ENABLE_BT = 0;
private static final int REQUEST_BLU = 1;
// private static final int REQUEST_DISCOVER_BT_ = 1;
private static int CUSTOM_DIALOG_ID = 0;
ListView dialog_ListView;
TextView mBluetoothStatus, mPairedDevicesList, mTextFolder;
ImageView mBluetoothIcon;
Button mOnButton, mDiscoverableButton, mPairedDevices, mbuttonOpenDialog, msendBluetooth, mbuttonUp;
File root, fileroot, curFolder;
EditText dataPath;
private static final int DISCOVER_DURATION = 300;
private List<String> fileList = new ArrayList<String>();
// -------------------------------------------------------
BluetoothAdapter mBlueAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dataPath =(EditText)findViewById(R.id.FilePath);
mTextFolder = findViewById(R.id.folder);
mBluetoothStatus = findViewById(R.id.BluetoothStatus);
mBluetoothIcon = findViewById(R.id.bluetoothIcon);
mOnButton = findViewById(R.id.onButton);
// mOffButton = findViewById(R.id.offButton);
mDiscoverableButton = findViewById(R.id.discoverableButton);
mPairedDevices = findViewById(R.id.pairedDevices);
mPairedDevicesList = findViewById(R.id.pairedDeviceList);
mbuttonOpenDialog = findViewById(R.id.opendailog);
msendBluetooth = findViewById(R.id.sendBluetooth);
mbuttonUp = findViewById(R.id.up);
mbuttonOpenDialog.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dataPath.setText("");
showDialog(CUSTOM_DIALOG_ID);
}
});
root = new
File(Environment.getExternalStorageDirectory().getAbsolutePath());
curFolder = root;
msendBluetooth.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v) {
sendViaBluetooth();
}
});
//adapter
mBlueAdapter = BluetoothAdapter.getDefaultAdapter();
if(mBlueAdapter == null){
mBluetoothStatus.setText("Bluetooth is not available");
return;
}
else {
mBluetoothStatus.setText("Bluetooth is available");
}
//if Bluetooth isnt enabled, enable it
if (!mBlueAdapter.isEnabled()) {
Intent enableBtIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
//set image according to bluetooth Status
if (mBlueAdapter.isEnabled()) {
mBluetoothIcon.setImageResource(R.drawable.action_on);
}
else {
mBluetoothIcon.setImageResource(R.drawable.action_off);
}
//on button Click
mOnButton.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v) {
if (!mBlueAdapter.isEnabled()) {
showToast("Turning Bluetooth on...");
// intent to on bluetooth
Intent intent = new
Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, REQUEST_ENABLE_BT);
}
else {
showToast("Bluetooth is already on");
}
}
});
//discover Bluetooth button
mDiscoverableButton.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
if (!mBlueAdapter.isDiscovering()) {
showToast("Making device discoverable");
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
startActivityForResult(intent, REQUEST_BLU);
}
}
});
// off button click
// mOffButton.setOnClickListener(new View.OnClickListener() {
// #Override
// public void onClick(View v) {
// if (mBlueAdapter.isEnabled()) {
// showToast("Turning Bluetooth off...");
// // intent to turn off bluetooth
// mBluetoothIcon.setImageResource(R.drawable.action_off);
// }
// else{
// showToast("Bluetooth is already off");
// }
//
// }
//});
//get paired device button click
mPairedDevices.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mBlueAdapter.isEnabled()) {
mPairedDevices.setText("Paired Devices");
Set<BluetoothDevice> devices = mBlueAdapter.getBondedDevices();
for (BluetoothDevice device : devices){
mPairedDevices.append("\nDevice: " + device.getName() + "," + device );
}
}
else {
//bluetooth is off and cant get paired devices
showToast("Turn on bluetooth to get paired devices");
}
}
});
}
// #Override
// protected void onPrepareDialog(int id, Dialog dialog) {
// super.onPrepareDialog(id, dialog);
// switch (id) {
// case CUSTOM_DIALOG_ID:
// ListDir(curFolder);
// break;
// }
// }
#Override
protected Dialog onCreateDialog(int id) {
Dialog dialog = null;
if (id == CUSTOM_DIALOG_ID) {
dialog = new Dialog(MainActivity.this);
dialog.setContentView(R.layout.dailoglayout);
dialog.setTitle("Select Files");
dialog.setCancelable(true);
dialog.setCanceledOnTouchOutside(true);
mTextFolder = (TextView) dialog.findViewById(R.id.folder);
mbuttonUp = (Button) dialog.findViewById(R.id.up);
mbuttonUp.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ListDir(curFolder.getParentFile());
}
});
dialog_ListView = (ListView) dialog.findViewById(R.id.dialoglist);
dialog_ListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
File selected = new File(fileList.get(position));
if (selected.isDirectory()) {
ListDir(selected);
}
else if (selected.isFile()) {
getSelectedFile(selected);
}
else {
dismissDialog(CUSTOM_DIALOG_ID);
}
}
});
}
return dialog;
}
#Override
protected void onPrepareDialog(int id, Dialog dialog) {
super.onPrepareDialog(id, dialog);
if (id == CUSTOM_DIALOG_ID) {
ListDir(curFolder);
}
}
public void getSelectedFile(File f) {
dataPath.setText(f.getAbsolutePath());
fileList.clear();
dismissDialog(CUSTOM_DIALOG_ID);
}
public void ListDir(File f) {
if (f.equals(root)) {
mbuttonUp.setEnabled(false);
}
else {
mbuttonUp.setEnabled(true);
}
curFolder = f;
mTextFolder.setText(f.getAbsolutePath());
dataPath.setText(f.getAbsolutePath());
File[] files = f.listFiles();
fileList.clear();
for (File file : files) {
fileList.add(file.getPath());
}
ArrayAdapter<String> directoryList = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, fileList);
dialog_ListView.setAdapter(directoryList);
}
// exits to app --------------------------------
public void exit(View V) {
mBlueAdapter.disable();
Toast.makeText(this, "*** Now bluetooth is off...", Toast.LENGTH_LONG).show();
finish();
}
// send file via bluetooth ------------------------
public void sendViaBluetooth() {
if(!dataPath.equals(null)) {
if(mBlueAdapter == null) {
Toast.makeText(this, "Device doesnt support bluetooth", Toast.LENGTH_LONG).show();
}
else {
enableBluetooth();
}
}
else {
Toast.makeText(this, "please select a file", Toast.LENGTH_LONG).show();
}
}
public void enableBluetooth() {
showToast("Making device discoverable");
Intent discoveryIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoveryIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, DISCOVER_DURATION);
startActivityForResult(discoveryIntent, REQUEST_BLU);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == DISCOVER_DURATION && requestCode == REQUEST_BLU) {
Intent i = new Intent();
i.setAction(Intent.ACTION_SEND);// STOPPED HERE-----------------------------------------------------------------
i.setType("*/*");
File file = new File(dataPath.getText().toString());
i.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
PackageManager pm = getPackageManager();
List<ResolveInfo> list = pm.queryIntentActivities(i, 0);
if (list.size() > 0) {
String packageName = null;
String className = null;
boolean found = false;
for (ResolveInfo info : list) {
packageName = info.activityInfo.packageName;
if (packageName.equals("com.android.bluetooth")) {
className = info.activityInfo.name;
found = true;
break;
}
}
//CHECK BLUETOOTH available or not------------------------------------------------
if (!found) {
Toast.makeText(this, "Bluetooth not been found", Toast.LENGTH_LONG).show();
} else {
i.setClassName(packageName, className);
startActivity(i);
}
}
} else {
Toast.makeText(this, "Bluetooth is cancelled", Toast.LENGTH_LONG).show();
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
// if (id == R.id.action_settings) {
// Toast.makeText(this, "**********************************\nDeveloper: www.santoshkumarsingh.com\n**********************************", Toast.LENGTH_LONG).show();
// return true;
// }
return super.onOptionsItemSelected(item);
}
//toast message function
private void showToast(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT) .show();
}
}
You have trouble with file exchange not with BLE.
Did you ever read Android FileUriExposedException?
I have the following working code that is able to scan and connect to a BLE device:
import android.Manifest;
import android.app.AlertDialog;
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.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import com.google.android.gms.appindexing.Action;
import com.google.android.gms.appindexing.AppIndex;
import com.google.android.gms.common.api.GoogleApiClient;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MainActivity2 extends AppCompatActivity {
private final static String TAG = MainActivity2.class.getSimpleName();
BluetoothManager btManager;
BluetoothAdapter btAdapter;
BluetoothLeScanner btScanner;
private final static int REQUEST_ENABLE_BT = 1;
private static final int PERMISSION_REQUEST_COARSE_LOCATION = 1;
Boolean btScanning = false;
int deviceIndex = 0;
ArrayList<BluetoothDevice> devicesDiscovered = new ArrayList<BluetoothDevice>();
BluetoothGatt bluetoothGatt;
public final static String ACTION_GATT_CONNECTED =
"com.example.bluetooth.le.ACTION_GATT_CONNECTED";
public final static String ACTION_GATT_DISCONNECTED =
"com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
public final static String ACTION_GATT_SERVICES_DISCOVERED =
"com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
public final static String ACTION_DATA_AVAILABLE =
"com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
public final static String EXTRA_DATA =
"com.example.bluetooth.le.EXTRA_DATA";
public Map<String, String> uuids = new HashMap<String, String>();
// Stops scanning after 5 seconds.
private Handler mHandler = new Handler();
private static final long SCAN_PERIOD = 5000;
/**
* ATTENTION: This was auto-generated to implement the App Indexing API.
* See https://g.co/AppIndexing/AndroidStudio for more information.
*/
private GoogleApiClient client;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
btAdapter = btManager.getAdapter();
btScanner = btAdapter.getBluetoothLeScanner();
if (btAdapter != null && !btAdapter.isEnabled()) {
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
}
// Make sure we have access coarse location enabled, if not, prompt the user to enable it
if (this.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("This app needs location access");
builder.setMessage("Please grant location access so this app can detect peripherals.");
builder.setPositiveButton(android.R.string.ok, null);
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
#Override
public void onDismiss(DialogInterface dialog) {
requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSION_REQUEST_COARSE_LOCATION);
}
});
builder.show();
}
client = new GoogleApiClient.Builder(this).addApi(AppIndex.API).build();
}
// Device scan callback.
private ScanCallback leScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
devicesDiscovered.add(result.getDevice());
deviceIndex++;
}
};
// Device connect call back
private final BluetoothGattCallback btleGattCallback = new BluetoothGattCallback() {
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
}
#Override
public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
}
#Override
public void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
displayGattServices(bluetoothGatt.getServices());
}
#Override
// Result of a characteristic read operation
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
// >> DUNNO WHAT TO DO HERE <<
}
};
private void broadcastUpdate(final String action,
final BluetoothGattCharacteristic characteristic) {
System.out.println(characteristic.getUuid());
}
#Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case PERMISSION_REQUEST_COARSE_LOCATION: {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
System.out.println("coarse location permission granted");
} else {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Functionality limited");
builder.setMessage("Since location access has not been granted, this app will not be able to discover beacons when in the background.");
builder.setPositiveButton(android.R.string.ok, null);
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
#Override
public void onDismiss(DialogInterface dialog) {
}
});
builder.show();
}
return;
}
}
}
public void startScanning() {
System.out.println("start scanning");
btScanning = true;
deviceIndex = 0;
devicesDiscovered.clear();
AsyncTask.execute(new Runnable() {
#Override
public void run() {
btScanner.startScan(leScanCallback);
}
});
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
stopScanning();
}
}, SCAN_PERIOD);
}
public void stopScanning() {
System.out.println("stopping scanning");
btScanning = false;
AsyncTask.execute(new Runnable() {
#Override
public void run() {
btScanner.stopScan(leScanCallback);
}
});
}
public void connectToDeviceSelected() {
int deviceSelected = Integer.parseInt("0" /* MY DEVICE INDEX TAKEN FROM AN EDITTEXT*/);
bluetoothGatt = devicesDiscovered.get(deviceSelected).connectGatt(this, false, btleGattCallback);
}
public void disconnectDeviceSelected() {
bluetoothGatt.disconnect();
}
private void displayGattServices(List<BluetoothGattService> gattServices) {
if (gattServices == null) return;
// Loops through available GATT Services.
for (BluetoothGattService gattService : gattServices) {
new ArrayList<HashMap<String, String>>();
List<BluetoothGattCharacteristic> gattCharacteristics =
gattService.getCharacteristics();
// Loops through available Characteristics.
for (BluetoothGattCharacteristic gattCharacteristic :
gattCharacteristics) {
// SHOWS AVAILABLES
}
}
}
#Override
public void onStart() {
super.onStart();
client.connect();
Action viewAction = Action.newAction(
Action.TYPE_VIEW, // TODO: choose an action type.
"Main Page", // TODO: Define a title for the content shown.
// TODO: If you have web page content that matches this app activity's content,
// make sure this auto-generated web page URL is correct.
// Otherwise, set the URL to null.
Uri.parse("http://host/path"),
// TODO: Make sure this auto-generated app URL is correct.
Uri.parse("android-app://com.example.joelwasserman.androidbleconnectexample/http/host/path")
);
AppIndex.AppIndexApi.start(client, viewAction);
}
#Override
public void onStop() {
super.onStop();
Action viewAction = Action.newAction(
Action.TYPE_VIEW, // TODO: choose an action type.
"Main Page", // TODO: Define a title for the content shown.
// TODO: If you have web page content that matches this app activity's content,
// make sure this auto-generated web page URL is correct.
// Otherwise, set the URL to null.
Uri.parse("http://host/path"),
// TODO: Make sure this auto-generated app URL is correct.
Uri.parse("android-app://com.example.joelwasserman.androidbleconnectexample/http/host/path")
);
AppIndex.AppIndexApi.end(client, viewAction);
client.disconnect();
}
}
Now, once connected, I want to send data (in byte[] format) to the BLE device... How can I reach that goal? Thanks in advance
Obs.: I'm targetting APIs >= 21
Check out an example with writing a new value in characteristic:
public static final byte NO_ALERT = 0x00; //your byte data
public final static UUID IMMEDIATE_ALERT_UUID =
UUID.fromString("00001802-0000-1000-8000-00805f9b34fb"); //UUID of gatt service
public final static UUID IMMEDIATE_ALERT_LEVEL_UUID =
UUID.fromString("00002a06-0000-1000-8000-00805f9b34fb"); //UUID of characteristic
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
byte[] data;
byte b = NO_ALERT;
data = new byte[]{b};
BluetoothGattService service = gatt.getService(IMMEDIATE_ALERT_UUID);
if (service != null) {
BluetoothGattCharacteristic characteristic = service.getCharacteristic(IMMEDIATE_ALERT_LEVEL_UUID);
characteristic.setValue(data);
gatt.writeCharacteristic(characteristic);
}
}
I am trying to use Google's Nearby Messages API for the first time. I downloaded some source code from GitHub and tried to compile it using Android Studio. There is an import that Android Studio cannot resolve:
import com.google.android.gms.nearby.messages.SubscribeOptions;
My Gradle file defines the following dependencies:
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.2.0'
compile 'com.google.android.gms:play-services:7.8.0'
compile 'com.google.android.gms:play-services-nearby:7.8.0'
and this is the Activity in which I am using the aforementioned library:
package com.example.android.nearbybeacons;
import android.Manifest;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.nearby.Nearby;
import com.google.android.gms.nearby.messages.Message;
import com.google.android.gms.nearby.messages.MessageListener;
import com.google.android.gms.nearby.messages.Strategy;
import com.google.android.gms.nearby.messages.SubscribeOptions;
public class MainActivity extends AppCompatActivity implements
AdapterView.OnItemClickListener,
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
private static final String TAG =
MainActivity.class.getSimpleName();
private static final int REQUEST_RESOLVE_ERROR = 100;
private static final int REQUEST_PERMISSION = 42;
private GoogleApiClient mGoogleApiClient;
private ArrayAdapter<OfferBeacon> mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView list = (ListView) findViewById(R.id.list);
mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
list.setAdapter(mAdapter);
list.setOnItemClickListener(this);
//Construct a connection to Play Services
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Nearby.MESSAGES_API)
.build();
//When launching from a notification link
if (BeaconService.ACTION_DISMISS.equals(getIntent().getAction())) {
//Fire a clear action to the service
Intent intent = new Intent(this, BeaconService.class);
intent.setAction(BeaconService.ACTION_DISMISS);
startService(intent);
}
}
#Override
protected void onStart() {
super.onStart();
//Initiate connection to Play Services
mGoogleApiClient.connect();
//The location permission is required on API 23+ to obtain BLE scan results
int result = ActivityCompat
.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION);
if (result != PackageManager.PERMISSION_GRANTED) {
//Ask for the location permission
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
REQUEST_PERMISSION);
}
}
#Override
protected void onStop() {
super.onStop();
//Tear down Play Services connection
if (mGoogleApiClient.isConnected()) {
Log.d(TAG, "Un-subscribing…");
Nearby.Messages.unsubscribe(
mGoogleApiClient,
mMessageListener);
mAdapter.clear();
mGoogleApiClient.disconnect();
}
}
// This is called in response to a button tap in the system permissions dialog.
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_RESOLVE_ERROR) {
if (resultCode == RESULT_OK) {
// Permission granted or error resolved successfully then we proceed
// with publish and subscribe..
subscribe();
} else {
// This may mean that user had rejected to grant nearby permission.
showToast("Failed to resolve error with code " + resultCode);
}
}
if (requestCode == REQUEST_PERMISSION) {
if (resultCode != RESULT_OK) {
showToast("We need location permission to get scan results!");
finish();
}
}
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
OfferBeacon item = mAdapter.getItem(position);
showToast(item.offer);
}
/* Nearby Messages Callbacks */
//NOTE: These callbacks are NOT triggered on the main thread!
private MessageListener mMessageListener = new MessageListener() {
// Called each time a new message is discovered nearby.
#Override
public void onFound(Message message) {
Log.i(TAG, "Found message: " + message);
final OfferBeacon beacon = new OfferBeacon(message);
runOnUiThread(new Runnable() {
#Override
public void run() {
mAdapter.add(beacon);
}
});
}
// Called when the publisher (beacon) is no longer nearby.
#Override
public void onLost(Message message) {
Log.i(TAG, "Lost message: " + message);
final OfferBeacon beacon = new OfferBeacon(message);
runOnUiThread(new Runnable() {
#Override
public void run() {
mAdapter.remove(beacon);
}
});
}
};
/* API Client Callbacks */
#Override
public void onConnected(Bundle bundle) {
//Once connected, we have to check that the user has opted in
Runnable runOnSuccess = new Runnable() {
#Override
public void run() {
//Subscribe once user permission is verified
subscribe();
}
};
ResultCallback<Status> callback =
new ErrorCheckingCallback(runOnSuccess);
Nearby.Messages.getPermissionStatus(mGoogleApiClient)
.setResultCallback(callback);
}
#Override
public void onConnectionSuspended(int i) {
Log.d(TAG, "OnConnectionSuspended");
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.w(TAG, "OnConnectionFailed");
}
private void subscribe() {
Log.d(TAG, "Subscribing…");
SubscribeOptions options = new SubscribeOptions.Builder()
.setStrategy(Strategy.BLE_ONLY)
.build();
//Active subscription for foreground messages
Nearby.Messages.subscribe(mGoogleApiClient,
mMessageListener, options);
//Passive subscription for background messages
Intent serviceIntent = new Intent(this, BeaconService.class);
PendingIntent trigger = PendingIntent.getService(this, 0,
serviceIntent, PendingIntent.FLAG_UPDATE_CURRENT);
ResultCallback<Status> callback = new BackgroundRegisterCallback();
Nearby.Messages.subscribe(mGoogleApiClient, trigger, options)
.setResultCallback(callback);
}
private class BackgroundRegisterCallback implements ResultCallback<Status> {
#Override
public void onResult(#NonNull Status status) {
//Validate if we were able to register for background scans
if (status.isSuccess()) {
Log.d(TAG, "Background Register Success!");
} else {
Log.w(TAG, "Background Register Error ("
+ status.getStatusCode() + "): "
+ status.getStatusMessage());
}
}
}
//ResultCallback triggered when to handle Nearby permissions check
private class ErrorCheckingCallback implements ResultCallback<Status> {
private final Runnable runOnSuccess;
private ErrorCheckingCallback(#Nullable Runnable runOnSuccess) {
this.runOnSuccess = runOnSuccess;
}
#Override
public void onResult(#NonNull Status status) {
if (status.isSuccess()) {
Log.i(TAG, "Permission status succeeded.");
if (runOnSuccess != null) {
runOnSuccess.run();
}
} else {
// Currently, the only resolvable error is that the device is not opted
// in to Nearby. Starting the resolution displays an opt-in dialog.
if (status.hasResolution()) {
try {
status.startResolutionForResult(MainActivity.this,
REQUEST_RESOLVE_ERROR);
} catch (IntentSender.SendIntentException e) {
showToastAndLog(Log.ERROR, "Request failed with exception: " + e);
}
} else {
showToastAndLog(Log.ERROR, "Request failed with : " + status);
}
}
}
}
private void showToast(String text) {
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
}
private void showToastAndLog(int logLevel, String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
Log.println(logLevel, TAG, message);
}
}
Can anyone tell me what I'm doing wrong?
My guess is that you will be able to resolve this by updating the Play Services version you are using from v7.8.0 to v8.4.0 (the latest version as of this answer date). The Nearby APIs were introduced in v7.8.0, at which time subscription could be accomplished using a method signature of the form
subscribe(GoogleApiClient, MessageListener, Strategy)
However, this method is now (in v8.4.0) marked as deprecated, and a new method with the following signature is recommended in its place:
subscribe (GoogleApiClient, MessageListener, SubscribeOptions)
Based on this information it seems fairly likely that the SubscribeOptions type was introduced whenever the previously-described deprecation took place, which was clearly post-v7.8.0.
See these docs for reference.