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

2
.idea/gradle.xml generated
View File

@@ -11,6 +11,8 @@
<option value="$PROJECT_DIR$/../DFULibrary/dfu" />
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/common" />
<option value="$PROJECT_DIR$/wear" />
</set>
</option>
</GradleProjectSettings>

5
.idea/modules.xml generated
View File

@@ -3,9 +3,10 @@
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
<module fileurl="file://$PROJECT_DIR$/common/common.iml" filepath="$PROJECT_DIR$/common/common.iml" />
<module fileurl="file://$PROJECT_DIR$/../DFULibrary/dfu/dfu.iml" filepath="$PROJECT_DIR$/../DFULibrary/dfu/dfu.iml" />
<module fileurl="file://$PROJECT_DIR$/nRFToolbox.iml" filepath="$PROJECT_DIR$/nRFToolbox.iml" />
<module fileurl="file://$PROJECT_DIR$/wear/wear.iml" filepath="$PROJECT_DIR$/wear/wear.iml" />
</modules>
</component>
</project>
</project>

View File

@@ -71,9 +71,12 @@
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.0.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/design/23.0.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.0.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/design/23.0.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.google.android.gms/play-services-base/7.8.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.google.android.gms/play-services-wearable/7.8.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/no.nordicsemi.android.support.v18/scanner/0.1.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/no.nordicsemi.android/log/2.0.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
@@ -92,15 +95,18 @@
<orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="gson-2.3.1" level="project" />
<orderEntry type="library" exported="" name="design-23.0.0" level="project" />
<orderEntry type="library" exported="" name="nrf-logger-v2.0" level="project" />
<orderEntry type="library" exported="" name="design-23.0.1" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-23.0.1" level="project" />
<orderEntry type="library" exported="" name="stax-1.2.0" level="project" />
<orderEntry type="library" exported="" name="play-services-wearable-7.8.0" level="project" />
<orderEntry type="library" exported="" name="scanner-0.1.1" level="project" />
<orderEntry type="library" exported="" name="achartengine-1.1.0" level="project" />
<orderEntry type="library" exported="" name="simple-xml-2.7.1" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-23.0.0" level="project" />
<orderEntry type="library" exported="" name="support-v4-23.0.0" level="project" />
<orderEntry type="library" exported="" name="support-annotations-23.0.0" level="project" />
<orderEntry type="library" exported="" name="log-2.0.0" level="project" />
<orderEntry type="library" exported="" name="play-services-base-7.8.0" level="project" />
<orderEntry type="library" exported="" name="support-v4-23.0.1" level="project" />
<orderEntry type="library" exported="" name="support-annotations-23.0.1" level="project" />
<orderEntry type="module" module-name="common" exported="" />
<orderEntry type="module" module-name="dfu" exported="" />
</component>
</module>

View File

@@ -2,13 +2,14 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion '23.0.0'
buildToolsVersion '23.0.1'
defaultConfig {
applicationId "no.nordicsemi.android.nrftoolbox"
minSdkVersion 18
targetSdkVersion 23
versionCode 37
versionName "1.15.0"
versionCode 39
versionName "1.16.0"
}
buildTypes {
release {
@@ -20,14 +21,17 @@ android {
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.0.0'
compile 'com.android.support:design:23.0.0'
compile 'com.google.android.gms:play-services-wearable:7.8.0'
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.android.support:design:23.0.1'
compile 'no.nordicsemi.android.support.v18:scanner:0.1.1'
compile 'no.nordicsemi.android:log:2.0.0'
compile('org.simpleframework:simple-xml:2.7.1') {
exclude group: 'stax', module: 'stax-api'
exclude group: 'xpp3', module: 'xpp3'
}
compile project(':dfu')
compile files('libs/achartengine-1.1.0.jar')
compile files('libs/nrf-logger-v2.0.jar')
compile project(':dfu')
compile project(':common')
wearApp project(':wear')
}

Binary file not shown.

View File

@@ -37,9 +37,14 @@
<application
android:fullBackupContent="true"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<activity
android:name="no.nordicsemi.android.nrftoolbox.SplashscreenActivity"
android:label="@string/app_name"
@@ -226,6 +231,11 @@
<service
android:name="no.nordicsemi.android.nrftoolbox.uart.UARTService"
android:label="@string/uart_feature_title" />
<service android:name=".wearable.MainWearableListenerService">
<intent-filter>
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
</intent-filter>
</service>
<provider android:name="no.nordicsemi.android.nrftoolbox.uart.UARTLocalLogContentProvider"
android:authorities="no.nordicsemi.android.nrftoolbox.uart.log"

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

@@ -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;
}
}
}

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -59,10 +59,7 @@
<string name="scanner_subtitle_bonded">BONDED DEVICES:</string>
<string name="scanner_subtitle_not_bonded">AVAILABLE DEVICES:</string>
<string name="scanner_permission_rationale">Since Android 6.0 Marshmallow system requires granting access to device\'s location in order to scan for Bluetooth Smart devices. Bluetooth beacons may be used to determine the phone\'s and user\'s location.</string>
<string name="bonding">Bonding with the device&#8230;</string>
<string name="bonded">The device is now bonded.</string>
<string name="log" formatted="false">%1$tR:%1$tS.%1$tL</string>
<string name="permission_title">Permission required</string>

View File

@@ -21,103 +21,106 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
<resources>
<string name="dfu_feature_title">DFU</string>
<string name="dfu_service_title">nRF Toolbox DFU Proxy</string>
<string name="dfu_settings_title">DFU Settings</string>
<string name="dfu_feature_title_long">DEVICE FIRMWARE UPDATE</string>
<dimen name="dfu_feature_title_long_margin">-186dp</dimen>
<string name="dfu_example_files_created">Example HEX files were copied to /sdcard/Nordic Semiconductor.</string>
<string name="dfu_example_new_files_created">New example HEX files were copied to /sdcard/Nordic Semiconductor.</string>
<string name="dfu_scripts_created">DFU script files were copied to /sdcard/Nordic Semiconductor.</string>
<string name="dfu_section_application_header">APPLICATION</string>
<string name="dfu_file_name_label">File Name:</string>
<string name="dfu_file_type_label">File Type:</string>
<string name="dfu_file_size_label">File Size:</string>
<string name="dfu_file_status_label">Status:</string>
<string name="dfu_file_size_text">%d bytes</string>
<string name="dfu_file_status_ok">OK</string>
<string name="dfu_file_status_ok_with_init">OK (Init file selected)</string>
<string name="dfu_file_status_no_file">File not loaded</string>
<string name="dfu_file_status_invalid">Invalid file</string>
<string name="dfu_file_status_error">Reading file failed</string>
<string name="dfu_file_status_invalid_message">Please, select valid HEX file</string>
<string name="dfu_file_info">Info</string>
<string name="dfu_action_select_file">SELECT FILE</string>
<string name="dfu_alert_no_filebrowser_title">File Browser not found</string>
<string name="dfu_alert_no_filebrowser_message">File browser application has not been found on your device. Would you like to download one?</string>
<string-array name="dfu_app_file_browser">
<item>Drive</item>
<item>File Manager</item>
<item>Total Commander</item>
<item>Search for others</item>
</string-array>
<string-array name="dfu_app_file_browser_action">
<item>market://details?id=com.google.android.apps.docs</item>
<item>market://details?id=com.rhmsoft.fm</item>
<item>market://details?id=com.ghisler.android.TotalCommander</item>
<item>market://search?q=file manager</item>
</string-array>
<string name="dfu_section_dfu_header">DEVICE FIRMWARE UPDATE</string>
<string name="dfu_action_upload">UPLOAD</string>
<string name="dfu_action_upload_cancel">CANCEL</string>
<string name="dfu_settings_dfu">DFU options</string>
<string name="dfu_settings_dfu_packets_receipt_notification_label">Packets receipt notification procedure</string>
<string name="dfu_settings_dfu_number_of_packets_label">Number of packets</string>
<string name="dfu_settings_dfu_mbr_size">MBR size</string>
<string name="dfu_settings_dfu_mbr_size_title">MBR size (4096 on nRF51, 12288 on nRF52)</string>
<string name="dfu_feature_title">DFU</string>
<string name="dfu_service_title">nRF Toolbox DFU Proxy</string>
<string name="dfu_settings_title">DFU Settings</string>
<string name="dfu_feature_title_long">DEVICE FIRMWARE UPDATE</string>
<dimen name="dfu_feature_title_long_margin">-186dp</dimen>
<string name="dfu_example_files_created">Example HEX files were copied to /sdcard/Nordic Semiconductor.</string>
<string name="dfu_example_new_files_created">New example HEX files were copied to /sdcard/Nordic Semiconductor.</string>
<string name="dfu_scripts_created">DFU script files were copied to /sdcard/Nordic Semiconductor.</string>
<string name="dfu_section_application_header">APPLICATION</string>
<string name="dfu_file_name_label">File Name:</string>
<string name="dfu_file_type_label">File Type:</string>
<string name="dfu_file_size_label">File Size:</string>
<string name="dfu_file_status_label">Status:</string>
<string name="dfu_file_size_text">%d bytes</string>
<string name="dfu_file_status_ok">OK</string>
<string name="dfu_file_status_ok_with_init">OK (Init file selected)</string>
<string name="dfu_file_status_no_file">File not loaded</string>
<string name="dfu_file_status_invalid">Invalid file</string>
<string name="dfu_file_status_error">Reading file failed</string>
<string name="dfu_file_status_invalid_message">Please, select valid HEX file</string>
<string name="dfu_file_info">Info</string>
<string name="dfu_action_select_file">SELECT FILE</string>
<string name="dfu_alert_no_filebrowser_title">File Browser not found</string>
<string name="dfu_alert_no_filebrowser_message">File browser application has not been found on your device. Would you like to download one?</string>
<string-array name="dfu_app_file_browser">
<item>Drive</item>
<item>File Manager</item>
<item>Total Commander</item>
<item>Search for others</item>
</string-array>
<string-array name="dfu_app_file_browser_action">
<item>market://details?id=com.google.android.apps.docs</item>
<item>market://details?id=com.rhmsoft.fm</item>
<item>market://details?id=com.ghisler.android.TotalCommander</item>
<item>market://search?q=file manager</item>
</string-array>
<string name="dfu_section_dfu_header">DEVICE FIRMWARE UPDATE</string>
<string name="dfu_action_upload">UPLOAD</string>
<string name="dfu_action_upload_cancel">CANCEL</string>
<string name="dfu_settings_dfu">DFU options</string>
<string name="dfu_settings_dfu_packets_receipt_notification_label">Packets receipt notification procedure</string>
<string name="dfu_settings_dfu_number_of_packets_label">Number of packets</string>
<string name="dfu_settings_dfu_mbr_size">MBR size</string>
<string name="dfu_settings_dfu_mbr_size_title">MBR size (4096 on nRF51, 12288 on nRF52)</string>
<string name="dfu_settings_dfu_keep_bond">Keep bond information</string>
<string name="dfu_settings_dfu_about">About DFU</string>
<string name="dfu_settings_dfu_about_summary">DFU documentation on Nordic\'s Developer Zone</string>
<string name="dfu_settings_dfu_information">Information</string>
<string name="dfu_settings_dfu_number_of_packets_info">During a DFU operation a lot of data packets are being sent to the target. The <i>onCharacteristicWrite(...)</i>
<string name="dfu_settings_dfu_assume_dfu_mode">External MCU DFU</string>
<string name="dfu_settings_dfu_assume_dfu_mode_info">Check this switch to ON if you want to perform a DFU operation without jumping to the bootloader mode on a device
with a DFU implementation from SDK 6.1 or older. To achieve the same result on any newer implementation the DFU Version characteristic should return value other than 0x0100.</string>
<string name="dfu_settings_dfu_about">About DFU</string>
<string name="dfu_settings_dfu_about_summary">DFU documentation on Nordic\'s Developer Zone</string>
<string name="dfu_settings_dfu_information">Information</string>
<string name="dfu_settings_dfu_number_of_packets_info">During a DFU operation a lot of data packets are being sent to the target. The <i>onCharacteristicWrite(...)</i>
callback in Android API is invoked when the data has been written to the outgoing queue, not when physically sent. Packet receipt notifications were introduced to
prevent from overflowing the queue. Depending on the device model, disabling the notifications or setting the value to “high” (> ~300) may make the DFU process freeze
at some point.</string>
<string name="dfu_file_type_title">Select file type</string>
<string-array name="dfu_file_type">
<string name="dfu_file_type_title">Select file type</string>
<string-array name="dfu_file_type">
<item>Distribution packet (ZIP)</item>
<item>Soft Device</item>
<item>Bootloader</item>
<item>Application</item>
</string-array>
<string name="dfu_file_init_title">Init packet</string>
<string name="dfu_file_init_message">Do you want to select the Init packet file?\n
</string-array>
<string name="dfu_file_init_title">Init packet</string>
<string name="dfu_file_init_message">Do you want to select the Init packet file?\n
The Init packet file (*.dat) should contain the device type and revision, application version, list of supported Soft Devices and the firmware CRC
in binary format or, with old versions of the DFU bootloader, only the CRC (CRC-CCITT-16).
With the new version of the bootloader the extended Init packet is <b>required</b>.</string>
<string name="dfu_unknown_name">unnamed device</string>
<string name="dfu_uploading_percentage_label">0%</string>
<string name="dfu_uploading_percentage">%d%%</string>
<string name="dfu_default_name">DEFAULT DFU</string>
<string name="dfu_confirmation_dialog_title">Application Uploading</string>
<string name="dfu_upload_dialog_cancel_message">Are you sure to cancel upload?</string>
<string name="dfu_success">Application has been transferred successfully.</string>
<string name="dfu_aborted">Uploading of the application has been canceled.</string>
<string name="dfu_help_title">Select file</string>
<string name="dfu_help_message">A file browser application must be installed on the device before selecting the file.
\n\nThere are number of applications available on Google Play store, e.g. Total Commander or File Manager, that allow you to pick a file from internal memory of the device. To upload
a file from the Internet you may use f.e. Drive or Dropbox application.
\n\nYou will be asked to select an application if more than one is installed. A single application will be launched automatically.
\n\nSince <b>Android KitKat</b> you may use the preinstalled document picker application. Ensure <i>Display advanced devices</i> option is enabled in settings to use the <i>Internal storage</i>.
\n\nSample applications were copied to <i>Nordic Semiconductor</i> folder in the internal storage.</string>
<string name="dfu_zip_info_text">Starting from nRF Toolbox v1.12 the new Distribution packet (ZIP) is the recommended method for distributing firmware upgrades.
<string name="dfu_unknown_name">unnamed device</string>
<string name="dfu_uploading_percentage_label">0%</string>
<string name="dfu_uploading_percentage">%d%%</string>
<string name="dfu_default_name">DEFAULT DFU</string>
<string name="dfu_confirmation_dialog_title">Application Uploading</string>
<string name="dfu_upload_dialog_cancel_message">Are you sure to cancel upload?</string>
<string name="dfu_success">Application has been transferred successfully.</string>
<string name="dfu_aborted">Uploading of the application has been canceled.</string>
<string name="dfu_help_title">Select file</string>
<string name="dfu_help_message">A file browser application must be installed on the device before selecting the file.
\n\nThere are number of applications available on Google Play store, e.g. Total Commander or File Manager, that allow you to pick a file from internal memory of the device. To upload
a file from the Internet you may use f.e. Drive or Dropbox application.
\n\nYou will be asked to select an application if more than one is installed. A single application will be launched automatically.
\n\nSince <b>Android KitKat</b> you may use the preinstalled document picker application. Ensure <i>Display advanced devices</i> option is enabled in settings to use the <i>Internal storage</i>.
\n\nSample applications were copied to <i>Nordic Semiconductor</i> folder in the internal storage.</string>
<string name="dfu_zip_info_text">Starting from nRF Toolbox v1.12 the new Distribution packet (ZIP) is the recommended method for distributing firmware upgrades.
You can create the ZIP file using the <b>nrf utility</b> tool, which is part of Master Control Panel 3.8.0+. For more detailed information, see the DFU documentation.
\n\n<b>Backward compatibility</b>
\nThe nRF Toolbox also supports all old file formats: HEX and BIN files, separate DAT files and ZIP files without a manifest file but with a fixed naming convention:
</string>
<string name="dfu_about_text">The Device Firmware Update (DFU) app allows you to update the firmware of your Bluetooth Smart device over-the-air (OTA).
<string name="dfu_about_text">The Device Firmware Update (DFU) app allows you to update the firmware of your Bluetooth Smart device over-the-air (OTA).
It is compatible with Nordic Semiconductor nRF51822 or nRF51422 devices with S110 SoftDevice and DFU bootloader enabled. With SoftDevice s110 7.0.0+,
the SoftDevice itself and/or a bootloader may also be updated.
\n\nFor more information about the DFU, see the About DFU section in Settings.</string>
\n\nFor more information about the DFU, see the About DFU section in Settings.</string>
</resources>

