start foreground service

This commit is contained in:
Rajaratnam, Roshan
2019-08-06 15:15:36 +02:00
parent 12abbe1a24
commit e36b7a1e0c
7 changed files with 1070 additions and 904 deletions

View File

@@ -33,6 +33,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="no.nordicsemi.android.LOG" /> <uses-permission android:name="no.nordicsemi.android.LOG" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-feature <uses-feature
android:name="android.hardware.bluetooth_le" android:name="android.hardware.bluetooth_le"

View File

@@ -8,11 +8,12 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.Build;
import android.util.SparseArray;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import android.util.SparseArray;
import no.nordicsemi.android.log.Logger; import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.FeaturesActivity; import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
import no.nordicsemi.android.nrftoolbox.R; import no.nordicsemi.android.nrftoolbox.R;
@@ -21,270 +22,294 @@ import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
import no.nordicsemi.android.nrftoolbox.profile.LoggableBleManager; import no.nordicsemi.android.nrftoolbox.profile.LoggableBleManager;
public class CGMService extends BleProfileService implements CGMSManagerCallbacks { public class CGMService extends BleProfileService implements CGMSManagerCallbacks {
private static final String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.cgms.ACTION_DISCONNECT"; private static final String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.cgms.ACTION_DISCONNECT";
public static final String BROADCAST_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.BROADCAST_BATTERY_LEVEL"; public static final String BROADCAST_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.BROADCAST_BATTERY_LEVEL";
public static final String EXTRA_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.EXTRA_BATTERY_LEVEL"; public static final String EXTRA_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.EXTRA_BATTERY_LEVEL";
public static final String BROADCAST_NEW_CGMS_VALUE = "no.nordicsemi.android.nrftoolbox.cgms.BROADCAST_NEW_CGMS_VALUE"; public static final String BROADCAST_NEW_CGMS_VALUE = "no.nordicsemi.android.nrftoolbox.cgms.BROADCAST_NEW_CGMS_VALUE";
public static final String BROADCAST_DATA_SET_CLEAR = "no.nordicsemi.android.nrftoolbox.cgms.BROADCAST_DATA_SET_CLEAR"; public static final String BROADCAST_DATA_SET_CLEAR = "no.nordicsemi.android.nrftoolbox.cgms.BROADCAST_DATA_SET_CLEAR";
public static final String OPERATION_STARTED = "no.nordicsemi.android.nrftoolbox.cgms.OPERATION_STARTED"; public static final String OPERATION_STARTED = "no.nordicsemi.android.nrftoolbox.cgms.OPERATION_STARTED";
public static final String OPERATION_COMPLETED = "no.nordicsemi.android.nrftoolbox.cgms.OPERATION_COMPLETED"; public static final String OPERATION_COMPLETED = "no.nordicsemi.android.nrftoolbox.cgms.OPERATION_COMPLETED";
public static final String OPERATION_SUPPORTED = "no.nordicsemi.android.nrftoolbox.cgms.OPERATION_SUPPORTED"; public static final String OPERATION_SUPPORTED = "no.nordicsemi.android.nrftoolbox.cgms.OPERATION_SUPPORTED";
public static final String OPERATION_NOT_SUPPORTED = "no.nordicsemi.android.nrftoolbox.cgms.OPERATION_NOT_SUPPORTED"; public static final String OPERATION_NOT_SUPPORTED = "no.nordicsemi.android.nrftoolbox.cgms.OPERATION_NOT_SUPPORTED";
public static final String OPERATION_FAILED = "no.nordicsemi.android.nrftoolbox.cgms.OPERATION_FAILED"; public static final String OPERATION_FAILED = "no.nordicsemi.android.nrftoolbox.cgms.OPERATION_FAILED";
public static final String OPERATION_ABORTED = "no.nordicsemi.android.nrftoolbox.cgms.OPERATION_ABORTED"; public static final String OPERATION_ABORTED = "no.nordicsemi.android.nrftoolbox.cgms.OPERATION_ABORTED";
public static final String EXTRA_CGMS_RECORD = "no.nordicsemi.android.nrftoolbox.cgms.EXTRA_CGMS_RECORD"; public static final String EXTRA_CGMS_RECORD = "no.nordicsemi.android.nrftoolbox.cgms.EXTRA_CGMS_RECORD";
public static final String EXTRA_DATA = "no.nordicsemi.android.nrftoolbox.cgms.EXTRA_DATA"; public static final String EXTRA_DATA = "no.nordicsemi.android.nrftoolbox.cgms.EXTRA_DATA";
private final static int NOTIFICATION_ID = 229; private final static int NOTIFICATION_ID = 229;
private final static int OPEN_ACTIVITY_REQ = 0; private final static int OPEN_ACTIVITY_REQ = 0;
private final static int DISCONNECT_REQ = 1; private final static int DISCONNECT_REQ = 1;
private CGMSManager mManager; private CGMSManager mManager;
private final LocalBinder mBinder = new CGMSBinder(); private final LocalBinder mBinder = new CGMSBinder();
/** /**
* This local binder is an interface for the bonded activity to operate with the RSC sensor * This local binder is an interface for the bonded activity to operate with the RSC sensor
*/ */
public class CGMSBinder extends LocalBinder { public class CGMSBinder extends LocalBinder {
/** /**
* Returns all records as a sparse array where sequence number is the key. * Returns all records as a sparse array where sequence number is the key.
* *
* @return the records list * @return the records list
*/ */
public SparseArray<CGMSRecord> getRecords() { public SparseArray<CGMSRecord> getRecords() {
return mManager.getRecords(); return mManager.getRecords();
} }
/** /**
* Clears the records list locally * Clears the records list locally
*/ */
public void clear() { public void clear() {
if (mManager != null) if (mManager != null)
mManager.clear(); mManager.clear();
} }
/** /**
* Sends the request to obtain the first (oldest) record from glucose device. * Sends the request to obtain the first (oldest) record from glucose device.
* The data will be returned to Glucose Measurement characteristic as a notification followed by Record Access Control * The data will be returned to Glucose Measurement characteristic as a notification followed by Record Access Control
* Point indication with status code ({@link CGMSManager# RESPONSE_SUCCESS} or other in case of error. * Point indication with status code ({@link CGMSManager# RESPONSE_SUCCESS} or other in case of error.
*/ */
public void getFirstRecord() { public void getFirstRecord() {
if (mManager != null) if (mManager != null)
mManager.getFirstRecord(); mManager.getFirstRecord();
} }
/** /**
* Sends the request to obtain the last (most recent) record from glucose device. * Sends the request to obtain the last (most recent) record from glucose device.
* The data will be returned to Glucose Measurement characteristic as a notification followed by Record Access * The data will be returned to Glucose Measurement characteristic as a notification followed by Record Access
* Control Point indication with status code Success or other in case of error. * Control Point indication with status code Success or other in case of error.
*/ */
public void getLastRecord() { public void getLastRecord() {
if (mManager != null) if (mManager != null)
mManager.getLastRecord(); mManager.getLastRecord();
} }
/** /**
* Sends the request to obtain all records from glucose device. * Sends the request to obtain all records from glucose device.
* Initially we want to notify user about the number of the records so the Report Number of Stored Records is send. * Initially we want to notify user about the number of the records so the Report Number of Stored Records is send.
* The data will be returned to Glucose Measurement characteristic as a series of notifications followed * The data will be returned to Glucose Measurement characteristic as a series of notifications followed
* by Record Access Control Point indication with status code Success or other in case of error. * by Record Access Control Point indication with status code Success or other in case of error.
*/ */
public void getAllRecords() { public void getAllRecords() {
if (mManager != null) if (mManager != null)
mManager.getAllRecords(); mManager.getAllRecords();
} }
/** /**
* Sends the request to obtain all records from glucose device with sequence number greater * Sends the request to obtain all records from glucose device with sequence number greater
* than the last one already obtained. The data will be returned to Glucose Measurement * than the last one already obtained. The data will be returned to Glucose Measurement
* characteristic as a series of notifications followed by Record Access Control Point * characteristic as a series of notifications followed by Record Access Control Point
* indication with status code Success or other in case of error. * indication with status code Success or other in case of error.
*/ */
public void refreshRecords() { public void refreshRecords() {
if (mManager != null) if (mManager != null)
mManager.refreshRecords(); mManager.refreshRecords();
} }
/** /**
* Sends abort operation signal to the device * Sends abort operation signal to the device
*/ */
public void abort() { public void abort() {
if (mManager != null) if (mManager != null)
mManager.abort(); mManager.abort();
} }
/** /**
* Sends Delete op code with All stored records parameter. This method may not be supported by the SDK sample. * Sends Delete op code with All stored records parameter. This method may not be supported by the SDK sample.
*/ */
public void deleteAllRecords() { public void deleteAllRecords() {
if (mManager != null) if (mManager != null)
mManager.deleteAllRecords(); mManager.deleteAllRecords();
} }
} }
@Override @Override
protected LocalBinder getBinder() { protected LocalBinder getBinder() {
return mBinder; return mBinder;
} }
@Override @Override
protected LoggableBleManager<CGMSManagerCallbacks> initializeManager() { protected LoggableBleManager<CGMSManagerCallbacks> initializeManager() {
return mManager = new CGMSManager(this); return mManager = new CGMSManager(this);
} }
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
final IntentFilter filter = new IntentFilter(); final IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_DISCONNECT); filter.addAction(ACTION_DISCONNECT);
registerReceiver(mDisconnectActionBroadcastReceiver, filter); registerReceiver(mDisconnectActionBroadcastReceiver, filter);
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
// when user has disconnected from the sensor, we have to cancel the notification that we've created some milliseconds before using unbindService // when user has disconnected from the sensor, we have to cancel the notification that we've created some milliseconds before using unbindService
cancelNotification(); stopForegroundService();
unregisterReceiver(mDisconnectActionBroadcastReceiver); unregisterReceiver(mDisconnectActionBroadcastReceiver);
super.onDestroy(); super.onDestroy();
} }
@Override @Override
protected void onRebind() { protected void onRebind() {
// when the activity rebinds to the service, remove the notification startForegroundService();
cancelNotification(); }
}
@Override @Override
protected void onUnbind() { protected void onUnbind() {
// when the activity closes we need to show the notification that user is connected to the sensor startForegroundService();
createNotification(R.string.csc_notification_connected_message, 0); }
}
/** /**
* Creates the notification * Sets the service as a foreground service
* */
* @param messageResId the message resource id. The message must have one String parameter,<br /> private void startForegroundService(){
* f.e. <code>&lt;string name="name"&gt;%s is connected&lt;/string&gt;</code> // when the activity closes we need to show the notification that user is connected to the peripheral sensor
* @param defaults signals that will be used to notify the user // We start the service as a foreground service as Android 8.0 (Oreo) onwards kills any running background services
*/ final Notification notification = createNotification(R.string.uart_notification_connected_message, 0);
private void createNotification(final int messageResId, final int defaults) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
final Intent parentIntent = new Intent(this, FeaturesActivity.class); startForeground(NOTIFICATION_ID, notification);
parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } else {
final Intent targetIntent = new Intent(this, CGMSActivity.class); final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(NOTIFICATION_ID, notification);
}
}
final Intent disconnect = new Intent(ACTION_DISCONNECT); /**
final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ, disconnect, PendingIntent.FLAG_UPDATE_CURRENT); * Stops the service as a foreground service
*/
private void stopForegroundService(){
// when the activity rebinds to the service, remove the notification and stop the foreground service
// on devices running Android 8.0 (Oreo) or above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
stopForeground(true);
} else {
cancelNotification();
}
}
// both activities above have launchMode="singleTask" in the AndroidManifest.xml file, so if the task is already running, it will be resumed /**
final PendingIntent pendingIntent = PendingIntent.getActivities(this, OPEN_ACTIVITY_REQ, new Intent[]{parentIntent, targetIntent}, PendingIntent.FLAG_UPDATE_CURRENT); * Creates the notification
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this, ToolboxApplication.CONNECTED_DEVICE_CHANNEL); *
builder.setContentIntent(pendingIntent); * @param messageResId the message resource id. The message must have one String parameter,<br />
builder.setContentTitle(getString(R.string.app_name)).setContentText(getString(messageResId, getDeviceName())); * f.e. <code>&lt;string name="name"&gt;%s is connected&lt;/string&gt;</code>
builder.setSmallIcon(R.drawable.ic_stat_notify_cgms); * @param defaults signals that will be used to notify the user
builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(true); */
builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.csc_notification_action_disconnect), disconnectAction)); private Notification createNotification(final int messageResId, final int defaults) {
final Intent parentIntent = new Intent(this, FeaturesActivity.class);
parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final Intent targetIntent = new Intent(this, CGMSActivity.class);
final Notification notification = builder.build(); final Intent disconnect = new Intent(ACTION_DISCONNECT);
final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ, disconnect, PendingIntent.FLAG_UPDATE_CURRENT);
nm.notify(NOTIFICATION_ID, notification);
}
/** // both activities above have launchMode="singleTask" in the AndroidManifest.xml file, so if the task is already running, it will be resumed
* Cancels the existing notification. If there is no active notification this method does nothing final PendingIntent pendingIntent = PendingIntent.getActivities(this, OPEN_ACTIVITY_REQ, new Intent[]{parentIntent, targetIntent}, PendingIntent.FLAG_UPDATE_CURRENT);
*/ final NotificationCompat.Builder builder = new NotificationCompat.Builder(this, ToolboxApplication.CONNECTED_DEVICE_CHANNEL);
private void cancelNotification() { builder.setContentIntent(pendingIntent);
final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); builder.setContentTitle(getString(R.string.app_name)).setContentText(getString(messageResId, getDeviceName()));
nm.cancel(NOTIFICATION_ID); builder.setSmallIcon(R.drawable.ic_stat_notify_cgms);
} builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(true);
builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.csc_notification_action_disconnect), disconnectAction));
/** return builder.build();
* This broadcast receiver listens for {@link #ACTION_DISCONNECT} that may be fired by pressing Disconnect action button on the notification. }
*/
private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
Logger.i(getLogSession(), "[Notification] Disconnect action pressed");
if (isConnected())
getBinder().disconnect();
else
stopSelf();
}
};
@Override /**
public void onCGMValueReceived(@NonNull final BluetoothDevice device, final CGMSRecord record) { * Cancels the existing notification. If there is no active notification this method does nothing
final Intent broadcast = new Intent(BROADCAST_NEW_CGMS_VALUE); */
broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice()); private void cancelNotification() {
broadcast.putExtra(EXTRA_CGMS_RECORD, record); final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); nm.cancel(NOTIFICATION_ID);
} }
@Override /**
public void onOperationStarted(@NonNull final BluetoothDevice device) { * This broadcast receiver listens for {@link #ACTION_DISCONNECT} that may be fired by pressing Disconnect action button on the notification.
final Intent broadcast = new Intent(OPERATION_STARTED); */
broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice()); private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() {
broadcast.putExtra(EXTRA_DATA, true); @Override
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); public void onReceive(final Context context, final Intent intent) {
} Logger.i(getLogSession(), "[Notification] Disconnect action pressed");
if (isConnected())
getBinder().disconnect();
else
stopSelf();
}
};
@Override @Override
public void onOperationCompleted(@NonNull final BluetoothDevice device) { public void onCGMValueReceived(@NonNull final BluetoothDevice device, final CGMSRecord record) {
final Intent broadcast = new Intent(OPERATION_COMPLETED); final Intent broadcast = new Intent(BROADCAST_NEW_CGMS_VALUE);
broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice()); broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
broadcast.putExtra(EXTRA_DATA, true); broadcast.putExtra(EXTRA_CGMS_RECORD, record);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
} }
@Override @Override
public void onOperationFailed(@NonNull final BluetoothDevice device) { public void onOperationStarted(@NonNull final BluetoothDevice device) {
final Intent broadcast = new Intent(OPERATION_FAILED); final Intent broadcast = new Intent(OPERATION_STARTED);
broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice()); broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
broadcast.putExtra(EXTRA_DATA, true); broadcast.putExtra(EXTRA_DATA, true);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
} }
@Override @Override
public void onOperationAborted(@NonNull final BluetoothDevice device) { public void onOperationCompleted(@NonNull final BluetoothDevice device) {
final Intent broadcast = new Intent(OPERATION_ABORTED); final Intent broadcast = new Intent(OPERATION_COMPLETED);
broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice()); broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
broadcast.putExtra(EXTRA_DATA, true); broadcast.putExtra(EXTRA_DATA, true);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
} }
@Override @Override
public void onOperationNotSupported(@NonNull final BluetoothDevice device) { public void onOperationFailed(@NonNull final BluetoothDevice device) {
final Intent broadcast = new Intent(OPERATION_NOT_SUPPORTED); final Intent broadcast = new Intent(OPERATION_FAILED);
broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice()); broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
broadcast.putExtra(EXTRA_DATA, false); broadcast.putExtra(EXTRA_DATA, true);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
} }
@Override @Override
public void onDatasetCleared(@NonNull final BluetoothDevice device) { public void onOperationAborted(@NonNull final BluetoothDevice device) {
final Intent broadcast = new Intent(BROADCAST_DATA_SET_CLEAR); final Intent broadcast = new Intent(OPERATION_ABORTED);
broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice()); broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); broadcast.putExtra(EXTRA_DATA, true);
} LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
}
@Override @Override
public void onNumberOfRecordsRequested(@NonNull final BluetoothDevice device, final int value) { public void onOperationNotSupported(@NonNull final BluetoothDevice device) {
if (value == 0) final Intent broadcast = new Intent(OPERATION_NOT_SUPPORTED);
showToast(R.string.gls_progress_zero); broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
else broadcast.putExtra(EXTRA_DATA, false);
showToast(getResources().getQuantityString(R.plurals.gls_progress, value, value)); LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
}
} @Override
public void onDatasetCleared(@NonNull final BluetoothDevice device) {
final Intent broadcast = new Intent(BROADCAST_DATA_SET_CLEAR);
broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
}
@Override @Override
public void onBatteryLevelChanged(@NonNull final BluetoothDevice device, final int batteryLevel) { public void onNumberOfRecordsRequested(@NonNull final BluetoothDevice device, final int value) {
final Intent broadcast = new Intent(BROADCAST_BATTERY_LEVEL); if (value == 0)
broadcast.putExtra(EXTRA_DEVICE, device); showToast(R.string.gls_progress_zero);
broadcast.putExtra(EXTRA_BATTERY_LEVEL, batteryLevel); else
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); showToast(getResources().getQuantityString(R.plurals.gls_progress, value, value));
}
}
@Override
public void onBatteryLevelChanged(@NonNull final BluetoothDevice device, final int batteryLevel) {
final Intent broadcast = new Intent(BROADCAST_BATTERY_LEVEL);
broadcast.putExtra(EXTRA_DEVICE, device);
broadcast.putExtra(EXTRA_BATTERY_LEVEL, batteryLevel);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
}
} }

