diff --git a/app/build.gradle b/app/build.gradle
index ad89eb42..3a164826 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,13 +1,13 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 28
- buildToolsVersion "28.0.3"
+ compileSdkVersion 29
+ buildToolsVersion "29.0.0"
defaultConfig {
applicationId "no.nordicsemi.android.nrftoolbox"
minSdkVersion 18
- targetSdkVersion 28
+ targetSdkVersion 29
versionCode 69
versionName "2.7.2"
resConfigs "en"
@@ -42,9 +42,9 @@ dependencies {
//noinspection GradleDependency
implementation 'com.google.android.gms:play-services-wearable:10.2.0'
- implementation 'androidx.appcompat:appcompat:1.1.0-alpha04'
- implementation 'androidx.preference:preference:1.1.0-alpha04'
- implementation 'com.google.android.material:material:1.1.0-alpha05'
+ implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
+ implementation 'androidx.preference:preference:1.1.0-rc01'
+ implementation 'com.google.android.material:material:1.1.0-alpha09'
implementation 'no.nordicsemi.android:log:2.2.0'
implementation 'no.nordicsemi.android.support.v18:scanner:1.4.0'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 795435df..aef5c37b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -33,6 +33,7 @@
+
getRecords() {
- return mManager.getRecords();
- }
+ public class CGMSBinder extends LocalBinder {
+ /**
+ * Returns all records as a sparse array where sequence number is the key.
+ *
+ * @return the records list
+ */
+ public SparseArray getRecords() {
+ return mManager.getRecords();
+ }
- /**
- * Clears the records list locally
- */
- public void clear() {
- if (mManager != null)
- mManager.clear();
- }
+ /**
+ * Clears the records list locally
+ */
+ public void clear() {
+ if (mManager != null)
+ mManager.clear();
+ }
- /**
- * 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
- * Point indication with status code ({@link CGMSManager# RESPONSE_SUCCESS} or other in case of error.
- */
- public void getFirstRecord() {
- if (mManager != null)
- mManager.getFirstRecord();
- }
+ /**
+ * 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
+ * Point indication with status code ({@link CGMSManager# RESPONSE_SUCCESS} or other in case of error.
+ */
+ public void getFirstRecord() {
+ if (mManager != null)
+ mManager.getFirstRecord();
+ }
- /**
- * 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
- * Control Point indication with status code Success or other in case of error.
- */
- public void getLastRecord() {
- if (mManager != null)
- mManager.getLastRecord();
- }
+ /**
+ * 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
+ * Control Point indication with status code Success or other in case of error.
+ */
+ public void getLastRecord() {
+ if (mManager != null)
+ mManager.getLastRecord();
+ }
- /**
- * 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.
- * 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.
- */
- public void getAllRecords() {
- if (mManager != null)
- mManager.getAllRecords();
- }
+ /**
+ * 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.
+ * 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.
+ */
+ public void getAllRecords() {
+ if (mManager != null)
+ mManager.getAllRecords();
+ }
- /**
- * 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
- * characteristic as a series of notifications followed by Record Access Control Point
- * indication with status code Success or other in case of error.
- */
- public void refreshRecords() {
- if (mManager != null)
- mManager.refreshRecords();
- }
+ /**
+ * 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
+ * characteristic as a series of notifications followed by Record Access Control Point
+ * indication with status code Success or other in case of error.
+ */
+ public void refreshRecords() {
+ if (mManager != null)
+ mManager.refreshRecords();
+ }
- /**
- * Sends abort operation signal to the device
- */
- public void abort() {
- if (mManager != null)
- mManager.abort();
- }
+ /**
+ * Sends abort operation signal to the device
+ */
+ public void abort() {
+ if (mManager != null)
+ mManager.abort();
+ }
- /**
- * Sends Delete op code with All stored records parameter. This method may not be supported by the SDK sample.
- */
- public void deleteAllRecords() {
- if (mManager != null)
- mManager.deleteAllRecords();
- }
- }
+ /**
+ * Sends Delete op code with All stored records parameter. This method may not be supported by the SDK sample.
+ */
+ public void deleteAllRecords() {
+ if (mManager != null)
+ mManager.deleteAllRecords();
+ }
+ }
- @Override
- protected LocalBinder getBinder() {
- return mBinder;
- }
+ @Override
+ protected LocalBinder getBinder() {
+ return mBinder;
+ }
- @Override
- protected LoggableBleManager initializeManager() {
- return mManager = new CGMSManager(this);
- }
+ @Override
+ protected LoggableBleManager initializeManager() {
+ return mManager = new CGMSManager(this);
+ }
- @Override
- public void onCreate() {
- super.onCreate();
- final IntentFilter filter = new IntentFilter();
- filter.addAction(ACTION_DISCONNECT);
- registerReceiver(mDisconnectActionBroadcastReceiver, filter);
- }
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_DISCONNECT);
+ registerReceiver(mDisconnectActionBroadcastReceiver, filter);
+ }
- @Override
- 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
- cancelNotification();
- unregisterReceiver(mDisconnectActionBroadcastReceiver);
+ @Override
+ 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
+ stopForegroundService();
+ unregisterReceiver(mDisconnectActionBroadcastReceiver);
- super.onDestroy();
- }
+ super.onDestroy();
+ }
- @Override
- protected void onRebind() {
- // when the activity rebinds to the service, remove the notification
- cancelNotification();
- }
+ @Override
+ protected void onRebind() {
+ startForegroundService();
+ }
- @Override
- protected void onUnbind() {
- // when the activity closes we need to show the notification that user is connected to the sensor
- createNotification(R.string.csc_notification_connected_message, 0);
- }
+ @Override
+ protected void onUnbind() {
+ startForegroundService();
+ }
- /**
- * Creates the notification
- *
- * @param messageResId the message resource id. The message must have one String parameter,
- * f.e. <string name="name">%s is connected</string>
- * @param defaults signals that will be used to notify the user
- */
- private void 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);
+ /**
+ * 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);
+ }
+ }
- 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);
- 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_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));
+ /**
+ * Creates the notification
+ *
+ * @param messageResId the message resource id. The message must have one String parameter,
+ * f.e. <string name="name">%s is connected</string>
+ * @param defaults signals that will be used to notify the user
+ */
+ 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 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);
- /**
- * 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);
- }
+ // 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);
+ 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_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));
- /**
- * 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();
- }
- };
+ return builder.build();
+ }
- @Override
- public void onCGMValueReceived(@NonNull final BluetoothDevice device, final CGMSRecord record) {
- final Intent broadcast = new Intent(BROADCAST_NEW_CGMS_VALUE);
- broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
- broadcast.putExtra(EXTRA_CGMS_RECORD, record);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- }
+ /**
+ * 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);
+ }
- @Override
- public void onOperationStarted(@NonNull final BluetoothDevice device) {
- final Intent broadcast = new Intent(OPERATION_STARTED);
- broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
- broadcast.putExtra(EXTRA_DATA, true);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- }
+ /**
+ * 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 onOperationCompleted(@NonNull final BluetoothDevice device) {
- final Intent broadcast = new Intent(OPERATION_COMPLETED);
- broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
- broadcast.putExtra(EXTRA_DATA, true);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- }
+ @Override
+ public void onCGMValueReceived(@NonNull final BluetoothDevice device, final CGMSRecord record) {
+ final Intent broadcast = new Intent(BROADCAST_NEW_CGMS_VALUE);
+ broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
+ broadcast.putExtra(EXTRA_CGMS_RECORD, record);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
- @Override
- public void onOperationFailed(@NonNull final BluetoothDevice device) {
- final Intent broadcast = new Intent(OPERATION_FAILED);
- broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
- broadcast.putExtra(EXTRA_DATA, true);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- }
+ @Override
+ public void onOperationStarted(@NonNull final BluetoothDevice device) {
+ final Intent broadcast = new Intent(OPERATION_STARTED);
+ broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
+ broadcast.putExtra(EXTRA_DATA, true);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
- @Override
- public void onOperationAborted(@NonNull final BluetoothDevice device) {
- final Intent broadcast = new Intent(OPERATION_ABORTED);
- broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
- broadcast.putExtra(EXTRA_DATA, true);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- }
+ @Override
+ public void onOperationCompleted(@NonNull final BluetoothDevice device) {
+ final Intent broadcast = new Intent(OPERATION_COMPLETED);
+ broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
+ broadcast.putExtra(EXTRA_DATA, true);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
- @Override
- public void onOperationNotSupported(@NonNull final BluetoothDevice device) {
- final Intent broadcast = new Intent(OPERATION_NOT_SUPPORTED);
- broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
- broadcast.putExtra(EXTRA_DATA, false);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- }
+ @Override
+ public void onOperationFailed(@NonNull final BluetoothDevice device) {
+ final Intent broadcast = new Intent(OPERATION_FAILED);
+ broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
+ broadcast.putExtra(EXTRA_DATA, true);
+ 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
+ public void onOperationAborted(@NonNull final BluetoothDevice device) {
+ final Intent broadcast = new Intent(OPERATION_ABORTED);
+ broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
+ broadcast.putExtra(EXTRA_DATA, true);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
- @Override
- public void onNumberOfRecordsRequested(@NonNull final BluetoothDevice device, final int value) {
- if (value == 0)
- showToast(R.string.gls_progress_zero);
- else
- showToast(getResources().getQuantityString(R.plurals.gls_progress, value, value));
+ @Override
+ public void onOperationNotSupported(@NonNull final BluetoothDevice device) {
+ final Intent broadcast = new Intent(OPERATION_NOT_SUPPORTED);
+ broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
+ broadcast.putExtra(EXTRA_DATA, false);
+ 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
- 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);
- }
+ @Override
+ public void onNumberOfRecordsRequested(@NonNull final BluetoothDevice device, final int value) {
+ if (value == 0)
+ showToast(R.string.gls_progress_zero);
+ else
+ 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);
+ }
}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCService.java
index 2a99f0c3..e29bc71d 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCService.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCService.java
@@ -30,10 +30,11 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.Build;
+
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
-
import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
import no.nordicsemi.android.nrftoolbox.R;
@@ -42,168 +43,195 @@ import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
import no.nordicsemi.android.nrftoolbox.profile.LoggableBleManager;
public class CSCService extends BleProfileService implements CSCManagerCallbacks {
- @SuppressWarnings("unused")
- private static final String TAG = "CSCService";
+ @SuppressWarnings("unused")
+ private static final String TAG = "CSCService";
- 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";
- /** 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_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";
+ /**
+ * 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 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 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_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 EXTRA_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.EXTRA_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";
- 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 OPEN_ACTIVITY_REQ = 0;
- private final static int DISCONNECT_REQ = 1;
+ private final static int NOTIFICATION_ID = 200;
+ private final static int OPEN_ACTIVITY_REQ = 0;
+ private final static int DISCONNECT_REQ = 1;
- private final LocalBinder mBinder = new CSCBinder();
- private CSCManager mManager;
+ private final LocalBinder mBinder = new CSCBinder();
+ private CSCManager mManager;
- /**
- * This local binder is an interface for the bonded activity to operate with the RSC sensor
- */
- class CSCBinder extends LocalBinder {
- // empty
- }
+ /**
+ * This local binder is an interface for the bonded activity to operate with the RSC sensor
+ */
+ class CSCBinder extends LocalBinder {
+ // empty
+ }
- @Override
- protected LocalBinder getBinder() {
- return mBinder;
- }
+ @Override
+ protected LocalBinder getBinder() {
+ return mBinder;
+ }
- @Override
- protected LoggableBleManager initializeManager() {
- return mManager = new CSCManager(this);
- }
+ @Override
+ protected LoggableBleManager initializeManager() {
+ return mManager = new CSCManager(this);
+ }
- @Override
- public void onCreate() {
- super.onCreate();
+ @Override
+ public void onCreate() {
+ super.onCreate();
- final IntentFilter filter = new IntentFilter();
- filter.addAction(ACTION_DISCONNECT);
- registerReceiver(mDisconnectActionBroadcastReceiver, filter);
- }
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_DISCONNECT);
+ registerReceiver(mDisconnectActionBroadcastReceiver, filter);
+ }
- @Override
- 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
- cancelNotification();
- unregisterReceiver(mDisconnectActionBroadcastReceiver);
+ @Override
+ 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
+ cancelNotification();
+ unregisterReceiver(mDisconnectActionBroadcastReceiver);
- super.onDestroy();
- }
+ super.onDestroy();
+ }
- @Override
- protected void onRebind() {
- // when the activity rebinds to the service, remove the notification
- cancelNotification();
+ @Override
+ protected void onRebind() {
+ stopForegroundService();
- if (isConnected()) {
- // 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.
- mManager.readBatteryLevelCharacteristic();
- }
- }
+ if (isConnected()) {
+ // 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.
+ mManager.readBatteryLevelCharacteristic();
+ }
+ }
- @Override
- protected void onUnbind() {
- // 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.
- if (isConnected())
- mManager.disableBatteryLevelCharacteristicNotifications();
+ @Override
+ protected void onUnbind() {
+ // 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.
+ if (isConnected())
+ mManager.disableBatteryLevelCharacteristicNotifications();
+ startForegroundService();
+ }
- // when the activity closes we need to show the notification that user is connected to the sensor
- createNotification(R.string.csc_notification_connected_message, 0);
- }
+ @Override
+ 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
- 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
+ public void onCrankDataChanged(@NonNull final BluetoothDevice device, final float crankCadence, final float gearRatio) {
+ final Intent broadcast = new Intent(BROADCAST_CRANK_DATA);
+ broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
+ broadcast.putExtra(EXTRA_GEAR_RATIO, gearRatio);
+ broadcast.putExtra(EXTRA_CADENCE, (int) crankCadence);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
- @Override
- public void onCrankDataChanged(@NonNull final BluetoothDevice device, final float crankCadence, final float gearRatio) {
- final Intent broadcast = new Intent(BROADCAST_CRANK_DATA);
- broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
- broadcast.putExtra(EXTRA_GEAR_RATIO, gearRatio);
- broadcast.putExtra(EXTRA_CADENCE, (int) crankCadence);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- }
+ @Override
+ public void onBatteryLevelChanged(@NonNull final BluetoothDevice device, final int batteryLevel) {
+ final Intent broadcast = new Intent(BROADCAST_BATTERY_LEVEL);
+ broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
+ broadcast.putExtra(EXTRA_BATTERY_LEVEL, batteryLevel);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
- @Override
- public void onBatteryLevelChanged(@NonNull final BluetoothDevice device, final int batteryLevel) {
- final Intent broadcast = new Intent(BROADCAST_BATTERY_LEVEL);
- broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
- broadcast.putExtra(EXTRA_BATTERY_LEVEL, batteryLevel);
- 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);
+ }
+ }
- /**
- * Creates the notification
- *
- * @param messageResId
- * the message resource id. The message must have one String parameter,
- * f.e. <string name="name">%s is connected</string>
- * @param defaults
- * signals that will be used to notify the user
- */
- private void 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);
+ /**
+ * 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();
+ }
+ }
- 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,
+ * f.e. <string name="name">%s is connected</string>
+ * @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 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);
- 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 Intent disconnect = new Intent(ACTION_DISCONNECT);
+ final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ, disconnect, PendingIntent.FLAG_UPDATE_CURRENT);
- final Notification notification = builder.build();
- final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- 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
+ 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);
+ 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));
- /**
- * 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);
- }
+ 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();
- }
- };
+ /**
+ * 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.
+ */
+ 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();
+ }
+ };
}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSService.java
index 811740ea..2f9ed139 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSService.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSService.java
@@ -30,6 +30,8 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.Build;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
@@ -110,14 +112,12 @@ public class HTSService extends BleProfileService implements HTSManagerCallbacks
@Override
protected void onRebind() {
- // when the activity rebinds to the service, remove the notification
- cancelNotification();
+ stopForegroundService();
}
@Override
protected void onUnbind() {
- // when the activity closes we need to show the notification that user is connected to the sensor
- createNotification(R.string.hts_notification_connected_message, 0);
+ startForegroundService();
}
@Override
@@ -153,16 +153,42 @@ public class HTSService extends BleProfileService implements HTSManagerCallbacks
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
- *
- * @param messageResId
+ * @param messageResId
* message resource id. The message must have one String parameter,
* f.e. <string name="name">%s is connected</string>
* @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);
parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
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.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.hts_notification_action_disconnect), disconnectAction));
- final Notification notification = builder.build();
- final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- nm.notify(NOTIFICATION_ID, notification);
+ return builder.build();
}
/**
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileService.java
index e63c3777..999b1321 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileService.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileService.java
@@ -33,12 +33,12 @@ import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
-import androidx.annotation.NonNull;
-import androidx.annotation.StringRes;
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import android.util.Log;
import android.widget.Toast;
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import no.nordicsemi.android.ble.BleManager;
import no.nordicsemi.android.ble.BleManagerCallbacks;
import no.nordicsemi.android.ble.utils.ILogger;
@@ -47,557 +47,563 @@ import no.nordicsemi.android.log.Logger;
@SuppressWarnings("unused")
public abstract class BleProfileService extends Service implements BleManagerCallbacks {
- @SuppressWarnings("unused")
- private static final String TAG = "BleProfileService";
-
- public static final String BROADCAST_CONNECTION_STATE = "no.nordicsemi.android.nrftoolbox.BROADCAST_CONNECTION_STATE";
- public static final String BROADCAST_SERVICES_DISCOVERED = "no.nordicsemi.android.nrftoolbox.BROADCAST_SERVICES_DISCOVERED";
- public static final String BROADCAST_DEVICE_READY = "no.nordicsemi.android.nrftoolbox.DEVICE_READY";
- public static final String BROADCAST_BOND_STATE = "no.nordicsemi.android.nrftoolbox.BROADCAST_BOND_STATE";
- @Deprecated
- public static final String BROADCAST_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.BROADCAST_BATTERY_LEVEL";
- public static final String BROADCAST_ERROR = "no.nordicsemi.android.nrftoolbox.BROADCAST_ERROR";
-
- /** The parameter passed when creating the service. Must contain the address of the sensor that we want to connect to */
- public static final String EXTRA_DEVICE_ADDRESS = "no.nordicsemi.android.nrftoolbox.EXTRA_DEVICE_ADDRESS";
- /** The key for the device name that is returned in {@link #BROADCAST_CONNECTION_STATE} with state {@link #STATE_CONNECTED}. */
- public static final String EXTRA_DEVICE_NAME = "no.nordicsemi.android.nrftoolbox.EXTRA_DEVICE_NAME";
- public static final String EXTRA_DEVICE = "no.nordicsemi.android.nrftoolbox.EXTRA_DEVICE";
- public static final String EXTRA_LOG_URI = "no.nordicsemi.android.nrftoolbox.EXTRA_LOG_URI";
- public static final String EXTRA_CONNECTION_STATE = "no.nordicsemi.android.nrftoolbox.EXTRA_CONNECTION_STATE";
- public static final String EXTRA_BOND_STATE = "no.nordicsemi.android.nrftoolbox.EXTRA_BOND_STATE";
- public static final String EXTRA_SERVICE_PRIMARY = "no.nordicsemi.android.nrftoolbox.EXTRA_SERVICE_PRIMARY";
- public static final String EXTRA_SERVICE_SECONDARY = "no.nordicsemi.android.nrftoolbox.EXTRA_SERVICE_SECONDARY";
- @Deprecated
- public static final String EXTRA_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.EXTRA_BATTERY_LEVEL";
- public static final String EXTRA_ERROR_MESSAGE = "no.nordicsemi.android.nrftoolbox.EXTRA_ERROR_MESSAGE";
- public static final String EXTRA_ERROR_CODE = "no.nordicsemi.android.nrftoolbox.EXTRA_ERROR_CODE";
-
- public static final int STATE_LINK_LOSS = -1;
- public static final int STATE_DISCONNECTED = 0;
- public static final int STATE_CONNECTED = 1;
- public static final int STATE_CONNECTING = 2;
- public static final int STATE_DISCONNECTING = 3;
-
- private LoggableBleManager mBleManager;
- private Handler mHandler;
-
- protected boolean mBound;
- private boolean mActivityIsChangingConfiguration;
- private BluetoothDevice mBluetoothDevice;
- private String mDeviceName;
- private ILogSession mLogSession;
-
- private final BroadcastReceiver mBluetoothStateBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(final Context context, final Intent intent) {
- final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
- final ILogger logger = getBinder();
-
- final String stateString = "[Broadcast] Action received: " + BluetoothAdapter.ACTION_STATE_CHANGED + ", state changed to " + state2String(state);
- logger.log(Log.DEBUG, stateString);
-
- switch (state) {
- case BluetoothAdapter.STATE_ON:
- onBluetoothEnabled();
- break;
- case BluetoothAdapter.STATE_TURNING_OFF:
- case BluetoothAdapter.STATE_OFF:
- onBluetoothDisabled();
- break;
- }
- }
-
- private String state2String(final int state) {
- switch (state) {
- case BluetoothAdapter.STATE_TURNING_ON:
- return "TURNING ON";
- case BluetoothAdapter.STATE_ON:
- return "ON";
- case BluetoothAdapter.STATE_TURNING_OFF:
- return "TURNING OFF";
- case BluetoothAdapter.STATE_OFF:
- return "OFF";
- default:
- return "UNKNOWN (" + state + ")";
- }
- }
- };
-
- public class LocalBinder extends Binder implements ILogger {
- /**
- * Disconnects from the sensor.
- */
- public final void disconnect() {
- final int state = mBleManager.getConnectionState();
- if (state == BluetoothGatt.STATE_DISCONNECTED || state == BluetoothGatt.STATE_DISCONNECTING) {
- mBleManager.close();
- onDeviceDisconnected(mBluetoothDevice);
- return;
- }
-
- mBleManager.disconnect().enqueue();
- }
-
- /**
- * Sets whether the bound activity if changing configuration or not.
- * If false, we will turn off battery level notifications in onUnbind(..) method below.
- * @param changing true if the bound activity is finishing
- */
- public void setActivityIsChangingConfiguration(final boolean changing) {
- mActivityIsChangingConfiguration = changing;
- }
-
- /**
- * Returns the device address
- *
- * @return device address
- */
- public String getDeviceAddress() {
- return mBluetoothDevice.getAddress();
- }
-
- /**
- * Returns the device name
- *
- * @return the device name
- */
- public String getDeviceName() {
- return mDeviceName;
- }
-
- /**
- * Returns the Bluetooth device
- *
- * @return the Bluetooth device
- */
- public BluetoothDevice getBluetoothDevice() {
- return mBluetoothDevice;
- }
-
- /**
- * Returns true if the device is connected to the sensor.
- *
- * @return true if device is connected to the sensor, false otherwise
- */
- public boolean isConnected() {
- return mBleManager.isConnected();
- }
-
-
- /**
- * Returns the connection state of given device.
- * @return the connection state, as in {@link BleManager#getConnectionState()}.
- */
- public int getConnectionState() {
- return mBleManager.getConnectionState();
- }
-
- /**
- * Returns the log session that can be used to append log entries.
- * The log session is created when the service is being created.
- * The method returns null if the nRF Logger app was not installed.
- *
- * @return the log session
- */
- public ILogSession getLogSession() {
- return mLogSession;
- }
-
- @Override
- public void log(final int level, @NonNull final String message) {
- Logger.log(mLogSession, level, message);
- }
-
- @Override
- public void log(final int level, final @StringRes int messageRes, final Object... params) {
- Logger.log(mLogSession, level, messageRes, params);
- }
- }
-
- /**
- * Returns a handler that is created in onCreate().
- * The handler may be used to postpone execution of some operations or to run them in UI thread.
- */
- protected Handler getHandler() {
- return mHandler;
- }
-
- /**
- * Returns the binder implementation. This must return class implementing the additional manager interface that may be used in the bound activity.
- *
- * @return the service binder
- */
- protected LocalBinder getBinder() {
- // default implementation returns the basic binder. You can overwrite the LocalBinder with your own, wider implementation
- return new LocalBinder();
- }
-
- @Override
- public IBinder onBind(final Intent intent) {
- mBound = true;
- return getBinder();
- }
-
- @Override
- public final void onRebind(final Intent intent) {
- mBound = true;
-
- if (!mActivityIsChangingConfiguration)
- onRebind();
- }
-
- /**
- * Called when the activity has rebound to the service after being recreated.
- * This method is not called when the activity was killed to be recreated when the phone orientation changed
- * if prior to being killed called {@link BleProfileService.LocalBinder#setActivityIsChangingConfiguration(boolean)} with parameter true.
- */
- protected void onRebind() {
- // empty default implementation
- }
-
- @Override
- public final boolean onUnbind(final Intent intent) {
- mBound = false;
-
- if (!mActivityIsChangingConfiguration)
- onUnbind();
-
- // We want the onRebind method be called if anything else binds to it again
- return true;
- }
-
- /**
- * Called when the activity has unbound from the service before being finished.
- * This method is not called when the activity is killed to be recreated when the phone orientation changed.
- */
- protected void onUnbind() {
- // empty default implementation
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public void onCreate() {
- super.onCreate();
-
- mHandler = new Handler();
-
- // Initialize the manager
- mBleManager = initializeManager();
- mBleManager.setGattCallbacks(this);
-
- // Register broadcast receivers
- registerReceiver(mBluetoothStateBroadcastReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
-
- // Service has now been created
- onServiceCreated();
-
- // Call onBluetoothEnabled if Bluetooth enabled
- final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- if (bluetoothAdapter.isEnabled()) {
- onBluetoothEnabled();
- }
- }
-
- /**
- * Called when the service has been created, before the {@link #onBluetoothEnabled()} is called.
- */
- protected void onServiceCreated() {
- // empty default implementation
- }
-
- /**
- * Initializes the Ble Manager responsible for connecting to a single device.
- * @return a new BleManager object
- */
- @SuppressWarnings("rawtypes")
- protected abstract LoggableBleManager initializeManager();
-
- /**
- * This method returns whether autoConnect option should be used.
- *
- * @return true to use autoConnect feature, false (default) otherwise.
- */
- protected boolean shouldAutoConnect() {
- return false;
- }
-
- @Override
- public int onStartCommand(final Intent intent, final int flags, final int startId) {
- if (intent == null || !intent.hasExtra(EXTRA_DEVICE_ADDRESS))
- throw new UnsupportedOperationException("No device address at EXTRA_DEVICE_ADDRESS key");
-
- final Uri logUri = intent.getParcelableExtra(EXTRA_LOG_URI);
- mLogSession = Logger.openSession(getApplicationContext(), logUri);
- mDeviceName = intent.getStringExtra(EXTRA_DEVICE_NAME);
-
- Logger.i(mLogSession, "Service started");
-
- final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- final String deviceAddress = intent.getStringExtra(EXTRA_DEVICE_ADDRESS);
- mBluetoothDevice = adapter.getRemoteDevice(deviceAddress);
-
- mBleManager.setLogger(mLogSession);
- onServiceStarted();
- mBleManager.connect(mBluetoothDevice)
- .useAutoConnect(shouldAutoConnect())
- .retry(3, 100)
- .enqueue();
- return START_REDELIVER_INTENT;
- }
-
- /**
- * Called when the service has been started. The device name and address are set.
- * The BLE Manager will try to connect to the device after this method finishes.
- */
- protected void onServiceStarted() {
- // empty default implementation
- }
-
- @Override
- public void onTaskRemoved(final Intent rootIntent) {
- super.onTaskRemoved(rootIntent);
- // This method is called when user removed the app from Recents.
- // By default, the service will be killed and recreated immediately after that.
- // However, all managed devices will be lost and devices will be disconnected.
- stopSelf();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- // Unregister broadcast receivers
- unregisterReceiver(mBluetoothStateBroadcastReceiver);
-
- // shutdown the manager
- mBleManager.close();
- Logger.i(mLogSession, "Service destroyed");
- mBleManager = null;
- mBluetoothDevice = null;
- mDeviceName = null;
- mLogSession = null;
- mHandler = null;
- }
-
- /**
- * Method called when Bluetooth Adapter has been disabled.
- */
- protected void onBluetoothDisabled() {
- // empty default implementation
- }
-
- /**
- * This method is called when Bluetooth Adapter has been enabled and
- * after the service was created if Bluetooth Adapter was enabled at that moment.
- * This method could initialize all Bluetooth related features, for example open the GATT server.
- */
- protected void onBluetoothEnabled() {
- // empty default implementation
- }
-
- @Override
- public void onDeviceConnecting(@NonNull final BluetoothDevice device) {
- final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
- broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
- broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_CONNECTING);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- }
-
- @Override
- public void onDeviceConnected(@NonNull final BluetoothDevice device) {
- final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
- broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_CONNECTED);
- broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
- broadcast.putExtra(EXTRA_DEVICE_NAME, mDeviceName);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- }
-
- @Override
- public void onDeviceDisconnecting(@NonNull final BluetoothDevice device) {
- // Notify user about changing the state to DISCONNECTING
- final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
- broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
- broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_DISCONNECTING);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- }
-
- /**
- * This method should return false if the service needs to do some asynchronous work after if has disconnected from the device.
- * In that case the {@link #stopService()} method must be called when done.
- * @return true (default) to automatically stop the service when device is disconnected. False otherwise.
- */
- protected boolean stopWhenDisconnected() {
- return true;
- }
-
- @Override
- public void onDeviceDisconnected(@NonNull final BluetoothDevice device) {
- // Note 1: Do not use the device argument here unless you change calling onDeviceDisconnected from the binder above
-
- // Note 2: if BleManager#shouldAutoConnect() for this device returned true, this callback will be
- // invoked ONLY when user requested disconnection (using Disconnect button). If the device
- // disconnects due to a link loss, the onLinkLossOccurred(BluetoothDevice) method will be called instead.
-
- final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
- broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
- broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_DISCONNECTED);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
-
- if (stopWhenDisconnected())
- stopService();
- }
-
- protected void stopService() {
- // user requested disconnection. We must stop the service
- Logger.v(mLogSession, "Stopping service...");
- stopSelf();
- }
-
- @Override
- public void onLinkLossOccurred(@NonNull final BluetoothDevice device) {
- final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
- broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
- broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_LINK_LOSS);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- }
-
- @Override
- public void onServicesDiscovered(@NonNull final BluetoothDevice device, final boolean optionalServicesFound) {
- final Intent broadcast = new Intent(BROADCAST_SERVICES_DISCOVERED);
- broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
- broadcast.putExtra(EXTRA_SERVICE_PRIMARY, true);
- broadcast.putExtra(EXTRA_SERVICE_SECONDARY, optionalServicesFound);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- }
-
- @Override
- public void onDeviceReady(@NonNull final BluetoothDevice device) {
- final Intent broadcast = new Intent(BROADCAST_DEVICE_READY);
- broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- }
-
- @Override
- public void onDeviceNotSupported(@NonNull final BluetoothDevice device) {
- final Intent broadcast = new Intent(BROADCAST_SERVICES_DISCOVERED);
- broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
- broadcast.putExtra(EXTRA_SERVICE_PRIMARY, false);
- broadcast.putExtra(EXTRA_SERVICE_SECONDARY, false);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
-
- // no need for disconnecting, it will be disconnected by the manager automatically
- }
-
- @Override
- public void onBatteryValueReceived(@NonNull final BluetoothDevice device, final int value) {
- final Intent broadcast = new Intent(BROADCAST_BATTERY_LEVEL);
- broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
- broadcast.putExtra(EXTRA_BATTERY_LEVEL, value);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- }
-
- @Override
- public void onBondingRequired(@NonNull final BluetoothDevice device) {
- showToast(no.nordicsemi.android.nrftoolbox.common.R.string.bonding);
-
- final Intent broadcast = new Intent(BROADCAST_BOND_STATE);
- broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
- broadcast.putExtra(EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDING);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- }
-
- @Override
- public void onBonded(@NonNull final BluetoothDevice device) {
- showToast(no.nordicsemi.android.nrftoolbox.common.R.string.bonded);
-
- final Intent broadcast = new Intent(BROADCAST_BOND_STATE);
- broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
- broadcast.putExtra(EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDED);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- }
-
- @Override
- public void onBondingFailed(@NonNull final BluetoothDevice device) {
- showToast(no.nordicsemi.android.nrftoolbox.common.R.string.bonding_failed);
-
- final Intent broadcast = new Intent(BROADCAST_BOND_STATE);
- broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
- broadcast.putExtra(EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- }
-
- @Override
- public void onError(@NonNull final BluetoothDevice device, @NonNull final String message, final int errorCode) {
- final Intent broadcast = new Intent(BROADCAST_ERROR);
- broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
- broadcast.putExtra(EXTRA_ERROR_MESSAGE, message);
- broadcast.putExtra(EXTRA_ERROR_CODE, errorCode);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- }
-
- /**
- * Shows a message as a Toast notification. This method is thread safe, you can call it from any thread
- *
- * @param messageResId
- * an resource id of the message to be shown
- */
- protected void showToast(final int messageResId) {
- mHandler.post(() -> Toast.makeText(BleProfileService.this, messageResId, Toast.LENGTH_SHORT).show());
- }
-
- /**
- * Shows a message as a Toast notification. This method is thread safe, you can call it from any thread
- *
- * @param message
- * a message to be shown
- */
- protected void showToast(final String message) {
- mHandler.post(() -> Toast.makeText(BleProfileService.this, message, Toast.LENGTH_SHORT).show());
- }
-
- /**
- * Returns the log session that can be used to append log entries. The method returns null if the nRF Logger app was not installed. It is safe to use logger when
- * {@link #onServiceStarted()} has been called.
- *
- * @return the log session
- */
- protected ILogSession getLogSession() {
- return mLogSession;
- }
-
- /**
- * Returns the device address
- *
- * @return device address
- */
- protected String getDeviceAddress() {
- return mBluetoothDevice.getAddress();
- }
-
- /**
- * Returns the Bluetooth device object
- *
- * @return bluetooth device
- */
- protected BluetoothDevice getBluetoothDevice() {
- return mBluetoothDevice;
- }
-
- /**
- * Returns the device name
- *
- * @return the device name
- */
- protected String getDeviceName() {
- return mDeviceName;
- }
-
- /**
- * Returns true if the device is connected to the sensor.
- *
- * @return true if device is connected to the sensor, false otherwise
- */
- protected boolean isConnected() {
- return mBleManager != null && mBleManager.isConnected();
- }
+ @SuppressWarnings("unused")
+ private static final String TAG = "BleProfileService";
+
+ public static final String BROADCAST_CONNECTION_STATE = "no.nordicsemi.android.nrftoolbox.BROADCAST_CONNECTION_STATE";
+ public static final String BROADCAST_SERVICES_DISCOVERED = "no.nordicsemi.android.nrftoolbox.BROADCAST_SERVICES_DISCOVERED";
+ public static final String BROADCAST_DEVICE_READY = "no.nordicsemi.android.nrftoolbox.DEVICE_READY";
+ public static final String BROADCAST_BOND_STATE = "no.nordicsemi.android.nrftoolbox.BROADCAST_BOND_STATE";
+ @Deprecated
+ public static final String BROADCAST_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.BROADCAST_BATTERY_LEVEL";
+ public static final String BROADCAST_ERROR = "no.nordicsemi.android.nrftoolbox.BROADCAST_ERROR";
+
+ /**
+ * The parameter passed when creating the service. Must contain the address of the sensor that we want to connect to
+ */
+ public static final String EXTRA_DEVICE_ADDRESS = "no.nordicsemi.android.nrftoolbox.EXTRA_DEVICE_ADDRESS";
+ /**
+ * The key for the device name that is returned in {@link #BROADCAST_CONNECTION_STATE} with state {@link #STATE_CONNECTED}.
+ */
+ public static final String EXTRA_DEVICE_NAME = "no.nordicsemi.android.nrftoolbox.EXTRA_DEVICE_NAME";
+ public static final String EXTRA_DEVICE = "no.nordicsemi.android.nrftoolbox.EXTRA_DEVICE";
+ public static final String EXTRA_LOG_URI = "no.nordicsemi.android.nrftoolbox.EXTRA_LOG_URI";
+ public static final String EXTRA_CONNECTION_STATE = "no.nordicsemi.android.nrftoolbox.EXTRA_CONNECTION_STATE";
+ public static final String EXTRA_BOND_STATE = "no.nordicsemi.android.nrftoolbox.EXTRA_BOND_STATE";
+ public static final String EXTRA_SERVICE_PRIMARY = "no.nordicsemi.android.nrftoolbox.EXTRA_SERVICE_PRIMARY";
+ public static final String EXTRA_SERVICE_SECONDARY = "no.nordicsemi.android.nrftoolbox.EXTRA_SERVICE_SECONDARY";
+ @Deprecated
+ public static final String EXTRA_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.EXTRA_BATTERY_LEVEL";
+ public static final String EXTRA_ERROR_MESSAGE = "no.nordicsemi.android.nrftoolbox.EXTRA_ERROR_MESSAGE";
+ public static final String EXTRA_ERROR_CODE = "no.nordicsemi.android.nrftoolbox.EXTRA_ERROR_CODE";
+
+ public static final int STATE_LINK_LOSS = -1;
+ public static final int STATE_DISCONNECTED = 0;
+ public static final int STATE_CONNECTED = 1;
+ public static final int STATE_CONNECTING = 2;
+ public static final int STATE_DISCONNECTING = 3;
+
+ private LoggableBleManager mBleManager;
+ private Handler mHandler;
+
+ protected boolean mBound;
+ private boolean mActivityIsChangingConfiguration;
+ private BluetoothDevice mBluetoothDevice;
+ private String mDeviceName;
+ private ILogSession mLogSession;
+
+ private final BroadcastReceiver mBluetoothStateBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
+ final ILogger logger = getBinder();
+
+ final String stateString = "[Broadcast] Action received: " + BluetoothAdapter.ACTION_STATE_CHANGED + ", state changed to " + state2String(state);
+ logger.log(Log.DEBUG, stateString);
+
+ switch (state) {
+ case BluetoothAdapter.STATE_ON:
+ onBluetoothEnabled();
+ break;
+ case BluetoothAdapter.STATE_TURNING_OFF:
+ case BluetoothAdapter.STATE_OFF:
+ onBluetoothDisabled();
+ break;
+ }
+ }
+
+ private String state2String(final int state) {
+ switch (state) {
+ case BluetoothAdapter.STATE_TURNING_ON:
+ return "TURNING ON";
+ case BluetoothAdapter.STATE_ON:
+ return "ON";
+ case BluetoothAdapter.STATE_TURNING_OFF:
+ return "TURNING OFF";
+ case BluetoothAdapter.STATE_OFF:
+ return "OFF";
+ default:
+ return "UNKNOWN (" + state + ")";
+ }
+ }
+ };
+
+ public class LocalBinder extends Binder implements ILogger {
+ /**
+ * Disconnects from the sensor.
+ */
+ public final void disconnect() {
+ final int state = mBleManager.getConnectionState();
+ if (state == BluetoothGatt.STATE_DISCONNECTED || state == BluetoothGatt.STATE_DISCONNECTING) {
+ mBleManager.close();
+ onDeviceDisconnected(mBluetoothDevice);
+ return;
+ }
+
+ mBleManager.disconnect().enqueue();
+ }
+
+ /**
+ * Sets whether the bound activity if changing configuration or not.
+ * If false, we will turn off battery level notifications in onUnbind(..) method below.
+ *
+ * @param changing true if the bound activity is finishing
+ */
+ public void setActivityIsChangingConfiguration(final boolean changing) {
+ mActivityIsChangingConfiguration = changing;
+ }
+
+ /**
+ * Returns the device address
+ *
+ * @return device address
+ */
+ public String getDeviceAddress() {
+ return mBluetoothDevice.getAddress();
+ }
+
+ /**
+ * Returns the device name
+ *
+ * @return the device name
+ */
+ public String getDeviceName() {
+ return mDeviceName;
+ }
+
+ /**
+ * Returns the Bluetooth device
+ *
+ * @return the Bluetooth device
+ */
+ public BluetoothDevice getBluetoothDevice() {
+ return mBluetoothDevice;
+ }
+
+ /**
+ * Returns true if the device is connected to the sensor.
+ *
+ * @return true if device is connected to the sensor, false otherwise
+ */
+ public boolean isConnected() {
+ return mBleManager.isConnected();
+ }
+
+
+ /**
+ * Returns the connection state of given device.
+ *
+ * @return the connection state, as in {@link BleManager#getConnectionState()}.
+ */
+ public int getConnectionState() {
+ return mBleManager.getConnectionState();
+ }
+
+ /**
+ * Returns the log session that can be used to append log entries.
+ * The log session is created when the service is being created.
+ * The method returns null if the nRF Logger app was not installed.
+ *
+ * @return the log session
+ */
+ public ILogSession getLogSession() {
+ return mLogSession;
+ }
+
+ @Override
+ public void log(final int level, @NonNull final String message) {
+ Logger.log(mLogSession, level, message);
+ }
+
+ @Override
+ public void log(final int level, final @StringRes int messageRes, final Object... params) {
+ Logger.log(mLogSession, level, messageRes, params);
+ }
+ }
+
+ /**
+ * Returns a handler that is created in onCreate().
+ * The handler may be used to postpone execution of some operations or to run them in UI thread.
+ */
+ protected Handler getHandler() {
+ return mHandler;
+ }
+
+ /**
+ * Returns the binder implementation. This must return class implementing the additional manager interface that may be used in the bound activity.
+ *
+ * @return the service binder
+ */
+ protected LocalBinder getBinder() {
+ // default implementation returns the basic binder. You can overwrite the LocalBinder with your own, wider implementation
+ return new LocalBinder();
+ }
+
+ @Override
+ public IBinder onBind(final Intent intent) {
+ mBound = true;
+ return getBinder();
+ }
+
+ @Override
+ public final void onRebind(final Intent intent) {
+ mBound = true;
+
+ if (!mActivityIsChangingConfiguration)
+ onRebind();
+ }
+
+ /**
+ * Called when the activity has rebound to the service after being recreated.
+ * This method is not called when the activity was killed to be recreated when the phone orientation changed
+ * if prior to being killed called {@link BleProfileService.LocalBinder#setActivityIsChangingConfiguration(boolean)} with parameter true.
+ */
+ protected void onRebind() {
+ // empty default implementation
+ }
+
+ @Override
+ public final boolean onUnbind(final Intent intent) {
+ mBound = false;
+
+ if (!mActivityIsChangingConfiguration)
+ onUnbind();
+
+ // We want the onRebind method be called if anything else binds to it again
+ return true;
+ }
+
+ /**
+ * Called when the activity has unbound from the service before being finished.
+ * This method is not called when the activity is killed to be recreated when the phone orientation changed.
+ */
+ protected void onUnbind() {
+ // empty default implementation
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ mHandler = new Handler();
+
+ // Initialize the manager
+ mBleManager = initializeManager();
+ mBleManager.setGattCallbacks(this);
+
+ // Register broadcast receivers
+ registerReceiver(mBluetoothStateBroadcastReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
+
+ // Service has now been created
+ onServiceCreated();
+
+ // Call onBluetoothEnabled if Bluetooth enabled
+ final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (bluetoothAdapter.isEnabled()) {
+ onBluetoothEnabled();
+ }
+ }
+
+ /**
+ * Called when the service has been created, before the {@link #onBluetoothEnabled()} is called.
+ */
+ protected void onServiceCreated() {
+ // empty default implementation
+ }
+
+ /**
+ * Initializes the Ble Manager responsible for connecting to a single device.
+ *
+ * @return a new BleManager object
+ */
+ @SuppressWarnings("rawtypes")
+ protected abstract LoggableBleManager initializeManager();
+
+ /**
+ * This method returns whether autoConnect option should be used.
+ *
+ * @return true to use autoConnect feature, false (default) otherwise.
+ */
+ protected boolean shouldAutoConnect() {
+ return false;
+ }
+
+ @Override
+ public int onStartCommand(final Intent intent, final int flags, final int startId) {
+ if (intent == null || !intent.hasExtra(EXTRA_DEVICE_ADDRESS))
+ throw new UnsupportedOperationException("No device address at EXTRA_DEVICE_ADDRESS key");
+
+ final Uri logUri = intent.getParcelableExtra(EXTRA_LOG_URI);
+ mLogSession = Logger.openSession(getApplicationContext(), logUri);
+ mDeviceName = intent.getStringExtra(EXTRA_DEVICE_NAME);
+
+ Logger.i(mLogSession, "Service started");
+
+ final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ final String deviceAddress = intent.getStringExtra(EXTRA_DEVICE_ADDRESS);
+ mBluetoothDevice = adapter.getRemoteDevice(deviceAddress);
+
+ mBleManager.setLogger(mLogSession);
+ onServiceStarted();
+ mBleManager.connect(mBluetoothDevice)
+ .useAutoConnect(shouldAutoConnect())
+ .retry(3, 100)
+ .enqueue();
+ return START_REDELIVER_INTENT;
+ }
+
+ /**
+ * Called when the service has been started. The device name and address are set.
+ * The BLE Manager will try to connect to the device after this method finishes.
+ */
+ protected void onServiceStarted() {
+ // empty default implementation
+ }
+
+ @Override
+ public void onTaskRemoved(final Intent rootIntent) {
+ super.onTaskRemoved(rootIntent);
+ // This method is called when user removed the app from Recents.
+ // By default, the service will be killed and recreated immediately after that.
+ // However, all managed devices will be lost and devices will be disconnected.
+ stopSelf();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ // Unregister broadcast receivers
+ unregisterReceiver(mBluetoothStateBroadcastReceiver);
+
+ // shutdown the manager
+ mBleManager.close();
+ Logger.i(mLogSession, "Service destroyed");
+ mBleManager = null;
+ mBluetoothDevice = null;
+ mDeviceName = null;
+ mLogSession = null;
+ mHandler = null;
+ }
+
+ /**
+ * Method called when Bluetooth Adapter has been disabled.
+ */
+ protected void onBluetoothDisabled() {
+ // empty default implementation
+ }
+
+ /**
+ * This method is called when Bluetooth Adapter has been enabled and
+ * after the service was created if Bluetooth Adapter was enabled at that moment.
+ * This method could initialize all Bluetooth related features, for example open the GATT server.
+ */
+ protected void onBluetoothEnabled() {
+ // empty default implementation
+ }
+
+ @Override
+ public void onDeviceConnecting(@NonNull final BluetoothDevice device) {
+ final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
+ broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
+ broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_CONNECTING);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+
+ @Override
+ public void onDeviceConnected(@NonNull final BluetoothDevice device) {
+ final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
+ broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_CONNECTED);
+ broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
+ broadcast.putExtra(EXTRA_DEVICE_NAME, mDeviceName);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+
+ @Override
+ public void onDeviceDisconnecting(@NonNull final BluetoothDevice device) {
+ // Notify user about changing the state to DISCONNECTING
+ final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
+ broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
+ broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_DISCONNECTING);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+
+ /**
+ * This method should return false if the service needs to do some asynchronous work after if has disconnected from the device.
+ * In that case the {@link #stopService()} method must be called when done.
+ *
+ * @return true (default) to automatically stop the service when device is disconnected. False otherwise.
+ */
+ protected boolean stopWhenDisconnected() {
+ return true;
+ }
+
+ @Override
+ public void onDeviceDisconnected(@NonNull final BluetoothDevice device) {
+ // Note 1: Do not use the device argument here unless you change calling onDeviceDisconnected from the binder above
+
+ // Note 2: if BleManager#shouldAutoConnect() for this device returned true, this callback will be
+ // invoked ONLY when user requested disconnection (using Disconnect button). If the device
+ // disconnects due to a link loss, the onLinkLossOccurred(BluetoothDevice) method will be called instead.
+
+ final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
+ broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
+ broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_DISCONNECTED);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+
+ if (stopWhenDisconnected())
+ stopService();
+ }
+
+ protected void stopService() {
+ // user requested disconnection. We must stop the service
+ Logger.v(mLogSession, "Stopping service...");
+ stopSelf();
+ }
+
+ @Override
+ public void onLinkLossOccurred(@NonNull final BluetoothDevice device) {
+ final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
+ broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
+ broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_LINK_LOSS);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+
+ @Override
+ public void onServicesDiscovered(@NonNull final BluetoothDevice device, final boolean optionalServicesFound) {
+ final Intent broadcast = new Intent(BROADCAST_SERVICES_DISCOVERED);
+ broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
+ broadcast.putExtra(EXTRA_SERVICE_PRIMARY, true);
+ broadcast.putExtra(EXTRA_SERVICE_SECONDARY, optionalServicesFound);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+
+ @Override
+ public void onDeviceReady(@NonNull final BluetoothDevice device) {
+ final Intent broadcast = new Intent(BROADCAST_DEVICE_READY);
+ broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+
+ @Override
+ public void onDeviceNotSupported(@NonNull final BluetoothDevice device) {
+ final Intent broadcast = new Intent(BROADCAST_SERVICES_DISCOVERED);
+ broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
+ broadcast.putExtra(EXTRA_SERVICE_PRIMARY, false);
+ broadcast.putExtra(EXTRA_SERVICE_SECONDARY, false);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+
+ // no need for disconnecting, it will be disconnected by the manager automatically
+ }
+
+ @Override
+ public void onBatteryValueReceived(@NonNull final BluetoothDevice device, final int value) {
+ final Intent broadcast = new Intent(BROADCAST_BATTERY_LEVEL);
+ broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
+ broadcast.putExtra(EXTRA_BATTERY_LEVEL, value);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+
+ @Override
+ public void onBondingRequired(@NonNull final BluetoothDevice device) {
+ showToast(no.nordicsemi.android.nrftoolbox.common.R.string.bonding);
+
+ final Intent broadcast = new Intent(BROADCAST_BOND_STATE);
+ broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
+ broadcast.putExtra(EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDING);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+
+ @Override
+ public void onBonded(@NonNull final BluetoothDevice device) {
+ showToast(no.nordicsemi.android.nrftoolbox.common.R.string.bonded);
+
+ final Intent broadcast = new Intent(BROADCAST_BOND_STATE);
+ broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
+ broadcast.putExtra(EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDED);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+
+ @Override
+ public void onBondingFailed(@NonNull final BluetoothDevice device) {
+ showToast(no.nordicsemi.android.nrftoolbox.common.R.string.bonding_failed);
+
+ final Intent broadcast = new Intent(BROADCAST_BOND_STATE);
+ broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
+ broadcast.putExtra(EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+
+ @Override
+ public void onError(@NonNull final BluetoothDevice device, @NonNull final String message, final int errorCode) {
+ final Intent broadcast = new Intent(BROADCAST_ERROR);
+ broadcast.putExtra(EXTRA_DEVICE, mBluetoothDevice);
+ broadcast.putExtra(EXTRA_ERROR_MESSAGE, message);
+ broadcast.putExtra(EXTRA_ERROR_CODE, errorCode);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
+
+ /**
+ * Shows a message as a Toast notification. This method is thread safe, you can call it from any thread
+ *
+ * @param messageResId an resource id of the message to be shown
+ */
+ protected void showToast(final int messageResId) {
+ mHandler.post(() -> Toast.makeText(BleProfileService.this, messageResId, Toast.LENGTH_SHORT).show());
+ }
+
+ /**
+ * Shows a message as a Toast notification. This method is thread safe, you can call it from any thread
+ *
+ * @param message a message to be shown
+ */
+ protected void showToast(final String message) {
+ mHandler.post(() -> Toast.makeText(BleProfileService.this, message, Toast.LENGTH_SHORT).show());
+ }
+
+ /**
+ * Returns the log session that can be used to append log entries. The method returns null if the nRF Logger app was not installed. It is safe to use logger when
+ * {@link #onServiceStarted()} has been called.
+ *
+ * @return the log session
+ */
+ protected ILogSession getLogSession() {
+ return mLogSession;
+ }
+
+ /**
+ * Returns the device address
+ *
+ * @return device address
+ */
+ protected String getDeviceAddress() {
+ return mBluetoothDevice.getAddress();
+ }
+
+ /**
+ * Returns the Bluetooth device object
+ *
+ * @return bluetooth device
+ */
+ protected BluetoothDevice getBluetoothDevice() {
+ return mBluetoothDevice;
+ }
+
+ /**
+ * Returns the device name
+ *
+ * @return the device name
+ */
+ protected String getDeviceName() {
+ return mDeviceName;
+ }
+
+ /**
+ * Returns true if the device is connected to the sensor.
+ *
+ * @return true if device is connected to the sensor, false otherwise
+ */
+ protected boolean isConnected() {
+ return mBleManager != null && mBleManager.isConnected();
+ }
}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityService.java
index b9a195e1..039018c6 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityService.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityService.java
@@ -34,11 +34,7 @@ import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
import android.net.Uri;
-import androidx.annotation.NonNull;
-import androidx.core.app.NotificationCompat;
-import androidx.core.app.NotificationManagerCompat;
-import androidx.core.content.ContextCompat;
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
@@ -46,6 +42,11 @@ import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
+import androidx.annotation.NonNull;
+import androidx.core.app.NotificationCompat;
+import androidx.core.app.NotificationManagerCompat;
+import androidx.core.content.ContextCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import no.nordicsemi.android.log.LogContract;
import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
import no.nordicsemi.android.nrftoolbox.R;
@@ -54,517 +55,538 @@ import no.nordicsemi.android.nrftoolbox.profile.LoggableBleManager;
import no.nordicsemi.android.nrftoolbox.profile.multiconnect.BleMulticonnectProfileService;
public class ProximityService extends BleMulticonnectProfileService implements ProximityManagerCallbacks, ProximityServerManagerCallbacks {
- @SuppressWarnings("unused")
- private static final String TAG = "ProximityService";
-
- 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 BROADCAST_ALARM_SWITCHED = "no.nordicsemi.android.nrftoolbox.BROADCAST_ALARM_SWITCHED";
- public static final String EXTRA_ALARM_STATE = "no.nordicsemi.android.nrftoolbox.EXTRA_ALARM_STATE";
-
- private final static String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.proximity.ACTION_DISCONNECT";
- private final static String ACTION_FIND = "no.nordicsemi.android.nrftoolbox.proximity.ACTION_FIND";
- private final static String ACTION_SILENT = "no.nordicsemi.android.nrftoolbox.proximity.ACTION_SILENT";
-
- private final static String PROXIMITY_GROUP_ID = "proximity_connected_tags";
- private final static int NOTIFICATION_ID = 1000;
- private final static int OPEN_ACTIVITY_REQ = 0;
- private final static int DISCONNECT_REQ = 1;
- private final static int FIND_REQ = 2;
- private final static int SILENT_REQ = 3;
-
- private final ProximityBinder mBinder = new ProximityBinder();
- private ProximityServerManager mServerManager;
- private MediaPlayer mMediaPlayer;
- private int mOriginalVolume;
- /**
- * When a device starts an alarm on the phone it is added to this list.
- * Alarm is disabled when this list is empty.
- */
- private List mDevicesWithAlarm;
-
- private int mAttempt;
- private final static int MAX_ATTEMPTS = 1;
-
- /**
- * This local binder is an interface for the bonded activity to operate with the proximity
- * sensor.
- */
- public class ProximityBinder extends LocalBinder {
- /**
- * Toggles the Immediate Alert on given remote device.
- *
- * @param device the connected device.
- */
- public void toggleImmediateAlert(final BluetoothDevice device) {
- final ProximityManager manager = (ProximityManager) getBleManager(device);
- manager.toggleImmediateAlert();
- }
-
- /**
- * Returns the current alarm state on given device. This value is not read from the device,
- * it's just the last value written to it (initially false).
- *
- * @param device the connected device.
- * @return True if alarm has been enabled, false if disabled.
- */
- public boolean isImmediateAlertOn(final BluetoothDevice device) {
- final ProximityManager manager = (ProximityManager) getBleManager(device);
- return manager.isAlertEnabled();
- }
-
- /**
- * Returns the last received battery level value.
- *
- * @param device the device of which battery level should be returned.
- * @return Battery value or null if no value was received or Battery Level characteristic
- * was not found, or the device is disconnected.
- */
- public Integer getBatteryLevel(final BluetoothDevice device) {
- final ProximityManager manager = (ProximityManager) getBleManager(device);
- return manager.getBatteryLevel();
- }
- }
-
- @Override
- protected LocalBinder getBinder() {
- return mBinder;
- }
-
- @Override
- protected LoggableBleManager initializeManager() {
- return new ProximityManager(this);
- }
-
- /**
- * 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 BluetoothDevice device = intent.getParcelableExtra(EXTRA_DEVICE);
- mBinder.log(device, LogContract.Log.Level.INFO, "[Notification] DISCONNECT action pressed");
- mBinder.disconnect(device);
- }
- };
-
- /**
- * This broadcast receiver listens for {@link #ACTION_FIND} or {@link #ACTION_SILENT} that may
- * be fired by pressing Find me action button on the notification.
- */
- private final BroadcastReceiver mToggleAlarmActionBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(final Context context, final Intent intent) {
- final BluetoothDevice device = intent.getParcelableExtra(EXTRA_DEVICE);
- switch (intent.getAction()) {
- case ACTION_FIND:
- mBinder.log(device, LogContract.Log.Level.INFO, "[Notification] FIND action pressed");
- break;
- case ACTION_SILENT:
- mBinder.log(device, LogContract.Log.Level.INFO, "[Notification] SILENT action pressed");
- break;
- }
- mBinder.toggleImmediateAlert(device);
- }
- };
-
- @Override
- protected void onServiceCreated() {
- mServerManager = new ProximityServerManager(this);
- mServerManager.setLogger(mBinder);
-
- initializeAlarm();
-
- registerReceiver(mDisconnectActionBroadcastReceiver, new IntentFilter(ACTION_DISCONNECT));
- final IntentFilter filter = new IntentFilter();
- filter.addAction(ACTION_FIND);
- filter.addAction(ACTION_SILENT);
- registerReceiver(mToggleAlarmActionBroadcastReceiver, filter);
- }
-
- @Override
- public void onServiceStopped() {
- cancelNotifications();
-
- // Close the GATT server. If it hasn't been opened this method does nothing
- mServerManager.closeGattServer();
-
- releaseAlarm();
-
- unregisterReceiver(mDisconnectActionBroadcastReceiver);
- unregisterReceiver(mToggleAlarmActionBroadcastReceiver);
-
- super.onServiceStopped();
- }
-
- @Override
- protected void onBluetoothEnabled() {
- mAttempt = 0;
- getHandler().post(new Runnable() {
- @Override
- public void run() {
- final Runnable that = this;
- // Start the GATT Server only if Bluetooth is enabled
- mServerManager.openGattServer(ProximityService.this,
- new ProximityServerManager.OnServerOpenCallback() {
- @Override
- public void onGattServerOpen() {
- // We are now ready to reconnect devices
- ProximityService.super.onBluetoothEnabled();
- }
-
- @Override
- public void onGattServerFailed(final int error) {
- mServerManager.closeGattServer();
-
- if (mAttempt < MAX_ATTEMPTS) {
- mAttempt++;
- getHandler().postDelayed(that, 2000);
- } else {
- showToast(getString(R.string.proximity_server_error, error));
- // GATT server failed to start, but we may connect as a client
- ProximityService.super.onBluetoothEnabled();
- }
- }
- });
- }
- });
- }
-
- @Override
- protected void onBluetoothDisabled() {
- super.onBluetoothDisabled();
- // Close the GATT server
- mServerManager.closeGattServer();
- }
-
- @Override
- protected void onRebind() {
- // When the activity rebinds to the service, remove the notification
- cancelNotifications();
-
- // This method will read the Battery Level value from each connected device, 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.
- for (final BluetoothDevice device : getManagedDevices()) {
- final ProximityManager manager = (ProximityManager) getBleManager(device);
- manager.readBatteryLevelCharacteristic();
- manager.enableBatteryLevelCharacteristicNotifications();
- }
- }
-
- @Override
- public void onUnbind() {
- // 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.
- for (final BluetoothDevice device : getManagedDevices()) {
- final ProximityManager manager = (ProximityManager) getBleManager(device);
- manager.disableBatteryLevelCharacteristicNotifications();
- }
-
- createBackgroundNotification();
- }
-
- @Override
- public void onDeviceConnected(final BluetoothDevice device) {
- super.onDeviceConnected(device);
-
- if (!mBound) {
- createBackgroundNotification();
- }
- }
-
- @Override
- public void onServicesDiscovered(final BluetoothDevice device, final boolean optionalServicesFound) {
- super.onServicesDiscovered(device, optionalServicesFound);
- mServerManager.openConnection(device);
- }
-
- @Override
- public void onLinkLossOccurred(final BluetoothDevice device) {
- mServerManager.cancelConnection(device);
- stopAlarm(device);
- super.onLinkLossOccurred(device);
-
- if (!mBound) {
- createBackgroundNotification();
- if (BluetoothAdapter.getDefaultAdapter().isEnabled())
- createLinkLossNotification(device);
- else
- cancelNotification(device);
- }
- }
-
- @Override
- public void onDeviceDisconnected(final BluetoothDevice device) {
- mServerManager.cancelConnection(device);
- stopAlarm(device);
- super.onDeviceDisconnected(device);
-
- if (!mBound) {
- cancelNotification(device);
- createBackgroundNotification();
- }
- }
-
- @Override
- public void onAlarmTriggered(@NonNull final BluetoothDevice device) {
- playAlarm(device);
- }
-
- @Override
- public void onAlarmStopped(@NonNull final BluetoothDevice device) {
- stopAlarm(device);
- }
-
- @Override
- public void onRemoteAlarmSwitched(@NonNull final BluetoothDevice device, final boolean on) {
- final Intent broadcast = new Intent(BROADCAST_ALARM_SWITCHED);
- broadcast.putExtra(EXTRA_DEVICE, device);
- broadcast.putExtra(EXTRA_ALARM_STATE, on);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
-
- if (!mBound) {
- createBackgroundNotification();
- }
- }
-
- @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);
- }
-
- private void createBackgroundNotification() {
- final List connectedDevices = getConnectedDevices();
- for (final BluetoothDevice device : connectedDevices) {
- createNotificationForConnectedDevice(device);
- }
- createSummaryNotification();
- }
-
- private void createSummaryNotification() {
- final NotificationCompat.Builder builder = getNotificationBuilder();
- builder.setColor(ContextCompat.getColor(this, R.color.actionBarColorDark));
- builder.setShowWhen(false).setDefaults(0);
- // An ongoing notification will not be shown on Android Wear.
- builder.setOngoing(true);
- builder.setGroup(PROXIMITY_GROUP_ID).setGroupSummary(true);
- builder.setContentTitle(getString(R.string.app_name));
-
- final List managedDevices = getManagedDevices();
- final List connectedDevices = getConnectedDevices();
- if (connectedDevices.isEmpty()) {
- // No connected devices
- final int numberOfManagedDevices = managedDevices.size();
- if (numberOfManagedDevices == 1) {
- final String name = getDeviceName(managedDevices.get(0));
- // We don't use plurals here, as we only have the default language and 'one' is not
- // in every language (versions differ in %d or %s) and throw an exception in e.g. in Chinese.
- builder.setContentText(getString(R.string.proximity_notification_text_nothing_connected_one_disconnected, name));
- } else {
- builder.setContentText(getString(R.string.proximity_notification_text_nothing_connected_number_disconnected, numberOfManagedDevices));
- }
- } else {
- // There are some proximity tags connected
- final StringBuilder text = new StringBuilder();
-
- final int numberOfConnectedDevices = connectedDevices.size();
- if (numberOfConnectedDevices == 1) {
- final String name = getDeviceName(connectedDevices.get(0));
- text.append(getString(R.string.proximity_notification_summary_text_name, name));
- } else {
- text.append(getString(R.string.proximity_notification_summary_text_number, numberOfConnectedDevices));
- }
-
- // If there are some disconnected devices, also print them
- final int numberOfDisconnectedDevices = managedDevices.size() - numberOfConnectedDevices;
- if (numberOfDisconnectedDevices == 1) {
- text.append(", ");
- // Find the single disconnected device to get its name
- for (final BluetoothDevice device : managedDevices) {
- if (!isConnected(device)) {
- final String name = getDeviceName(device);
- text.append(getString(R.string.proximity_notification_text_nothing_connected_one_disconnected, name));
- break;
- }
- }
- } else if (numberOfDisconnectedDevices > 1) {
- text.append(", ");
- // If there are more, just write number of them
- text.append(getString(R.string.proximity_notification_text_nothing_connected_number_disconnected, numberOfDisconnectedDevices));
- }
- text.append(".");
- builder.setContentText(text);
- }
-
- final Notification notification = builder.build();
- final NotificationManagerCompat nm = NotificationManagerCompat.from(this);
- nm.notify(NOTIFICATION_ID, notification);
- }
-
- /**
- * Creates the notification for given connected device.
- * Adds 3 action buttons: DISCONNECT, FIND and SILENT which perform given action on the device.
- */
- private void createNotificationForConnectedDevice(final BluetoothDevice device) {
- final NotificationCompat.Builder builder = getNotificationBuilder();
- builder.setColor(ContextCompat.getColor(this, R.color.actionBarColorDark));
- builder.setGroup(PROXIMITY_GROUP_ID).setDefaults(0);
- // An ongoing notification will not be shown on Android Wear.
- builder.setOngoing(true);
- builder.setContentTitle(getString(R.string.proximity_notification_text, getDeviceName(device)));
-
- // Add DISCONNECT action
- final Intent disconnect = new Intent(ACTION_DISCONNECT);
- disconnect.putExtra(EXTRA_DEVICE, device);
- final PendingIntent disconnectAction =
- PendingIntent.getBroadcast(this, DISCONNECT_REQ + device.hashCode(),
- disconnect, PendingIntent.FLAG_UPDATE_CURRENT);
- builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.proximity_action_disconnect), disconnectAction));
- // This will keep the same order of notification even after an action was clicked on one of them.
- builder.setSortKey(getDeviceName(device) + device.getAddress());
-
- // Add FIND or SILENT action
- final ProximityManager manager = (ProximityManager) getBleManager(device);
- if (manager.isAlertEnabled()) {
- final Intent silentAllIntent = new Intent(ACTION_SILENT);
- silentAllIntent.putExtra(EXTRA_DEVICE, device);
- final PendingIntent silentAction =
- PendingIntent.getBroadcast(this, SILENT_REQ + device.hashCode(),
- silentAllIntent, PendingIntent.FLAG_UPDATE_CURRENT);
- builder.addAction(new NotificationCompat.Action(R.drawable.ic_stat_notify_proximity_silent, getString(R.string.proximity_action_silent), silentAction));
- } else {
- final Intent findAllIntent = new Intent(ACTION_FIND);
- findAllIntent.putExtra(EXTRA_DEVICE, device);
- final PendingIntent findAction =
- PendingIntent.getBroadcast(this, FIND_REQ + device.hashCode(),
- findAllIntent, PendingIntent.FLAG_UPDATE_CURRENT);
- builder.addAction(new NotificationCompat.Action(R.drawable.ic_stat_notify_proximity_find, getString(R.string.proximity_action_find), findAction));
- }
-
- final Notification notification = builder.build();
- final NotificationManagerCompat nm = NotificationManagerCompat.from(this);
- nm.notify(device.getAddress(), NOTIFICATION_ID, notification);
- }
-
- /**
- * Creates a notification showing information about a device that got disconnected.
- */
- private void createLinkLossNotification(final BluetoothDevice device) {
- final NotificationCompat.Builder builder = getNotificationBuilder();
- builder.setColor(ContextCompat.getColor(this, R.color.orange));
-
- final Uri notificationUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
- // Make sure the sound is played even in DND mode
- builder.setSound(notificationUri, AudioManager.STREAM_ALARM);
- builder.setPriority(NotificationCompat.PRIORITY_HIGH);
- builder.setCategory(NotificationCompat.CATEGORY_ALARM);
- builder.setShowWhen(true);
- // An ongoing notification would not be shown on Android Wear.
- builder.setOngoing(false);
- // This notification is to be shown not in a group
-
- final String name = getDeviceName(device);
- builder.setContentTitle(getString(R.string.proximity_notification_link_loss_alert, name));
- builder.setTicker(getString(R.string.proximity_notification_link_loss_alert, name));
-
- final Notification notification = builder.build();
- final NotificationManagerCompat nm = NotificationManagerCompat.from(this);
- nm.notify(device.getAddress(), NOTIFICATION_ID, notification);
- }
-
- private NotificationCompat.Builder getNotificationBuilder() {
- final Intent parentIntent = new Intent(this, FeaturesActivity.class);
- parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- final Intent targetIntent = new Intent(this, ProximityActivity.class);
-
- // 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);
-
- final NotificationCompat.Builder builder = new NotificationCompat.Builder(this, ToolboxApplication.PROXIMITY_WARNINGS_CHANNEL);
- builder.setContentIntent(pendingIntent).setAutoCancel(false);
- builder.setSmallIcon(R.drawable.ic_stat_notify_proximity);
- return builder;
- }
-
- /**
- * Cancels the existing notification. If there is no active notification this method does nothing
- */
- private void cancelNotifications() {
- final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
- nm.cancel(NOTIFICATION_ID);
-
- final List managedDevices = getManagedDevices();
- for (final BluetoothDevice device : managedDevices) {
- nm.cancel(device.getAddress(), NOTIFICATION_ID);
- }
- }
-
- /**
- * Cancels the existing notification for given device. If there is no active notification this method does nothing
- */
- private void cancelNotification(final BluetoothDevice device) {
- final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
- nm.cancel(device.getAddress(), NOTIFICATION_ID);
- }
-
- private void initializeAlarm() {
- mDevicesWithAlarm = new LinkedList<>();
- mMediaPlayer = new MediaPlayer();
- mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
- mMediaPlayer.setLooping(true);
- mMediaPlayer.setVolume(1.0f, 1.0f);
- try {
- mMediaPlayer.setDataSource(this, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM));
- } catch (final IOException e) {
- Log.e(TAG, "Initialize Alarm failed: ", e);
- }
- }
-
- private void releaseAlarm() {
- mMediaPlayer.release();
- mMediaPlayer = null;
- }
-
- private void playAlarm(final BluetoothDevice device) {
- final boolean alarmPlaying = !mDevicesWithAlarm.isEmpty();
- if (!mDevicesWithAlarm.contains(device))
- mDevicesWithAlarm.add(device);
-
- if (!alarmPlaying) {
- // Save the current alarm volume and set it to max
- final AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
- mOriginalVolume = am.getStreamVolume(AudioManager.STREAM_ALARM);
- am.setStreamVolume(AudioManager.STREAM_ALARM, am.getStreamMaxVolume(AudioManager.STREAM_ALARM), AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
- try {
- mMediaPlayer.prepare();
- mMediaPlayer.start();
- } catch (final IOException e) {
- Log.e(TAG, "Prepare Alarm failed: ", e);
- }
- }
- }
-
- private void stopAlarm(final BluetoothDevice device) {
- mDevicesWithAlarm.remove(device);
- if (mDevicesWithAlarm.isEmpty() && mMediaPlayer.isPlaying()) {
- mMediaPlayer.stop();
- // Restore original volume
- final AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
- am.setStreamVolume(AudioManager.STREAM_ALARM, mOriginalVolume, 0);
- }
- }
-
- private String getDeviceName(final BluetoothDevice device) {
- String name = device.getName();
- if (TextUtils.isEmpty(name))
- name = getString(R.string.proximity_default_device_name);
- return name;
- }
+ @SuppressWarnings("unused")
+ private static final String TAG = "ProximityService";
+
+ 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 BROADCAST_ALARM_SWITCHED = "no.nordicsemi.android.nrftoolbox.BROADCAST_ALARM_SWITCHED";
+ public static final String EXTRA_ALARM_STATE = "no.nordicsemi.android.nrftoolbox.EXTRA_ALARM_STATE";
+
+ private final static String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.proximity.ACTION_DISCONNECT";
+ private final static String ACTION_FIND = "no.nordicsemi.android.nrftoolbox.proximity.ACTION_FIND";
+ private final static String ACTION_SILENT = "no.nordicsemi.android.nrftoolbox.proximity.ACTION_SILENT";
+
+ private final static String PROXIMITY_GROUP_ID = "proximity_connected_tags";
+ private final static int NOTIFICATION_ID = 1000;
+ private final static int OPEN_ACTIVITY_REQ = 0;
+ private final static int DISCONNECT_REQ = 1;
+ private final static int FIND_REQ = 2;
+ private final static int SILENT_REQ = 3;
+
+ private final ProximityBinder mBinder = new ProximityBinder();
+ private ProximityServerManager mServerManager;
+ private MediaPlayer mMediaPlayer;
+ private int mOriginalVolume;
+ /**
+ * When a device starts an alarm on the phone it is added to this list.
+ * Alarm is disabled when this list is empty.
+ */
+ private List mDevicesWithAlarm;
+
+ private int mAttempt;
+ private final static int MAX_ATTEMPTS = 1;
+
+ /**
+ * This local binder is an interface for the bonded activity to operate with the proximity
+ * sensor.
+ */
+ public class ProximityBinder extends LocalBinder {
+ /**
+ * Toggles the Immediate Alert on given remote device.
+ *
+ * @param device the connected device.
+ */
+ public void toggleImmediateAlert(final BluetoothDevice device) {
+ final ProximityManager manager = (ProximityManager) getBleManager(device);
+ manager.toggleImmediateAlert();
+ }
+
+ /**
+ * Returns the current alarm state on given device. This value is not read from the device,
+ * it's just the last value written to it (initially false).
+ *
+ * @param device the connected device.
+ * @return True if alarm has been enabled, false if disabled.
+ */
+ public boolean isImmediateAlertOn(final BluetoothDevice device) {
+ final ProximityManager manager = (ProximityManager) getBleManager(device);
+ return manager.isAlertEnabled();
+ }
+
+ /**
+ * Returns the last received battery level value.
+ *
+ * @param device the device of which battery level should be returned.
+ * @return Battery value or null if no value was received or Battery Level characteristic
+ * was not found, or the device is disconnected.
+ */
+ public Integer getBatteryLevel(final BluetoothDevice device) {
+ final ProximityManager manager = (ProximityManager) getBleManager(device);
+ return manager.getBatteryLevel();
+ }
+ }
+
+ @Override
+ protected LocalBinder getBinder() {
+ return mBinder;
+ }
+
+ @Override
+ protected LoggableBleManager initializeManager() {
+ return new ProximityManager(this);
+ }
+
+ /**
+ * 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 BluetoothDevice device = intent.getParcelableExtra(EXTRA_DEVICE);
+ mBinder.log(device, LogContract.Log.Level.INFO, "[Notification] DISCONNECT action pressed");
+ mBinder.disconnect(device);
+ }
+ };
+
+ /**
+ * This broadcast receiver listens for {@link #ACTION_FIND} or {@link #ACTION_SILENT} that may
+ * be fired by pressing Find me action button on the notification.
+ */
+ private final BroadcastReceiver mToggleAlarmActionBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ final BluetoothDevice device = intent.getParcelableExtra(EXTRA_DEVICE);
+ switch (intent.getAction()) {
+ case ACTION_FIND:
+ mBinder.log(device, LogContract.Log.Level.INFO, "[Notification] FIND action pressed");
+ break;
+ case ACTION_SILENT:
+ mBinder.log(device, LogContract.Log.Level.INFO, "[Notification] SILENT action pressed");
+ break;
+ }
+ mBinder.toggleImmediateAlert(device);
+ }
+ };
+
+ @Override
+ protected void onServiceCreated() {
+ mServerManager = new ProximityServerManager(this);
+ mServerManager.setLogger(mBinder);
+
+ initializeAlarm();
+
+ registerReceiver(mDisconnectActionBroadcastReceiver, new IntentFilter(ACTION_DISCONNECT));
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_FIND);
+ filter.addAction(ACTION_SILENT);
+ registerReceiver(mToggleAlarmActionBroadcastReceiver, filter);
+ }
+
+ @Override
+ public void onServiceStopped() {
+ cancelNotifications();
+
+ // Close the GATT server. If it hasn't been opened this method does nothing
+ mServerManager.closeGattServer();
+
+ releaseAlarm();
+
+ unregisterReceiver(mDisconnectActionBroadcastReceiver);
+ unregisterReceiver(mToggleAlarmActionBroadcastReceiver);
+
+ super.onServiceStopped();
+ }
+
+ @Override
+ protected void onBluetoothEnabled() {
+ mAttempt = 0;
+ getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ final Runnable that = this;
+ // Start the GATT Server only if Bluetooth is enabled
+ mServerManager.openGattServer(ProximityService.this,
+ new ProximityServerManager.OnServerOpenCallback() {
+ @Override
+ public void onGattServerOpen() {
+ // We are now ready to reconnect devices
+ ProximityService.super.onBluetoothEnabled();
+ }
+
+ @Override
+ public void onGattServerFailed(final int error) {
+ mServerManager.closeGattServer();
+
+ if (mAttempt < MAX_ATTEMPTS) {
+ mAttempt++;
+ getHandler().postDelayed(that, 2000);
+ } else {
+ showToast(getString(R.string.proximity_server_error, error));
+ // GATT server failed to start, but we may connect as a client
+ ProximityService.super.onBluetoothEnabled();
+ }
+ }
+ });
+ }
+ });
+ }
+
+ @Override
+ protected void onBluetoothDisabled() {
+ super.onBluetoothDisabled();
+ // Close the GATT server
+ mServerManager.closeGattServer();
+ }
+
+ @Override
+ protected void onRebind() {
+ // When the activity rebinds to the service, remove the notification
+ cancelNotifications();
+
+ // This method will read the Battery Level value from each connected device, 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.
+ for (final BluetoothDevice device : getManagedDevices()) {
+ final ProximityManager manager = (ProximityManager) getBleManager(device);
+ manager.readBatteryLevelCharacteristic();
+ manager.enableBatteryLevelCharacteristicNotifications();
+ }
+ }
+
+ @Override
+ public void onUnbind() {
+ // 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.
+ for (final BluetoothDevice device : getManagedDevices()) {
+ final ProximityManager manager = (ProximityManager) getBleManager(device);
+ manager.disableBatteryLevelCharacteristicNotifications();
+ }
+
+ createBackgroundNotification();
+ }
+
+ @Override
+ public void onDeviceConnected(final BluetoothDevice device) {
+ super.onDeviceConnected(device);
+
+ if (!mBound) {
+ createBackgroundNotification();
+ }
+ }
+
+ @Override
+ public void onServicesDiscovered(final BluetoothDevice device, final boolean optionalServicesFound) {
+ super.onServicesDiscovered(device, optionalServicesFound);
+ mServerManager.openConnection(device);
+ }
+
+ @Override
+ public void onLinkLossOccurred(final BluetoothDevice device) {
+ mServerManager.cancelConnection(device);
+ stopAlarm(device);
+ super.onLinkLossOccurred(device);
+
+ if (!mBound) {
+ createBackgroundNotification();
+ if (BluetoothAdapter.getDefaultAdapter().isEnabled())
+ createLinkLossNotification(device);
+ else
+ cancelNotification(device);
+ }
+ }
+
+ @Override
+ public void onDeviceDisconnected(final BluetoothDevice device) {
+ mServerManager.cancelConnection(device);
+ stopAlarm(device);
+ super.onDeviceDisconnected(device);
+
+ if (!mBound) {
+ cancelNotification(device);
+ createBackgroundNotification();
+ }
+ }
+
+ @Override
+ public void onAlarmTriggered(@NonNull final BluetoothDevice device) {
+ playAlarm(device);
+ }
+
+ @Override
+ public void onAlarmStopped(@NonNull final BluetoothDevice device) {
+ stopAlarm(device);
+ }
+
+ @Override
+ public void onRemoteAlarmSwitched(@NonNull final BluetoothDevice device, final boolean on) {
+ final Intent broadcast = new Intent(BROADCAST_ALARM_SWITCHED);
+ broadcast.putExtra(EXTRA_DEVICE, device);
+ broadcast.putExtra(EXTRA_ALARM_STATE, on);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+
+ if (!mBound) {
+ createBackgroundNotification();
+ }
+ }
+
+ @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);
+ }
+
+ private void createBackgroundNotification() {
+ final List connectedDevices = getConnectedDevices();
+ for (final BluetoothDevice device : connectedDevices) {
+ createNotificationForConnectedDevice(device);
+ }
+ createSummaryNotification();
+ }
+
+ private void createSummaryNotification() {
+ final NotificationCompat.Builder builder = getNotificationBuilder();
+ builder.setColor(ContextCompat.getColor(this, R.color.actionBarColorDark));
+ builder.setShowWhen(false).setDefaults(0);
+ // An ongoing notification will not be shown on Android Wear.
+ builder.setOngoing(true);
+ builder.setGroup(PROXIMITY_GROUP_ID).setGroupSummary(true);
+ builder.setContentTitle(getString(R.string.app_name));
+
+ final List managedDevices = getManagedDevices();
+ final List connectedDevices = getConnectedDevices();
+ if (connectedDevices.isEmpty()) {
+ // No connected devices
+ final int numberOfManagedDevices = managedDevices.size();
+ if (numberOfManagedDevices == 1) {
+ final String name = getDeviceName(managedDevices.get(0));
+ // We don't use plurals here, as we only have the default language and 'one' is not
+ // in every language (versions differ in %d or %s) and throw an exception in e.g. in Chinese.
+ builder.setContentText(getString(R.string.proximity_notification_text_nothing_connected_one_disconnected, name));
+ } else {
+ builder.setContentText(getString(R.string.proximity_notification_text_nothing_connected_number_disconnected, numberOfManagedDevices));
+ }
+ } else {
+ // There are some proximity tags connected
+ final StringBuilder text = new StringBuilder();
+
+ final int numberOfConnectedDevices = connectedDevices.size();
+ if (numberOfConnectedDevices == 1) {
+ final String name = getDeviceName(connectedDevices.get(0));
+ text.append(getString(R.string.proximity_notification_summary_text_name, name));
+ } else {
+ text.append(getString(R.string.proximity_notification_summary_text_number, numberOfConnectedDevices));
+ }
+
+ // If there are some disconnected devices, also print them
+ final int numberOfDisconnectedDevices = managedDevices.size() - numberOfConnectedDevices;
+ if (numberOfDisconnectedDevices == 1) {
+ text.append(", ");
+ // Find the single disconnected device to get its name
+ for (final BluetoothDevice device : managedDevices) {
+ if (!isConnected(device)) {
+ final String name = getDeviceName(device);
+ text.append(getString(R.string.proximity_notification_text_nothing_connected_one_disconnected, name));
+ break;
+ }
+ }
+ } else if (numberOfDisconnectedDevices > 1) {
+ text.append(", ");
+ // If there are more, just write number of them
+ text.append(getString(R.string.proximity_notification_text_nothing_connected_number_disconnected, numberOfDisconnectedDevices));
+ }
+ text.append(".");
+ builder.setContentText(text);
+ }
+
+ final Notification notification = builder.build();
+ final NotificationManagerCompat nm = NotificationManagerCompat.from(this);
+ nm.notify(NOTIFICATION_ID, notification);
+ }
+
+ /**
+ * Creates the notification for given connected device.
+ * Adds 3 action buttons: DISCONNECT, FIND and SILENT which perform given action on the device.
+ */
+ private void createNotificationForConnectedDevice(final BluetoothDevice device) {
+ final NotificationCompat.Builder builder = getNotificationBuilder();
+ builder.setColor(ContextCompat.getColor(this, R.color.actionBarColorDark));
+ builder.setGroup(PROXIMITY_GROUP_ID).setDefaults(0);
+ // An ongoing notification will not be shown on Android Wear.
+ builder.setOngoing(true);
+ builder.setContentTitle(getString(R.string.proximity_notification_text, getDeviceName(device)));
+
+ // Add DISCONNECT action
+ final Intent disconnect = new Intent(ACTION_DISCONNECT);
+ disconnect.putExtra(EXTRA_DEVICE, device);
+ final PendingIntent disconnectAction =
+ PendingIntent.getBroadcast(this, DISCONNECT_REQ + device.hashCode(),
+ disconnect, PendingIntent.FLAG_UPDATE_CURRENT);
+ builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.proximity_action_disconnect), disconnectAction));
+ // This will keep the same order of notification even after an action was clicked on one of them.
+ builder.setSortKey(getDeviceName(device) + device.getAddress());
+
+ // Add FIND or SILENT action
+ final ProximityManager manager = (ProximityManager) getBleManager(device);
+ if (manager.isAlertEnabled()) {
+ final Intent silentAllIntent = new Intent(ACTION_SILENT);
+ silentAllIntent.putExtra(EXTRA_DEVICE, device);
+ final PendingIntent silentAction =
+ PendingIntent.getBroadcast(this, SILENT_REQ + device.hashCode(),
+ silentAllIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ builder.addAction(new NotificationCompat.Action(R.drawable.ic_stat_notify_proximity_silent, getString(R.string.proximity_action_silent), silentAction));
+ } else {
+ final Intent findAllIntent = new Intent(ACTION_FIND);
+ findAllIntent.putExtra(EXTRA_DEVICE, device);
+ final PendingIntent findAction =
+ PendingIntent.getBroadcast(this, FIND_REQ + device.hashCode(),
+ findAllIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ builder.addAction(new NotificationCompat.Action(R.drawable.ic_stat_notify_proximity_find, getString(R.string.proximity_action_find), findAction));
+ }
+
+ final Notification notification = builder.build();
+ 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 a notification showing information about a device that got disconnected.
+ */
+ private void createLinkLossNotification(final BluetoothDevice device) {
+ final NotificationCompat.Builder builder = getNotificationBuilder();
+ builder.setColor(ContextCompat.getColor(this, R.color.orange));
+
+ final Uri notificationUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
+ // Make sure the sound is played even in DND mode
+ builder.setSound(notificationUri, AudioManager.STREAM_ALARM);
+ builder.setPriority(NotificationCompat.PRIORITY_HIGH);
+ builder.setCategory(NotificationCompat.CATEGORY_ALARM);
+ builder.setShowWhen(true);
+ // An ongoing notification would not be shown on Android Wear.
+ builder.setOngoing(false);
+ // This notification is to be shown not in a group
+
+ final String name = getDeviceName(device);
+ builder.setContentTitle(getString(R.string.proximity_notification_link_loss_alert, name));
+ builder.setTicker(getString(R.string.proximity_notification_link_loss_alert, name));
+
+ final Notification notification = builder.build();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ startForeground(NOTIFICATION_ID, notification);
+ } else {
+ final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(device.getAddress(), NOTIFICATION_ID, notification);
+ }
+ }
+
+ private NotificationCompat.Builder getNotificationBuilder() {
+ final Intent parentIntent = new Intent(this, FeaturesActivity.class);
+ parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final Intent targetIntent = new Intent(this, ProximityActivity.class);
+
+ // 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);
+
+ final NotificationCompat.Builder builder = new NotificationCompat.Builder(this, ToolboxApplication.PROXIMITY_WARNINGS_CHANNEL);
+ builder.setContentIntent(pendingIntent).setAutoCancel(false);
+ builder.setSmallIcon(R.drawable.ic_stat_notify_proximity);
+ return builder;
+ }
+
+ /**
+ * Cancels the existing notification. If there is no active notification this method does nothing
+ */
+ private void cancelNotifications() {
+ final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ stopForeground(true);
+ } else {
+ nm.cancel(NOTIFICATION_ID);
+ }
+
+ final List managedDevices = getManagedDevices();
+ for (final BluetoothDevice device : managedDevices) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ stopForeground(true);
+ } else {
+ nm.cancel(device.getAddress(), NOTIFICATION_ID);
+ }
+ }
+ }
+
+ /**
+ * Cancels the existing notification for given device. If there is no active notification this method does nothing
+ */
+ private void cancelNotification(final BluetoothDevice device) {
+ final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ stopForeground(true);
+ } else {
+ nm.cancel(device.getAddress(), NOTIFICATION_ID);
+ }
+ }
+
+ private void initializeAlarm() {
+ mDevicesWithAlarm = new LinkedList<>();
+ mMediaPlayer = new MediaPlayer();
+ mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
+ mMediaPlayer.setLooping(true);
+ mMediaPlayer.setVolume(1.0f, 1.0f);
+ try {
+ mMediaPlayer.setDataSource(this, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM));
+ } catch (final IOException e) {
+ Log.e(TAG, "Initialize Alarm failed: ", e);
+ }
+ }
+
+ private void releaseAlarm() {
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ }
+
+ private void playAlarm(final BluetoothDevice device) {
+ final boolean alarmPlaying = !mDevicesWithAlarm.isEmpty();
+ if (!mDevicesWithAlarm.contains(device))
+ mDevicesWithAlarm.add(device);
+
+ if (!alarmPlaying) {
+ // Save the current alarm volume and set it to max
+ final AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ mOriginalVolume = am.getStreamVolume(AudioManager.STREAM_ALARM);
+ am.setStreamVolume(AudioManager.STREAM_ALARM, am.getStreamMaxVolume(AudioManager.STREAM_ALARM), AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
+ try {
+ mMediaPlayer.prepare();
+ mMediaPlayer.start();
+ } catch (final IOException e) {
+ Log.e(TAG, "Prepare Alarm failed: ", e);
+ }
+ }
+ }
+
+ private void stopAlarm(final BluetoothDevice device) {
+ mDevicesWithAlarm.remove(device);
+ if (mDevicesWithAlarm.isEmpty() && mMediaPlayer.isPlaying()) {
+ mMediaPlayer.stop();
+ // Restore original volume
+ final AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ am.setStreamVolume(AudioManager.STREAM_ALARM, mOriginalVolume, 0);
+ }
+ }
+
+ private String getDeviceName(final BluetoothDevice device) {
+ String name = device.getName();
+ if (TextUtils.isEmpty(name))
+ name = getString(R.string.proximity_default_device_name);
+ return name;
+ }
}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCService.java
index 04480330..a7b6f369 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCService.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCService.java
@@ -30,12 +30,13 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.Build;
import android.os.Handler;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
-
import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
import no.nordicsemi.android.nrftoolbox.R;
@@ -44,209 +45,239 @@ import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
import no.nordicsemi.android.nrftoolbox.profile.LoggableBleManager;
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 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_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_ACTIVITY = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_ACTIVITY";
+ 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_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_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 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_DISTANCE = "no.nordicsemi.android.nrftoolbox.rsc.EXTRA_DISTANCE";
+ 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_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 EXTRA_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.EXTRA_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";
- 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;
- /** Trip distance in cm */
- private long mDistance;
- /** 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();
+ /**
+ * The last value of a cadence
+ */
+ private float mCadence;
+ /**
+ * Trip distance in cm
+ */
+ private long mDistance;
+ /**
+ * 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 OPEN_ACTIVITY_REQ = 0;
- private final static int DISCONNECT_REQ = 1;
+ private final static int NOTIFICATION_ID = 200;
+ private final static int OPEN_ACTIVITY_REQ = 0;
+ 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.
- */
- class RSCBinder extends LocalBinder {
- // empty
- }
+ /**
+ * This local binder is an interface for the bound activity to operate with the RSC sensor.
+ */
+ class RSCBinder extends LocalBinder {
+ // empty
+ }
- @Override
- protected LocalBinder getBinder() {
- return mBinder;
- }
+ @Override
+ protected LocalBinder getBinder() {
+ return mBinder;
+ }
- @Override
- protected LoggableBleManager initializeManager() {
- return mManager = new RSCManager(this);
- }
+ @Override
+ protected LoggableBleManager initializeManager() {
+ return mManager = new RSCManager(this);
+ }
- @Override
- public void onCreate() {
- super.onCreate();
+ @Override
+ public void onCreate() {
+ super.onCreate();
- final IntentFilter filter = new IntentFilter();
- filter.addAction(ACTION_DISCONNECT);
- registerReceiver(mDisconnectActionBroadcastReceiver, filter);
- }
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_DISCONNECT);
+ registerReceiver(mDisconnectActionBroadcastReceiver, filter);
+ }
- @Override
- 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
- cancelNotification();
- unregisterReceiver(mDisconnectActionBroadcastReceiver);
+ @Override
+ 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
+ stopForegroundService();
+ unregisterReceiver(mDisconnectActionBroadcastReceiver);
- super.onDestroy();
- }
+ super.onDestroy();
+ }
- @Override
- protected void onRebind() {
- // when the activity rebinds to the service, remove the notification
- cancelNotification();
+ @Override
+ protected void onRebind() {
+ stopForegroundService();
- if (isConnected()) {
- // 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.
- mManager.readBatteryLevelCharacteristic();
- }
- }
+ if (isConnected()) {
+ // 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.
+ mManager.readBatteryLevelCharacteristic();
+ }
+ }
- @Override
- protected void onUnbind() {
- // 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.
- if (isConnected())
- mManager.disableBatteryLevelCharacteristicNotifications();
+ @Override
+ protected void onUnbind() {
+ // 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.
+ if (isConnected())
+ mManager.disableBatteryLevelCharacteristicNotifications();
- // when the activity closes we need to show the notification that user is connected to the sensor
- createNotification(R.string.rsc_notification_connected_message, 0);
- }
+ startForegroundService();
+ }
- private final Runnable mUpdateStridesTask = new Runnable() {
- @Override
- public void run() {
- if (!isConnected())
- return;
+ private final Runnable mUpdateStridesTask = new Runnable() {
+ @Override
+ public void run() {
+ if (!isConnected())
+ return;
- mStepsNumber++;
- mDistance += mStrideLength; // [cm]
- final Intent broadcast = new Intent(BROADCAST_STRIDES_UPDATE);
- broadcast.putExtra(EXTRA_STRIDES, mStepsNumber);
- broadcast.putExtra(EXTRA_DISTANCE, mDistance);
- LocalBroadcastManager.getInstance(RSCService.this).sendBroadcast(broadcast);
+ mStepsNumber++;
+ mDistance += mStrideLength; // [cm]
+ final Intent broadcast = new Intent(BROADCAST_STRIDES_UPDATE);
+ broadcast.putExtra(EXTRA_STRIDES, mStepsNumber);
+ broadcast.putExtra(EXTRA_DISTANCE, mDistance);
+ LocalBroadcastManager.getInstance(RSCService.this).sendBroadcast(broadcast);
- if (mCadence > 0) {
- final long interval = (long) (1000.0f * 60.0f / mCadence);
- mHandler.postDelayed(mUpdateStridesTask, interval);
- } else {
- mTaskInProgress = false;
- }
- }
- };
+ if (mCadence > 0) {
+ final long interval = (long) (1000.0f * 60.0f / mCadence);
+ mHandler.postDelayed(mUpdateStridesTask, interval);
+ } else {
+ mTaskInProgress = false;
+ }
+ }
+ };
- @Override
- public void onRSCMeasurementReceived(@NonNull final BluetoothDevice device, final boolean running,
- final float instantaneousSpeed, final int instantaneousCadence,
- @Nullable final Integer strideLength,
- @Nullable final Long totalDistance) {
- final Intent broadcast = new Intent(BROADCAST_RSC_MEASUREMENT);
- broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
- broadcast.putExtra(EXTRA_SPEED, instantaneousSpeed);
- broadcast.putExtra(EXTRA_CADENCE, instantaneousCadence);
- broadcast.putExtra(EXTRA_STRIDE_LENGTH, strideLength);
- broadcast.putExtra(EXTRA_TOTAL_DISTANCE, totalDistance);
- broadcast.putExtra(EXTRA_ACTIVITY, running);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ @Override
+ public void onRSCMeasurementReceived(@NonNull final BluetoothDevice device, final boolean running,
+ final float instantaneousSpeed, final int instantaneousCadence,
+ @Nullable final Integer strideLength,
+ @Nullable final Long totalDistance) {
+ final Intent broadcast = new Intent(BROADCAST_RSC_MEASUREMENT);
+ broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
+ broadcast.putExtra(EXTRA_SPEED, instantaneousSpeed);
+ broadcast.putExtra(EXTRA_CADENCE, instantaneousCadence);
+ broadcast.putExtra(EXTRA_STRIDE_LENGTH, strideLength);
+ broadcast.putExtra(EXTRA_TOTAL_DISTANCE, totalDistance);
+ broadcast.putExtra(EXTRA_ACTIVITY, running);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- // Start strides counter if not in progress
- mCadence = instantaneousCadence;
- if (strideLength != null) {
- mStrideLength = strideLength;
- }
- if (!mTaskInProgress && strideLength != null && instantaneousCadence > 0) {
- mTaskInProgress = true;
+ // Start strides counter if not in progress
+ mCadence = instantaneousCadence;
+ if (strideLength != null) {
+ mStrideLength = strideLength;
+ }
+ if (!mTaskInProgress && strideLength != null && instantaneousCadence > 0) {
+ mTaskInProgress = true;
- final long interval = (long) (1000.0f * 60.0f / mCadence);
- mHandler.postDelayed(mUpdateStridesTask, interval);
- }
- }
+ final long interval = (long) (1000.0f * 60.0f / mCadence);
+ mHandler.postDelayed(mUpdateStridesTask, interval);
+ }
+ }
- @Override
- public void onBatteryLevelChanged(@NonNull final BluetoothDevice device, final int value) {
- final Intent broadcast = new Intent(BROADCAST_BATTERY_LEVEL);
- broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
- broadcast.putExtra(EXTRA_BATTERY_LEVEL, value);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- }
+ @Override
+ public void onBatteryLevelChanged(@NonNull final BluetoothDevice device, final int value) {
+ final Intent broadcast = new Intent(BROADCAST_BATTERY_LEVEL);
+ broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
+ broadcast.putExtra(EXTRA_BATTERY_LEVEL, value);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ }
- /**
- * Creates the notification
- *
- * @param messageResId
- * message resource id. The message must have one String parameter,
- * f.e. <string name="name">%s is connected</string>
- * @param defaults
- * signals that will be used to notify the user
- */
- private void 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);
+ /**
+ * 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);
+ }
+ }
- 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);
- 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_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));
+ /**
+ * Creates the notification
+ *
+ * @param messageResId message resource id. The message must have one String parameter,
+ * f.e. <string name="name">%s is connected</string>
+ * @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, RSCActivity.class);
- final Notification notification = builder.build();
- 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);
- /**
- * 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);
- }
+ // 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);
+ 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_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));
- /**
- * 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();
- }
- };
+ 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.
+ */
+ 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();
+ }
+ };
}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/template/TemplateService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/template/TemplateService.java
index e6be074e..069d93c9 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/template/TemplateService.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/template/TemplateService.java
@@ -30,10 +30,11 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.Build;
+
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
-
import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
import no.nordicsemi.android.nrftoolbox.R;
@@ -42,144 +43,168 @@ import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
import no.nordicsemi.android.nrftoolbox.profile.LoggableBleManager;
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 EXTRA_DATA = "no.nordicsemi.android.nrftoolbox.template.EXTRA_DATA";
+ 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 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 BROADCAST_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.BROADCAST_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 OPEN_ACTIVITY_REQ = 0;
- private final static int DISCONNECT_REQ = 1;
+ private final static int NOTIFICATION_ID = 864;
+ private final static int OPEN_ACTIVITY_REQ = 0;
+ 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.
- */
- class TemplateBinder extends LocalBinder {
- // TODO Define service API that may be used by a bound Activity
+ /**
+ * This local binder is an interface for the bound activity to operate with the sensor.
+ */
+ class TemplateBinder extends LocalBinder {
+ // TODO Define service API that may be used by a bound Activity
- /**
- * Sends some important data to the device.
- *
- * @param parameter some parameter.
- */
- public void performAction(final String parameter) {
- mManager.performAction(parameter);
- }
- }
+ /**
+ * Sends some important data to the device.
+ *
+ * @param parameter some parameter.
+ */
+ public void performAction(final String parameter) {
+ mManager.performAction(parameter);
+ }
+ }
- @Override
- protected LocalBinder getBinder() {
- return mBinder;
- }
+ @Override
+ protected LocalBinder getBinder() {
+ return mBinder;
+ }
- @Override
- protected LoggableBleManager initializeManager() {
- return mManager = new TemplateManager(this);
- }
+ @Override
+ protected LoggableBleManager initializeManager() {
+ return mManager = new TemplateManager(this);
+ }
- @Override
- public void onCreate() {
- super.onCreate();
+ @Override
+ public void onCreate() {
+ super.onCreate();
- final IntentFilter filter = new IntentFilter();
- filter.addAction(ACTION_DISCONNECT);
- registerReceiver(mDisconnectActionBroadcastReceiver, filter);
- }
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_DISCONNECT);
+ registerReceiver(mDisconnectActionBroadcastReceiver, filter);
+ }
- @Override
- 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
- cancelNotification();
- unregisterReceiver(mDisconnectActionBroadcastReceiver);
+ @Override
+ 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
+ stopForegroundService();
+ unregisterReceiver(mDisconnectActionBroadcastReceiver);
- super.onDestroy();
- }
+ super.onDestroy();
+ }
- @Override
- protected void onRebind() {
- // when the activity rebinds to the service, remove the notification
- cancelNotification();
- }
+ @Override
+ protected void onRebind() {
+ stopForegroundService();
+ }
- @Override
- protected void onUnbind() {
- // when the activity closes we need to show the notification that user is connected to the sensor
- createNotification(R.string.template_notification_connected_message, 0);
- }
+ @Override
+ protected void onUnbind() {
+ startForegroundService();
+ }
- @Override
- public void onSampleValueReceived(@NonNull final BluetoothDevice device, final int value) {
- final Intent broadcast = new Intent(BROADCAST_TEMPLATE_MEASUREMENT);
- broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
- broadcast.putExtra(EXTRA_DATA, value);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ @Override
+ public void onSampleValueReceived(@NonNull final BluetoothDevice device, final int value) {
+ final Intent broadcast = new Intent(BROADCAST_TEMPLATE_MEASUREMENT);
+ broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
+ broadcast.putExtra(EXTRA_DATA, value);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- if (!mBound) {
- // Here we may update the notification to display the current value.
- // TODO modify the notification here
- }
- }
+ if (!mBound) {
+ // Here we may update the notification to display the current value.
+ // TODO modify the notification here
+ }
+ }
- @Override
- public void onBatteryLevelChanged(@NonNull final BluetoothDevice device, final int batteryLevel) {
+ @Override
+ public void onBatteryLevelChanged(@NonNull final BluetoothDevice device, final int batteryLevel) {
- }
+ }
- /**
- * Creates the notification.
- *
- * @param messageResId message resource id. The message must have one String parameter,
- * f.e. <string name="name">%s is connected</string>
- * @param defaults signals that will be used to notify the user
- */
- private void 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);
+ /**
+ * 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);
+ }
+ }
- 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);
- 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_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));
+ /**
+ * Creates the notification.
+ *
+ * @param messageResId message resource id. The message must have one String parameter,
+ * f.e. <string name="name">%s is connected</string>
+ * @param defaults signals that will be used to notify the user
+ */
+ 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 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);
- /**
- * 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);
- }
+ // 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);
+ 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_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));
- /**
- * 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();
- }
- };
+ 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.
+ */
+ 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();
+ }
+ };
}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTService.java
index 2e885ff7..9e97d53e 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTService.java
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTService.java
@@ -30,9 +30,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import androidx.annotation.NonNull;
-import androidx.core.app.NotificationCompat;
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import android.os.Build;
import android.text.TextUtils;
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.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.nrftoolbox.FeaturesActivity;
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;
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_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 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 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 is triggered when a message is received from the UART device. */
- private final static String ACTION_RECEIVE = "no.nordicsemi.android.nrftoolbox.uart.ACTION_RECEIVE";
- /** 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 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;
+ /**
+ * 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 is triggered when a message is received from the UART device.
+ */
+ private final static String ACTION_RECEIVE = "no.nordicsemi.android.nrftoolbox.uart.ACTION_RECEIVE";
+ /**
+ * 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 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 OPEN_ACTIVITY_REQ = 67; // random
- private final static int DISCONNECT_REQ = 97; // random
+ private final static int NOTIFICATION_ID = 349; // random
+ private final static int OPEN_ACTIVITY_REQ = 67; // random
+ private final static int DISCONNECT_REQ = 97; // random
- private GoogleApiClient mGoogleApiClient;
- private UARTManager mManager;
+ private GoogleApiClient mGoogleApiClient;
+ private UARTManager mManager;
- private final LocalBinder mBinder = new UARTBinder();
+ private final LocalBinder mBinder = new UARTBinder();
- public class UARTBinder extends LocalBinder implements UARTInterface {
- @Override
- public void send(final String text) {
- mManager.send(text);
- }
- }
+ public class UARTBinder extends LocalBinder implements UARTInterface {
+ @Override
+ public void send(final String text) {
+ mManager.send(text);
+ }
+ }
- @Override
- protected LocalBinder getBinder() {
- return mBinder;
- }
+ @Override
+ protected LocalBinder getBinder() {
+ return mBinder;
+ }
- @Override
- protected LoggableBleManager initializeManager() {
- return mManager = new UARTManager(this);
- }
+ @Override
+ protected LoggableBleManager initializeManager() {
+ return mManager = new UARTManager(this);
+ }
- @Override
- protected boolean shouldAutoConnect() {
- return true;
- }
+ @Override
+ protected boolean shouldAutoConnect() {
+ return true;
+ }
- @Override
- public void onCreate() {
- super.onCreate();
+ @Override
+ public void onCreate() {
+ super.onCreate();
- registerReceiver(mDisconnectActionBroadcastReceiver, new IntentFilter(ACTION_DISCONNECT));
- registerReceiver(mIntentBroadcastReceiver, new IntentFilter(ACTION_SEND));
+ registerReceiver(mDisconnectActionBroadcastReceiver, new IntentFilter(ACTION_DISCONNECT));
+ registerReceiver(mIntentBroadcastReceiver, new IntentFilter(ACTION_SEND));
- mGoogleApiClient = new GoogleApiClient.Builder(this)
- .addApi(Wearable.API)
- .build();
- mGoogleApiClient.connect();
- }
+ mGoogleApiClient = new GoogleApiClient.Builder(this)
+ .addApi(Wearable.API)
+ .build();
+ mGoogleApiClient.connect();
+ }
- @Override
- 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
- cancelNotification();
- unregisterReceiver(mDisconnectActionBroadcastReceiver);
- unregisterReceiver(mIntentBroadcastReceiver);
+ @Override
+ 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
+ stopForegroundService();
+ unregisterReceiver(mDisconnectActionBroadcastReceiver);
+ unregisterReceiver(mIntentBroadcastReceiver);
- mGoogleApiClient.disconnect();
+ mGoogleApiClient.disconnect();
- super.onDestroy();
- }
+ super.onDestroy();
+ }
- @Override
- protected void onRebind() {
- // when the activity rebinds to the service, remove the notification
- cancelNotification();
- }
+ @Override
+ protected void onRebind() {
+ stopForegroundService();
+ }
- @Override
- protected void onUnbind() {
- // when the activity closes we need to show the notification that user is connected to the sensor
- createNotification(R.string.uart_notification_connected_message, 0);
- }
+ @Override
+ protected void onUnbind() {
+ startForegroundService();
+ }
- @Override
- public void onDeviceConnected(@NonNull final BluetoothDevice device) {
- super.onDeviceConnected(device);
- sendMessageToWearables(Constants.UART.DEVICE_CONNECTED, notNull(getDeviceName()));
- }
+ @Override
+ public void onDeviceConnected(@NonNull final BluetoothDevice device) {
+ super.onDeviceConnected(device);
+ sendMessageToWearables(Constants.UART.DEVICE_CONNECTED, notNull(getDeviceName()));
+ }
- @Override
- protected boolean stopWhenDisconnected() {
- return false;
- }
+ @Override
+ protected boolean stopWhenDisconnected() {
+ return false;
+ }
- @Override
- public void onDeviceDisconnected(@NonNull final BluetoothDevice device) {
- super.onDeviceDisconnected(device);
- sendMessageToWearables(Constants.UART.DEVICE_DISCONNECTED, notNull(getDeviceName()));
- }
+ @Override
+ public void onDeviceDisconnected(@NonNull final BluetoothDevice device) {
+ super.onDeviceDisconnected(device);
+ sendMessageToWearables(Constants.UART.DEVICE_DISCONNECTED, notNull(getDeviceName()));
+ }
- @Override
- public void onLinkLossOccurred(@NonNull final BluetoothDevice device) {
- super.onLinkLossOccurred(device);
- sendMessageToWearables(Constants.UART.DEVICE_LINKLOSS, notNull(getDeviceName()));
- }
+ @Override
+ public void onLinkLossOccurred(@NonNull final BluetoothDevice device) {
+ super.onLinkLossOccurred(device);
+ sendMessageToWearables(Constants.UART.DEVICE_LINKLOSS, notNull(getDeviceName()));
+ }
- private String notNull(final String name) {
- if (!TextUtils.isEmpty(name))
- return name;
- return getString(R.string.not_available);
- }
+ private String notNull(final String name) {
+ if (!TextUtils.isEmpty(name))
+ return name;
+ return getString(R.string.not_available);
+ }
- @Override
- public void onDataReceived(final BluetoothDevice device, final String data) {
- final Intent broadcast = new Intent(BROADCAST_UART_RX);
- broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
- broadcast.putExtra(EXTRA_DATA, data);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
+ @Override
+ public void onDataReceived(final BluetoothDevice device, final String data) {
+ final Intent broadcast = new Intent(BROADCAST_UART_RX);
+ broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
+ broadcast.putExtra(EXTRA_DATA, data);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- // send the data received to other apps, e.g. the Tasker
- final Intent globalBroadcast = new Intent(ACTION_RECEIVE);
- globalBroadcast.putExtra(BluetoothDevice.EXTRA_DEVICE, getBluetoothDevice());
- globalBroadcast.putExtra(Intent.EXTRA_TEXT, data);
- sendBroadcast(globalBroadcast);
- }
+ // send the data received to other apps, e.g. the Tasker
+ final Intent globalBroadcast = new Intent(ACTION_RECEIVE);
+ globalBroadcast.putExtra(BluetoothDevice.EXTRA_DEVICE, getBluetoothDevice());
+ globalBroadcast.putExtra(Intent.EXTRA_TEXT, data);
+ sendBroadcast(globalBroadcast);
+ }
- @Override
- public void onDataSent(final BluetoothDevice device, final String data) {
- final Intent broadcast = new Intent(BROADCAST_UART_TX);
- broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
- broadcast.putExtra(EXTRA_DATA, data);
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
- }
+ @Override
+ public void onDataSent(final BluetoothDevice device, final String data) {
+ final Intent broadcast = new Intent(BROADCAST_UART_TX);
+ broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
+ broadcast.putExtra(EXTRA_DATA, data);
+ 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.
- * @param path message path
- * @param message the message
- */
- private void sendMessageToWearables(final @NonNull String path, final @NonNull String message) {
- if(mGoogleApiClient.isConnected()) {
- new Thread(() -> {
- NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await();
- for(Node node : nodes.getNodes()) {
- Logger.v(getLogSession(), "[WEAR] Sending message '" + path + "' to " + node.getDisplayName());
- final MessageApi.SendMessageResult result = Wearable.MessageApi.sendMessage(mGoogleApiClient, node.getId(), path, message.getBytes()).await();
- if(result.getStatus().isSuccess()){
- Logger.i(getLogSession(), "[WEAR] Message sent");
- } else {
- 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();
- }).start();
- } else {
- if (Constants.UART.DEVICE_DISCONNECTED.equals(path))
- stopService();
- }
- }
+ /**
+ * 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
+ */
+ private void sendMessageToWearables(final @NonNull String path, final @NonNull String message) {
+ if (mGoogleApiClient.isConnected()) {
+ new Thread(() -> {
+ NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await();
+ for (Node node : nodes.getNodes()) {
+ Logger.v(getLogSession(), "[WEAR] Sending message '" + path + "' to " + node.getDisplayName());
+ final MessageApi.SendMessageResult result = Wearable.MessageApi.sendMessage(mGoogleApiClient, node.getId(), path, message.getBytes()).await();
+ if (result.getStatus().isSuccess()) {
+ Logger.i(getLogSession(), "[WEAR] Message sent");
+ } else {
+ 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();
+ }).start();
+ } else {
+ if (Constants.UART.DEVICE_DISCONNECTED.equals(path))
+ stopService();
+ }
+ }
- /**
- * Creates the notification
- *
- * @param messageResId
- * message resource id. The message must have one String parameter,
- * f.e. <string name="name">%s is connected</string>
- * @param defaults
- * signals that will be used to notify the user
- */
- private void 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);
+ /**
+ * 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);
+ }
+ }
- final Intent disconnect = new Intent(ACTION_DISCONNECT);
- disconnect.putExtra(EXTRA_SOURCE, SOURCE_NOTIFICATION);
- 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);
- 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_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));
+ /**
+ * Creates the notification
+ *
+ * @param messageResId message resource id. The message must have one String parameter,
+ * f.e. <string name="name">%s is connected</string>
+ * @param defaults signals that will be used to notify the user
+ */
+ 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 NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- nm.notify(NOTIFICATION_ID, notification);
- }
+ final Intent disconnect = new Intent(ACTION_DISCONNECT);
+ disconnect.putExtra(EXTRA_SOURCE, SOURCE_NOTIFICATION);
+ final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ, disconnect, PendingIntent.FLAG_UPDATE_CURRENT);
- /**
- * 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);
- }
+ // 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);
+ 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_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));
- /**
- * 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();
- }
- };
+ return builder.build();
+ }
- /**
- * 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);
- }
+ /**
+ * 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);
+ }
- 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.");
- }
- };
+ /**
+ * 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.
+ * 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.");
+ }
+ };
}