View File

@@ -43,7 +43,7 @@
<string name="uart_empty">No data to display.</string>
<string name="uart_rename_configuration_title">Rename configuration</string>
<string name="uart_new_configuration_title">New GATT configuration</string>
<string name="uart_new_configuration_title">New configuration</string>
<string name="uart_new_configuration_hint">Configuration name</string>
<string name="uart_new_configuration_text">Please, provide a unique configuration name:</string>
<string name="uart_empty_name_error">Name must not be empty.</string>

View File

@@ -120,6 +120,7 @@
<item name="colorPrimaryDark">@color/actionBarColorDark</item>
<item name="colorAccent">@color/actionBarColorDark</item>
<item name="buttonStyle">@style/Widget.Button</item>
<item name="android:spinnerStyle">@style/Widget.AppCompat.Spinner</item> <!-- Required on pre-Lollipop to draw new spinner. -->
</style>
<style name="AppTheme" parent="@style/AppThemeBase">

View File

@@ -45,6 +45,10 @@
android:defaultValue="false"
android:key="settings_keep_bond"
android:title="@string/dfu_settings_dfu_keep_bond" />
<SwitchPreference
android:defaultValue="false"
android:key="settings_assume_dfu_mode"
android:title="@string/dfu_settings_dfu_assume_dfu_mode" />
<no.nordicsemi.android.nrftoolbox.dfu.settings.AboutDfuPreference
android:summary="@string/dfu_settings_dfu_about_summary"

1
common/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

46
common/build.gradle Normal file
View File

@@ -0,0 +1,46 @@
/*
* 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.
*/
apply plugin: 'com.android.library'
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
minSdkVersion 18
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:support-v4:23.0.1'
}

95
common/common.iml Normal file
View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":common" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="nRFToolbox" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":common" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" />
<afterSyncTasks>
<task>generateDebugAndroidTestSources</task>
<task>generateDebugSources</task>
</afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
<option name="LIBRARY_PROJECT" value="true" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/debug" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="support-v4-23.0.1" level="project" />
<orderEntry type="library" exported="" name="support-annotations-23.0.1" level="project" />
</component>
</module>

17
common/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\Users\alno\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -0,0 +1,28 @@
<!--
~ 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="no.nordicsemi.android.nrftoolbox.common">
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
</manifest>

View File

@@ -0,0 +1,150 @@
/*************************************************************************************************************************************************
* 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.error;
import android.bluetooth.BluetoothGatt;
/**
* Parses the error numbers according to the <b>gatt_api.h</b> file from bluedroid stack.
* See: https://android.googlesource.com/platform/external/bluetooth/bluedroid/+/android-5.1.0_r1/stack/include/gatt_api.h (and other versions) for details.
*/
public class GattError {
// Starts at line 106 of gatt_api.h file
/**
* Converts the connection status given by the {@link android.bluetooth.BluetoothGattCallback#onConnectionStateChange(BluetoothGatt, int, int)} to error name.
* @param error the status number
* @return the error name as stated in the gatt_api.h file
*/
public static String parseConnectionError(final int error) {
switch (error) {
case BluetoothGatt.GATT_SUCCESS:
return "SUCCESS";
case 0x01:
return "GATT CONN L2C FAILURE";
case 0x08:
return "GATT CONN TIMEOUT";
case 0x13:
return "GATT CONN TERMINATE PEER USER";
case 0x16:
return "GATT CONN TERMINATE LOCAL HOST";
case 0x3E:
return "GATT CONN FAIL ESTABLISH";
case 0x22:
return "GATT CONN LMP TIMEOUT";
case 0x0100:
return "GATT CONN CANCEL ";
case 0x0085:
return "GATT ERROR"; // Device not reachable
default:
return "UNKNOWN (" + error + ")";
}
}
// Starts at line 29 of the gatt_api.h file
/**
* Converts the bluetooth communication status given by other BluetoothGattCallbacks to error name. It also parses the DFU errors.
* @param error the status number
* @return the error name as stated in the gatt_api.h file
*/
public static String parse(final int error) {
switch (error) {
case 0x0001:
return "GATT INVALID HANDLE";
case 0x0002:
return "GATT READ NOT PERMIT";
case 0x0003:
return "GATT WRITE NOT PERMIT";
case 0x0004:
return "GATT INVALID PDU";
case 0x0005:
return "GATT INSUF AUTHENTICATION";
case 0x0006:
return "GATT REQ NOT SUPPORTED";
case 0x0007:
return "GATT INVALID OFFSET";
case 0x0008:
return "GATT INSUF AUTHORIZATION";
case 0x0009:
return "GATT PREPARE Q FULL";
case 0x000a:
return "GATT NOT FOUND";
case 0x000b:
return "GATT NOT LONG";
case 0x000c:
return "GATT INSUF KEY SIZE";
case 0x000d:
return "GATT INVALID ATTR LEN";
case 0x000e:
return "GATT ERR UNLIKELY";
case 0x000f:
return "GATT INSUF ENCRYPTION";
case 0x0010:
return "GATT UNSUPPORT GRP TYPE";
case 0x0011:
return "GATT INSUF RESOURCE";
case 0x0087:
return "GATT ILLEGAL PARAMETER";
case 0x0080:
return "GATT NO RESOURCES";
case 0x0081:
return "GATT INTERNAL ERROR";
case 0x0082:
return "GATT WRONG STATE";
case 0x0083:
return "GATT DB FULL";
case 0x0084:
return "GATT BUSY";
case 0x0085:
return "GATT ERROR";
case 0x0086:
return "GATT CMD STARTED";
case 0x0088:
return "GATT PENDING";
case 0x0089:
return "GATT AUTH FAIL";
case 0x008a:
return "GATT MORE";
case 0x008b:
return "GATT INVALID CFG";
case 0x008c:
return "GATT SERVICE STARTED";
case 0x008d:
return "GATT ENCRYPTED NO MITM";
case 0x008e:
return "GATT NOT ENCRYPTED";
case 0x008f:
return "GATT CONGESTED";
case 0x00FD:
return "GATT CCCD CFG ERROR";
case 0x00FE:
return "GATT PROCEDURE IN PROGRESS";
case 0x00FF:
return "GATT VALUE OUT OF RANGE";
case 0x0101:
return "TOO MANY OPEN CONNECTIONS";
default:
return "UNKNOWN (" + error + ")";
}
}
}

View File