View File

@@ -30,10 +30,11 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.Build;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import no.nordicsemi.android.log.Logger; import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.FeaturesActivity; import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
import no.nordicsemi.android.nrftoolbox.R; import no.nordicsemi.android.nrftoolbox.R;
@@ -42,168 +43,195 @@ import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
import no.nordicsemi.android.nrftoolbox.profile.LoggableBleManager; import no.nordicsemi.android.nrftoolbox.profile.LoggableBleManager;
public class CSCService extends BleProfileService implements CSCManagerCallbacks { public class CSCService extends BleProfileService implements CSCManagerCallbacks {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static final String TAG = "CSCService"; private static final String TAG = "CSCService";
public static final String BROADCAST_WHEEL_DATA = "no.nordicsemi.android.nrftoolbox.csc.BROADCAST_WHEEL_DATA"; public static final String BROADCAST_WHEEL_DATA = "no.nordicsemi.android.nrftoolbox.csc.BROADCAST_WHEEL_DATA";
/** Speed in meters per second. */ /**
public static final String EXTRA_SPEED = "no.nordicsemi.android.nrftoolbox.csc.EXTRA_SPEED"; * Speed in meters per second.
/** Distance in meters. */ */
public static final String EXTRA_DISTANCE = "no.nordicsemi.android.nrftoolbox.csc.EXTRA_DISTANCE"; public static final String EXTRA_SPEED = "no.nordicsemi.android.nrftoolbox.csc.EXTRA_SPEED";
/** Total distance in meters. */ /**
public static final String EXTRA_TOTAL_DISTANCE = "no.nordicsemi.android.nrftoolbox.csc.EXTRA_TOTAL_DISTANCE"; * Distance in meters.
*/
public static final String EXTRA_DISTANCE = "no.nordicsemi.android.nrftoolbox.csc.EXTRA_DISTANCE";
/**
* Total distance in meters.
*/
public static final String EXTRA_TOTAL_DISTANCE = "no.nordicsemi.android.nrftoolbox.csc.EXTRA_TOTAL_DISTANCE";
public static final String BROADCAST_CRANK_DATA = "no.nordicsemi.android.nrftoolbox.csc.BROADCAST_CRANK_DATA"; public static final String BROADCAST_CRANK_DATA = "no.nordicsemi.android.nrftoolbox.csc.BROADCAST_CRANK_DATA";
public static final String EXTRA_GEAR_RATIO = "no.nordicsemi.android.nrftoolbox.csc.EXTRA_GEAR_RATIO"; public static final String EXTRA_GEAR_RATIO = "no.nordicsemi.android.nrftoolbox.csc.EXTRA_GEAR_RATIO";
public static final String EXTRA_CADENCE = "no.nordicsemi.android.nrftoolbox.csc.EXTRA_CADENCE"; public static final String EXTRA_CADENCE = "no.nordicsemi.android.nrftoolbox.csc.EXTRA_CADENCE";
public static final String BROADCAST_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.BROADCAST_BATTERY_LEVEL"; public static final String BROADCAST_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.BROADCAST_BATTERY_LEVEL";
public static final String EXTRA_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.EXTRA_BATTERY_LEVEL"; public static final String EXTRA_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.EXTRA_BATTERY_LEVEL";
private static final String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.csc.ACTION_DISCONNECT"; private static final String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.csc.ACTION_DISCONNECT";
private final static int NOTIFICATION_ID = 200; private final static int NOTIFICATION_ID = 200;
private final static int OPEN_ACTIVITY_REQ = 0; private final static int OPEN_ACTIVITY_REQ = 0;
private final static int DISCONNECT_REQ = 1; private final static int DISCONNECT_REQ = 1;
private final LocalBinder mBinder = new CSCBinder(); private final LocalBinder mBinder = new CSCBinder();
private CSCManager mManager; private CSCManager mManager;
/** /**
* This local binder is an interface for the bonded activity to operate with the RSC sensor * This local binder is an interface for the bonded activity to operate with the RSC sensor
*/ */
class CSCBinder extends LocalBinder { class CSCBinder extends LocalBinder {
// empty // empty
} }
@Override @Override
protected LocalBinder getBinder() { protected LocalBinder getBinder() {
return mBinder; return mBinder;
} }
@Override @Override
protected LoggableBleManager<CSCManagerCallbacks> initializeManager() { protected LoggableBleManager<CSCManagerCallbacks> initializeManager() {
return mManager = new CSCManager(this); return mManager = new CSCManager(this);
} }
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
final IntentFilter filter = new IntentFilter(); final IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_DISCONNECT); filter.addAction(ACTION_DISCONNECT);
registerReceiver(mDisconnectActionBroadcastReceiver, filter); registerReceiver(mDisconnectActionBroadcastReceiver, filter);
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
// when user has disconnected from the sensor, we have to cancel the notification that we've created some milliseconds before using unbindService // when user has disconnected from the sensor, we have to cancel the notification that we've created some milliseconds before using unbindService
cancelNotification(); cancelNotification();
unregisterReceiver(mDisconnectActionBroadcastReceiver); unregisterReceiver(mDisconnectActionBroadcastReceiver);
super.onDestroy(); super.onDestroy();
} }
@Override @Override
protected void onRebind() { protected void onRebind() {
// when the activity rebinds to the service, remove the notification stopForegroundService();
cancelNotification();
if (isConnected()) { if (isConnected()) {
// This method will read the Battery Level value, if possible and then try to enable battery notifications (if it has NOTIFY property). // This method will read the Battery Level value, if possible and then try to enable battery notifications (if it has NOTIFY property).
// If the Battery Level characteristic has only the NOTIFY property, it will only try to enable notifications. // If the Battery Level characteristic has only the NOTIFY property, it will only try to enable notifications.
mManager.readBatteryLevelCharacteristic(); mManager.readBatteryLevelCharacteristic();
} }
} }
@Override @Override
protected void onUnbind() { protected void onUnbind() {
// When we are connected, but the application is not open, we are not really interested in battery level notifications. // When we are connected, but the application is not open, we are not really interested in battery level notifications.
// But we will still be receiving other values, if enabled. // But we will still be receiving other values, if enabled.
if (isConnected()) if (isConnected())
mManager.disableBatteryLevelCharacteristicNotifications(); mManager.disableBatteryLevelCharacteristicNotifications();
startForegroundService();
}
// when the activity closes we need to show the notification that user is connected to the sensor @Override
createNotification(R.string.csc_notification_connected_message, 0); public void onDistanceChanged(@NonNull final BluetoothDevice device, final float totalDistance, final float distance, final float speed) {
} final Intent broadcast = new Intent(BROADCAST_WHEEL_DATA);
broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
broadcast.putExtra(EXTRA_SPEED, speed);
broadcast.putExtra(EXTRA_DISTANCE, distance);
broadcast.putExtra(EXTRA_TOTAL_DISTANCE, totalDistance);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
}
@Override @Override
public void onDistanceChanged(@NonNull final BluetoothDevice device, final float totalDistance, final float distance, final float speed) { public void onCrankDataChanged(@NonNull final BluetoothDevice device, final float crankCadence, final float gearRatio) {
final Intent broadcast = new Intent(BROADCAST_WHEEL_DATA); final Intent broadcast = new Intent(BROADCAST_CRANK_DATA);
broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice()); broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
broadcast.putExtra(EXTRA_SPEED, speed); broadcast.putExtra(EXTRA_GEAR_RATIO, gearRatio);
broadcast.putExtra(EXTRA_DISTANCE, distance); broadcast.putExtra(EXTRA_CADENCE, (int) crankCadence);
broadcast.putExtra(EXTRA_TOTAL_DISTANCE, totalDistance); LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); }
}
@Override @Override
public void onCrankDataChanged(@NonNull final BluetoothDevice device, final float crankCadence, final float gearRatio) { public void onBatteryLevelChanged(@NonNull final BluetoothDevice device, final int batteryLevel) {
final Intent broadcast = new Intent(BROADCAST_CRANK_DATA); final Intent broadcast = new Intent(BROADCAST_BATTERY_LEVEL);
broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice()); broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
broadcast.putExtra(EXTRA_GEAR_RATIO, gearRatio); broadcast.putExtra(EXTRA_BATTERY_LEVEL, batteryLevel);
broadcast.putExtra(EXTRA_CADENCE, (int) crankCadence); LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); }
}
@Override /**
public void onBatteryLevelChanged(@NonNull final BluetoothDevice device, final int batteryLevel) { * Sets the service as a foreground service
final Intent broadcast = new Intent(BROADCAST_BATTERY_LEVEL); */
broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice()); private void startForegroundService(){
broadcast.putExtra(EXTRA_BATTERY_LEVEL, batteryLevel); // when the activity closes we need to show the notification that user is connected to the peripheral sensor
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); // We start the service as a foreground service as Android 8.0 (Oreo) onwards kills any running background services
} final Notification notification = createNotification(R.string.uart_notification_connected_message, 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForeground(NOTIFICATION_ID, notification);
} else {
final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(NOTIFICATION_ID, notification);
}
}
/** /**
* Creates the notification * Stops the service as a foreground service
* */
* @param messageResId private void stopForegroundService(){
* the message resource id. The message must have one String parameter,<br /> // when the activity rebinds to the service, remove the notification and stop the foreground service
* f.e. <code>&lt;string name="name"&gt;%s is connected&lt;/string&gt;</code> // on devices running Android 8.0 (Oreo) or above
* @param defaults if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
* signals that will be used to notify the user stopForeground(true);
*/ } else {
private void createNotification(final int messageResId, final int defaults) { cancelNotification();
final Intent parentIntent = new Intent(this, FeaturesActivity.class); }
parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); }
final Intent targetIntent = new Intent(this, CSCActivity.class);
final Intent disconnect = new Intent(ACTION_DISCONNECT); /**
final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ, disconnect, PendingIntent.FLAG_UPDATE_CURRENT); * Creates the notification
*
* @param messageResId the message resource id. The message must have one String parameter,<br />
* f.e. <code>&lt;string name="name"&gt;%s is connected&lt;/string&gt;</code>
* @param defaults
*/
private Notification createNotification(final int messageResId, final int defaults) {
final Intent parentIntent = new Intent(this, FeaturesActivity.class);
parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final Intent targetIntent = new Intent(this, CSCActivity.class);
// both activities above have launchMode="singleTask" in the AndroidManifest.xml file, so if the task is already running, it will be resumed final Intent disconnect = new Intent(ACTION_DISCONNECT);
final PendingIntent pendingIntent = PendingIntent.getActivities(this, OPEN_ACTIVITY_REQ, new Intent[] { parentIntent, targetIntent }, PendingIntent.FLAG_UPDATE_CURRENT); final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ, disconnect, PendingIntent.FLAG_UPDATE_CURRENT);
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this, ToolboxApplication.CONNECTED_DEVICE_CHANNEL);
builder.setContentIntent(pendingIntent);
builder.setContentTitle(getString(R.string.app_name)).setContentText(getString(messageResId, getDeviceName()));
builder.setSmallIcon(R.drawable.ic_stat_notify_csc);
builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(true);
builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.csc_notification_action_disconnect), disconnectAction));
final Notification notification = builder.build(); // both activities above have launchMode="singleTask" in the AndroidManifest.xml file, so if the task is already running, it will be resumed
final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); final PendingIntent pendingIntent = PendingIntent.getActivities(this, OPEN_ACTIVITY_REQ, new Intent[]{parentIntent, targetIntent}, PendingIntent.FLAG_UPDATE_CURRENT);
nm.notify(NOTIFICATION_ID, notification); final NotificationCompat.Builder builder = new NotificationCompat.Builder(this, ToolboxApplication.CONNECTED_DEVICE_CHANNEL);
} builder.setContentIntent(pendingIntent);
builder.setContentTitle(getString(R.string.app_name)).setContentText(getString(messageResId, getDeviceName()));
builder.setSmallIcon(R.drawable.ic_stat_notify_csc);
builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(true);
builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.csc_notification_action_disconnect), disconnectAction));
/** return builder.build();
* Cancels the existing notification. If there is no active notification this method does nothing }
*/
private void cancelNotification() {
final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nm.cancel(NOTIFICATION_ID);
}
/** /**
* This broadcast receiver listens for {@link #ACTION_DISCONNECT} that may be fired by pressing Disconnect action button on the notification. * Cancels the existing notification. If there is no active notification this method does nothing
*/ */
private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() { private void cancelNotification() {
@Override final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
public void onReceive(final Context context, final Intent intent) { nm.cancel(NOTIFICATION_ID);
Logger.i(getLogSession(), "[Notification] Disconnect action pressed"); }
if (isConnected())
getBinder().disconnect(); /**
else * This broadcast receiver listens for {@link #ACTION_DISCONNECT} that may be fired by pressing Disconnect action button on the notification.
stopSelf(); */
} private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() {
}; @Override
public void onReceive(final Context context, final Intent intent) {
Logger.i(getLogSession(), "[Notification] Disconnect action pressed");
if (isConnected())
getBinder().disconnect();
else
stopSelf();
}
};
} }

