Android Wear support added to UART profile

This commit is contained in:
Aleksander Nowakowski
2015-09-17 14:09:48 +02:00
parent a0837c8192
commit 981f8d487c
111 changed files with 4757 additions and 147 deletions

View File

@@ -32,9 +32,9 @@ import java.util.Queue;
import java.util.UUID;
import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.profile.BleManager;
import no.nordicsemi.android.nrftoolbox.parser.BloodPressureMeasurementParser;
import no.nordicsemi.android.nrftoolbox.parser.IntermediateCuffPressureParser;
import no.nordicsemi.android.nrftoolbox.profile.BleManager;
public class BPMManager extends BleManager<BPMManagerCallbacks> {
/** Blood Pressure service UUID */

View File

@@ -36,9 +36,9 @@ import android.widget.TextView;
import java.util.UUID;
import no.nordicsemi.android.nrftoolbox.R;
import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
import no.nordicsemi.android.nrftoolbox.csc.settings.SettingsActivity;
import no.nordicsemi.android.nrftoolbox.csc.settings.SettingsFragment;
import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
import no.nordicsemi.android.nrftoolbox.profile.BleProfileServiceReadyActivity;
public class CSCActivity extends BleProfileServiceReadyActivity<CSCService.CSCBinder> {

View File

@@ -32,8 +32,8 @@ import java.util.Queue;
import java.util.UUID;
import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.parser.CSCMeasurementParser;
import no.nordicsemi.android.nrftoolbox.profile.BleManager;
import no.nordicsemi.android.nrftoolbox.parser.CSCMeasurementParser;
public class CSCManager extends BleManager<CSCManagerCallbacks> {
/** Cycling Speed and Cadence service UUID */

View File

@@ -37,9 +37,9 @@ import android.support.v7.app.NotificationCompat;
import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
import no.nordicsemi.android.nrftoolbox.R;
import no.nordicsemi.android.nrftoolbox.csc.settings.SettingsFragment;
import no.nordicsemi.android.nrftoolbox.profile.BleManager;
import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
import no.nordicsemi.android.nrftoolbox.csc.settings.SettingsFragment;
public class CSCService extends BleProfileService implements CSCManagerCallbacks {
private static final String TAG = "CSCService";

View File

@@ -69,12 +69,16 @@ public class SettingsFragment extends PreferenceFragment implements DfuSettingsC
final boolean disabled = !preferences.getBoolean(SETTINGS_PACKET_RECEIPT_NOTIFICATION_ENABLED, true);
if (disabled) {
new AlertDialog.Builder(getActivity()).setMessage(R.string.dfu_settings_dfu_number_of_packets_info).setTitle(R.string.dfu_settings_dfu_information)
.setNeutralButton(R.string.ok, null).show();
.setPositiveButton(R.string.ok, null).show();
}
} else if (SETTINGS_NUMBER_OF_PACKETS.equals(key)) {
updateNumberOfPacketsSummary();
} else if (SETTINGS_MBR_SIZE.equals(key)) {
updateMBRSize();
} else if (SETTINGS_ASSUME_DFU_NODE.equals(key) && sharedPreferences.getBoolean(key, false)) {
new AlertDialog.Builder(getActivity()).setMessage(R.string.dfu_settings_dfu_assume_dfu_mode_info).setTitle(R.string.dfu_settings_dfu_information)
.setPositiveButton(R.string.ok, null)
.show();
}
}
@@ -93,7 +97,7 @@ public class SettingsFragment extends PreferenceFragment implements DfuSettingsC
final int valueInt = Integer.parseInt(value);
if (valueInt > 200) {
new AlertDialog.Builder(getActivity()).setMessage(R.string.dfu_settings_dfu_number_of_packets_info).setTitle(R.string.dfu_settings_dfu_information)
.setNeutralButton(R.string.ok, null)
.setPositiveButton(R.string.ok, null)
.show();
}
}

View File

@@ -34,10 +34,10 @@ import java.util.Queue;
import java.util.UUID;
import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.profile.BleManager;
import no.nordicsemi.android.nrftoolbox.parser.GlucoseMeasurementContextParser;
import no.nordicsemi.android.nrftoolbox.parser.GlucoseMeasurementParser;
import no.nordicsemi.android.nrftoolbox.parser.RecordAccessControlPointParser;
import no.nordicsemi.android.nrftoolbox.profile.BleManager;
import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
@SuppressWarnings("unused")

View File

@@ -32,9 +32,9 @@ import java.util.UUID;
import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.R;
import no.nordicsemi.android.nrftoolbox.profile.BleManager;
import no.nordicsemi.android.nrftoolbox.parser.BodySensorLocationParser;
import no.nordicsemi.android.nrftoolbox.parser.HeartRateMeasurementParser;
import no.nordicsemi.android.nrftoolbox.profile.BleManager;
/**
* HRSManager class performs BluetoothGatt operations for connection, service discovery, enabling notification and reading characteristics. All operations required to connect to device with BLE HR

View File

@@ -36,9 +36,9 @@ import java.text.DecimalFormat;
import java.util.UUID;
import no.nordicsemi.android.nrftoolbox.R;
import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
import no.nordicsemi.android.nrftoolbox.hts.settings.SettingsActivity;
import no.nordicsemi.android.nrftoolbox.hts.settings.SettingsFragment;
import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
import no.nordicsemi.android.nrftoolbox.profile.BleProfileServiceReadyActivity;
/**

View File

@@ -37,9 +37,9 @@ import android.os.Handler;
import java.util.Queue;
import java.util.UUID;
import no.nordicsemi.android.error.GattError;
import no.nordicsemi.android.log.ILogSession;
import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.error.GattError;
import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
import no.nordicsemi.android.nrftoolbox.utility.ParserUtils;
@@ -52,7 +52,7 @@ import no.nordicsemi.android.nrftoolbox.utility.ParserUtils;
* leaving this to the developers.</li>
* <li>The manager tries to read the Battery Level characteristic. No matter the result of this operation (for example the Battery Level characteristic may not have the READ property)
* it tries to enable Battery Level notifications, to get battery updates from the device.</li>
* <li>Afterwards, the manager initializes the device using given queue of commands. See {@link BleManagerGattCallback#initGatt(android.bluetooth.BluetoothGatt)} method for more details.</li>
* <li>Afterwards, the manager initializes the device using given queue of commands. See {@link BleManagerGattCallback#initGatt(BluetoothGatt)} method for more details.</li>
* <li>When initialization complete, the {@link BleManagerCallbacks#onDeviceReady()} callback is called.</li>
* </ol>The manager also is responsible for parsing the Battery Level values and calling {@link BleManagerCallbacks#onBatteryValueReceived(int)} method.</p>
* <p>Events from all profiles are being logged into the nRF Logger application,
@@ -830,7 +830,7 @@ public abstract class BleManager<E extends BleManagerCallbacks> {
final Queue<Request> requests = mInitQueue;
// Get the first request from the queue
final Request request = requests.poll();
final Request request = requests != null ? requests.poll() : null;
// Are we done?
if (request == null) {

View File

@@ -38,7 +38,7 @@ public interface BleManagerCallbacks {
public void onDeviceDisconnecting();
/**
* Called when the device has disconnected (when the callback returned {@link BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} with state DISCONNECTED.
* Called when the device has disconnected (when the callback returned {@link BluetoothGattCallback#onConnectionStateChange(BluetoothGatt, int, int)} with state DISCONNECTED.
*/
public void onDeviceDisconnected();

View File

@@ -35,7 +35,6 @@ import android.widget.Toast;
import no.nordicsemi.android.log.ILogSession;
import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.R;
public abstract class BleProfileService extends Service implements BleManagerCallbacks {
@SuppressWarnings("unused")
@@ -132,7 +131,7 @@ public abstract class BleProfileService extends Service implements BleManagerCal
*
* @return the log session
*/
protected ILogSession getLogSession() {
public ILogSession getLogSession() {
return mLogSession;
}
}
@@ -278,6 +277,15 @@ public abstract class BleProfileService extends Service implements BleManagerCal
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() {
mConnected = false;
@@ -288,6 +296,11 @@ public abstract class BleProfileService extends Service implements BleManagerCal
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();
@@ -335,7 +348,7 @@ public abstract class BleProfileService extends Service implements BleManagerCal
@Override
public void onBondingRequired() {
showToast(R.string.bonding);
showToast(no.nordicsemi.android.nrftoolbox.common.R.string.bonding);
final Intent broadcast = new Intent(BROADCAST_BOND_STATE);
broadcast.putExtra(EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDING);
@@ -344,7 +357,7 @@ public abstract class BleProfileService extends Service implements BleManagerCal
@Override
public void onBonded() {
showToast(R.string.bonded);
showToast(no.nordicsemi.android.nrftoolbox.common.R.string.bonded);
final Intent broadcast = new Intent(BROADCAST_BOND_STATE);
broadcast.putExtra(EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDED);

View File

@@ -40,8 +40,8 @@ import java.util.Queue;
import java.util.UUID;
import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.parser.AlertLevelParser;
import no.nordicsemi.android.nrftoolbox.profile.BleManager;
import no.nordicsemi.android.nrftoolbox.parser.AlertLevelParser;
import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
import no.nordicsemi.android.nrftoolbox.utility.ParserUtils;

View File

@@ -32,6 +32,7 @@ import android.media.AudioManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v7.app.NotificationCompat;
import no.nordicsemi.android.log.Logger;
@@ -232,13 +233,13 @@ public class ProximityService extends BleProfileService implements ProximityMana
builder.setContentIntent(pendingIntent);
builder.setContentTitle(getString(R.string.app_name)).setContentText(getString(messageResId, getDeviceName()));
builder.setSmallIcon(R.drawable.ic_stat_notify_proximity);
builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(true);
builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(defaults == 0); // an ongoing notification would not be shown on Android Wear
builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.proximity_notification_action_disconnect), disconnectAction));
if (isConnected())
builder.addAction(new NotificationCompat.Action(R.drawable.ic_stat_notify_proximity, getString(isImmediateAlertOn ? R.string.proximity_action_silentme : R.string.proximity_action_findme), secondAction));
final Notification notification = builder.build();
final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
final NotificationManagerCompat nm = NotificationManagerCompat.from(this);
nm.notify(NOTIFICATION_ID, notification);
}

View File

@@ -32,8 +32,8 @@ import java.util.Queue;
import java.util.UUID;
import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.parser.RSCMeasurementParser;
import no.nordicsemi.android.nrftoolbox.profile.BleManager;
import no.nordicsemi.android.nrftoolbox.parser.RSCMeasurementParser;
public class RSCManager extends BleManager<RSCManagerCallbacks> {
private static final byte INSTANTANEOUS_STRIDE_LENGTH_PRESENT = 0x01; // 1 bit

View File

@@ -31,8 +31,8 @@ import java.util.Queue;
import java.util.UUID;
import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.parser.TemplateParser;
import no.nordicsemi.android.nrftoolbox.profile.BleManager;
import no.nordicsemi.android.nrftoolbox.parser.TemplateParser;
/**
* Modify to template manager to match your requirements.

View File

@@ -37,6 +37,7 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.TransitionDrawable;
@@ -60,6 +61,8 @@ import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;
import com.google.android.gms.common.api.GoogleApiClient;
import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.core.Persister;
import org.simpleframework.xml.strategy.Strategy;
@@ -84,28 +87,35 @@ import java.io.StringWriter;
import java.util.UUID;
import no.nordicsemi.android.nrftoolbox.R;
import no.nordicsemi.android.nrftoolbox.dfu.adapter.FileBrowserAppsAdapter;
import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
import no.nordicsemi.android.nrftoolbox.dfu.adapter.FileBrowserAppsAdapter;
import no.nordicsemi.android.nrftoolbox.profile.BleProfileServiceReadyActivity;
import no.nordicsemi.android.nrftoolbox.uart.database.DatabaseHelper;
import no.nordicsemi.android.nrftoolbox.uart.domain.Command;
import no.nordicsemi.android.nrftoolbox.uart.domain.UartConfiguration;
import no.nordicsemi.android.nrftoolbox.uart.wearable.UARTConfigurationSynchronizer;
import no.nordicsemi.android.nrftoolbox.utility.FileHelper;
import no.nordicsemi.android.nrftoolbox.widget.ClosableSpinner;
public class UARTActivity extends BleProfileServiceReadyActivity<UARTService.UARTBinder> implements UARTInterface,
UARTNewConfigurationDialogFragment.NewConfigurationDialogListener, UARTConfigurationsAdapter.ActionListener, AdapterView.OnItemSelectedListener {
UARTNewConfigurationDialogFragment.NewConfigurationDialogListener, UARTConfigurationsAdapter.ActionListener, AdapterView.OnItemSelectedListener,
GoogleApiClient.ConnectionCallbacks {
private final static String TAG = "UARTActivity";
private final static String PREFS_BUTTON_ENABLED = "prefs_uart_enabled_";
private final static String PREFS_BUTTON_COMMAND = "prefs_uart_command_";
private final static String PREFS_BUTTON_ICON = "prefs_uart_icon_";
/** This preference keeps the ID of the selected configuration. */
private final static String PREFS_CONFIGURATION = "configuration_id";
/** This preference is set to true when initial data synchronization for wearables has been completed. */
private final static String PREFS_WEAR_SYNCED = "prefs_uart_synced";
private final static String SIS_EDIT_MODE = "sis_edit_mode";
private final static int SELECT_FILE_REQ = 2678; // random
private final static int PERMISSION_REQ = 24; // random, 8-bit
UARTConfigurationSynchronizer mWearableSynchronizer;
/** The current configuration. */
private UartConfiguration mConfiguration;
private DatabaseHelper mDatabaseHelper;
@@ -162,7 +172,57 @@ public class UARTActivity extends BleProfileServiceReadyActivity<UARTService.UAR
mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
mDatabaseHelper = new DatabaseHelper(this);
ensureFirstConfiguration(mDatabaseHelper);
mConfigurationsAdapter = new UARTConfigurationsAdapter(this, this, mDatabaseHelper.getServerConfigurationsNames());
mConfigurationsAdapter = new UARTConfigurationsAdapter(this, this, mDatabaseHelper.getConfigurationsNames());
// Initialize Wearable synchronizer
mWearableSynchronizer = UARTConfigurationSynchronizer.from(this, this);
}
/**
* Method called when Google API Client connects to Wearable.API.
*/
@Override
public void onConnected(final Bundle bundle) {
if (!mPreferences.getBoolean(PREFS_WEAR_SYNCED, false)) {
new Thread(new Runnable() {
@Override
public void run() {
final Cursor cursor = mDatabaseHelper.getConfigurations();
try {
while (cursor.moveToNext()) {
final long id = cursor.getLong(0 /* _ID */);
try {
final String xml = cursor.getString(2 /* XML */);
final Format format = new Format(new HyphenStyle());
final Serializer serializer = new Persister(format);
final UartConfiguration configuration = serializer.read(UartConfiguration.class, xml);
mWearableSynchronizer.onConfigurationAddedOrEdited(id, configuration).await();
} catch (final Exception e) {
Log.w(TAG, "Deserializing configuration with id " + id + " failed", e);
}
}
mPreferences.edit().putBoolean(PREFS_WEAR_SYNCED, true).apply();
} finally {
cursor.close();
}
}
}).start();
}
}
/**
* Method called then Google API client connection was suspended.
* @param cause the cause of suspension
*/
@Override
public void onConnectionSuspended(final int cause) {
// dp nothing
}
@Override
protected void onDestroy() {
super.onDestroy();
mWearableSynchronizer.close();
}
@Override
@@ -322,13 +382,18 @@ public class UARTActivity extends BleProfileServiceReadyActivity<UARTService.UAR
}
case R.id.action_remove: {
mDatabaseHelper.removeDeletedServerConfigurations(); // just to be sure nothing has left
mDatabaseHelper.deleteConfiguration(name);
final UartConfiguration removedConfiguration = mConfiguration;
final long id = mDatabaseHelper.deleteConfiguration(name);
if (id >= 0)
mWearableSynchronizer.onConfigurationDeleted(id);
refreshConfigurations();
final Snackbar snackbar = Snackbar.make(mSlider, R.string.uart_configuration_deleted, Snackbar.LENGTH_INDEFINITE).setAction(R.string.uart_action_undo, new View.OnClickListener() {
@Override
public void onClick(final View v) {
mDatabaseHelper.restoreDeletedServerConfigurations();
final long id = mDatabaseHelper.restoreDeletedServerConfiguration(name);
if (id >= 0)
mWearableSynchronizer.onConfigurationAddedOrEdited(id, removedConfiguration);
refreshConfigurations();
}
});
@@ -521,6 +586,7 @@ public class UARTActivity extends BleProfileServiceReadyActivity<UARTService.UAR
final String xml = writer.toString();
final long id = mDatabaseHelper.addConfiguration(name, xml);
mWearableSynchronizer.onConfigurationAddedOrEdited(id, configuration);
refreshConfigurations();
selectConfiguration(mConfigurationsAdapter.getItemPosition(id));
} catch (final Exception e) {
@@ -548,6 +614,7 @@ public class UARTActivity extends BleProfileServiceReadyActivity<UARTService.UAR
final String xml = writer.toString();
mDatabaseHelper.renameConfiguration(oldName, newName, xml);
mWearableSynchronizer.onConfigurationAddedOrEdited(mPreferences.getLong(PREFS_CONFIGURATION, 0), mConfiguration);
refreshConfigurations();
} catch (final Exception e) {
Log.e(TAG, "Error while renaming configuration", e);
@@ -555,7 +622,7 @@ public class UARTActivity extends BleProfileServiceReadyActivity<UARTService.UAR
}
private void refreshConfigurations() {
mConfigurationsAdapter.swapCursor(mDatabaseHelper.getServerConfigurationsNames());
mConfigurationsAdapter.swapCursor(mDatabaseHelper.getConfigurationsNames());
mConfigurationsAdapter.notifyDataSetChanged();
invalidateOptionsMenu();
}
@@ -634,6 +701,7 @@ public class UARTActivity extends BleProfileServiceReadyActivity<UARTService.UAR
final String xml = writer.toString();
mDatabaseHelper.updateConfiguration(configuration.getName(), xml);
mWearableSynchronizer.onConfigurationAddedOrEdited(mPreferences.getLong(PREFS_CONFIGURATION, 0), configuration);
} catch (final Exception e) {
Log.e(TAG, "Error while creating a new configuration", e);
}
@@ -659,6 +727,7 @@ public class UARTActivity extends BleProfileServiceReadyActivity<UARTService.UAR
final String name = configuration.getName();
if (!mDatabaseHelper.configurationExists(name)) {
final long id = mDatabaseHelper.addConfiguration(name, xml);
mWearableSynchronizer.onConfigurationAddedOrEdited(id, configuration);
refreshConfigurations();
new Handler().post(new Runnable() {
@Override

View File

@@ -29,8 +29,17 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.annotation.NonNull;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.NotificationCompat;
import android.text.TextUtils;
import android.util.Log;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;
import no.nordicsemi.android.log.ILogSession;
import no.nordicsemi.android.log.Logger;
@@ -38,23 +47,32 @@ import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
import no.nordicsemi.android.nrftoolbox.R;
import no.nordicsemi.android.nrftoolbox.profile.BleManager;
import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
import no.nordicsemi.android.nrftoolbox.wearable.common.Constants;
public class UARTService extends BleProfileService implements UARTManagerCallbacks {
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";
/** A broadcast message with this action and the message in {@link Intent#EXTRA_TEXT} will be sent t the UART device. */
private final static String ACTION_SEND = "no.nordicsemi.android.nrftoolbox.uart.ACTION_SEND";
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. */
private final static String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.uart.ACTION_DISCONNECT";
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 GoogleApiClient mGoogleApiClient;
private UARTManager mManager;
private final LocalBinder mBinder = new UARTBinder();
@@ -87,6 +105,11 @@ public class UARTService extends BleProfileService implements UARTManagerCallbac
registerReceiver(mDisconnectActionBroadcastReceiver, new IntentFilter(ACTION_DISCONNECT));
registerReceiver(mIntentBroadcastReceiver, new IntentFilter(ACTION_SEND));
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.build();
mGoogleApiClient.connect();
}
@Override
@@ -96,6 +119,8 @@ public class UARTService extends BleProfileService implements UARTManagerCallbac
unregisterReceiver(mDisconnectActionBroadcastReceiver);
unregisterReceiver(mIntentBroadcastReceiver);
mGoogleApiClient.disconnect();
super.onDestroy();
}
@@ -117,6 +142,35 @@ public class UARTService extends BleProfileService implements UARTManagerCallbac
mManager.setLogger(getLogSession());
}
@Override
public void onDeviceConnected() {
super.onDeviceConnected();
sendMessageToWearables(Constants.UART.DEVICE_CONNECTED, notNull(getDeviceName()));
}
@Override
protected boolean stopWhenDisconnected() {
return false;
}
@Override
public void onDeviceDisconnected() {
super.onDeviceDisconnected();
sendMessageToWearables(Constants.UART.DEVICE_DISCONNECTED, notNull(getDeviceName()));
}
@Override
public void onLinklossOccur() {
super.onLinklossOccur();
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);
}
@Override
public void onDataReceived(final String data) {
Logger.a(getLogSession(), "\"" + data + "\" received");
@@ -140,6 +194,37 @@ public class UARTService extends BleProfileService implements UARTManagerCallbac
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(new Runnable() {
@Override
public void run() {
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
*
@@ -155,6 +240,7 @@ public class UARTService extends BleProfileService implements UARTManagerCallbac
final Intent targetIntent = new Intent(this, UARTActivity.class);
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);
// both activities above have launchMode="singleTask" in the AndroidManifest.xml file, so if the task is already running, it will be resumed
@@ -185,7 +271,15 @@ public class UARTService extends BleProfileService implements UARTManagerCallbac
private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
Logger.i(getLogSession(), "[Notification] Disconnect action pressed");
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
@@ -210,7 +304,16 @@ public class UARTService extends BleProfileService implements UARTManagerCallbac
}
if (message != null) {
Logger.i(getLogSession(), "[Broadcast] " + ACTION_SEND + " broadcast received with data: \"" + message + "\"");
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;
}
@@ -222,5 +325,4 @@ public class UARTService extends BleProfileService implements UARTManagerCallbac
Logger.i(getLogSession(), "[Broadcast] " + ACTION_SEND + " broadcast received incompatible data type. Only String and int are supported.");
}
};
}