@@ -23,7 +23,7 @@ package no.nordicsemi.android.nrftoolbox.utility;
import android.util.Log;
import no.nordicsemi.android.nrftoolbox.BuildConfig;
import no.nordicsemi.android.nrftoolbox.common.BuildConfig;
public class DebugLogger {
public static void v(final String tag, final String text) {

View File

@@ -0,0 +1,66 @@
/*
* 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.common;
/**
* Constants used for exchanging data and messages between handheld and Android Wear devices.
*/
public final class Constants {
/** Base path for all messages between the handheld and wearable. */
private static final String BASE_PATH = "/nrftoolbox";
/** Action sent from a wearable to disconnect from a device. It must have the profile name set as data. */
public static final String ACTION_DISCONNECT = BASE_PATH + "/disconnect";
/**
* Constants for the UART profile.
*/
public static final class UART {
/** The profile name. */
public static final String PROFILE = "uart";
/** Base path for UART messages between the handheld and wearable. */
private static final String PROFILE_PATH = BASE_PATH + "/uart";
/** An UART device is connected. */
public static final String DEVICE_CONNECTED = PROFILE_PATH + "/connected";
/** An UART device is disconnected. */
public static final String DEVICE_DISCONNECTED = PROFILE_PATH + "/disconnected";
/** An UART device is disconnected due to a link loss. */
public static final String DEVICE_LINKLOSS = PROFILE_PATH + "/link_loss";
/** Path used for syncing UART configurations. */
public static final String CONFIGURATIONS = PROFILE_PATH + "/configurations";
/** An action with a command was clicked. */
public static final String COMMAND = PROFILE_PATH + "/command";
public static final class Configuration {
public static final String NAME = "name";
public static final String COMMANDS = "commands";
public static final class Command {
public static final String ICON_ID = "icon_id";
public static final String MESSAGE = "message";
}
}
}
}

View File

@@ -0,0 +1,26 @@
<!--
~ 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.
-->
<resources>
<string name="bonding">Bonding with the device&#8230;</string>
<string name="bonded">The device is now bonded.</string>
</resources>

View File

@@ -1,6 +1,6 @@
#Wed Apr 10 15:27:10 PDT 2013
#Tue Sep 15 10:34:57 CEST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip

View File

@@ -1,4 +1,4 @@
include ':app'
include ':app', ':wear', ':common'
include ':dfu'
project(':dfu').projectDir = file('../DFULibrary/dfu')

1
wear/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

29
wear/build.gradle Normal file
View File

@@ -0,0 +1,29 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
applicationId "no.nordicsemi.android.nrftoolbox"
minSdkVersion 20
targetSdkVersion 23
versionCode 39
versionName "1.16.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.google.android.support:wearable:1.3.0'
compile 'com.google.android.gms:play-services-wearable:7.8.0'
compile 'no.nordicsemi.android.support.v18:scanner:0.1.1'
compile project(':common')
}

17
wear/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\Users\alno\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<manifest package="no.nordicsemi.android.nrftoolbox"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.type.watch"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<application
android:allowBackup="true"
android:fullBackupContent="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@android:style/Theme.DeviceDefault.Light">
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<activity
android:name=".ScannerActivity"
android:label="@string/app_name"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service android:name=".ble.BleProfileService" />
<activity android:name=".uart.UARTConfigurationsActivity"
android:launchMode="singleTop"/>
<activity android:name=".uart.UARTCommandsActivity"
android:launchMode="singleTop"/>
<!-- This receiver needs to be exported as it listens for notification broadcasts. -->
<receiver android:name=".wearable.ActionReceiver">
<intent-filter>
<action android:name="no.nordicsemi.android.nrftoolbox.ACTION_DISCONNECT" />
</intent-filter>
</receiver>
<!-- Service for handling Android Wear synchronization events. -->
<service android:name=".wearable.MainWearableListenerService">
<intent-filter>
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
</intent-filter>
</service>
</application>
</manifest>

View File

@@ -0,0 +1,121 @@
/*
* 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;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.support.wearable.view.CircledImageView;
import android.support.wearable.view.WearableListView;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
import android.widget.TextView;
public class DeviceItemLayout extends RelativeLayout implements WearableListView.OnCenterProximityListener {
private static final int ANIMATION_DURATION_MS = 150;
/**
* The ratio for the size of a circle in shrink state.
*/
private static final float SHRINK_CIRCLE_RATIO = .75f;
private static final float SHRINK_LABEL_ALPHA = .5f;
private static final float EXPAND_LABEL_ALPHA = 1f;
private float mExpandCircleRadius;
private float mShrinkCircleRadius;
private ObjectAnimator mExpandCircleAnimator;
private ObjectAnimator mFadeInLabelAnimator;
private AnimatorSet mExpandAnimator;
private ObjectAnimator mShrinkCircleAnimator;
private ObjectAnimator mFadeOutLabelAnimator;
private AnimatorSet mShrinkAnimator;
private TextView mName;
private CircledImageView mIcon;
public DeviceItemLayout(final Context context) {
this(context, null, 0);
}
public DeviceItemLayout(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
public DeviceItemLayout(final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mName = (TextView) findViewById(R.id.name);
mIcon = (CircledImageView) findViewById(R.id.icon);
mExpandCircleRadius = mIcon.getCircleRadius();
mShrinkCircleRadius = mExpandCircleRadius * SHRINK_CIRCLE_RATIO;
mShrinkCircleAnimator = ObjectAnimator.ofFloat(mIcon, "circleRadius", mExpandCircleRadius, mShrinkCircleRadius);
mFadeOutLabelAnimator = ObjectAnimator.ofFloat(mName, "alpha", EXPAND_LABEL_ALPHA, SHRINK_LABEL_ALPHA);
mShrinkAnimator = new AnimatorSet().setDuration(ANIMATION_DURATION_MS);
mShrinkAnimator.playTogether(mShrinkCircleAnimator, mFadeOutLabelAnimator);
mExpandCircleAnimator = ObjectAnimator.ofFloat(mIcon, "circleRadius", mShrinkCircleRadius, mExpandCircleRadius);
mFadeInLabelAnimator = ObjectAnimator.ofFloat(mName, "alpha", SHRINK_LABEL_ALPHA, EXPAND_LABEL_ALPHA);
mExpandAnimator = new AnimatorSet().setDuration(ANIMATION_DURATION_MS);
mExpandAnimator.playTogether(mExpandCircleAnimator, mFadeInLabelAnimator);
}
@Override
public void onCenterPosition(final boolean animate) {
if (animate) {
mShrinkAnimator.cancel();
if (!mExpandAnimator.isRunning()) {
mExpandCircleAnimator.setFloatValues(mIcon.getCircleRadius(), mExpandCircleRadius);
mFadeInLabelAnimator.setFloatValues(mName.getAlpha(), EXPAND_LABEL_ALPHA);
mExpandAnimator.start();
}
} else {
mExpandAnimator.cancel();
mIcon.setCircleRadius(mExpandCircleRadius);
mName.setAlpha(EXPAND_LABEL_ALPHA);
}
}
@Override
public void onNonCenterPosition(final boolean animate) {
if (animate) {
mExpandAnimator.cancel();
if (!mShrinkAnimator.isRunning()) {
mShrinkCircleAnimator.setFloatValues(mIcon.getCircleRadius(), mShrinkCircleRadius);
mFadeOutLabelAnimator.setFloatValues(mName.getAlpha(), SHRINK_LABEL_ALPHA);
mShrinkAnimator.start();
}
} else {
mShrinkAnimator.cancel();
mIcon.setCircleRadius(mShrinkCircleRadius);
mName.setAlpha(SHRINK_LABEL_ALPHA);
}
}
}

View File

@@ -0,0 +1,219 @@
/*
* 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;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.os.Handler;
import android.support.wearable.view.CircledImageView;
import android.support.wearable.view.WearableListView;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import no.nordicsemi.android.support.v18.scanner.BluetoothLeScannerCompat;
import no.nordicsemi.android.support.v18.scanner.ScanCallback;
import no.nordicsemi.android.support.v18.scanner.ScanResult;
import no.nordicsemi.android.support.v18.scanner.ScanSettings;
public class DevicesAdapter extends WearableListView.Adapter {
private static final String TAG = "DevicesAdapter";
private final static long SCAN_DURATION = 5000;
private final List<BluetoothDevice> mDevices = new ArrayList<>();
private final LayoutInflater mInflater;
private final Handler mHandler;
private final WearableListView mListView;
private final String mNotAvailable;
private final String mConnectingText;
private final String mAvailableText;
private final String mBondedText;
private final String mBondingText;
/** A position of a device that the activity is currently connecting to. */
private int mConnectingPosition = -1;
/** Flag set to true when scanner is active. */
private boolean mScanning;
public DevicesAdapter(final WearableListView listView) {
final Context context = listView.getContext();
mInflater = LayoutInflater.from(context);
mNotAvailable = context.getString(R.string.not_available);
mConnectingText = context.getString(R.string.state_connecting);
mAvailableText = context.getString(R.string.devices_list_available);
mBondedText = context.getString(R.string.devices_list_bonded);
mBondingText = context.getString(R.string.devices_list_bonding);
mListView = listView;
mHandler = new Handler();
final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mDevices.addAll(bluetoothAdapter.getBondedDevices());
}
@Override
public WearableListView.ViewHolder onCreateViewHolder(final ViewGroup viewGroup, final int position) {
return new ItemViewHolder(mInflater.inflate(R.layout.device_item, viewGroup, false));
}
@Override
public void onBindViewHolder(final WearableListView.ViewHolder holder, final int position) {
final ItemViewHolder viewHolder = (ItemViewHolder) holder;
if (position < mDevices.size()) {
final BluetoothDevice device = mDevices.get(position);
viewHolder.mDevice = device;
viewHolder.mName.setText(TextUtils.isEmpty(device.getName()) ? mNotAvailable : device.getName());
viewHolder.mAddress.setText(getState(device, position));
viewHolder.mIcon.showIndeterminateProgress(position == mConnectingPosition);
} else {
viewHolder.mDevice = null;
viewHolder.mName.setText(mScanning ? R.string.devices_list_scanning : R.string.devices_list_start_scan);
viewHolder.mAddress.setText(null);
viewHolder.mIcon.showIndeterminateProgress(mScanning);
}
}
@Override
public int getItemCount() {
return mDevices.size() + (mConnectingPosition == -1 ? 1 : 0);
}
public void setConnectingPosition(final int connectingPosition) {
final int oldPosition = mConnectingPosition;
this.mConnectingPosition = connectingPosition;
if (connectingPosition >= 0) {
// The "Scan for nearby device' item is removed
notifyItemChanged(connectingPosition);
notifyItemRemoved(mDevices.size());
} else {
if (oldPosition >= 0)
notifyItemChanged(oldPosition);
notifyItemInserted(mDevices.size());
}
}
public void startLeScan() {
// Scanning is disabled when we are connecting or connected.
if (mConnectingPosition >= 0)
return;
if (mScanning) {
// Extend scanning for some time more
mHandler.removeCallbacks(mStopScanTask);
mHandler.postDelayed(mStopScanTask, SCAN_DURATION);
return;
}
final BluetoothLeScannerCompat scanner = BluetoothLeScannerCompat.getScanner();
final ScanSettings settings = new ScanSettings.Builder().setReportDelay(1000).setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
scanner.startScan(null, settings, mScanCallback);
// Setup timer that will stop scanning
mHandler.postDelayed(mStopScanTask, SCAN_DURATION);
mScanning = true;
notifyItemChanged(mDevices.size());
}
public void stopLeScan() {
if (!mScanning)
return;
final BluetoothLeScannerCompat scanner = BluetoothLeScannerCompat.getScanner();
scanner.stopScan(mScanCallback);
mHandler.removeCallbacks(mStopScanTask);
mScanning = false;
notifyItemChanged(mDevices.size());
}
private String getState(final BluetoothDevice device, final int position) {
if (mConnectingPosition == position)
return mConnectingText;
else if (device.getBondState() == BluetoothDevice.BOND_BONDED)
return mBondedText;
else if (device.getBondState() == BluetoothDevice.BOND_BONDING)
return mBondingText;
return mAvailableText;
}
private Runnable mStopScanTask = new Runnable() {
@Override
public void run() {
stopLeScan();
}
};
private ScanCallback mScanCallback = new ScanCallback() {
@Override
public void onScanResult(final int callbackType, final ScanResult result) {
// empty
}
@Override
public void onBatchScanResults(final List<ScanResult> results) {
final int size = mDevices.size();
for (final ScanResult result : results) {
final BluetoothDevice device = result.getDevice();
if (!mDevices.contains(device))
mDevices.add(device);
}
if (size != mDevices.size()) {
notifyItemRangeInserted(size, mDevices.size() - size);
if (size == 0)
mListView.scrollToPosition(0);
}
}
@Override
public void onScanFailed(final int errorCode) {
// empty
}
};
public static class ItemViewHolder extends WearableListView.ViewHolder {
private CircledImageView mIcon;
private TextView mName;
private TextView mAddress;
private BluetoothDevice mDevice;
public ItemViewHolder(final View itemView) {
super(itemView);
mIcon = (CircledImageView) itemView.findViewById(R.id.icon);
mName = (TextView) itemView.findViewById(R.id.name);
mAddress = (TextView) itemView.findViewById(R.id.state);
}
/** Returns the Bluetooth device for that holder, or null for "Scanning for nearby devices" row. */
public BluetoothDevice getDevice() {
return mDevice;
}
}
}

View File