View File

@@ -30,6 +30,8 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.Build;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
@@ -110,14 +112,12 @@ public class HTSService extends BleProfileService implements HTSManagerCallbacks
@Override @Override
protected void onRebind() { protected void onRebind() {
// when the activity rebinds to the service, remove the notification stopForegroundService();
cancelNotification();
} }
@Override @Override
protected void onUnbind() { protected void onUnbind() {
// when the activity closes we need to show the notification that user is connected to the sensor startForegroundService();
createNotification(R.string.hts_notification_connected_message, 0);
} }
@Override @Override
@@ -153,16 +153,42 @@ public class HTSService extends BleProfileService implements HTSManagerCallbacks
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
} }
/**
* Sets the service as a foreground service
*/
private void startForegroundService(){
// when the activity closes we need to show the notification that user is connected to the peripheral sensor
// We start the service as a foreground service as Android 8.0 (Oreo) onwards kills any running background services
final Notification notification = createNotification(R.string.uart_notification_connected_message, 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForeground(NOTIFICATION_ID, notification);
} else {
final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(NOTIFICATION_ID, notification);
}
}
/**
* Stops the service as a foreground service
*/
private void stopForegroundService(){
// when the activity rebinds to the service, remove the notification and stop the foreground service
// on devices running Android 8.0 (Oreo) or above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
stopForeground(true);
} else {
cancelNotification();
}
}
/** /**
* Creates the notification * Creates the notification
* * @param messageResId
* @param messageResId
* message resource id. The message must have one String parameter,<br /> * message resource id. The message must have one String parameter,<br />
* f.e. <code>&lt;string name="name"&gt;%s is connected&lt;/string&gt;</code> * f.e. <code>&lt;string name="name"&gt;%s is connected&lt;/string&gt;</code>
* @param defaults * @param defaults
* signals that will be used to notify the user
*/ */
private void createNotification(final int messageResId, final int defaults) { private Notification createNotification(final int messageResId, final int defaults) {
final Intent parentIntent = new Intent(this, FeaturesActivity.class); final Intent parentIntent = new Intent(this, FeaturesActivity.class);
parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final Intent targetIntent = new Intent(this, HTSActivity.class); final Intent targetIntent = new Intent(this, HTSActivity.class);
@@ -179,9 +205,7 @@ public class HTSService extends BleProfileService implements HTSManagerCallbacks
builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(true); builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(true);
builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.hts_notification_action_disconnect), disconnectAction)); builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.hts_notification_action_disconnect), disconnectAction));
final Notification notification = builder.build(); return builder.build();
final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(NOTIFICATION_ID, notification);
} }
/** /**

View File

@@ -30,12 +30,13 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.Build;
import android.os.Handler; import android.os.Handler;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import no.nordicsemi.android.log.Logger; import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.FeaturesActivity; import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
import no.nordicsemi.android.nrftoolbox.R; import no.nordicsemi.android.nrftoolbox.R;
@@ -44,209 +45,239 @@ import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
import no.nordicsemi.android.nrftoolbox.profile.LoggableBleManager; import no.nordicsemi.android.nrftoolbox.profile.LoggableBleManager;
public class RSCService extends BleProfileService implements RSCManagerCallbacks { public class RSCService extends BleProfileService implements RSCManagerCallbacks {
private static final String TAG = "RSCService"; private static final String TAG = "RSCService";
public static final String BROADCAST_RSC_MEASUREMENT = "no.nordicsemi.android.nrftoolbox.rsc.BROADCAST_RSC_MEASUREMENT"; public static final String BROADCAST_RSC_MEASUREMENT = "no.nordicsemi.android.nrftoolbox.rsc.BROADCAST_RSC_MEASUREMENT";
public static final String EXTRA_SPEED = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_SPEED"; public static final String EXTRA_SPEED = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_SPEED";
public static final String EXTRA_CADENCE = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_CADENCE"; public static final String EXTRA_CADENCE = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_CADENCE";
public static final String EXTRA_STRIDE_LENGTH = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_STRIDE_LENGTH"; public static final String EXTRA_STRIDE_LENGTH = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_STRIDE_LENGTH";
public static final String EXTRA_TOTAL_DISTANCE = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_TOTAL_DISTANCE"; public static final String EXTRA_TOTAL_DISTANCE = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_TOTAL_DISTANCE";
public static final String EXTRA_ACTIVITY = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_ACTIVITY"; public static final String EXTRA_ACTIVITY = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_ACTIVITY";
public static final String BROADCAST_STRIDES_UPDATE = "no.nordicsemi.android.nrftoolbox.rsc.BROADCAST_STRIDES_UPDATE"; public static final String BROADCAST_STRIDES_UPDATE = "no.nordicsemi.android.nrftoolbox.rsc.BROADCAST_STRIDES_UPDATE";
public static final String EXTRA_STRIDES = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_STRIDES"; public static final String EXTRA_STRIDES = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_STRIDES";
public static final String EXTRA_DISTANCE = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_DISTANCE"; public static final String EXTRA_DISTANCE = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_DISTANCE";
public static final String BROADCAST_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.BROADCAST_BATTERY_LEVEL"; public static final String BROADCAST_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.BROADCAST_BATTERY_LEVEL";
public static final String EXTRA_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.EXTRA_BATTERY_LEVEL"; public static final String EXTRA_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.EXTRA_BATTERY_LEVEL";
private final static String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.rsc.ACTION_DISCONNECT"; private final static String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.rsc.ACTION_DISCONNECT";
private RSCManager mManager; private RSCManager mManager;
/** The last value of a cadence */ /**
private float mCadence; * The last value of a cadence
/** Trip distance in cm */ */
private long mDistance; private float mCadence;
/** Stride length in cm */ /**
private Integer mStrideLength; * Trip distance in cm
/** Number of steps in the trip */ */
private int mStepsNumber; private long mDistance;
private boolean mTaskInProgress; /**
private final Handler mHandler = new Handler(); * Stride length in cm
*/
private Integer mStrideLength;
/**
* Number of steps in the trip
*/
private int mStepsNumber;
private boolean mTaskInProgress;
private final Handler mHandler = new Handler();
private final static int NOTIFICATION_ID = 200; private final static int NOTIFICATION_ID = 200;
private final static int OPEN_ACTIVITY_REQ = 0; private final static int OPEN_ACTIVITY_REQ = 0;
private final static int DISCONNECT_REQ = 1; private final static int DISCONNECT_REQ = 1;
private final LocalBinder mBinder = new RSCBinder(); private final LocalBinder mBinder = new RSCBinder();
/** /**
* This local binder is an interface for the bound activity to operate with the RSC sensor. * This local binder is an interface for the bound activity to operate with the RSC sensor.
*/ */
class RSCBinder extends LocalBinder { class RSCBinder extends LocalBinder {
// empty // empty
} }
@Override @Override
protected LocalBinder getBinder() { protected LocalBinder getBinder() {
return mBinder; return mBinder;
} }
@Override @Override
protected LoggableBleManager<RSCManagerCallbacks> initializeManager() { protected LoggableBleManager<RSCManagerCallbacks> initializeManager() {
return mManager = new RSCManager(this); return mManager = new RSCManager(this);
} }
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
final IntentFilter filter = new IntentFilter(); final IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_DISCONNECT); filter.addAction(ACTION_DISCONNECT);
registerReceiver(mDisconnectActionBroadcastReceiver, filter); registerReceiver(mDisconnectActionBroadcastReceiver, filter);
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
// when user has disconnected from the sensor, we have to cancel the notification that we've created some milliseconds before using unbindService // when user has disconnected from the sensor, we have to cancel the notification that we've created some milliseconds before using unbindService
cancelNotification(); stopForegroundService();
unregisterReceiver(mDisconnectActionBroadcastReceiver); unregisterReceiver(mDisconnectActionBroadcastReceiver);
super.onDestroy(); super.onDestroy();
} }
@Override @Override
protected void onRebind() { protected void onRebind() {
// when the activity rebinds to the service, remove the notification stopForegroundService();
cancelNotification();
if (isConnected()) { if (isConnected()) {
// This method will read the Battery Level value, if possible and then try to enable battery notifications (if it has NOTIFY property). // This method will read the Battery Level value, if possible and then try to enable battery notifications (if it has NOTIFY property).
// If the Battery Level characteristic has only the NOTIFY property, it will only try to enable notifications. // If the Battery Level characteristic has only the NOTIFY property, it will only try to enable notifications.
mManager.readBatteryLevelCharacteristic(); mManager.readBatteryLevelCharacteristic();
} }
} }
@Override @Override
protected void onUnbind() { protected void onUnbind() {
// When we are connected, but the application is not open, we are not really interested in battery level notifications. // When we are connected, but the application is not open, we are not really interested in battery level notifications.
// But we will still be receiving other values, if enabled. // But we will still be receiving other values, if enabled.
if (isConnected()) if (isConnected())
mManager.disableBatteryLevelCharacteristicNotifications(); mManager.disableBatteryLevelCharacteristicNotifications();
// when the activity closes we need to show the notification that user is connected to the sensor startForegroundService();
createNotification(R.string.rsc_notification_connected_message, 0); }
}
private final Runnable mUpdateStridesTask = new Runnable() { private final Runnable mUpdateStridesTask = new Runnable() {
@Override @Override
public void run() { public void run() {
if (!isConnected()) if (!isConnected())
return; return;
mStepsNumber++; mStepsNumber++;
mDistance += mStrideLength; // [cm] mDistance += mStrideLength; // [cm]
final Intent broadcast = new Intent(BROADCAST_STRIDES_UPDATE); final Intent broadcast = new Intent(BROADCAST_STRIDES_UPDATE);
broadcast.putExtra(EXTRA_STRIDES, mStepsNumber); broadcast.putExtra(EXTRA_STRIDES, mStepsNumber);
broadcast.putExtra(EXTRA_DISTANCE, mDistance); broadcast.putExtra(EXTRA_DISTANCE, mDistance);
LocalBroadcastManager.getInstance(RSCService.this).sendBroadcast(broadcast); LocalBroadcastManager.getInstance(RSCService.this).sendBroadcast(broadcast);
if (mCadence > 0) { if (mCadence > 0) {
final long interval = (long) (1000.0f * 60.0f / mCadence); final long interval = (long) (1000.0f * 60.0f / mCadence);
mHandler.postDelayed(mUpdateStridesTask, interval); mHandler.postDelayed(mUpdateStridesTask, interval);
} else { } else {
mTaskInProgress = false; mTaskInProgress = false;
} }
} }
}; };
@Override @Override
public void onRSCMeasurementReceived(@NonNull final BluetoothDevice device, final boolean running, public void onRSCMeasurementReceived(@NonNull final BluetoothDevice device, final boolean running,
final float instantaneousSpeed, final int instantaneousCadence, final float instantaneousSpeed, final int instantaneousCadence,
@Nullable final Integer strideLength, @Nullable final Integer strideLength,
@Nullable final Long totalDistance) { @Nullable final Long totalDistance) {
final Intent broadcast = new Intent(BROADCAST_RSC_MEASUREMENT); final Intent broadcast = new Intent(BROADCAST_RSC_MEASUREMENT);
broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice()); broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
broadcast.putExtra(EXTRA_SPEED, instantaneousSpeed); broadcast.putExtra(EXTRA_SPEED, instantaneousSpeed);
broadcast.putExtra(EXTRA_CADENCE, instantaneousCadence); broadcast.putExtra(EXTRA_CADENCE, instantaneousCadence);
broadcast.putExtra(EXTRA_STRIDE_LENGTH, strideLength); broadcast.putExtra(EXTRA_STRIDE_LENGTH, strideLength);
broadcast.putExtra(EXTRA_TOTAL_DISTANCE, totalDistance); broadcast.putExtra(EXTRA_TOTAL_DISTANCE, totalDistance);
broadcast.putExtra(EXTRA_ACTIVITY, running); broadcast.putExtra(EXTRA_ACTIVITY, running);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
// Start strides counter if not in progress // Start strides counter if not in progress
mCadence = instantaneousCadence; mCadence = instantaneousCadence;
if (strideLength != null) { if (strideLength != null) {
mStrideLength = strideLength; mStrideLength = strideLength;
} }
if (!mTaskInProgress && strideLength != null && instantaneousCadence > 0) { if (!mTaskInProgress && strideLength != null && instantaneousCadence > 0) {
mTaskInProgress = true; mTaskInProgress = true;
final long interval = (long) (1000.0f * 60.0f / mCadence); final long interval = (long) (1000.0f * 60.0f / mCadence);
mHandler.postDelayed(mUpdateStridesTask, interval); mHandler.postDelayed(mUpdateStridesTask, interval);
} }
} }
@Override @Override
public void onBatteryLevelChanged(@NonNull final BluetoothDevice device, final int value) { public void onBatteryLevelChanged(@NonNull final BluetoothDevice device, final int value) {
final Intent broadcast = new Intent(BROADCAST_BATTERY_LEVEL); final Intent broadcast = new Intent(BROADCAST_BATTERY_LEVEL);
broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice()); broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
broadcast.putExtra(EXTRA_BATTERY_LEVEL, value); broadcast.putExtra(EXTRA_BATTERY_LEVEL, value);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
} }
/** /**
* Creates the notification * Sets the service as a foreground service
* */
* @param messageResId private void startForegroundService(){
* message resource id. The message must have one String parameter,<br /> // when the activity closes we need to show the notification that user is connected to the peripheral sensor
* f.e. <code>&lt;string name="name"&gt;%s is connected&lt;/string&gt;</code> // We start the service as a foreground service as Android 8.0 (Oreo) onwards kills any running background services
* @param defaults final Notification notification = createNotification(R.string.uart_notification_connected_message, 0);
* signals that will be used to notify the user if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
*/ startForeground(NOTIFICATION_ID, notification);
private void createNotification(final int messageResId, final int defaults) { } else {
final Intent parentIntent = new Intent(this, FeaturesActivity.class); final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); nm.notify(NOTIFICATION_ID, notification);
final Intent targetIntent = new Intent(this, RSCActivity.class); }
}
final Intent disconnect = new Intent(ACTION_DISCONNECT); /**
final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ, disconnect, PendingIntent.FLAG_UPDATE_CURRENT); * Stops the service as a foreground service
*/
private void stopForegroundService(){
// when the activity rebinds to the service, remove the notification and stop the foreground service
// on devices running Android 8.0 (Oreo) or above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
stopForeground(true);
} else {
cancelNotification();
}
}
// both activities above have launchMode="singleTask" in the AndroidManifest.xml file, so if the task is already running, it will be resumed /**
final PendingIntent pendingIntent = PendingIntent.getActivities(this, OPEN_ACTIVITY_REQ, new Intent[] { parentIntent, targetIntent }, PendingIntent.FLAG_UPDATE_CURRENT); * Creates the notification
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this, ToolboxApplication.CONNECTED_DEVICE_CHANNEL); *
builder.setContentIntent(pendingIntent); * @param messageResId message resource id. The message must have one String parameter,<br />
builder.setContentTitle(getString(R.string.app_name)).setContentText(getString(messageResId, getDeviceName())); * f.e. <code>&lt;string name="name"&gt;%s is connected&lt;/string&gt;</code>
builder.setSmallIcon(R.drawable.ic_stat_notify_rsc); * @param defaults
builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(true); */
builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.rsc_notification_action_disconnect), disconnectAction)); private Notification createNotification(final int messageResId, final int defaults) {
final Intent parentIntent = new Intent(this, FeaturesActivity.class);
parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final Intent targetIntent = new Intent(this, RSCActivity.class);
final Notification notification = builder.build(); final Intent disconnect = new Intent(ACTION_DISCONNECT);
final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ, disconnect, PendingIntent.FLAG_UPDATE_CURRENT);
nm.notify(NOTIFICATION_ID, notification);
}
/** // both activities above have launchMode="singleTask" in the AndroidManifest.xml file, so if the task is already running, it will be resumed
* Cancels the existing notification. If there is no active notification this method does nothing final PendingIntent pendingIntent = PendingIntent.getActivities(this, OPEN_ACTIVITY_REQ, new Intent[]{parentIntent, targetIntent}, PendingIntent.FLAG_UPDATE_CURRENT);
*/ final NotificationCompat.Builder builder = new NotificationCompat.Builder(this, ToolboxApplication.CONNECTED_DEVICE_CHANNEL);
private void cancelNotification() { builder.setContentIntent(pendingIntent);
final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); builder.setContentTitle(getString(R.string.app_name)).setContentText(getString(messageResId, getDeviceName()));
nm.cancel(NOTIFICATION_ID); builder.setSmallIcon(R.drawable.ic_stat_notify_rsc);
} builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(true);
builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.rsc_notification_action_disconnect), disconnectAction));
/** return builder.build();
* This broadcast receiver listens for {@link #ACTION_DISCONNECT} that may be fired by pressing Disconnect action button on the notification. }
*/
private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() { /**
@Override * Cancels the existing notification. If there is no active notification this method does nothing
public void onReceive(final Context context, final Intent intent) { */
Logger.i(getLogSession(), "[Notification] Disconnect action pressed"); private void cancelNotification() {
if (isConnected()) final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
getBinder().disconnect(); nm.cancel(NOTIFICATION_ID);
else }
stopSelf();
} /**
}; * This broadcast receiver listens for {@link #ACTION_DISCONNECT} that may be fired by pressing Disconnect action button on the notification.
*/
private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
Logger.i(getLogSession(), "[Notification] Disconnect action pressed");
if (isConnected())
getBinder().disconnect();
else
stopSelf();
}
};
} }

