I've been looking for this for a while and everything I've tried has not worked yet. I implemented a Bluetooth connection service class that let's me connect and send messages via Bluetooth to a HC-05 module. I'm able to see each message within the console (with a Log), however, no matter what I tried, I can't seem to put the bytes received into my main activity where I can treat it. Here is the code I have in the BluetoothConnectionService class where my Log is located:
BluetoothConnectionService:
private Handler mHandler; // handler that gets info from Bluetooth service
// Defines several constants used when transmitting messages between the
// service and the UI.
private interface MessageConstants {
public static final int MESSAGE_READ = 0;
public static final int MESSAGE_WRITE = 1;
public static final int MESSAGE_TOAST = 2;
// ... (Add other message types here as needed.)
}
public void run(){
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
while (true) {
// Read from the InputStream
try {
bytes = mmInStream.read(buffer);
String incomingMessage = new String(buffer, 0, bytes);
Log.d(TAG, "InputStream: " + incomingMessage);
// Send the obtained bytes to the MainActivity
Handler mainActivityHandler = new Handler();
mainActivityHandler.obtainMessage(MessageConstants.MESSAGE_READ, bytes, -1, buffer);
// Send the obtained bytes to the UI activity.
/*Message readMsg = mHandler.obtainMessage(
MessageConstants.MESSAGE_READ, bytes, -1,
buffer);
readMsg.sendToTarget();*/
} catch (IOException e) {
Log.e(TAG, "write: Error reading Input Stream. " + e.getMessage() );
break;
}
}
}
MainActivity: (in the onCreate)
btnReadGlucose.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//On va envoyer quelle personne il faut lire le data
String patientName = mSpinner.getSelectedItem().toString();
int patientPosition = mSpinner.getSelectedItemPosition();
Log.d(TAG, "Le patient " + patientName + " a la position " + patientPosition + " est selectionne");
//Trouver quelle lettre envoyer
DataEnvoyer = mappingPatients(patientPosition);
RequestData = true;
//Envoi du data
envoyerCommandeBluetooth(DataEnvoyer);
//How do I call my handler ?
}
});
I'm still a newbie with Bluetooth communication handlers. I think I'm close to the answer but I really don't know how to get the message in the byte and save it to a value in my main activity.
Can anyone help ?
Thanks,
luisarcher.
METHOD 1 : If this service running on the same thread as the activity then bind the service with activity.
//IN YOUR ACTIVITY
startService(new Intent(getApplicationContext(), BluetoothService.class));
bindService(new Intent(getApplicationContext(), BluetoothService.class), mServiceConnection, Context.BIND_AUTO_CREATE);
private ServiceConnection mServiceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
BluetoothService.BackgroundBinder backgroundBinder = (BluetoothService.BackgroundBinder) iBinder;
mBackgroundService = backgroundBinder.getBackgroundService();
startPinging();
}
#Override
public void onServiceDisconnected(ComponentName componentName) {
mBackgroundService = null;
}
};
//IN SERVICE
public class BluetoothBinder extends Binder {
public BluetoothService getBluetoothService() {
return BluetoothService.this;
}
}
#Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "Inside onBind");
return new BluetoothBinder();
}
Now that the service is binded you can declare a getter in service for incomingMessage so when you press the button in activity it returns you the message.
METHOD 2(VIA HANDLER):if you need an interface to communicate across processes you can create a Messenger. It handles communication on single thread.
I haven't done this but a good post about this can be found here.
METHOD 3(VIA LocalBroadCast): In your bluetooth service send a localBroadcast whenever you receive a message
//SERVICE
private void sendMessage(String incomingMessage) {
Intent intent = new Intent("UNIQUE_ACTION");
intent.putExtra("incomingMessage", incomingMessage);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
public void run(){
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
while (true) {
// Read from the InputStream
try {
bytes = mmInStream.read(buffer);
String incomingMessage = new String(buffer, 0, bytes);
Log.d(TAG, "InputStream: " + incomingMessage);
sendMessage(incomingMessage);
//ACTIVITY
#Override
public void onResume() {
super.onResume();
// This registers mMessageReceiver to receive messages.
LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver,new IntentFilter("UNIQUE_ACTION"));
}
// Handling the received Intents for the "UNIQUE_ACTION" event
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Extract data included in the Intent
String incomingMessage = intent.getStringExtra()("incomingMessage");
Log.d(TAG, incomingMessage);
}
};
#Override
protected void onPause() {
// Unregister since the activity is not visible
LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
super.onPause();
}
Also, I would suggest looking at this link for communication between service and activity.
P.S:Have a look at this library for bluetooth communication.It does provide methods to get data from Bluetooth and I have personally tested that it works with HC-05 and also has examples.
Related
I am very new to flutter+dart framework. I am trying to understand how EventChannel works. I have set up EventChannel to capture the number of an incoming call.
On the android side, I have set up an BroadcastReceiver as follows.
public class CallEventHandler extends BroadcastReceiver implements EventChannel.StreamHandler {
private static final String TAG = "[SAMPLE]";
private static final int NUMBER_LEN = 10;
private EventChannel.EventSink eventSink = null;
private Activity activity = null;
public CallEventHandler(Activity activity) {
this.activity = activity;
}
#Override
public void onListen(Object arguments, EventChannel.EventSink events) {
Log.i(TAG, "[onListen] setting up events");
eventSink = events;
}
#Override
public void onCancel(Object arguments) {
Log.i(TAG, "[onCancel] cancel events");
eventSink = null;
activity = null;
}
#Override
public void onReceive(Context context, Intent intent) {
try {
String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
String incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
if(state.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
if(incomingNumber != null) {
Log.i(TAG, "[CallEventHandler] Incoming number : " + incomingNumber);
if(incomingNumber.length() > NUMBER_LEN) {
incomingNumber = incomingNumber.substring(incomingNumber.length() - NUMBER_LEN, incomingNumber.length());
Log.i(TAG, "[CallEventHandler] Incoming number after : " + incomingNumber);
if(activity != null) {
String finalIncomingNumber = incomingNumber;
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
if(eventSink != null) {
Log.i(TAG, "[CallEventHandler] HERESSSSS : " + finalIncomingNumber);
eventSink.success(finalIncomingNumber);
}
}
});
}
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
In the onReceive method, I am getting the incoming number and I am sending it to EventSink.
In my MainActivity I am setting up the CallEventHandler as follows:
private final String eventId = "SAMPLE_ID";
private CallEventHandler handler = new CallEventHandler(this);
#Override
public void onStart() {
super.onStart();
...
registerReceiver(handler, filter);
}
#Override
public void onStop() {
super.onStop();
unregisterReceiver(handler);
}
#Override
public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
new EventChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), eventId)
.setStreamHandler(handler);
}
On the Flutter side, the code is as follows:
class EventHandler {
static const String TAG = "[SAMPLE]";
final String _eventId = "SAMPLE_ID";
EventChannel? _evtChannel;
Stream<String>? _evtStream;
EventHandler() {
debugPrint(TAG + " Setting up EventHandler");
_evtChannel = EventChannel(_eventId);
_evtStream = _evtChannel?.receiveBroadcastStream().distinct().map((dynamic
event) => getString(event as String));
}
void startListening(void Function(String data)? onData) {
debugPrint(TAG + " starting listening");
_evtStream?.listen((data) {
debugPrint(TAG + " In listening");
onData!(data);
});
}
}
In my UI code, I have a StatefulWidget (MySamplePage) where I am registering my callback when the call is received
void initState() {
widget.handler.startListening((incomingNumber) {
debugPrint(_tag + " data : $incomingNumber");
...
});
}
In my stateful home page build method, I initialize the handler in initState and added a route in build method
class _HomeScreenState extends State<HomeScreen> {
#override
void initState() {
super.initState();
debugPrint(_tag + "initState");
_handler = EventHandler();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'/caller': (context) => MySamplePage(
handler: _handler
),
},
...
);
}
}
The issue I am facing is that, when the widget is opened I am receiving the first incoming call, as expected. But if I make another call, then that second call is not captured by the stream. If I press the back button, and reopen the Widget everything works as expected, the first incoming call is printed in the console. I know the the Android code is sending the event from the onReceive method (The `HERESSSSS' line is printed every time), but the flutter stream is not getting the values. I am not sure what I am doing wrong here. Can anyone please help?
My log is
I/flutter (11836): [SAMPLE][HomeScreen]initState
I/flutter (11836): [SAMPLE][EventHandler] Setting up EventHandler
V/AutofillManager(11836): requestHideFillUi(null): anchor = null
I/flutter (11836): [SAMPLE][EventHandler] starting listening
I/[SAMPLE] (11836): [onListen] setting up events
I/[SAMPLE] (11836): [CallEventHandler] Receiver start
I/[SAMPLE] (11836): [CallEventHandler] Receiver start
I/[SAMPLE] (11836): [CallEventHandler] Incoming number : +91XXXXXXXXXX
I/[SAMPLE] (11836): [CallEventHandler] Incoming number after : XXXXXXXXXX
I/[SAMPLE] (11836): [CallEventHandler] HERESSSSS : XXXXXXXXXX
I/flutter (11836): [SAMPLE][EventHandler] In listening
I/flutter (11836): [SAMPLE] data : XXXXXXXXXX
In the subsequent incoming calls, the last line is not printed
Thank you
Ok, I have managed to resolve it, but don't know if this is the correct approach. The issue is that MySamplePage is a StatefulWidget, And I am calling setState in its State object. That might be the reason it's unable to listen to the stream anymore. I have called startListening is the setState method and changed the code accordingly (remove the previous subscription and re-listen to the stream)
void startListening(void Function(String data)? onData) {
debugPrint(TAG + " starting listening");
if(_subscription != null) {
_subscription?.cancel();
_subscription = null;
}
_subscription ??= _evtStream?.listen((data) {
debugPrint(TAG + " In listening");
onData!(data);
});
}
Here _subscription is a variable of type StreamSubscription<String>?. Hope this answer is helpful. And I should have posted complete code earlier.
I'm making an Android app that is supposed to connect to a BLE-module and should be able to read it's services and characteristics. My app is based on the code of Accent Systems' "iBKS Hello World" application.
I am not using iBeacons, but a different kind of BLE-module. But when I connect to it, the app is not showing a list of services and characteristics as the code indicates should happen.
Can anyone help me fix this problem? Please let me know!
My code:
public class ScanActivity extends AppCompatActivity {
//DEFINE VARS
String TAG = "ScanActivity";
BluetoothAdapter mBluetoothAdapter;
BluetoothGatt mBluetoothGatt;
BluetoothLeScanner scanner;
ScanSettings scanSettings;
private List<String> scannedDeivcesList;
private ArrayAdapter<String> adapter;
//DEFINE LAYOUT
ListView devicesList;
//THIS METHOD RUNS ON APP LAUNCH
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scan);
//Define listview in layout
devicesList = (ListView) findViewById(R.id.devicesList);
//Setup list on device click listener
setupListClickListener();
//Initialize de devices list
scannedDeivcesList = new ArrayList<>();
//Initialize the list adapter for the listview with params: Context / Layout file / TextView ID in layout file / Devices list
adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, android.R.id.text1, scannedDeivcesList);
//Set the adapter to the listview
devicesList.setAdapter(adapter);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
SpannableString s = new SpannableString("Scan for modules");
s.setSpan(new com.accent_systems.ibkshelloworld.TypefaceSpan(this, "Khand-Bold.ttf"), 0, s.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
s.setSpan(new ForegroundColorSpan(Color.parseColor("#3a3c3e")), 0, s.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
setTitle(s);
getSupportActionBar().setDisplayUseLogoEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
//init Bluetooth adapter
initBT();
//Start scan of bluetooth devices
startLeScan(true);
}
#Override
protected void onStop() {
super.onStop();
startLeScan(false);
}
private void initBT(){
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
//Create the scan settings
ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder();
//Set scan latency mode. Lower latency, faster device detection/more battery and resources consumption
scanSettingsBuilder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
//Wrap settings together and save on a settings var (declared globally).
scanSettings = scanSettingsBuilder.build();
//Get the BLE scanner from the BT adapter (var declared globally)
scanner = mBluetoothAdapter.getBluetoothLeScanner();
}
private void startLeScan(boolean endis) {
if (endis) {
//********************
//START THE BLE SCAN
//********************
//Scanning parameters FILTER / SETTINGS / RESULT CALLBACK. Filter are used to define a particular
//device to scan for. The Callback is defined above as a method.
scanner.startScan(null, scanSettings, mScanCallback);
}else{
//Stop scan
scanner.stopScan(mScanCallback);
}
}
private ScanCallback mScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
//Here all the detected BLE devices will be received . "result" contains the device
//address and name as a BLEPeripheral, the advertising content as a ScanRecord, the Rx RSSI
//and the timestamp when received. Type result.get... to see all the available methods you can call.
//Convert advertising bytes to string for a easier parsing. GetBytes may return a NullPointerException. Treat it right(try/catch).
String advertisingString = byteArrayToHex(result.getScanRecord().getBytes());
//Print the advertising String in the LOG with other device info (ADDRESS - RSSI - ADVERTISING - NAME)
Log.i(TAG, result.getDevice().getAddress()+" - RSSI: "+result.getRssi()+"\t - "+advertisingString+" - "+result.getDevice().getName());
//Check if scanned device is already in the list by mac address
boolean contains = false;
for(int i=0; i<scannedDeivcesList.size(); i++){
if(scannedDeivcesList.get(i).contains(result.getDevice().getAddress())){
//Device already added
contains = true;
//Replace the device with updated values in that position
scannedDeivcesList.set(i, result.getRssi()+" "+result.getDevice().getName()+ "\n ("+result.getDevice().getAddress()+")");
break;
}
}
if(!contains){
//Scanned device not found in the list. NEW => add to list
scannedDeivcesList.add(result.getRssi()+" "+result.getDevice().getName()+ "\n ("+result.getDevice().getAddress()+")");
}
//After modify the list, notify the adapter that changes have been made so it updates the UI.
//UI changes must be done in the main thread
runOnUiThread(new Runnable() {
#Override
public void run() {
adapter.notifyDataSetChanged();
}
});
}
};
//Method to convert a byte array to a HEX. string.
private String byteArrayToHex(byte[] a) {
StringBuilder sb = new StringBuilder(a.length * 2);
for(byte b: a)
sb.append(String.format("%02x", b & 0xff));
return sb.toString();
}
void setupListClickListener(){
devicesList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//Stop the scan
Log.i(TAG, "SCAN STOPPED");
scanner.stopScan(mScanCallback);
//Get the string from the item clicked
String fullString = scannedDeivcesList.get(position);
//Get only the address from the previous string. Substring from '(' to ')'
String address = fullString.substring(fullString.indexOf("(")+1, fullString.indexOf(")"));
//Get BLE device with address
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
//******************************
//START CONNECTION WITH DEVICE AND DECLARE GATT
//******************************
Log.i(TAG,"*************************************************");
Log.i(TAG, "CONNECTION STARTED TO DEVICE "+address);
Log.i(TAG,"*************************************************");
//ConnectGatt parameters are CONTEXT / AUTOCONNECT to connect the next time it is scanned / GATT CALLBACK to receive GATT notifications and data
// Note: On Samsung devices, the connection must be done on main thread
mBluetoothGatt = device.connectGatt(ScanActivity.this, false, mGattCallback);
/*
There is also another simplest way to connect to a device. If you already stored
the device in a list (List<BluetoothDevice>) you can retrieve it directly and
connect to it:
mBluetoothGatt = mList.get(position).connectGatt(MainActivity.this, false, mGattCallback);
*/
}
});
}
//Connection callback
BluetoothGattCallback mGattCallback =
new BluetoothGattCallback() {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
//Device connected, start discovering services
Log.i(TAG, "DEVICE CONNECTED. DISCOVERING SERVICES...");
mBluetoothGatt.discoverServices();
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
//Device disconnected
Log.i(TAG, "DEVICE DISCONNECTED");
}
}
// On discover services method
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
//Services discovered successfully. Start parsing services and characteristics
Log.i(TAG, "SERVICES DISCOVERED. PARSING...");
displayGattServices(gatt.getServices());
} else {
//Failed to discover services
Log.i(TAG, "FAILED TO DISCOVER SERVICES");
}
}
//When reading a characteristic, here you receive the task result and the value
#Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
//READ WAS SUCCESSFUL
Log.i(TAG, "ON CHARACTERISTIC READ SUCCESSFUL");
//Read characteristic value like:
//characteristic.getValue();
//Which it returns a byte array. Convert it to HEX. string.
} else {
Log.i(TAG, "ERROR READING CHARACTERISTIC");
}
}
//When writing, here you can check whether the task was completed successfully or not
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.i(TAG, "ON CHARACTERISTIC WRITE SUCCESSFUL");
} else {
Log.i(TAG, "ERROR WRITING CHARACTERISTIC");
}
}
//In this method you can read the new values from a received notification
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
Log.i(TAG, "NEW NOTIFICATION RECEIVED");
//New notification received. Check the characteristic it comes from and parse to string
/*if(characteristic.getUuid().toString().contains("0000fff3")){
characteristic.getValue();
}*/
}
//RSSI values from the connection with the remote device are received here
#Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
Log.i(TAG, "NEW RSSI VALUE RECEIVED");
//Read remote RSSI like: mBluetoothGatt.readRemoteRssi();
//Here you get the gatt table where the rssi comes from, the rssi value and the
//status of the task.
}
};
//Method which parses all services and characteristics from the GATT table.
private void displayGattServices(List<BluetoothGattService> gattServices) {
//Check if there is any gatt services. If not, return.
if (gattServices == null) return;
// Loop through available GATT Services.
for (BluetoothGattService gattService : gattServices) {
Log.i(TAG, "SERVICE FOUND: "+gattService.getUuid().toString());
//Loop through available characteristics for each service
for (BluetoothGattCharacteristic gattCharacteristic : gattService.getCharacteristics()) {
Log.i(TAG, " CHAR. FOUND: "+gattCharacteristic.getUuid().toString());
}
}
//****************************************
// CONNECTION PROCESS FINISHED!
//****************************************
Log.i(TAG, "*************************************");
Log.i(TAG, "CONNECTION COMPLETED SUCCESFULLY");
Log.i(TAG, "*************************************");
}
}
You may find full description here. And here short example. So you bring list of services after BLE connection and services request. Later you may request characteristics for every service. Main idea when you bring services one by one you have it UUID and reference. If you have know some service that you like to use you should know it before bring all services. And on collecting them save somewhere reference of service test every by known UUID. Later use this reference for bring characteristics and interconnection with device.
I am currently developing an Application to consistently running a Service which gets all received Messages and pushes them to Google Sheets. My Service includes an Async Task which is created once a Message is received. Here is the AsyncTask which is inside my Service class. The SMSListener is a Broadcast Receiver class.
SmsListener
public class SmsListener extends BroadcastReceiver {
private boolean RECEIVED = false;
#Override
public void onReceive(Context context, Intent intent) {
if (Telephony.Sms.Intents.SMS_RECEIVED_ACTION.equals(intent.getAction())) {
setRECEIVED(true);
Log.d("DEBUGSMSLISTENER", "SMS is received.");
}
}
public boolean isRECEIVED() {
return RECEIVED;
}
public void setRECEIVED(boolean RECEIVED) {
this.RECEIVED = RECEIVED;
}
}
AsyncTask
static class AsyncPushTask extends AsyncTask<Void, Integer, Uri> {
//Fields
private AsyncPushTask(SmsListener listener, Context context, ContactManager manager, Context application) {
smsListener = listener;
contentResolver = context.getContentResolver();
contactManager = manager;
applicationContext = application;
}
#Override
protected Uri doInBackground(Void... voids) {
Log.d(TAG, "ASYNC BACKGROUND started");
Log.d(TAG, "is received " + smsListener.isRECEIVED());
//if (isPermitted() & smsListener.isRECEIVED()) {
//if (true) {
Log.d(TAG, "is permitted and received SMS TASK");
Cursor cursor = contentResolver.query(Uri.parse("content://sms/inbox"), null, null, null, null);
if (cursor.moveToFirst()) { // must check the result to prevent exception
//Do the work to push SMS to Google Sheets
The Code works as expected but sometimes, I cannot recreate the behavior, the First SMS in the inbox isnt recognized correctly, so that the pushed message equals the one that was pushed in the last cycle.
TY for the help.
I want to send value from string (distance to obstacle) to my TextView in main activity.
I tried to use Handler, but still not working (crash) or receive nothing.
A part code which receive data from HC-05 (screen where you see in debug value assignet to variable)
enter image description here
#Override
public void run() {
byte[] buffer = new byte[1024];
int bytes;
while(true){
try {
bytes = inputStream.read(buffer);
final String comingMsg = new String(buffer,0,bytes);
Log.d(TAG,"InputStream: " + comingMsg);
/*mHandler2.post(new Runnable() {
#Override
public void run() {
Message message = new Message();
message.obj = comingMsg;
mHandler2.sendMessage(message);
}
});*/
}catch (IOException e){
Log.e(TAG,"Write: Error reading input." + e.getMessage());
active=false;
break;
}
}
}
Here It's parts of code from MainActivity where I tried put something to get values from service.
[I add, that for this moment i want to see something values from bluetooth in textView. Later I want to create parse string and send custom text to custom TextView - example: FL: (Front Left)- to one textView, FR: (Front Right) - to second textView]
There is method implementThreads(), because I wanted to do 6 Threads to 6 TextView which every time is refreshing value from string in Services (there I tried get value from Bluetooth Service)
Log.d(TAG,"Check intent - result");
if(getIntent().getIntExtra("result",0)==RESULT_OK){
mDevice = getIntent().getExtras().getParcelable("bonded device");
myBluetoothService = new MyBluetoothService(getApplicationContext());
startConnection(mDevice,MY_UUID);
Log.d(TAG,"Check is active service");
checkIfActive();
}
Log.d(TAG,"Check intent - connect_to_paired");
if(getIntent().getIntExtra("connect_to_paired",0)==RESULT_OK){
mDevice = getIntent().getExtras().getParcelable("bonded_paired_device");
myBluetoothService = new MyBluetoothService(getApplicationContext());
startConnection(mDevice,MY_UUID);
Log.d(TAG,"Check is active service");
checkIfActive();
}
}
#Override
public void onStart(){
super.onStart();
myBluetoothService = new MyBluetoothService(getApplicationContext());
}
public void checkIfActive(){
Log.d(TAG,"CheckIfActive: Started");
if(myBluetoothService.active){
Log.d(TAG,"CheckIfActive: Running method implementThreads()");
implementThreads();
}
}
public void implementThreads(){
Log.d(TAG,"ImplementThreads: Started");
Thread thread = new Thread(){
#Override
public void run() {
try{
sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
}
};
thread.start();
}
public void startConnection(BluetoothDevice device,UUID uuid){
Log.d(TAG,"StartConnection: Initializing connection");
myBluetoothService.startClient(device,uuid);
}
Thanks all for help, because It's very important for me !
Use this to interect with UI Thread for operations like updating textviews etc.
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
//YOUR CODE HERE
Message message = new Message();
message.obj = comingMsg;
mHandler2.sendMessage(message);
}
});
I am trying to develop a simple app which will record the user's activity (accelerometer values) on a txt or csv file.
My app consists of 2 java classes MainActivity and MyService. The MainActivity includes two buttons to start and stop the service and the required permissions. However, the onSensorChanged normally logs for the first 3 minutes after locking the phone (turning off the screen) and then stops logging. As soon as I open the screen the logd starts working again. Same behavior for the records in txt file. I found out that the app seems to be working excellent if I override the battery optimizations. However, I need the phone to also be working in doze mode to save some battery drain. Has anyone else had a similar issue?
Here is my Foreground Service:
public class MyService extends Service implements SensorEventListener {
public static final String CHANNEL_ID = "ForegroundServiceChannel";
private static final String TAG = "MyService";
private Messenger messageHandler;
private SensorManager mSensorManager;
private Sensor mAccelerometer;
private Context mContext;
private PowerManager.WakeLock wakeLock = null;
//private HandlerThread mSensorThread;
//private Handler mHandler;
#SuppressLint("MissingPermission")
#Override
public void onCreate() {
super.onCreate();
Log.v("shake service startup", "registering for shake");
mContext = getApplicationContext();
//mHandler = new Handler(mSensorThread.getLooper());
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mAccelerometer = mSensorManager
.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(this, mAccelerometer,
SensorManager.SENSOR_DELAY_NORMAL);
PowerManager manager = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = manager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Wakelock :: TAG");
// Register our receiver for the ACTION_SCREEN_OFF action. This will make our receiver
// code be called whenever the phone enters standby mode.
//IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
//registerReceiver(mReceiver, filter);
}
/*
// BroadcastReceiver for handling ACTION_SCREEN_OFF.
public BroadcastReceiver mReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Check action just to be on the safe side.
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
Log.v("shake mediator screen off","trying re-registration");
// Unregisters the listener and registers it again.
mSensorManager.unregisterListener(MyService.this);
mSensorManager.registerListener(MyService.this, mAccelerometer,
SensorManager.SENSOR_DELAY_NORMAL, mHandler);
}
}
};
*/
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
String input = intent.getStringExtra("inputExtra");
createNotificationChannel();
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Foreground Service")
.setContentText(input)
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
return START_STICKY;
//stopSelf();
//return START_NOT_STICKY;
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel serviceChannel = new NotificationChannel(
CHANNEL_ID,
"Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(serviceChannel);
}
}
#Override
public void onDestroy() {
super.onDestroy();
if(mSensorManager != null){
//noinspection MissingPermission
mSensorManager.unregisterListener(MyService.this);
}
//unregisterReceiver(mReceiver);
try{
wakeLock.release();//always release before acquiring for safety just in case
}
catch(Exception e){
//probably already released
Log.e(TAG, e.getMessage());
}
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onSensorChanged(SensorEvent event) {
Log.d(TAG, "onSensorChanged: " + event.timestamp + " " + event.values[0] + " " + event.values[1] + " " + event.values[2]);
recordAccelValues(String.valueOf(event.timestamp), event.values[0] + " " + event.values[1] + " " + event.values[2]);
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
private void recordAccelValues(String time, String accel_values) {
String record = time + " " + accel_values + "\n";
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
if (Build.VERSION.SDK_INT >= 23) {
File sdcard = Environment.getExternalStorageDirectory();
File dir = new File(sdcard.getAbsolutePath() + "/text/");
if(!dir.exists()) {
dir.mkdir();
}
File file = new File(dir, "dailyRecordsAccel.dat");
FileOutputStream os = null;
try {
os = new FileOutputStream(file, true);
os.write(record.getBytes());
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
As you can see in the code I tried several recommendations from other questions I found, like wakelock and Intent.ACTION_SCREEN_OFF but they didn't seem to work.
Accelerometer stops delivering samples when the screen is off on Droid/Nexus One even with a WakeLock
The only one way to keep alive your service it's to avoid battery optimization for your application. Which is possible within two ways below. Please note! In both cases you will keep device alive, which means that device will never sleep (enter doze states obviously). It's whole point of device sleep, to avoid pending work of background services like yours.
Using Android WakeLocks, For ex. below.
val wakeLock: PowerManager.WakeLock =
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager. FULL_WAKE_LOCK, "MyApp::MyWakelockTag").apply {
acquire()
}
}
Changing setting to avoid battery optimization for specific app. As you mentioned in your question.
It is normal behavior. Android delete all proceses to save power. If you want do a job then ask user to keep screen on, else you can use AlarmManager only to call a Service (Intent, Reciver) do "small job" and go to sleep again.