@@ -0,0 +1,174 @@
/*
* 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;
import android.app.Activity;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.content.LocalBroadcastManager;
import android.support.wearable.view.WearableListView;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import no.nordicsemi.android.nrftoolbox.ble.BleProfileService;
import no.nordicsemi.android.nrftoolbox.uart.UARTConfigurationsActivity;
public class ScannerActivity extends Activity {
private static final String TAG = "ScannerActivity";
private DevicesAdapter mDeviceAdapter;
private View mHeader;
private BroadcastReceiver mServiceBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
switch (action) {
case BleProfileService.BROADCAST_CONNECTION_STATE: {
final int state = intent.getIntExtra(BleProfileService.EXTRA_CONNECTION_STATE, BleProfileService.STATE_DISCONNECTED);
if (state == BleProfileService.STATE_DISCONNECTED)
mDeviceAdapter.setConnectingPosition(-1);
break;
}
case BleProfileService.BROADCAST_DEVICE_READY: {
final Intent activity = new Intent(ScannerActivity.this, UARTConfigurationsActivity.class);
startActivity(activity);
finish();
break;
}
case BleProfileService.BROADCAST_DEVICE_NOT_SUPPORTED: {
Toast.makeText(ScannerActivity.this, R.string.devices_list_device_not_supported, Toast.LENGTH_SHORT).show();
mDeviceAdapter.setConnectingPosition(-1);
break;
}
case BleProfileService.BROADCAST_ERROR: {
final String message = intent.getStringExtra(BleProfileService.EXTRA_ERROR_MESSAGE);
// final int errorCode = intent.getIntExtra(BleProfileService.EXTRA_ERROR_CODE, 0);
Toast.makeText(ScannerActivity.this, message, Toast.LENGTH_SHORT).show();
// TODO error handing
break;
}
case BleProfileService.BROADCAST_BOND_STATE: {
mDeviceAdapter.notifyDataSetChanged(); // TODO check this. Bonding was never tested.
break;
}
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_with_header);
// Get the list component from the layout of the activity
final WearableListView listView = (WearableListView) findViewById(R.id.devices_list);
listView.setAdapter(mDeviceAdapter = new DevicesAdapter(listView));
listView.setClickListener(mOnRowClickListener);
listView.addOnScrollListener(mOnScrollListener);
// The header will be moved as the list is scrolled
mHeader = findViewById(R.id.header);
// Register a broadcast receiver that will listen for events from the service.
LocalBroadcastManager.getInstance(this).registerReceiver(mServiceBroadcastReceiver, BleProfileService.makeIntentFilter());
}
@Override
protected void onDestroy() {
super.onDestroy();
LocalBroadcastManager.getInstance(this).unregisterReceiver(mServiceBroadcastReceiver);
}
@Override
protected void onResume() {
super.onResume();
mDeviceAdapter.startLeScan();
}
@Override
protected void onPause() {
super.onPause();
mDeviceAdapter.stopLeScan();
}
/** List click listener. */
private WearableListView.ClickListener mOnRowClickListener = new WearableListView.ClickListener() {
@Override
public void onClick(final WearableListView.ViewHolder holder) {
final DevicesAdapter.ItemViewHolder viewHolder = (DevicesAdapter.ItemViewHolder) holder;
final BluetoothDevice device = viewHolder.getDevice();
if (device != null) {
mDeviceAdapter.stopLeScan();
mDeviceAdapter.setConnectingPosition(holder.getAdapterPosition());
// Start the service that will connect to selected device
final Intent service = new Intent(ScannerActivity.this, BleProfileService.class);
service.putExtra(BleProfileService.EXTRA_DEVICE_ADDRESS, device.getAddress());
startService(service);
} else {
mDeviceAdapter.startLeScan();
}
}
@Override
public void onTopEmptyRegionClick() {
// do nothing
}
};
/** The following code ensures that the title scrolls as the user scrolls up or down the list/ */
private WearableListView.OnScrollListener mOnScrollListener = new WearableListView.OnScrollListener() {
@Override
public void onAbsoluteScrollChange(final int i) {
if (i > 0)
mHeader.setY(-i);
else
mHeader.setY(0);
}
@Override
public void onScroll(final int i) {
// Placeholder
}
@Override
public void onScrollStateChanged(final int i) {
// Placeholder
}
@Override
public void onCentralPositionChanged(final int i) {
// Placeholder
}
};
}

View File

@@ -0,0 +1,528 @@
/*
* 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.ble;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import java.util.Queue;
import java.util.UUID;
import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
/**
* <p>The BleManager is responsible for managing the low level communication with a Bluetooth Smart device. Please see profiles implementation for an example of use.
* This base manager has been tested against number of devices and samples from Nordic SDK.</p>
* <p>The manager handles connection events and initializes the device after establishing the connection.
* <ol>
* <li>For bonded devices it ensures that the Service Changed indications, if this characteristic is present, are enabled. Android does not enable them by default,
* 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 BleProfile#initGatt(BluetoothGatt)} method for more details.</li>
* <li>When initialization complete, the {@link BleManagerCallbacks#onDeviceReady()} callback is called.</li>
* </ol>
* </p>
* <p>Events from all profiles are being logged into the nRF Logger application,
* which may be downloaded from Google Play: <a href="https://play.google.com/store/apps/details?id=no.nordicsemi.android.log">https://play.google.com/store/apps/details?id=no.nordicsemi.android.log</a></p>
* <p>The nRF Logger application allows you to see application logs without need to connect it to the computer.</p>
*/
public class BleManager implements BleProfileApi {
private final static String TAG = "BleManager";
private static final UUID CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
private final static UUID GENERIC_ATTRIBUTE_SERVICE = UUID.fromString("00001801-0000-1000-8000-00805f9b34fb");
private final static UUID SERVICE_CHANGED_CHARACTERISTIC = UUID.fromString("00002A05-0000-1000-8000-00805f9b34fb");
private final static String ERROR_CONNECTION_STATE_CHANGE = "Error on connection state change";
private final static String ERROR_DISCOVERY_SERVICE = "Error on discovering services";
private final static String ERROR_AUTH_ERROR_WHILE_BONDED = "Phone has lost bonding information";
private final static String ERROR_WRITE_DESCRIPTOR = "Error on writing descriptor";
private final static String ERROR_READ_CHARACTERISTIC = "Error on reading characteristic";
protected final BleManagerCallbacks mCallbacks;
protected BleProfile mProfile;
private final Context mContext;
private Handler mHandler;
private BluetoothGatt mBluetoothGatt;
private boolean mUserDisconnected;
private boolean mConnected;
private BroadcastReceiver mBondingBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
final int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1);
// Skip other devices
if (mBluetoothGatt == null || !device.getAddress().equals(mBluetoothGatt.getDevice().getAddress()))
return;
DebugLogger.i(TAG, "Bond state changed for: " + device.getName() + " new state: " + bondState + " previous: " + previousBondState);
switch (bondState) {
case BluetoothDevice.BOND_BONDING:
mCallbacks.onBondingRequired();
break;
case BluetoothDevice.BOND_BONDED:
mCallbacks.onBonded();
// Start initializing again.
// In fact, bonding forces additional, internal service discovery (at least on Nexus devices), so this method may safely be used to start this process again.
mBluetoothGatt.discoverServices();
break;
}
}
};
public BleManager(final Context context, final BleManagerCallbacks callbacks) {
mCallbacks = callbacks;
mContext = context;
mHandler = new Handler();
mUserDisconnected = false;
// Register bonding broadcast receiver
context.registerReceiver(mBondingBroadcastReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
}
/**
* Returns the Profile API. Profile may be null if service discovery has not been performed or the device does not match any profile.
*/
public BleProfile getProfile() {
return mProfile;
}
/**
* Returns the context that the manager was created with.
*
* @return the context
*/
@Override
public Context getContext() {
return mContext;
}
/**
* Returns whether to directly connect to the remote device (false) or to automatically connect as soon as the remote
* device becomes available (true).
*
* @return autoConnect flag value
*/
protected boolean shouldAutoConnect() {
return false;
}
/**
* Connects to the Bluetooth Smart device
*
* @param device a device to connect to
*/
public void connect(final BluetoothDevice device) {
if (mConnected)
return;
if (mBluetoothGatt != null) {
mBluetoothGatt.close();
mBluetoothGatt = null;
}
final boolean autoConnect = shouldAutoConnect();
mUserDisconnected = !autoConnect; // We will receive Linkloss events only when the device is connected with autoConnect=true
mBluetoothGatt = device.connectGatt(mContext, autoConnect, mGattCallback);
}
/**
* Disconnects from the device. Does nothing if not connected.
* @return true if device is to be disconnected. False if it was already disconnected.
*/
public boolean disconnect() {
mUserDisconnected = true;
if (mConnected && mBluetoothGatt != null) {
mCallbacks.onDeviceDisconnecting();
mBluetoothGatt.disconnect();
return true;
}
return false;
}
/**
* Closes and releases resources. May be also used to unregister broadcast listeners.
*/
public void close() {
try {
mContext.unregisterReceiver(mBondingBroadcastReceiver);
} catch (Exception e) {
// the receiver must have been not registered or unregistered before
}
if (mBluetoothGatt != null) {
mBluetoothGatt.close();
mBluetoothGatt = null;
}
mUserDisconnected = false;
}
/**
* When the device is bonded and has the Generic Attribute service and the Service Changed characteristic this method enables indications on this characteristic.
* In case one of the requirements is not fulfilled this method returns <code>false</code>.
*
* @param gatt the gatt device with services discovered
* @return <code>true</code> when the request has been sent, <code>false</code> when the device is not bonded, does not have the Generic Attribute service, the GA service does not have
* the Service Changed characteristic or this characteristic does not have the CCCD.
*/
private boolean ensureServiceChangedEnabled(final BluetoothGatt gatt) {
if (gatt == null)
return false;
// The Service Changed indications have sense only on bonded devices
final BluetoothDevice device = gatt.getDevice();
if (device.getBondState() != BluetoothDevice.BOND_BONDED)
return false;
final BluetoothGattService gaService = gatt.getService(GENERIC_ATTRIBUTE_SERVICE);
if (gaService == null)
return false;
final BluetoothGattCharacteristic scCharacteristic = gaService.getCharacteristic(SERVICE_CHANGED_CHARACTERISTIC);
if (scCharacteristic == null)
return false;
return enableIndications(scCharacteristic);
}
@Override
public final boolean enableNotifications(final BluetoothGattCharacteristic characteristic) {
final BluetoothGatt gatt = mBluetoothGatt;
if (gatt == null || characteristic == null)
return false;
// Check characteristic property
final int properties = characteristic.getProperties();
if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == 0)
return false;
gatt.setCharacteristicNotification(characteristic, true);
final BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
if (descriptor != null) {
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
return gatt.writeDescriptor(descriptor);
}
return false;
}
@Override
public final boolean enableIndications(final BluetoothGattCharacteristic characteristic) {
final BluetoothGatt gatt = mBluetoothGatt;
if (gatt == null || characteristic == null)
return false;
// Check characteristic property
final int properties = characteristic.getProperties();
if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == 0)
return false;
gatt.setCharacteristicNotification(characteristic, true);
final BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
if (descriptor != null) {
descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
return gatt.writeDescriptor(descriptor);
}
return false;
}
@Override
public final boolean readCharacteristic(final BluetoothGattCharacteristic characteristic) {
final BluetoothGatt gatt = mBluetoothGatt;
if (gatt == null || characteristic == null)
return false;
// Check characteristic property
final int properties = characteristic.getProperties();
if ((properties & BluetoothGattCharacteristic.PROPERTY_READ) == 0)
return false;
return gatt.readCharacteristic(characteristic);
}
@Override
public final boolean writeCharacteristic(final BluetoothGattCharacteristic characteristic) {
final BluetoothGatt gatt = mBluetoothGatt;
if (gatt == null || characteristic == null)
return false;
// Check characteristic property
final int properties = characteristic.getProperties();
if ((properties & (BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE)) == 0)
return false;
return gatt.writeCharacteristic(characteristic);
}
public static final class Request {
private enum Type {
WRITE,
READ,
ENABLE_NOTIFICATIONS,
ENABLE_INDICATIONS
}
private final Type type;
private final BluetoothGattCharacteristic characteristic;
private final byte[] value;
private Request(final Type type, final BluetoothGattCharacteristic characteristic) {
this.type = type;
this.characteristic = characteristic;
this.value = null;
}
private Request(final Type type, final BluetoothGattCharacteristic characteristic, final byte[] value) {
this.type = type;
this.characteristic = characteristic;
this.value = value;
}
public static Request newReadRequest(final BluetoothGattCharacteristic characteristic) {
return new Request(Type.READ, characteristic);
}
public static Request newWriteRequest(final BluetoothGattCharacteristic characteristic, final byte[] value) {
return new Request(Type.WRITE, characteristic, value);
}
public static Request newEnableNotificationsRequest(final BluetoothGattCharacteristic characteristic) {
return new Request(Type.ENABLE_NOTIFICATIONS, characteristic);
}
public static Request newEnableIndicationsRequest(final BluetoothGattCharacteristic characteristic) {
return new Request(Type.ENABLE_INDICATIONS, characteristic);
}
}
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
private Queue<Request> mInitQueue;
private boolean mInitInProgress;
private void onError(final String message, final int errorCode) {
mCallbacks.onError(message, errorCode);
if (mProfile != null)
mProfile.onError(message, errorCode);
}
@Override
public final void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
// Notify the parent activity/service
mConnected = true;
mCallbacks.onDeviceConnected();
/*
* The onConnectionStateChange event is triggered just after the Android connects to a device.
* In case of bonded devices, the encryption is reestablished AFTER this callback is called.
* Moreover, when the device has Service Changed indication enabled, and the list of services has changed (e.g. using the DFU),
* the indication is received few milliseconds later, depending on the connection interval.
* When received, Android will start performing a service discovery operation itself, internally.
*
* If the mBluetoothGatt.discoverServices() method would be invoked here, if would returned cached services,
* as the SC indication wouldn't be received yet.
* Therefore we have to postpone the service discovery operation until we are (almost, as there is no such callback) sure, that it had to be handled.
* Our tests has shown that 600 ms is enough. It is important to call it AFTER receiving the SC indication, but not necessarily
* after Android finishes the internal service discovery.
*
* NOTE: This applies only for bonded devices with Service Changed characteristic, but to be sure we will postpone
* service discovery for all devices.
*/
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
// Some proximity tags (e.g. nRF PROXIMITY) initialize bonding automatically when connected.
if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_BONDING) {
gatt.discoverServices();
}
}
}, 600);
} else {
if (newState == BluetoothProfile.STATE_DISCONNECTED) {
mConnected = false;
if (mUserDisconnected) {
mCallbacks.onDeviceDisconnected();
close();
} else {
mCallbacks.onLinklossOccur();
// We are not closing the connection here as the device should try to reconnect automatically.
// This may be only called when the shouldAutoConnect() method returned true.
}
if (mProfile != null)
mProfile.release();
return;
}
// TODO Should the disconnect method be called or the connection is still valid? Does this ever happen?
mProfile.onError(ERROR_CONNECTION_STATE_CHANGE, status);
}
}
@Override
public final void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
final BleProfile profile = BleProfileProvider.findProfile(gatt);
if (profile != null) {
profile.setApi(BleManager.this);
mProfile = profile;
// Obtain the queue of initialization requests
mInitInProgress = true;
mInitQueue = profile.initGatt(gatt);
// When the device is bonded and has Service Changed characteristic, the indications must be enabled first.
// In case this method returns true we have to continue in the onDescriptorWrite callback
if (ensureServiceChangedEnabled(gatt))
return;
// We have discovered services, proceed with the initialization queue.
nextRequest();
} else {
mCallbacks.onDeviceNotSupported();
disconnect();
}
} else {
DebugLogger.e(TAG, "onServicesDiscovered error " + status);
onError(ERROR_DISCOVERY_SERVICE, status);
}
}
@Override
public final void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// The value has been read. Notify the profile and proceed with the initialization queue.
mProfile.onCharacteristicRead(gatt, characteristic);
nextRequest();
} else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) {
DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED);
onError(ERROR_AUTH_ERROR_WHILE_BONDED, status);
}
} else {
DebugLogger.e(TAG, "onCharacteristicRead error " + status);
onError(ERROR_READ_CHARACTERISTIC, status);
}
}
@Override
public void onCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// The value has been written. Notify the profile and proceed with the initialization queue.
mProfile.onCharacteristicWrite(gatt, characteristic);
nextRequest();
} else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) {
DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED);
onError(ERROR_AUTH_ERROR_WHILE_BONDED, status);
}
} else {
DebugLogger.e(TAG, "onCharacteristicRead error " + status);
onError(ERROR_READ_CHARACTERISTIC, status);
}
}
@Override
public final void onDescriptorWrite(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// The value has been written. Notify the profile and proceed with the initialization queue.
mProfile.onDescriptorWrite(gatt, descriptor);
nextRequest();
} else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) {
DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED);
onError(ERROR_AUTH_ERROR_WHILE_BONDED, status);
}
} else {
DebugLogger.e(TAG, "onDescriptorWrite error " + status);
onError(ERROR_WRITE_DESCRIPTOR, status);
}
}
@Override
public final void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
final BluetoothGattDescriptor cccd = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
final boolean notifications = cccd == null || cccd.getValue() == null || cccd.getValue().length != 2 || cccd.getValue()[0] == 0x01;
if (notifications) {
mProfile.onCharacteristicNotified(gatt, characteristic);
} else { // indications
mProfile.onCharacteristicIndicated(gatt, characteristic);
}
}
/**
* Executes the next initialization request. If the last element from the queue has been executed a {@link BleManagerCallbacks#onDeviceReady()} callback is called.
*/
private void nextRequest() {
final Queue<Request> requests = mInitQueue;
// Get the first request from the queue
final Request request = requests != null ? requests.poll() : null;
// Are we done?
if (request == null) {
if (mInitInProgress) {
mInitInProgress = false;
mCallbacks.onDeviceReady();
}
return;
}
switch (request.type) {
case READ: {
readCharacteristic(request.characteristic);
break;
}
case WRITE: {
final BluetoothGattCharacteristic characteristic = request.characteristic;
characteristic.setValue(request.value);
writeCharacteristic(characteristic);
break;
}
case ENABLE_NOTIFICATIONS: {
enableNotifications(request.characteristic);
break;
}
case ENABLE_INDICATIONS: {
enableIndications(request.characteristic);
break;
}
}
}
};
}