View File

@@ -30,10 +30,11 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.Build;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import no.nordicsemi.android.log.Logger; import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.FeaturesActivity; import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
import no.nordicsemi.android.nrftoolbox.R; import no.nordicsemi.android.nrftoolbox.R;
@@ -42,144 +43,168 @@ import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
import no.nordicsemi.android.nrftoolbox.profile.LoggableBleManager; import no.nordicsemi.android.nrftoolbox.profile.LoggableBleManager;
public class TemplateService extends BleProfileService implements TemplateManagerCallbacks { public class TemplateService extends BleProfileService implements TemplateManagerCallbacks {
public static final String BROADCAST_TEMPLATE_MEASUREMENT = "no.nordicsemi.android.nrftoolbox.template.BROADCAST_MEASUREMENT"; public static final String BROADCAST_TEMPLATE_MEASUREMENT = "no.nordicsemi.android.nrftoolbox.template.BROADCAST_MEASUREMENT";
public static final String EXTRA_DATA = "no.nordicsemi.android.nrftoolbox.template.EXTRA_DATA"; public static final String EXTRA_DATA = "no.nordicsemi.android.nrftoolbox.template.EXTRA_DATA";
public static final String BROADCAST_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.BROADCAST_BATTERY_LEVEL"; public static final String BROADCAST_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.BROADCAST_BATTERY_LEVEL";
public static final String EXTRA_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.EXTRA_BATTERY_LEVEL"; public static final String EXTRA_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.EXTRA_BATTERY_LEVEL";
private final static String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.template.ACTION_DISCONNECT"; private final static String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.template.ACTION_DISCONNECT";
private final static int NOTIFICATION_ID = 864; private final static int NOTIFICATION_ID = 864;
private final static int OPEN_ACTIVITY_REQ = 0; private final static int OPEN_ACTIVITY_REQ = 0;
private final static int DISCONNECT_REQ = 1; private final static int DISCONNECT_REQ = 1;
private TemplateManager mManager; private TemplateManager mManager;
private final LocalBinder mBinder = new TemplateBinder(); private final LocalBinder mBinder = new TemplateBinder();
/** /**
* This local binder is an interface for the bound activity to operate with the sensor. * This local binder is an interface for the bound activity to operate with the sensor.
*/ */
class TemplateBinder extends LocalBinder { class TemplateBinder extends LocalBinder {
// TODO Define service API that may be used by a bound Activity // TODO Define service API that may be used by a bound Activity
/** /**
* Sends some important data to the device. * Sends some important data to the device.
* *
* @param parameter some parameter. * @param parameter some parameter.
*/ */
public void performAction(final String parameter) { public void performAction(final String parameter) {
mManager.performAction(parameter); mManager.performAction(parameter);
} }
} }
@Override @Override
protected LocalBinder getBinder() { protected LocalBinder getBinder() {
return mBinder; return mBinder;
} }
@Override @Override
protected LoggableBleManager<TemplateManagerCallbacks> initializeManager() { protected LoggableBleManager<TemplateManagerCallbacks> initializeManager() {
return mManager = new TemplateManager(this); return mManager = new TemplateManager(this);
} }
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
final IntentFilter filter = new IntentFilter(); final IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_DISCONNECT); filter.addAction(ACTION_DISCONNECT);
registerReceiver(mDisconnectActionBroadcastReceiver, filter); registerReceiver(mDisconnectActionBroadcastReceiver, filter);
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
// when user has disconnected from the sensor, we have to cancel the notification that we've created some milliseconds before using unbindService // when user has disconnected from the sensor, we have to cancel the notification that we've created some milliseconds before using unbindService
cancelNotification(); stopForegroundService();
unregisterReceiver(mDisconnectActionBroadcastReceiver); unregisterReceiver(mDisconnectActionBroadcastReceiver);
super.onDestroy(); super.onDestroy();
} }
@Override @Override
protected void onRebind() { protected void onRebind() {
// when the activity rebinds to the service, remove the notification stopForegroundService();
cancelNotification(); }
}
@Override @Override
protected void onUnbind() { protected void onUnbind() {
// when the activity closes we need to show the notification that user is connected to the sensor startForegroundService();
createNotification(R.string.template_notification_connected_message, 0); }
}
@Override @Override
public void onSampleValueReceived(@NonNull final BluetoothDevice device, final int value) { public void onSampleValueReceived(@NonNull final BluetoothDevice device, final int value) {
final Intent broadcast = new Intent(BROADCAST_TEMPLATE_MEASUREMENT); final Intent broadcast = new Intent(BROADCAST_TEMPLATE_MEASUREMENT);
broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice()); broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
broadcast.putExtra(EXTRA_DATA, value); broadcast.putExtra(EXTRA_DATA, value);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
if (!mBound) { if (!mBound) {
// Here we may update the notification to display the current value. // Here we may update the notification to display the current value.
// TODO modify the notification here // TODO modify the notification here
} }
} }
@Override @Override
public void onBatteryLevelChanged(@NonNull final BluetoothDevice device, final int batteryLevel) { public void onBatteryLevelChanged(@NonNull final BluetoothDevice device, final int batteryLevel) {
} }
/** /**
* Creates the notification. * Sets the service as a foreground service
* */
* @param messageResId message resource id. The message must have one String parameter,<br /> private void startForegroundService(){
* f.e. <code>&lt;string name="name"&gt;%s is connected&lt;/string&gt;</code> // when the activity closes we need to show the notification that user is connected to the peripheral sensor
* @param defaults signals that will be used to notify the user // We start the service as a foreground service as Android 8.0 (Oreo) onwards kills any running background services
*/ final Notification notification = createNotification(R.string.uart_notification_connected_message, 0);
private void createNotification(final int messageResId, final int defaults) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
final Intent parentIntent = new Intent(this, FeaturesActivity.class); startForeground(NOTIFICATION_ID, notification);
parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } else {
final Intent targetIntent = new Intent(this, TemplateActivity.class); final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(NOTIFICATION_ID, notification);
}
}
final Intent disconnect = new Intent(ACTION_DISCONNECT); /**
final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ, disconnect, PendingIntent.FLAG_UPDATE_CURRENT); * Stops the service as a foreground service
*/
private void stopForegroundService(){
// when the activity rebinds to the service, remove the notification and stop the foreground service
// on devices running Android 8.0 (Oreo) or above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
stopForeground(true);
} else {
cancelNotification();
}
}
// both activities above have launchMode="singleTask" in the AndroidManifest.xml file, so if the task is already running, it will be resumed /**
final PendingIntent pendingIntent = PendingIntent.getActivities(this, OPEN_ACTIVITY_REQ, new Intent[]{parentIntent, targetIntent}, PendingIntent.FLAG_UPDATE_CURRENT); * Creates the notification.
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this, ToolboxApplication.CONNECTED_DEVICE_CHANNEL); *
builder.setContentIntent(pendingIntent); * @param messageResId message resource id. The message must have one String parameter,<br />
builder.setContentTitle(getString(R.string.app_name)).setContentText(getString(messageResId, getDeviceName())); * f.e. <code>&lt;string name="name"&gt;%s is connected&lt;/string&gt;</code>
builder.setSmallIcon(R.drawable.ic_stat_notify_template); * @param defaults signals that will be used to notify the user
builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(true); */
builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.template_notification_action_disconnect), disconnectAction)); private Notification createNotification(final int messageResId, final int defaults) {
final Intent parentIntent = new Intent(this, FeaturesActivity.class);
parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final Intent targetIntent = new Intent(this, TemplateActivity.class);
final Notification notification = builder.build(); final Intent disconnect = new Intent(ACTION_DISCONNECT);
final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ, disconnect, PendingIntent.FLAG_UPDATE_CURRENT);
nm.notify(NOTIFICATION_ID, notification);
}
/** // both activities above have launchMode="singleTask" in the AndroidManifest.xml file, so if the task is already running, it will be resumed
* Cancels the existing notification. If there is no active notification this method does nothing final PendingIntent pendingIntent = PendingIntent.getActivities(this, OPEN_ACTIVITY_REQ, new Intent[]{parentIntent, targetIntent}, PendingIntent.FLAG_UPDATE_CURRENT);
*/ final NotificationCompat.Builder builder = new NotificationCompat.Builder(this, ToolboxApplication.CONNECTED_DEVICE_CHANNEL);
private void cancelNotification() { builder.setContentIntent(pendingIntent);
final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); builder.setContentTitle(getString(R.string.app_name)).setContentText(getString(messageResId, getDeviceName()));
nm.cancel(NOTIFICATION_ID); builder.setSmallIcon(R.drawable.ic_stat_notify_template);
} builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(true);
builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.template_notification_action_disconnect), disconnectAction));
/** return builder.build();
* This broadcast receiver listens for {@link #ACTION_DISCONNECT} that may be fired by pressing Disconnect action button on the notification. }
*/
private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() { /**
@Override * Cancels the existing notification. If there is no active notification this method does nothing
public void onReceive(final Context context, final Intent intent) { */
Logger.i(getLogSession(), "[Notification] Disconnect action pressed"); private void cancelNotification() {
if (isConnected()) final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
getBinder().disconnect(); nm.cancel(NOTIFICATION_ID);
else }
stopSelf();
} /**
}; * This broadcast receiver listens for {@link #ACTION_DISCONNECT} that may be fired by pressing Disconnect action button on the notification.
*/
private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
Logger.i(getLogSession(), "[Notification] Disconnect action pressed");
if (isConnected())
getBinder().disconnect();
else
stopSelf();
}
};
} }