View File

@@ -42,6 +42,7 @@ public class DatabaseHelper {
private static final String[] ID_PROJECTION = new String[] { BaseColumns._ID };
private static final String[] NAME_PROJECTION = new String[] { BaseColumns._ID, NameColumns.NAME };
private static final String[] XML_PROJECTION = new String[] { BaseColumns._ID, ConfigurationContract.Configuration.XML };
private static final String[] CONFIGURATION_PROJECTION = new String[] { BaseColumns._ID, NameColumns.NAME, ConfigurationContract.Configuration.XML };
private static final String ID_SELECTION = BaseColumns._ID + "=?";
private static final String NAME_SELECTION = NameColumns.NAME + "=?";
@@ -72,11 +73,19 @@ public class DatabaseHelper {
}
}
/**
* Returns the list of all saved configurations.
* @return cursor
*/
public Cursor getConfigurations() {
return mDatabase.query(Tables.CONFIGURATIONS, CONFIGURATION_PROJECTION, NOT_DELETED_SELECTION, null, null, null, ConfigurationContract.Configuration.NAME + " ASC");
}
/**
* Returns the list of names of all saved configurations.
* @return cursor
*/
public Cursor getServerConfigurationsNames() {
public Cursor getConfigurationsNames() {
return mDatabase.query(Tables.CONFIGURATIONS, NAME_PROJECTION, NOT_DELETED_SELECTION, null, null, null, ConfigurationContract.Configuration.NAME + " ASC");
}
@@ -132,26 +141,50 @@ public class DatabaseHelper {
/**
* Marks the configuration with given name as deleted. If may be restored or removed permanently afterwards.
* @param name the configuration name
* @return number of rows affected
* @return id of the deleted configuration
*/
public int deleteConfiguration(final String name) {
public long deleteConfiguration(final String name) {
mSingleArg[0] = name;
final ContentValues values = mValues;
values.clear();
values.put(ConfigurationContract.Configuration.DELETED, 1);
return mDatabase.update(Tables.CONFIGURATIONS, values, NAME_SELECTION, mSingleArg);
mDatabase.update(Tables.CONFIGURATIONS, values, NAME_SELECTION, mSingleArg);
final Cursor cursor = mDatabase.query(Tables.CONFIGURATIONS, ID_PROJECTION, NAME_SELECTION, mSingleArg, null, null, null);
try {
if (cursor.moveToNext())
return cursor.getLong(0 /* _ID */);
return -1;
} finally {
cursor.close();
}
}
public int removeDeletedServerConfigurations() {
return mDatabase.delete(Tables.CONFIGURATIONS, DELETED_SELECTION, null);
}
public int restoreDeletedServerConfigurations() {
/**
* Restores deleted configuration. Returns the ID of the first one.
* @return the DI of the restored configuration.
*/
public long restoreDeletedServerConfiguration(final String name) {
mSingleArg[0] = name;
final ContentValues values = mValues;
values.clear();
values.put(ConfigurationContract.Configuration.DELETED, 0);
return mDatabase.update(Tables.CONFIGURATIONS, values, null, null);
mDatabase.update(Tables.CONFIGURATIONS, values, NAME_SELECTION, mSingleArg);
final Cursor cursor = mDatabase.query(Tables.CONFIGURATIONS, ID_PROJECTION, NAME_SELECTION, mSingleArg, null, null, null);
try {
if (cursor.moveToNext())
return cursor.getLong(0 /* _ID */);
return -1;
} finally {
cursor.close();
}
}
/**

View File

@@ -0,0 +1,129 @@
/*
* Copyright (c) 2015, Nordic Semiconductor
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package no.nordicsemi.android.nrftoolbox.uart.wearable;
import android.content.Context;
import android.net.Uri;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.wearable.DataApi;
import com.google.android.gms.wearable.DataMap;
import com.google.android.gms.wearable.PutDataMapRequest;
import com.google.android.gms.wearable.PutDataRequest;
import com.google.android.gms.wearable.Wearable;
import java.util.ArrayList;
import no.nordicsemi.android.nrftoolbox.wearable.common.Constants;
import no.nordicsemi.android.nrftoolbox.uart.domain.Command;
import no.nordicsemi.android.nrftoolbox.uart.domain.UartConfiguration;
public class UARTConfigurationSynchronizer {
private static final String WEAR_URI_PREFIX = "wear:"; // no / at the end as the path already has it
private static UARTConfigurationSynchronizer mInstance;
private GoogleApiClient mGoogleApiClient;
/**
* Initializes the synchronizer.
* @param context the activity context
* @param listener the connection callbacks listener
*/
public static UARTConfigurationSynchronizer from(final Context context, final GoogleApiClient.ConnectionCallbacks listener) {
if (mInstance == null)
mInstance = new UARTConfigurationSynchronizer();
mInstance.init(context, listener);
return mInstance;
}
private UARTConfigurationSynchronizer() {
// private constructor
}
private void init(final Context context, final GoogleApiClient.ConnectionCallbacks listener) {
if (mGoogleApiClient != null)
return;
mGoogleApiClient = new GoogleApiClient.Builder(context)
.addApi(Wearable.API)
.addConnectionCallbacks(listener)
.build();
mGoogleApiClient.connect();
}
/**
* Closes the synchronizer.
*/
public void close() {
mGoogleApiClient.disconnect();
mGoogleApiClient = null;
}
/**
* Synchronizes the UART configurations between handheld and wearables.
* Call this when configuration has been created or altered.
* @return pending result
*/
public PendingResult<DataApi.DataItemResult> onConfigurationAddedOrEdited(final long id, final UartConfiguration configuration) {
if (mGoogleApiClient == null || !mGoogleApiClient.isConnected())
return null;
final PutDataMapRequest mapRequest = PutDataMapRequest.create(Constants.UART.CONFIGURATIONS + "/" + id);
final DataMap map = mapRequest.getDataMap();
map.putString(Constants.UART.Configuration.NAME, configuration.getName());
final ArrayList<DataMap> commands = new ArrayList<>(UartConfiguration.COMMANDS_COUNT);
for (Command command : configuration.getCommands()) {
if (command != null && command.isActive()) {
final DataMap item = new DataMap();
item.putInt(Constants.UART.Configuration.Command.ICON_ID, command.getIconIndex());
item.putString(Constants.UART.Configuration.Command.MESSAGE, command.getCommand());
commands.add(item);
}
}
map.putDataMapArrayList(Constants.UART.Configuration.COMMANDS, commands);
final PutDataRequest request = mapRequest.asPutDataRequest();
return Wearable.DataApi.putDataItem(mGoogleApiClient, request);
}
/**
* Synchronizes the UART configurations between handheld and wearables.
* Call this when configuration has been deleted.
* @return pending result
*/
public PendingResult<DataApi.DeleteDataItemsResult> onConfigurationDeleted(final long id) {
if (mGoogleApiClient == null || !mGoogleApiClient.isConnected())
return null;
return Wearable.DataApi.deleteDataItems(mGoogleApiClient, id2Uri(id));
}
/**
* Creates URI without nodeId.
* @param id the configuration id in the database
* @return Uri that may be used to delete the associated DataMap.
*/
private Uri id2Uri(final long id) {
return Uri.parse(WEAR_URI_PREFIX + Constants.UART.CONFIGURATIONS + "/" + id);
}
}

View File

@@ -1,72 +0,0 @@
/*
* Copyright (c) 2015, Nordic Semiconductor
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package no.nordicsemi.android.nrftoolbox.utility;
import android.util.Log;
import no.nordicsemi.android.nrftoolbox.BuildConfig;
public class DebugLogger {
public static void v(final String tag, final String text) {
if (BuildConfig.DEBUG)
Log.v(tag, text);
}
public static void d(final String tag, final String text) {
if (BuildConfig.DEBUG) {
Log.d(tag, text);
}
}
public static void i(final String tag, final String text) {
if (BuildConfig.DEBUG)
Log.i(tag, text);
}
public static void w(final String tag, final String text) {
if (BuildConfig.DEBUG) {
Log.w(tag, text);
}
}
public static void e(final String tag, final String text) {
if (BuildConfig.DEBUG)
Log.e(tag, text);
}
public static void e(final String tag, final String text, final Throwable e) {
if (BuildConfig.DEBUG)
Log.e(tag, text, e);
}
public static void wtf(final String tag, final String text) {
if (BuildConfig.DEBUG) {
Log.wtf(tag, text);
}
}
public static void wtf(final String tag, final String text, final Throwable e) {
if (BuildConfig.DEBUG) {
Log.wtf(tag, text, e);
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (c) 2015, Nordic Semiconductor
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package no.nordicsemi.android.nrftoolbox.wearable;
import android.content.Intent;
import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.WearableListenerService;
import no.nordicsemi.android.nrftoolbox.wearable.common.Constants;
import no.nordicsemi.android.nrftoolbox.uart.UARTService;
/**
* The main listener for messages from Wearable devices. There may be only one such service per application so it has to handle messages from all profiles.
*/
public class MainWearableListenerService extends WearableListenerService {
@Override
public void onMessageReceived(final MessageEvent messageEvent) {
switch (messageEvent.getPath()) {
case Constants.ACTION_DISCONNECT: {
// A disconnect message was sent. The information which profile should be disconnected is in the data.
final String profile = new String(messageEvent.getData());
switch (profile) {
// Currently only UART profile has Wear support
case Constants.UART.PROFILE: {
final Intent disconnectIntent = new Intent(UARTService.ACTION_DISCONNECT);
disconnectIntent.putExtra(UARTService.EXTRA_SOURCE, UARTService.SOURCE_WEARABLE);
sendBroadcast(disconnectIntent);
break;
}
}
break;
}
case Constants.UART.COMMAND: {
final String command = new String(messageEvent.getData());
final Intent intent = new Intent(UARTService.ACTION_SEND);
intent.putExtra(UARTService.EXTRA_SOURCE, UARTService.SOURCE_WEARABLE);
intent.putExtra(Intent.EXTRA_TEXT, command);
sendBroadcast(intent);
}
default:
super.onMessageReceived(messageEvent);
break;
}
}
}