View File

@@ -0,0 +1,80 @@
/*
* 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.ble;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
public interface BleManagerCallbacks {
/**
* Called when the device has been connected. This does not mean that the application may start communication. A service discovery will be handled automatically after this call. Service discovery
* may ends up with calling {@link #onDeviceReady()} or {@link #onDeviceNotSupported()} if required services have not been found.
*/
public void onDeviceConnected();
/**
* Method called when all initialization requests has been completed.
*/
public void onDeviceReady();
/**
* Called when user initialized disconnection.
*/
public void onDeviceDisconnecting();
/**
* Called when the device has disconnected (when the callback returned {@link BluetoothGattCallback#onConnectionStateChange(BluetoothGatt, int, int)} with state DISCONNECTED.
*/
public void onDeviceDisconnected();
/**
* This callback is invoked when the Ble Manager lost connection to a device that has been connected with autoConnect option. Otherwise a {@link #onDeviceDisconnected()}
* method will be called on such event.
*/
public void onLinklossOccur();
/**
* Called when an {@link BluetoothGatt#GATT_INSUFFICIENT_AUTHENTICATION} error occurred and the device bond state is NOT_BONDED
*/
public void onBondingRequired();
/**
* Called when the device has been successfully bonded.
*/
public void onBonded();
/**
* Called when a BLE error has occurred
*
* @param message
* the error message
* @param errorCode
* the error code
*/
public void onError(final String message, final int errorCode);
/**
* Called when service discovery has finished but the main services were not found on the device.
*/
public void onDeviceNotSupported();
}

View File

@@ -0,0 +1,124 @@
/*
* 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.ble;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.content.Context;
import java.util.Queue;
public abstract class BleProfile {
private Context mContext;
private BleProfileApi mApi;
/* package */ void setApi(final BleProfileApi api) {
this.mContext = api.getContext();
this.mApi = api;
}
/**
* Returns the BLE API for sending data to the remote device.
*/
public BleProfileApi getApi() {
return mApi;
}
/**
* Returns the service context.
* @return the context
*/
public Context getContext() {
return mContext;
}
/**
* This method should return a list of requests needed to initialize the profile.
* Enabling Service Change indications for bonded devices and reading the Battery Level value and enabling Battery Level notifications
* is handled before executing this queue. The queue should not have requests that are not available, e.g. should not
* read an optional service when it is not supported by the connected device.
* <p>This method is called when the services has been discovered and the device is supported (has required service).</p>
*
* @param gatt the gatt device with services discovered
* @return the queue of requests
*/
protected abstract Queue<BleManager.Request> initGatt(final BluetoothGatt gatt);
/**
* Releases all profile resources. The device is no longer connected.
*/
protected abstract void release();
/**
* Callback reporting the result of a characteristic read operation.
*
* @param gatt GATT client invoked {@link BluetoothGatt#readCharacteristic}
* @param characteristic Characteristic that was read from the associated
* remote device.
*/
protected void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
// do nothing
}
/**
* Callback indicating the result of a characteristic write operation.
* <p/>
* <p>If this callback is invoked while a reliable write transaction is
* in progress, the value of the characteristic represents the value
* reported by the remote device. An application should compare this
* value to the desired value to be written. If the values don't match,
* the application must abort the reliable write transaction.
*
* @param gatt GATT client invoked {@link BluetoothGatt#writeCharacteristic}
* @param characteristic Characteristic that was written to the associated
* remote device.
*/
protected void onCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
// do nothing
}
protected void onDescriptorWrite(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor) {
// do nothing
}
protected void onCharacteristicNotified(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
// do nothing
}
protected void onCharacteristicIndicated(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
// do nothing
}
/**
* Called when a BLE error has occurred
*
* @param message
* the error message
* @param errorCode
* the error code
*/
public void onError(final String message, final int errorCode) {
// do nothing
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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.ble;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Context;
public interface BleProfileApi {
/**
* Returns the context.
*/
public Context getContext();
/**
* Enables notifications on given characteristic
*
* @return true is the request has been sent, false if one of the arguments was <code>null</code> or the characteristic does not have the CCCD.
*/
public boolean enableNotifications(final BluetoothGattCharacteristic characteristic);
/**
* Enables indications on given characteristic
*
* @return true is the request has been sent, false if one of the arguments was <code>null</code> or the characteristic does not have the CCCD.
*/
public boolean enableIndications(final BluetoothGattCharacteristic characteristic);
/**
* Sends the read request to the given characteristic.
*
* @param characteristic the characteristic to read
* @return true if request has been sent
*/
public boolean readCharacteristic(final BluetoothGattCharacteristic characteristic);
/**
* Writes the characteristic value to the given characteristic.
*
* @param characteristic the characteristic to write to
* @return true if request has been sent
*/
public boolean writeCharacteristic(final BluetoothGattCharacteristic characteristic);
}

View File

@@ -0,0 +1,36 @@
/*
* 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.ble;
import android.bluetooth.BluetoothGatt;
import no.nordicsemi.android.nrftoolbox.uart.UARTProfile;
public class BleProfileProvider {
public static BleProfile findProfile(final BluetoothGatt gatt) {
if (UARTProfile.matchDevice(gatt))
return new UARTProfile();
return null;
}
}

View File

@@ -0,0 +1,302 @@
/*
* 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.ble;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.support.v4.content.LocalBroadcastManager;
import android.widget.Toast;
public 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_DEVICE_NOT_SUPPORTED = "no.nordicsemi.android.nrftoolbox.BROADCAST_DEVICE_NOT_SUPPORTED";
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";
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_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_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 BleManager mBleManager;
private Handler mHandler;
protected boolean mBinded;
private boolean mConnected;
private String mDeviceAddress;
private String mDeviceName;
public class LocalBinder extends Binder {
/**
* Disconnects from the sensor.
*/
public final void disconnect() {
if (!mConnected) {
mBleManager.close();
onDeviceDisconnected();
return;
}
mBleManager.disconnect();
}
/**
* Returns the device address
*
* @return device address
*/
public String getDeviceAddress() {
return mDeviceAddress;
}
/**
* Returns the device name
*
* @return the device name
*/
public String getDeviceName() {
return mDeviceName;
}
/**
* Returns <code>true</code> if the device is connected to the sensor.
*
* @return <code>true</code> if device is connected to the sensor, <code>false</code> otherwise
*/
public boolean isConnected() {
return mConnected;
}
/**
* Returns the Profile API. Profile may be null if service discovery has not been performed or the device does not match any profile.
*/
public BleProfile getProfile() {
return mBleManager.getProfile();
}
}
/**
* Returns the binder implementation. This must return class implementing the additional manager interface that may be used in the binded 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) {
mBinded = true;
return getBinder();
}
@Override
public final void onRebind(final Intent intent) {
mBinded = true;
}
@Override
public final boolean onUnbind(final Intent intent) {
mBinded = false;
// we must allow to rebind to the same service
return true;
}
@SuppressWarnings("unchecked")
@Override
public void onCreate() {
super.onCreate();
mHandler = new Handler();
// initialize the manager
mBleManager = new BleManager(this, this);
}
@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");
mDeviceAddress = intent.getStringExtra(EXTRA_DEVICE_ADDRESS);
// notify user about changing the state to CONNECTING
final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_CONNECTING);
LocalBroadcastManager.getInstance(BleProfileService.this).sendBroadcast(broadcast);
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
final BluetoothAdapter adapter = bluetoothManager.getAdapter();
final BluetoothDevice device = adapter.getRemoteDevice(mDeviceAddress);
mDeviceName = device.getName();
mBleManager.connect(device);
return START_REDELIVER_INTENT;
}
@Override
public void onDestroy() {
super.onDestroy();
// shutdown the manager
mBleManager.close();
mBleManager = null;
mDeviceAddress = null;
mDeviceName = null;
mConnected = false;
}
@Override
public void onDeviceConnected() {
mConnected = true;
final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_CONNECTED);
broadcast.putExtra(EXTRA_DEVICE_ADDRESS, mDeviceAddress);
broadcast.putExtra(EXTRA_DEVICE_NAME, mDeviceName);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
}
@Override
public void onDeviceDisconnecting() {
// Notify user about changing the state to DISCONNECTING
final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_DISCONNECTING);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
}
@Override
public void onDeviceDisconnected() {
mConnected = false;
mDeviceAddress = null;
mDeviceName = null;
final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_DISCONNECTED);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
stopSelf();
}
@Override
public void onLinklossOccur() {
mConnected = false;
final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_LINK_LOSS);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
}
@Override
public void onDeviceReady() {
final Intent broadcast = new Intent(BROADCAST_DEVICE_READY);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
}
@Override
public void onDeviceNotSupported() {
final Intent broadcast = new Intent(BROADCAST_DEVICE_NOT_SUPPORTED);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
// no need for disconnecting, it will be disconnected by the manager automatically
}
@Override
public void onBondingRequired() {
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);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
}
@Override
public void onBonded() {
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);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
}
@Override
public void onError(final String message, final int errorCode) {
final Intent broadcast = new Intent(BROADCAST_ERROR);
broadcast.putExtra(EXTRA_ERROR_MESSAGE, message);
broadcast.putExtra(EXTRA_ERROR_CODE, errorCode);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
mBleManager.disconnect();
stopSelf();
}
/**
* 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
*/
private void showToast(final int messageResId) {
mHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(BleProfileService.this, messageResId, Toast.LENGTH_SHORT).show();
}
});
}
/**
* Creates an intent filter that filters for all broadcast events sent by this service.
*/
public static IntentFilter makeIntentFilter() {
final IntentFilter filter = new IntentFilter();
filter.addAction(BROADCAST_CONNECTION_STATE);
filter.addAction(BROADCAST_BOND_STATE);
filter.addAction(BROADCAST_DEVICE_READY);
filter.addAction(BROADCAST_DEVICE_NOT_SUPPORTED);
filter.addAction(BROADCAST_ERROR);
return filter;
}
}