View File

@@ -30,9 +30,7 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import androidx.annotation.NonNull; import android.os.Build;
import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
@@ -42,6 +40,9 @@ import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi; import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable; import com.google.android.gms.wearable.Wearable;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import no.nordicsemi.android.log.Logger; import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.FeaturesActivity; import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
import no.nordicsemi.android.nrftoolbox.R; import no.nordicsemi.android.nrftoolbox.R;
@@ -51,269 +52,300 @@ import no.nordicsemi.android.nrftoolbox.profile.LoggableBleManager;
import no.nordicsemi.android.nrftoolbox.wearable.common.Constants; import no.nordicsemi.android.nrftoolbox.wearable.common.Constants;
public class UARTService extends BleProfileService implements UARTManagerCallbacks { public class UARTService extends BleProfileService implements UARTManagerCallbacks {
private static final String TAG = "UARTService"; private static final String TAG = "UARTService";
public static final String BROADCAST_UART_TX = "no.nordicsemi.android.nrftoolbox.uart.BROADCAST_UART_TX"; public static final String BROADCAST_UART_TX = "no.nordicsemi.android.nrftoolbox.uart.BROADCAST_UART_TX";
public static final String BROADCAST_UART_RX = "no.nordicsemi.android.nrftoolbox.uart.BROADCAST_UART_RX"; public static final String BROADCAST_UART_RX = "no.nordicsemi.android.nrftoolbox.uart.BROADCAST_UART_RX";
public static final String EXTRA_DATA = "no.nordicsemi.android.nrftoolbox.uart.EXTRA_DATA"; public static final String EXTRA_DATA = "no.nordicsemi.android.nrftoolbox.uart.EXTRA_DATA";
/** A broadcast message with this action and the message in {@link Intent#EXTRA_TEXT} will be sent t the UART device. */ /**
public final static String ACTION_SEND = "no.nordicsemi.android.nrftoolbox.uart.ACTION_SEND"; * A broadcast message with this action and the message in {@link Intent#EXTRA_TEXT} will be sent t the UART device.
/** A broadcast message with this action is triggered when a message is received from the UART device. */ */
private final static String ACTION_RECEIVE = "no.nordicsemi.android.nrftoolbox.uart.ACTION_RECEIVE"; public final static String ACTION_SEND = "no.nordicsemi.android.nrftoolbox.uart.ACTION_SEND";
/** Action send when user press the DISCONNECT button on the notification. */ /**
public final static String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.uart.ACTION_DISCONNECT"; * A broadcast message with this action is triggered when a message is received from the UART device.
/** A source of an action. */ */
public final static String EXTRA_SOURCE = "no.nordicsemi.android.nrftoolbox.uart.EXTRA_SOURCE"; private final static String ACTION_RECEIVE = "no.nordicsemi.android.nrftoolbox.uart.ACTION_RECEIVE";
public final static int SOURCE_NOTIFICATION = 0; /**
public final static int SOURCE_WEARABLE = 1; * Action send when user press the DISCONNECT button on the notification.
public final static int SOURCE_3RD_PARTY = 2; */
public final static String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.uart.ACTION_DISCONNECT";
/**
* A source of an action.
*/
public final static String EXTRA_SOURCE = "no.nordicsemi.android.nrftoolbox.uart.EXTRA_SOURCE";
public final static int SOURCE_NOTIFICATION = 0;
public final static int SOURCE_WEARABLE = 1;
public final static int SOURCE_3RD_PARTY = 2;
private final static int NOTIFICATION_ID = 349; // random private final static int NOTIFICATION_ID = 349; // random
private final static int OPEN_ACTIVITY_REQ = 67; // random private final static int OPEN_ACTIVITY_REQ = 67; // random
private final static int DISCONNECT_REQ = 97; // random private final static int DISCONNECT_REQ = 97; // random
private GoogleApiClient mGoogleApiClient; private GoogleApiClient mGoogleApiClient;
private UARTManager mManager; private UARTManager mManager;
private final LocalBinder mBinder = new UARTBinder(); private final LocalBinder mBinder = new UARTBinder();
public class UARTBinder extends LocalBinder implements UARTInterface { public class UARTBinder extends LocalBinder implements UARTInterface {
@Override @Override
public void send(final String text) { public void send(final String text) {
mManager.send(text); mManager.send(text);
} }
} }
@Override @Override
protected LocalBinder getBinder() { protected LocalBinder getBinder() {
return mBinder; return mBinder;
} }
@Override @Override
protected LoggableBleManager<UARTManagerCallbacks> initializeManager() { protected LoggableBleManager<UARTManagerCallbacks> initializeManager() {
return mManager = new UARTManager(this); return mManager = new UARTManager(this);
} }
@Override @Override
protected boolean shouldAutoConnect() { protected boolean shouldAutoConnect() {
return true; return true;
} }
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
registerReceiver(mDisconnectActionBroadcastReceiver, new IntentFilter(ACTION_DISCONNECT)); registerReceiver(mDisconnectActionBroadcastReceiver, new IntentFilter(ACTION_DISCONNECT));
registerReceiver(mIntentBroadcastReceiver, new IntentFilter(ACTION_SEND)); registerReceiver(mIntentBroadcastReceiver, new IntentFilter(ACTION_SEND));
mGoogleApiClient = new GoogleApiClient.Builder(this) mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API) .addApi(Wearable.API)
.build(); .build();
mGoogleApiClient.connect(); mGoogleApiClient.connect();
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
// when user has disconnected from the sensor, we have to cancel the notification that we've created some milliseconds before using unbindService // when user has disconnected from the sensor, we have to cancel the notification that we've created some milliseconds before using unbindService
cancelNotification(); stopForegroundService();
unregisterReceiver(mDisconnectActionBroadcastReceiver); unregisterReceiver(mDisconnectActionBroadcastReceiver);
unregisterReceiver(mIntentBroadcastReceiver); unregisterReceiver(mIntentBroadcastReceiver);
mGoogleApiClient.disconnect(); mGoogleApiClient.disconnect();
super.onDestroy(); super.onDestroy();
} }
@Override @Override
protected void onRebind() { protected void onRebind() {
// when the activity rebinds to the service, remove the notification stopForegroundService();
cancelNotification(); }
}
@Override @Override
protected void onUnbind() { protected void onUnbind() {
// when the activity closes we need to show the notification that user is connected to the sensor startForegroundService();
createNotification(R.string.uart_notification_connected_message, 0); }
}
@Override @Override
public void onDeviceConnected(@NonNull final BluetoothDevice device) { public void onDeviceConnected(@NonNull final BluetoothDevice device) {
super.onDeviceConnected(device); super.onDeviceConnected(device);
sendMessageToWearables(Constants.UART.DEVICE_CONNECTED, notNull(getDeviceName())); sendMessageToWearables(Constants.UART.DEVICE_CONNECTED, notNull(getDeviceName()));
} }
@Override @Override
protected boolean stopWhenDisconnected() { protected boolean stopWhenDisconnected() {
return false; return false;
} }
@Override @Override
public void onDeviceDisconnected(@NonNull final BluetoothDevice device) { public void onDeviceDisconnected(@NonNull final BluetoothDevice device) {
super.onDeviceDisconnected(device); super.onDeviceDisconnected(device);
sendMessageToWearables(Constants.UART.DEVICE_DISCONNECTED, notNull(getDeviceName())); sendMessageToWearables(Constants.UART.DEVICE_DISCONNECTED, notNull(getDeviceName()));
} }
@Override @Override
public void onLinkLossOccurred(@NonNull final BluetoothDevice device) { public void onLinkLossOccurred(@NonNull final BluetoothDevice device) {
super.onLinkLossOccurred(device); super.onLinkLossOccurred(device);
sendMessageToWearables(Constants.UART.DEVICE_LINKLOSS, notNull(getDeviceName())); sendMessageToWearables(Constants.UART.DEVICE_LINKLOSS, notNull(getDeviceName()));
} }
private String notNull(final String name) { private String notNull(final String name) {
if (!TextUtils.isEmpty(name)) if (!TextUtils.isEmpty(name))
return name; return name;
return getString(R.string.not_available); return getString(R.string.not_available);
} }
@Override @Override
public void onDataReceived(final BluetoothDevice device, final String data) { public void onDataReceived(final BluetoothDevice device, final String data) {
final Intent broadcast = new Intent(BROADCAST_UART_RX); final Intent broadcast = new Intent(BROADCAST_UART_RX);
broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice()); broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
broadcast.putExtra(EXTRA_DATA, data); broadcast.putExtra(EXTRA_DATA, data);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
// send the data received to other apps, e.g. the Tasker // send the data received to other apps, e.g. the Tasker
final Intent globalBroadcast = new Intent(ACTION_RECEIVE); final Intent globalBroadcast = new Intent(ACTION_RECEIVE);
globalBroadcast.putExtra(BluetoothDevice.EXTRA_DEVICE, getBluetoothDevice()); globalBroadcast.putExtra(BluetoothDevice.EXTRA_DEVICE, getBluetoothDevice());
globalBroadcast.putExtra(Intent.EXTRA_TEXT, data); globalBroadcast.putExtra(Intent.EXTRA_TEXT, data);
sendBroadcast(globalBroadcast); sendBroadcast(globalBroadcast);
} }
@Override @Override
public void onDataSent(final BluetoothDevice device, final String data) { public void onDataSent(final BluetoothDevice device, final String data) {
final Intent broadcast = new Intent(BROADCAST_UART_TX); final Intent broadcast = new Intent(BROADCAST_UART_TX);
broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice()); broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
broadcast.putExtra(EXTRA_DATA, data); broadcast.putExtra(EXTRA_DATA, data);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
} }
/** /**
* Sends the given message to all connected wearables. If the path is equal to {@link Constants.UART#DEVICE_DISCONNECTED} the service will be stopped afterwards. * Sends the given message to all connected wearables. If the path is equal to {@link Constants.UART#DEVICE_DISCONNECTED} the service will be stopped afterwards.
* @param path message path *
* @param message the message * @param path message path
*/ * @param message the message
private void sendMessageToWearables(final @NonNull String path, final @NonNull String message) { */
if(mGoogleApiClient.isConnected()) { private void sendMessageToWearables(final @NonNull String path, final @NonNull String message) {
new Thread(() -> { if (mGoogleApiClient.isConnected()) {
NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await(); new Thread(() -> {
for(Node node : nodes.getNodes()) { NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await();
Logger.v(getLogSession(), "[WEAR] Sending message '" + path + "' to " + node.getDisplayName()); for (Node node : nodes.getNodes()) {
final MessageApi.SendMessageResult result = Wearable.MessageApi.sendMessage(mGoogleApiClient, node.getId(), path, message.getBytes()).await(); Logger.v(getLogSession(), "[WEAR] Sending message '" + path + "' to " + node.getDisplayName());
if(result.getStatus().isSuccess()){ final MessageApi.SendMessageResult result = Wearable.MessageApi.sendMessage(mGoogleApiClient, node.getId(), path, message.getBytes()).await();
Logger.i(getLogSession(), "[WEAR] Message sent"); if (result.getStatus().isSuccess()) {
} else { Logger.i(getLogSession(), "[WEAR] Message sent");
Logger.w(getLogSession(), "[WEAR] Sending message failed: " + result.getStatus().getStatusMessage()); } else {
Log.w(TAG, "Failed to send " + path + " to " + node.getDisplayName()); Logger.w(getLogSession(), "[WEAR] Sending message failed: " + result.getStatus().getStatusMessage());
} Log.w(TAG, "Failed to send " + path + " to " + node.getDisplayName());
} }
if (Constants.UART.DEVICE_DISCONNECTED.equals(path)) }
stopService(); if (Constants.UART.DEVICE_DISCONNECTED.equals(path))
}).start(); stopService();
} else { }).start();
if (Constants.UART.DEVICE_DISCONNECTED.equals(path)) } else {
stopService(); if (Constants.UART.DEVICE_DISCONNECTED.equals(path))
} stopService();
} }
}
/** /**
* Creates the notification * Sets the service as a foreground service
* */
* @param messageResId private void startForegroundService() {
* message resource id. The message must have one String parameter,<br /> // when the activity closes we need to show the notification that user is connected to the peripheral sensor
* f.e. <code>&lt;string name="name"&gt;%s is connected&lt;/string&gt;</code> // We start the service as a foreground service as Android 8.0 (Oreo) onwards kills any running background services
* @param defaults final Notification notification = createNotification(R.string.uart_notification_connected_message, 0);
* signals that will be used to notify the user if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
*/ startForeground(NOTIFICATION_ID, notification);
private void createNotification(final int messageResId, final int defaults) { } else {
final Intent parentIntent = new Intent(this, FeaturesActivity.class); final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); nm.notify(NOTIFICATION_ID, notification);
final Intent targetIntent = new Intent(this, UARTActivity.class); }
}
final Intent disconnect = new Intent(ACTION_DISCONNECT); /**
disconnect.putExtra(EXTRA_SOURCE, SOURCE_NOTIFICATION); * Stops the service as a foreground service
final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ, disconnect, PendingIntent.FLAG_UPDATE_CURRENT); */
private void stopForegroundService() {
// when the activity rebinds to the service, remove the notification and stop the foreground service
// on devices running Android 8.0 (Oreo) or above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
stopForeground(true);
} else {
cancelNotification();
}
}
// both activities above have launchMode="singleTask" in the AndroidManifest.xml file, so if the task is already running, it will be resumed /**
final PendingIntent pendingIntent = PendingIntent.getActivities(this, OPEN_ACTIVITY_REQ, new Intent[] { parentIntent, targetIntent }, PendingIntent.FLAG_UPDATE_CURRENT); * Creates the notification
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this, ToolboxApplication.CONNECTED_DEVICE_CHANNEL); *
builder.setContentIntent(pendingIntent); * @param messageResId message resource id. The message must have one String parameter,<br />
builder.setContentTitle(getString(R.string.app_name)).setContentText(getString(messageResId, getDeviceName())); * f.e. <code>&lt;string name="name"&gt;%s is connected&lt;/string&gt;</code>
builder.setSmallIcon(R.drawable.ic_stat_notify_uart); * @param defaults signals that will be used to notify the user
builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(true); */
builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.uart_notification_action_disconnect), disconnectAction)); protected Notification createNotification(final int messageResId, final int defaults) {
final Intent parentIntent = new Intent(this, FeaturesActivity.class);
parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final Intent targetIntent = new Intent(this, UARTActivity.class);
final Notification notification = builder.build(); final Intent disconnect = new Intent(ACTION_DISCONNECT);
final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); disconnect.putExtra(EXTRA_SOURCE, SOURCE_NOTIFICATION);
nm.notify(NOTIFICATION_ID, notification); final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ, disconnect, PendingIntent.FLAG_UPDATE_CURRENT);
}
/** // both activities above have launchMode="singleTask" in the AndroidManifest.xml file, so if the task is already running, it will be resumed
* Cancels the existing notification. If there is no active notification this method does nothing final PendingIntent pendingIntent = PendingIntent.getActivities(this, OPEN_ACTIVITY_REQ, new Intent[]{parentIntent, targetIntent}, PendingIntent.FLAG_UPDATE_CURRENT);
*/ final NotificationCompat.Builder builder = new NotificationCompat.Builder(this, ToolboxApplication.CONNECTED_DEVICE_CHANNEL);
private void cancelNotification() { builder.setContentIntent(pendingIntent);
final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); builder.setContentTitle(getString(R.string.app_name)).setContentText(getString(messageResId, getDeviceName()));
nm.cancel(NOTIFICATION_ID); builder.setSmallIcon(R.drawable.ic_stat_notify_uart);
} builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(true);
builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.uart_notification_action_disconnect), disconnectAction));
/** return builder.build();
* This broadcast receiver listens for {@link #ACTION_DISCONNECT} that may be fired by pressing Disconnect action button on the notification. }
*/
private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
final int source = intent.getIntExtra(EXTRA_SOURCE, SOURCE_NOTIFICATION);
switch (source) {
case SOURCE_NOTIFICATION:
Logger.i(getLogSession(), "[Notification] Disconnect action pressed");
break;
case SOURCE_WEARABLE:
Logger.i(getLogSession(), "[WEAR] '" + Constants.ACTION_DISCONNECT + "' message received");
break;
}
if (isConnected())
getBinder().disconnect();
else
stopSelf();
}
};
/** /**
* Broadcast receiver that listens for {@link #ACTION_SEND} from other apps. Sends the String or int content of the {@link Intent#EXTRA_TEXT} extra to the remote device. * Cancels the existing notification. If there is no active notification this method does nothing
* The integer content will be sent as String (65 -> "65", not 65 -> "A"). */
*/ private void cancelNotification() {
private BroadcastReceiver mIntentBroadcastReceiver = new BroadcastReceiver() { final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
@Override nm.cancel(NOTIFICATION_ID);
public void onReceive(final Context context, final Intent intent) { }
final boolean hasMessage = intent.hasExtra(Intent.EXTRA_TEXT);
if (hasMessage) {
String message = intent.getStringExtra(Intent.EXTRA_TEXT);
if (message == null) {
final int intValue = intent.getIntExtra(Intent.EXTRA_TEXT, Integer.MIN_VALUE); // how big is the chance of such data?
if (intValue != Integer.MIN_VALUE)
message = String.valueOf(intValue);
}
if (message != null) { /**
final int source = intent.getIntExtra(EXTRA_SOURCE, SOURCE_3RD_PARTY); * This broadcast receiver listens for {@link #ACTION_DISCONNECT} that may be fired by pressing Disconnect action button on the notification.
switch (source) { */
case SOURCE_WEARABLE: private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() {
Logger.i(getLogSession(), "[WEAR] '" + Constants.UART.COMMAND + "' message received with data: \"" + message + "\""); @Override
break; public void onReceive(final Context context, final Intent intent) {
case SOURCE_3RD_PARTY: final int source = intent.getIntExtra(EXTRA_SOURCE, SOURCE_NOTIFICATION);
default: switch (source) {
Logger.i(getLogSession(), "[Broadcast] " + ACTION_SEND + " broadcast received with data: \"" + message + "\""); case SOURCE_NOTIFICATION:
break; Logger.i(getLogSession(), "[Notification] Disconnect action pressed");
} break;
mManager.send(message); case SOURCE_WEARABLE:
return; Logger.i(getLogSession(), "[WEAR] '" + Constants.ACTION_DISCONNECT + "' message received");
} break;
} }
// No data od incompatible type of EXTRA_TEXT if (isConnected())
if (!hasMessage) getBinder().disconnect();
Logger.i(getLogSession(), "[Broadcast] " + ACTION_SEND + " broadcast received no data."); else
else stopSelf();
Logger.i(getLogSession(), "[Broadcast] " + ACTION_SEND + " broadcast received incompatible data type. Only String and int are supported."); }
} };
};
/**
* Broadcast receiver that listens for {@link #ACTION_SEND} from other apps. Sends the String or int content of the {@link Intent#EXTRA_TEXT} extra to the remote device.
* The integer content will be sent as String (65 -> "65", not 65 -> "A").
*/
private BroadcastReceiver mIntentBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
final boolean hasMessage = intent.hasExtra(Intent.EXTRA_TEXT);
if (hasMessage) {
String message = intent.getStringExtra(Intent.EXTRA_TEXT);
if (message == null) {
final int intValue = intent.getIntExtra(Intent.EXTRA_TEXT, Integer.MIN_VALUE); // how big is the chance of such data?
if (intValue != Integer.MIN_VALUE)
message = String.valueOf(intValue);
}
if (message != null) {
final int source = intent.getIntExtra(EXTRA_SOURCE, SOURCE_3RD_PARTY);
switch (source) {
case SOURCE_WEARABLE:
Logger.i(getLogSession(), "[WEAR] '" + Constants.UART.COMMAND + "' message received with data: \"" + message + "\"");
break;
case SOURCE_3RD_PARTY:
default:
Logger.i(getLogSession(), "[Broadcast] " + ACTION_SEND + " broadcast received with data: \"" + message + "\"");
break;
}
mManager.send(message);
return;
}
}
// No data od incompatible type of EXTRA_TEXT
if (!hasMessage)
Logger.i(getLogSession(), "[Broadcast] " + ACTION_SEND + " broadcast received no data.");
else
Logger.i(getLogSession(), "[Broadcast] " + ACTION_SEND + " broadcast received incompatible data type. Only String and int are supported.");
}
};
} }