View File

@@ -0,0 +1,282 @@
/*
* 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;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.annotation.NonNull;
import android.support.v4.content.LocalBroadcastManager;
import android.support.wearable.view.DotsPageIndicator;
import android.support.wearable.view.GridViewPager;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.DataApi;
import com.google.android.gms.wearable.DataEvent;
import com.google.android.gms.wearable.DataEventBuffer;
import com.google.android.gms.wearable.DataItem;
import com.google.android.gms.wearable.DataMap;
import com.google.android.gms.wearable.DataMapItem;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.MessageEvent;
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.nrftoolbox.R;
import no.nordicsemi.android.nrftoolbox.ble.BleProfile;
import no.nordicsemi.android.nrftoolbox.ble.BleProfileService;
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 UARTCommandsActivity extends Activity implements UARTCommandsAdapter.OnCommandSelectedListener, GoogleApiClient.ConnectionCallbacks,
DataApi.DataListener, GoogleApiClient.OnConnectionFailedListener, MessageApi.MessageListener {
private static final String TAG = "UARTCommandsActivity";
public static final String CONFIGURATION = "configuration";
private GoogleApiClient mGoogleApiClient;
private UARTCommandsAdapter mAdapter;
private UARTProfile mProfile;
private long mConfigurationId;
private BroadcastReceiver mServiceBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
switch (action) {
case BleProfileService.BROADCAST_CONNECTION_STATE: {
final int state = intent.getIntExtra(BleProfileService.EXTRA_CONNECTION_STATE, BleProfileService.STATE_DISCONNECTED);
if (state == BleProfileService.STATE_DISCONNECTED)
finish();
break;
}
case BleProfileService.BROADCAST_ERROR: {
final String message = intent.getStringExtra(BleProfileService.EXTRA_ERROR_MESSAGE);
// final int errorCode = intent.getIntExtra(BleProfileService.EXTRA_ERROR_CODE, 0);
Toast.makeText(UARTCommandsActivity.this, message, Toast.LENGTH_SHORT).show();
// TODO error handing
break;
}
case UARTProfile.BROADCAST_DATA_RECEIVED: {
// Here we could have shown the incoming message somehow.
// However, notifications on TX characteristics are not enabled so this does not have to be implemented.
// final String message = intent.getStringExtra(UARTProfile.EXTRA_DATA);
// Toast.makeText(UARTCommandsActivity.this, message, Toast.LENGTH_SHORT).show();
break;
}
}
}
};
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(final ComponentName name, final IBinder service) {
final BleProfileService.LocalBinder binder = (BleProfileService.LocalBinder) service;
mProfile = (UARTProfile) binder.getProfile();
}
@Override
public void onServiceDisconnected(final ComponentName name) {
mProfile = null;
}
};
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_grid_pager);
final Intent intent = getIntent();
final UartConfiguration configuration = intent.getParcelableExtra(CONFIGURATION);
mConfigurationId = configuration.getId();
// Check if the WEAR device is connected to the UART device itself, or by the phone.
// Binding will fail if we are using phone as proxy as the service has not been started before.
final Intent service = new Intent(this, BleProfileService.class);
bindService(service, mServiceConnection, 0);
// Set up tht grid
final GridViewPager pager = (GridViewPager) findViewById(R.id.pager);
pager.setAdapter(mAdapter = new UARTCommandsAdapter(configuration, this));
final DotsPageIndicator dotsPageIndicator = (DotsPageIndicator) findViewById(R.id.page_indicator);
dotsPageIndicator.setPager(pager);
// Configure Google API client
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
// Register the broadcast receiver that will listen for events from the device
final IntentFilter filter = new IntentFilter();
filter.addAction(BleProfileService.BROADCAST_CONNECTION_STATE);
filter.addAction(BleProfileService.BROADCAST_ERROR);
filter.addAction(UARTProfile.BROADCAST_DATA_RECEIVED);
LocalBroadcastManager.getInstance(this).registerReceiver(mServiceBroadcastReceiver, filter);
}
@Override
protected void onDestroy() {
super.onDestroy();
mGoogleApiClient.unregisterConnectionCallbacks(this);
mGoogleApiClient.unregisterConnectionFailedListener(this);
mGoogleApiClient = null;
// unbind if we were binded to the service.
unbindService(mServiceConnection);
LocalBroadcastManager.getInstance(this).unregisterReceiver(mServiceBroadcastReceiver);
}
@Override
protected void onStart() {
super.onStart();
mGoogleApiClient.connect();
}
@Override
protected void onStop() {
super.onStop();
Wearable.MessageApi.removeListener(mGoogleApiClient, this);
Wearable.DataApi.removeListener(mGoogleApiClient, this);
mGoogleApiClient.disconnect();
}
@Override
public void onConnected(final Bundle bundle) {
Wearable.DataApi.addListener(mGoogleApiClient, this);
Wearable.MessageApi.addListener(mGoogleApiClient, this);
}
@Override
public void onConnectionSuspended(final int cause) {
finish();
}
@Override
public void onConnectionFailed(final ConnectionResult connectionResult) {
finish();
}
@Override
public void onDataChanged(final DataEventBuffer dataEventBuffer) {
for (final DataEvent event : dataEventBuffer) {
final DataItem item = event.getDataItem();
final long id = ContentUris.parseId(item.getUri());
// Update the configuration only if ID matches
if (id != mConfigurationId)
continue;
// Configuration added or edited
if (event.getType() == DataEvent.TYPE_CHANGED) {
final DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap();
final UartConfiguration configuration = new UartConfiguration(dataMap, id);
// Update UI on UI thread
runOnUiThread(new Runnable() {
@Override
public void run() {
mAdapter.setConfiguration(configuration);
}
});
} else if (event.getType() == DataEvent.TYPE_DELETED) {
// Configuration removed
// Update UI on UI thread
runOnUiThread(new Runnable() {
@Override
public void run() {
mAdapter.setConfiguration(null);
}
});
}
}
}
@Override
public void onMessageReceived(final MessageEvent messageEvent) {
// If the activity is binded to service it means that it has connected directly to the device. We ignore messages from the handheld.
if (mProfile != null)
return;
switch (messageEvent.getPath()) {
case Constants.UART.DEVICE_LINKLOSS:
case Constants.UART.DEVICE_DISCONNECTED: {
finish();
break;
}
}
}
@Override
public void onCommandSelected(final Command command) {
// Send command to handheld if the watch is not connected directly to the UART device.
if (mProfile != null)
mProfile.send(command.getCommand());
else
sendMessageToHandheld(this, command.getCommand());
}
/**
* Sends the given command to the handheld.
*
* @param command the message
*/
private void sendMessageToHandheld(final @NonNull Context context, final @NonNull String command) {
new Thread(new Runnable() {
@Override
public void run() {
final GoogleApiClient client = new GoogleApiClient.Builder(context)
.addApi(Wearable.API)
.build();
client.blockingConnect();
final NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(client).await();
for (Node node : nodes.getNodes()) {
final MessageApi.SendMessageResult result = Wearable.MessageApi.sendMessage(client, node.getId(), Constants.UART.COMMAND, command.getBytes()).await();
if (!result.getStatus().isSuccess()) {
Log.w(TAG, "Failed to send " + Constants.UART.COMMAND + " to " + node.getDisplayName());
}
}
client.disconnect();
}
}).start();
}
}

View File

@@ -0,0 +1,106 @@
/*
* 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;
import android.support.wearable.view.CircularButton;
import android.support.wearable.view.GridPagerAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import no.nordicsemi.android.nrftoolbox.R;
import no.nordicsemi.android.nrftoolbox.uart.domain.Command;
import no.nordicsemi.android.nrftoolbox.uart.domain.UartConfiguration;
public class UARTCommandsAdapter extends GridPagerAdapter {
private final OnCommandSelectedListener mListener;
private UartConfiguration mConfiguration;
public interface OnCommandSelectedListener {
public void onCommandSelected(final Command command);
}
public UARTCommandsAdapter(final UartConfiguration configuration, final OnCommandSelectedListener listener) {
this.mConfiguration = configuration;
this.mListener = listener;
}
public void setConfiguration(final UartConfiguration configuration) {
// Configuration is null when it has been deleted on the handheld
this.mConfiguration = configuration;
notifyDataSetChanged();
}
@Override
public int getRowCount() {
return 1;
}
@Override
public int getColumnCount(final int row) {
final int count = mConfiguration != null ? mConfiguration.getCommands().length : 0;
return count > 0 ? count : 1; // Empty view
}
@Override
public Object instantiateItem(final ViewGroup viewGroup, final int row, final int column) {
final View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.action_item, viewGroup, false);
viewGroup.addView(view);
final Command[] commands = mConfiguration != null ? mConfiguration.getCommands() : null;
if (commands != null && commands.length > 0) {
final Command command = commands[column];
final CircularButton icon = (CircularButton) view.findViewById(R.id.icon);
icon.getImageDrawable().setLevel(command.getIconIndex());
icon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
mListener.onCommandSelected(command);
}
});
} else {
// Hide the icon
view.findViewById(R.id.icon).setVisibility(View.GONE);
// and show the message
final TextView emptyView = (TextView) view.findViewById(R.id.empty);
emptyView.setVisibility(View.VISIBLE);
if (commands == null)
emptyView.setText(R.string.configuration_deleted);
}
return view;
}
@Override
public void destroyItem(final ViewGroup viewGroup, final int row, final int column, final Object object) {
final View view = (View) object;
viewGroup.removeView(view);
}
@Override
public boolean isViewFromObject(final View view, final Object object) {
return view == object;
}
}

View File

@@ -0,0 +1,126 @@
/*
* 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;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.support.wearable.view.CircledImageView;
import android.support.wearable.view.WearableListView;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import no.nordicsemi.android.nrftoolbox.R;
public class UARTConfigurationItemLayout extends LinearLayout implements WearableListView.OnCenterProximityListener {
private static final int ANIMATION_DURATION_MS = 150;
/**
* The ratio for the size of a circle in shrink state.
*/
private static final float SHRINK_CIRCLE_RATIO = .75f;
private static final float SHRINK_LABEL_ALPHA = .5f;
private static final float EXPAND_LABEL_ALPHA = 1f;
private float mExpandCircleRadius;
private float mShrinkCircleRadius;
private ObjectAnimator mExpandCircleAnimator;
private ObjectAnimator mFadeInLabelAnimator;
private AnimatorSet mExpandAnimator;
private ObjectAnimator mShrinkCircleAnimator;
private ObjectAnimator mFadeOutLabelAnimator;
private AnimatorSet mShrinkAnimator;
private TextView mName;
private CircledImageView mIcon;
public UARTConfigurationItemLayout(final Context context) {
this(context, null, 0);
}
public UARTConfigurationItemLayout(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
public UARTConfigurationItemLayout(final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mName = (TextView) findViewById(R.id.name);
mIcon = (CircledImageView) findViewById(R.id.icon);
mExpandCircleRadius = mIcon.getCircleRadius();
mShrinkCircleRadius = mExpandCircleRadius * SHRINK_CIRCLE_RATIO;
mShrinkCircleAnimator = ObjectAnimator.ofFloat(mIcon, "circleRadius", mExpandCircleRadius, mShrinkCircleRadius);
mFadeOutLabelAnimator = ObjectAnimator.ofFloat(mName, "alpha", EXPAND_LABEL_ALPHA, SHRINK_LABEL_ALPHA);
mShrinkAnimator = new AnimatorSet().setDuration(ANIMATION_DURATION_MS);
mShrinkAnimator.playTogether(mShrinkCircleAnimator, mFadeOutLabelAnimator);
mExpandCircleAnimator = ObjectAnimator.ofFloat(mIcon, "circleRadius", mShrinkCircleRadius, mExpandCircleRadius);
mFadeInLabelAnimator = ObjectAnimator.ofFloat(mName, "alpha", SHRINK_LABEL_ALPHA, EXPAND_LABEL_ALPHA);
mExpandAnimator = new AnimatorSet().setDuration(ANIMATION_DURATION_MS);
mExpandAnimator.playTogether(mExpandCircleAnimator, mFadeInLabelAnimator);
}
@Override
public void onCenterPosition(final boolean animate) {
if (animate) {
mShrinkAnimator.cancel();
if (!mExpandAnimator.isRunning()) {
mExpandCircleAnimator.setFloatValues(mIcon.getCircleRadius(), mExpandCircleRadius);
mFadeInLabelAnimator.setFloatValues(mName.getAlpha(), EXPAND_LABEL_ALPHA);
mExpandAnimator.start();
}
} else {
mExpandAnimator.cancel();
mIcon.setCircleRadius(mExpandCircleRadius);
mName.setAlpha(EXPAND_LABEL_ALPHA);
}
mIcon.setEnabled(true);
}
@Override
public void onNonCenterPosition(final boolean animate) {
if (animate) {
mExpandAnimator.cancel();
if (!mShrinkAnimator.isRunning()) {
mShrinkCircleAnimator.setFloatValues(mIcon.getCircleRadius(), mShrinkCircleRadius);
mFadeOutLabelAnimator.setFloatValues(mName.getAlpha(), SHRINK_LABEL_ALPHA);
mShrinkAnimator.start();
}
} else {
mShrinkAnimator.cancel();
mIcon.setCircleRadius(mShrinkCircleRadius);
mName.setAlpha(SHRINK_LABEL_ALPHA);
}
mIcon.setEnabled(false);
}
}

View File

@@ -0,0 +1,238 @@
/*
* 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;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.content.LocalBroadcastManager;
import android.support.wearable.view.WearableListView;
import android.widget.Toast;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.wearable.DataApi;
import com.google.android.gms.wearable.DataEventBuffer;
import com.google.android.gms.wearable.DataItem;
import com.google.android.gms.wearable.DataItemBuffer;
import com.google.android.gms.wearable.DataMap;
import com.google.android.gms.wearable.DataMapItem;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.Wearable;
import java.util.ArrayList;
import java.util.List;
import no.nordicsemi.android.nrftoolbox.R;
import no.nordicsemi.android.nrftoolbox.ble.BleProfileService;
import no.nordicsemi.android.nrftoolbox.wearable.common.Constants;
import no.nordicsemi.android.nrftoolbox.uart.domain.UartConfiguration;
public class UARTConfigurationsActivity extends Activity implements GoogleApiClient.ConnectionCallbacks,
DataApi.DataListener, GoogleApiClient.OnConnectionFailedListener, WearableListView.ClickListener, MessageApi.MessageListener {
private UARTConfigurationsAdapter mAdapter;
private GoogleApiClient mGoogleApiClient;
private BleProfileService.LocalBinder mBinder;
private BroadcastReceiver mServiceBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
switch (action) {
case BleProfileService.BROADCAST_CONNECTION_STATE: {
final int state = intent.getIntExtra(BleProfileService.EXTRA_CONNECTION_STATE, BleProfileService.STATE_DISCONNECTED);
if (state == BleProfileService.STATE_DISCONNECTED)
finish();
break;
}
case BleProfileService.BROADCAST_ERROR: {
final String message = intent.getStringExtra(BleProfileService.EXTRA_ERROR_MESSAGE);
// final int errorCode = intent.getIntExtra(BleProfileService.EXTRA_ERROR_CODE, 0);
Toast.makeText(UARTConfigurationsActivity.this, message, Toast.LENGTH_SHORT).show();
// TODO error handing
break;
}
}
}
};
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(final ComponentName name, final IBinder service) {
mBinder = (BleProfileService.LocalBinder) service;
}
@Override
public void onServiceDisconnected(final ComponentName name) {
mBinder = null;
}
};
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list);
// Check if the WEAR device is connected to the UART device itself, or by the phone.
// Binding will fail if we are using phone as proxy as the service has not been started before.
final Intent service = new Intent(this, BleProfileService.class);
bindService(service, mServiceConnection, 0);
final WearableListView listView = (WearableListView) findViewById(R.id.list);
listView.setClickListener(this);
listView.setAdapter(mAdapter = new UARTConfigurationsAdapter(this));
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
// Register the broadcast receiver that will listen for events from the device
final IntentFilter filter = new IntentFilter();
filter.addAction(BleProfileService.BROADCAST_CONNECTION_STATE);
filter.addAction(BleProfileService.BROADCAST_ERROR);
LocalBroadcastManager.getInstance(this).registerReceiver(mServiceBroadcastReceiver, filter);
}
@Override
protected void onDestroy() {
super.onDestroy();
mGoogleApiClient.unregisterConnectionCallbacks(this);
mGoogleApiClient.unregisterConnectionFailedListener(this);
mGoogleApiClient = null;
// If we were binded to the service, disconnect and unbind. The service will terminate itself when disconnected.
if (mBinder != null) {
mBinder.disconnect();
}
unbindService(mServiceConnection);
LocalBroadcastManager.getInstance(this).unregisterReceiver(mServiceBroadcastReceiver);
}
@Override
protected void onStart() {
super.onStart();
mGoogleApiClient.connect();
}
@Override
protected void onStop() {
super.onStop();
Wearable.MessageApi.removeListener(mGoogleApiClient, this);
Wearable.DataApi.removeListener(mGoogleApiClient, this);
mGoogleApiClient.disconnect();
}
@Override
public void onConnected(final Bundle bundle) {
Wearable.DataApi.addListener(mGoogleApiClient, this);
Wearable.MessageApi.addListener(mGoogleApiClient, this);
populateConfigurations();
}
@Override
public void onConnectionSuspended(final int cause) {
Wearable.DataApi.removeListener(mGoogleApiClient, this);
finish();
}
@Override
public void onConnectionFailed(final ConnectionResult connectionResult) {
finish();
}
@Override
public void onDataChanged(final DataEventBuffer dataEventBuffer) {
populateConfigurations();
}
@Override
public void onMessageReceived(final MessageEvent messageEvent) {
// If the activity is binded to service it means that it has connected directly to the device. We ignore messages from the handheld.
if (mBinder != null)
return;
switch (messageEvent.getPath()) {
case Constants.UART.DEVICE_LINKLOSS:
case Constants.UART.DEVICE_DISCONNECTED: {
finish();
break;
}
}
}
@Override
public void onClick(final WearableListView.ViewHolder viewHolder) {
if (viewHolder instanceof UARTConfigurationsAdapter.ConfigurationViewHolder) {
final UARTConfigurationsAdapter.ConfigurationViewHolder holder = (UARTConfigurationsAdapter.ConfigurationViewHolder) viewHolder;
final UartConfiguration configuration = holder.getConfiguration();
final Intent intent = new Intent(this, UARTCommandsActivity.class);
intent.putExtra(UARTCommandsActivity.CONFIGURATION, configuration);
startActivity(intent);
}
}
@Override
public void onTopEmptyRegionClick() {
// do nothing
}
/**
* This method read the UART configurations from the DataApi and populates the adapter with them.
*/
private void populateConfigurations() {
if (mGoogleApiClient.isConnected()) {
final PendingResult<DataItemBuffer> results = Wearable.DataApi.getDataItems(mGoogleApiClient, Uri.parse("wear:" + Constants.UART.CONFIGURATIONS), DataApi.FILTER_PREFIX);
results.setResultCallback(new ResultCallback<DataItemBuffer>() {
@Override
public void onResult(final DataItemBuffer dataItems) {
final List<UartConfiguration> configurations = new ArrayList<>(dataItems.getCount());
for (int i = 0; i < dataItems.getCount(); ++i) {
final DataItem item = dataItems.get(i);
final long id = ContentUris.parseId(item.getUri());
final DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap();
final UartConfiguration configuration = new UartConfiguration(dataMap, id);
configurations.add(configuration);
}
mAdapter.setConfigurations(configurations);
dataItems.release();
}
});
}
}
}

View File

@@ -0,0 +1,88 @@
/*
* 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;
import android.content.Context;
import android.support.wearable.view.WearableListView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.List;
import no.nordicsemi.android.nrftoolbox.R;
import no.nordicsemi.android.nrftoolbox.uart.domain.UartConfiguration;
public class UARTConfigurationsAdapter extends WearableListView.Adapter {
private final LayoutInflater mInflater;
private List<UartConfiguration> mConfigurations;
public UARTConfigurationsAdapter(final Context context) {
mInflater = LayoutInflater.from(context);
}
/**
* Populates the adapter with list of configurations.
*/
public void setConfigurations(final List<UartConfiguration> configurations) {
mConfigurations = configurations;
notifyDataSetChanged();
}
@Override
public WearableListView.ViewHolder onCreateViewHolder(final ViewGroup viewGroup, final int viewType) {
return new ConfigurationViewHolder(mInflater.inflate(R.layout.configuration_item, viewGroup, false));
}
@Override
public void onBindViewHolder(final WearableListView.ViewHolder holder, final int position) {
final ConfigurationViewHolder viewHolder = (ConfigurationViewHolder) holder;
viewHolder.setConfiguration(mConfigurations.get(position));
}
@Override
public int getItemCount() {
return mConfigurations != null ? mConfigurations.size() : 0;
}
public static class ConfigurationViewHolder extends WearableListView.ViewHolder {
private UartConfiguration mConfiguration;
private TextView mName;
public ConfigurationViewHolder(final View itemView) {
super(itemView);
mName = (TextView) itemView.findViewById(R.id.name);
}
private void setConfiguration(final UartConfiguration configuration) {
mConfiguration = configuration;
mName.setText(configuration.getName());
}
public UartConfiguration getConfiguration() {
return mConfiguration;
}
}
}

View File

@@ -0,0 +1,151 @@
/*
* 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;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import java.util.LinkedList;
import java.util.Queue;
import java.util.UUID;
import no.nordicsemi.android.nrftoolbox.ble.BleManager;
import no.nordicsemi.android.nrftoolbox.ble.BleProfile;
public class UARTProfile extends BleProfile {
/** Broadcast sent when a UART message is received. */
public static final String BROADCAST_DATA_RECEIVED = "no.nordicsemi.android.nrftoolbox.uart.BROADCAST_DATA_RECEIVED";
/** The message. */
public static final String EXTRA_DATA = "no.nordicsemi.android.nrftoolbox.EXTRA_DATA";
/** Nordic UART Service UUID */
private final static UUID UART_SERVICE_UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
/** RX characteristic UUID */
private final static UUID UART_RX_CHARACTERISTIC_UUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
/** TX characteristic UUID */
private final static UUID UART_TX_CHARACTERISTIC_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");
/** The maximum packet size is 20 bytes. */
private static final int MAX_PACKET_SIZE = 20;
/**
* This method should return true if the profile matches the given device. That means if the device has the required services.
* @param gatt the GATT device
* @return true if the device is supported by that profile, false otherwise.
*/
public static boolean matchDevice(final BluetoothGatt gatt) {
final BluetoothGattService service = gatt.getService(UART_SERVICE_UUID);
return service != null && service.getCharacteristic(UART_TX_CHARACTERISTIC_UUID) != null && service.getCharacteristic(UART_RX_CHARACTERISTIC_UUID) != null;
}
private BluetoothGattCharacteristic mTXCharacteristic;
private BluetoothGattCharacteristic mRXCharacteristic;
private byte[] mOutgoingBuffer;
private int mBufferOffset;
@Override
protected Queue<BleManager.Request> initGatt(final BluetoothGatt gatt) {
final BluetoothGattService service = gatt.getService(UART_SERVICE_UUID);
mTXCharacteristic = service.getCharacteristic(UART_TX_CHARACTERISTIC_UUID);
mRXCharacteristic = service.getCharacteristic(UART_RX_CHARACTERISTIC_UUID);
final int rxProperties = mRXCharacteristic.getProperties();
boolean writeRequest = (rxProperties & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0;
// Set the WRITE REQUEST type when the characteristic supports it. This will allow to send long write (also if the characteristic support it).
// In case there is no WRITE REQUEST property, this manager will divide texts longer then 20 bytes into up to 20 bytes chunks.
if (writeRequest)
mRXCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
// We don't want to enable notifications on TX characteristic as we are not showing them here. A watch may be just used to send data. At least now.
// final LinkedList<BleManager.Request> requests = new LinkedList<>();
// requests.push(BleManager.Request.newEnableNotificationsRequest(mTXCharacteristic));
// return requests;
return null;
}
@Override
protected void release() {
mTXCharacteristic = null;
mRXCharacteristic = null;
}
@Override
protected void onCharacteristicNotified(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
// This method will not be called as notifications were not enabled in initGatt(..).
// final Intent intent = new Intent(BROADCAST_DATA_RECEIVED);
// intent.putExtra(EXTRA_DATA, characteristic.getStringValue(0));
// LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
}
@Override
protected void onCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
// When the whole buffer has been sent
final byte[] buffer = mOutgoingBuffer;
if (mBufferOffset == buffer.length) {
mOutgoingBuffer = null;
} else { // Otherwise...
final int length = Math.min(buffer.length - mBufferOffset, MAX_PACKET_SIZE);
final byte[] data = new byte[length]; // We send at most 20 bytes
System.arraycopy(buffer, mBufferOffset, data, 0, length);
mBufferOffset += length;
mRXCharacteristic.setValue(data);
getApi().writeCharacteristic(mRXCharacteristic);
}
}
/**
* Sends the given text to RX characteristic.
* @param text the text to be sent
*/
public void send(final String text) {
// Are we connected?
if (mRXCharacteristic == null)
return;
// An outgoing buffer may not be null if there is already another packet being sent. We do nothing in this case.
if (!TextUtils.isEmpty(text) && mOutgoingBuffer == null) {
final byte[] buffer = mOutgoingBuffer = text.getBytes();
mBufferOffset = 0;
// Depending on whether the characteristic has the WRITE REQUEST property or not, we will either send it as it is (hoping the long write is implemented),
// or divide it into up to 20 bytes chunks and send them one by one.
final boolean writeRequest = (mRXCharacteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0;
if (!writeRequest) { // no WRITE REQUEST property
final int length = Math.min(buffer.length, MAX_PACKET_SIZE);
final byte[] data = new byte[length]; // We send at most 20 bytes
System.arraycopy(buffer, 0, data, 0, length);
mBufferOffset += length;
mRXCharacteristic.setValue(data);
} else { // there is WRITE REQUEST property
mRXCharacteristic.setValue(buffer);
mBufferOffset = buffer.length;
}
getApi().writeCharacteristic(mRXCharacteristic);
}
}
}

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.domain;
import android.os.Parcel;
import android.os.Parcelable;
import com.google.android.gms.wearable.DataMap;
import no.nordicsemi.android.nrftoolbox.wearable.common.Constants;
public class Command implements Parcelable {
public enum Icon {
LEFT(0),
UP(1),
RIGHT(2),
DOWN(3),
SETTINGS(4),
REW(5),
PLAY(6),
PAUSE(7),
STOP(8),
FWD(9),
INFO(10),
NUMBER_1(11),
NUMBER_2(12),
NUMBER_3(13),
NUMBER_4(14),
NUMBER_5(15),
NUMBER_6(16),
NUMBER_7(17),
NUMBER_8(18),
NUMBER_9(19);
public int index;
private Icon(final int index) {
this.index = index;
}
}
private Icon icon = Icon.LEFT;
private String command;
/* package */ Command(final DataMap dataMap) {
icon = Icon.values()[dataMap.getInt(Constants.UART.Configuration.Command.ICON_ID)];
command = dataMap.getString(Constants.UART.Configuration.Command.MESSAGE);
}
private Command(final Parcel in) {
icon = (Icon) in.readSerializable();
command = in.readString();
}
/**
* Sets the command.
* @param command the command that will be sent to UART device
*/
/* package */ void setCommand(final String command) {
this.command = command;
}
/**
* Sets the icon index.
* @param index index of the icon.
*/
/* package */ void setIconIndex(final int index) {
this.icon = Icon.values()[index];
}
/**
* Returns the command that will be sent to UART device.
* @return the command
*/
public String getCommand() {
return command;
}
/**
* Returns the icon index.
* @return the icon index
*/
public int getIconIndex() {
return icon.index;
}
@Override
public int describeContents() {
return 0;
}
public static final Parcelable.Creator<Command> CREATOR = new Parcelable.Creator<Command>() {
@Override
public Command createFromParcel(final Parcel in) {
return new Command(in);
}
@Override
public Command[] newArray(final int size) {
return new Command[size];
}
};
@Override
public void writeToParcel(final Parcel dest, int flags) {
dest.writeSerializable(icon);
dest.writeString(command);
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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.domain;
import android.os.Parcel;
import android.os.Parcelable;
import com.google.android.gms.wearable.DataMap;
import java.util.ArrayList;
import no.nordicsemi.android.nrftoolbox.wearable.common.Constants;
public class UartConfiguration implements Parcelable {
private long id;
private String name;
private Command[] commands;
public UartConfiguration(final DataMap dataMap, final long id) {
name = dataMap.getString(Constants.UART.Configuration.NAME);
final ArrayList<DataMap> maps = dataMap.getDataMapArrayList(Constants.UART.Configuration.COMMANDS);
commands = new Command[maps.size()];
for (int i = 0; i < maps.size(); ++i) {
commands[i] = new Command(maps.get(i));
}
this.id = id;
}
private UartConfiguration(final Parcel in) {
id = in.readLong();
name = in.readString();
commands = in.createTypedArray(Command.CREATOR);
}
/**
* Returns the configuration ID.
* @return the ID of the configuration in the handheld's database.
*/
public long getId() {
return id;
}
/**
* Returns the field name
*
* @return optional name
*/
public String getName() {
return name;
}
/**
* Returns the array of commands. There is always 9 of them.
* @return the commands array
*/
public Command[] getCommands() {
return commands;
}
@Override
public int describeContents() {
return 0;
}
public static final Parcelable.Creator<UartConfiguration> CREATOR = new Parcelable.Creator<UartConfiguration>() {
@Override
public UartConfiguration createFromParcel(final Parcel in) {
return new UartConfiguration(in);
}
@Override
public UartConfiguration[] newArray(final int size) {
return new UartConfiguration[size];
}
};
@Override
public void writeToParcel(final Parcel dest, int flags) {
dest.writeLong(id);
dest.writeString(name);
dest.writeTypedArray(commands, 0);
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
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.nrftoolbox.wearable.common.Constants;
public class ActionReceiver extends BroadcastReceiver {
private static final String TAG = "ActionReceiver";
public static final String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.ACTION_DISCONNECT";
public static final String EXTRA_DATA = "no.nordicsemi.android.nrftoolbox.EXTRA_DATA";
@Override
public void onReceive(final Context context, final Intent intent) {
switch (intent.getAction()) {
case ACTION_DISCONNECT: {
final String profile = intent.getStringExtra(EXTRA_DATA);
sendMessageToHandheld(context, Constants.ACTION_DISCONNECT, profile);
break;
}
}
}
/**
* Sends the given message to the handheld.
* @param path message path
* @param message the message
*/
private void sendMessageToHandheld(final @NonNull Context context, final @NonNull String path, final @NonNull String message) {
new Thread(new Runnable() {
@Override
public void run() {
final GoogleApiClient client = new GoogleApiClient.Builder(context)
.addApi(Wearable.API)
.build();
client.blockingConnect();
final NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(client).await();
for(Node node : nodes.getNodes()) {
final MessageApi.SendMessageResult result = Wearable.MessageApi.sendMessage(client, node.getId(), path, message.getBytes()).await();
if (!result.getStatus().isSuccess()){
Log.w(TAG, "Failed to send " + path + " to " + node.getDisplayName());
}
}
client.disconnect();
}
}).start();
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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.app.PendingIntent;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.Node;
import no.nordicsemi.android.nrftoolbox.R;
import no.nordicsemi.android.nrftoolbox.wearable.common.Constants;
import no.nordicsemi.android.nrftoolbox.uart.UARTConfigurationsActivity;
public class MainWearableListenerService extends com.google.android.gms.wearable.WearableListenerService {
public static final String TAG = "UARTWLS";
private static final int UART_SHOW_CONFIGURATIONS = 1;
private static final int UART_DISCONNECT = 2;
private static final int UART_NOTIFICATION_ID = 1;
@Override
public void onMessageReceived(final MessageEvent messageEvent) {
final String message = new String(messageEvent.getData());
switch (messageEvent.getPath()) {
case Constants.UART.DEVICE_CONNECTED: {
// Disconnect action
final Intent disconnectIntent = new Intent(ActionReceiver.ACTION_DISCONNECT);
disconnectIntent.putExtra(ActionReceiver.EXTRA_DATA, Constants.UART.PROFILE);
final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, UART_DISCONNECT, disconnectIntent, PendingIntent.FLAG_CANCEL_CURRENT);
// Open action
final Intent intent = new Intent(this, UARTConfigurationsActivity.class);
final PendingIntent pendingIntent = PendingIntent.getActivity(this, UART_SHOW_CONFIGURATIONS, intent, PendingIntent.FLAG_UPDATE_CURRENT);
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setContentIntent(pendingIntent)
.setOngoing(true)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(getString(R.string.notif_uart_device_connected))
.setContentText(message)
.addAction(new NotificationCompat.Action(R.drawable.ic_full_bluetooth, getString(R.string.action_disconnect), disconnectAction))
.setLocalOnly(true);
NotificationManagerCompat.from(this).notify(UART_NOTIFICATION_ID, builder.build());
break;
}
case Constants.UART.DEVICE_LINKLOSS:
case Constants.UART.DEVICE_DISCONNECTED: {
NotificationManagerCompat.from(this).cancel(UART_NOTIFICATION_ID);
}
default:
super.onMessageReceived(messageEvent);
break;
}
}
@Override
public void onPeerDisconnected(final Node peer) {
super.onPeerDisconnected(peer);
NotificationManagerCompat.from(this).cancel(UART_NOTIFICATION_ID);
}
}

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/button_pressed" android:state_pressed="true" android:state_enabled="true"/>
<item android:color="@color/button_disabled" android:state_enabled="false"/>
<item android:color="@color/button_normal" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 914 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 848 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 B

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:maxLevel="0" android:drawable="@drawable/ic_uart_left" />
<item android:maxLevel="1" android:drawable="@drawable/ic_uart_up" />
<item android:maxLevel="2" android:drawable="@drawable/ic_uart_right" />
<item android:maxLevel="3" android:drawable="@drawable/ic_uart_down" />
<item android:maxLevel="4" android:drawable="@drawable/ic_uart_settings" />
<item android:maxLevel="5" android:drawable="@drawable/ic_uart_rewind" />
<item android:maxLevel="6" android:drawable="@drawable/ic_uart_play" />
<item android:maxLevel="7" android:drawable="@drawable/ic_uart_pause" />
<item android:maxLevel="8" android:drawable="@drawable/ic_uart_stop" />
<item android:maxLevel="9" android:drawable="@drawable/ic_uart_forward" />
<item android:maxLevel="10" android:drawable="@drawable/ic_uart_about" />
<item android:maxLevel="11" android:drawable="@drawable/ic_uart_1" />
<item android:maxLevel="12" android:drawable="@drawable/ic_uart_2" />
<item android:maxLevel="13" android:drawable="@drawable/ic_uart_3" />
<item android:maxLevel="14" android:drawable="@drawable/ic_uart_4" />
<item android:maxLevel="15" android:drawable="@drawable/ic_uart_5" />
<item android:maxLevel="16" android:drawable="@drawable/ic_uart_6" />
<item android:maxLevel="17" android:drawable="@drawable/ic_uart_7" />
<item android:maxLevel="18" android:drawable="@drawable/ic_uart_8" />
<item android:maxLevel="19" android:drawable="@drawable/ic_uart_9" />
</level-list>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.wearable.view.CircularButton
android:id="@+id/icon"
android:layout_width="104dp"
android:layout_height="104dp"
android:layout_gravity="center"
android:src="@drawable/ic_uart_action"
android:color="@color/button_normal"
app:buttonRippleColor="@color/button_pressed"/>
<TextView
android:id="@+id/empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/configuration_empty"
android:textColor="@color/white"
android:visibility="gone"/>
</FrameLayout>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/backgroundColor">
<android.support.wearable.view.GridViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true"/>
<android.support.wearable.view.DotsPageIndicator
android:id="@+id/page_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"/>
</FrameLayout>

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<android.support.wearable.view.BoxInsetLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_box="left|right">
<android.support.wearable.view.WearableListView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:dividerHeight="0dp"
android:scrollbars="none"/>
</RelativeLayout>
</android.support.wearable.view.BoxInsetLayout>

Some files were not shown because too many files have changed in this diff Show More