commit 732763a3283e0fe5953a65af3753d2d15f5856ba Author: Ruben van de Ven Date: Mon Oct 22 11:42:35 2018 +0200 Detect heart rate sensor diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd45b12 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +*.iml +.gradle +/local.properties +/.idea/caches/build_file_checksums.ser +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..30aa626 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..7ac24c7 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..e0d5b93 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..9c99446 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "com.rubenvandeven.heartbeatstreamer" + minSdkVersion 21 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + + implementation files("libs/antpluginlib_3-6-0.jar") + implementation files("libs/fit_16.00.jar") +} diff --git a/app/libs/antpluginlib_3-6-0.jar b/app/libs/antpluginlib_3-6-0.jar new file mode 100644 index 0000000..06627a9 Binary files /dev/null and b/app/libs/antpluginlib_3-6-0.jar differ diff --git a/app/libs/fit_16.00.jar b/app/libs/fit_16.00.jar new file mode 100644 index 0000000..af9c071 Binary files /dev/null and b/app/libs/fit_16.00.jar differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# 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 *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/rubenvandeven/heartbeatstreamer/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/rubenvandeven/heartbeatstreamer/ExampleInstrumentedTest.java new file mode 100644 index 0000000..ae18192 --- /dev/null +++ b/app/src/androidTest/java/com/rubenvandeven/heartbeatstreamer/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.rubenvandeven.heartbeatstreamer; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.rubenvandeven.heartbeatstreamer", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a13aa5a --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/rubenvandeven/heartbeatstreamer/MainActivity.java b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/MainActivity.java new file mode 100644 index 0000000..7b55d09 --- /dev/null +++ b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/MainActivity.java @@ -0,0 +1,21 @@ + +package com.rubenvandeven.heartbeatstreamer; + +import android.content.Intent; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; + +import com.rubenvandeven.heartbeatstreamer.heartrate.Activity_SearchUiHeartRateSampler; + +public class MainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + Intent intent = new Intent(this, Activity_SearchUiHeartRateSampler.class); + startActivity(intent); + + } +} diff --git a/app/src/main/java/com/rubenvandeven/heartbeatstreamer/heartrate/Activity_AsyncScanHeartRateSampler.java b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/heartrate/Activity_AsyncScanHeartRateSampler.java new file mode 100644 index 0000000..b466d1c --- /dev/null +++ b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/heartrate/Activity_AsyncScanHeartRateSampler.java @@ -0,0 +1,235 @@ +/* +This software is subject to the license described in the License.txt file +included with this software distribution. You may not use this file except in compliance +with this license. + +Copyright (c) Dynastream Innovations Inc. 2013 +All rights reserved. + */ + +package com.rubenvandeven.heartbeatstreamer.heartrate; + +import android.os.Bundle; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + + +import com.dsi.ant.plugins.antplus.pcc.AntPlusHeartRatePcc; +import com.dsi.ant.plugins.antplus.pcc.defines.DeviceState; +import com.dsi.ant.plugins.antplus.pcc.defines.RequestAccessResult; +import com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc.IPluginAccessResultReceiver; +import com.dsi.ant.plugins.antplus.pccbase.AsyncScanController; +import com.dsi.ant.plugins.antplus.pccbase.AsyncScanController.AsyncScanResultDeviceInfo; +import com.dsi.ant.plugins.antplus.pccbase.AsyncScanController.IAsyncScanResultReceiver; +import com.rubenvandeven.heartbeatstreamer.R; + +import java.util.ArrayList; + + + +/** + * Requests access to the heart rate using the asynchronous scan method, showing the scan results as a list. + */ +public class Activity_AsyncScanHeartRateSampler extends Activity_HeartRateDisplayBase +{ + TextView mTextView_Status; + ArrayList mAlreadyConnectedDeviceInfos; + ArrayList mScannedDeviceInfos; + ArrayAdapter adapter_devNameList; + ArrayAdapter adapter_connDevNameList; + + AsyncScanController hrScanCtrl; + + @Override + protected void onCreate(Bundle savedInstanceState) + { + initScanDisplay(); + super.onCreate(savedInstanceState); + } + + private void initScanDisplay() + { + setContentView(R.layout.activity_async_scan); + + mTextView_Status = (TextView)findViewById(R.id.textView_Status); + mTextView_Status.setText("Scanning for heart rate devices asynchronously..."); + + mAlreadyConnectedDeviceInfos = new ArrayList(); + mScannedDeviceInfos = new ArrayList(); + + //Setup already connected devices list + adapter_connDevNameList = new ArrayAdapter(this, android.R.layout.simple_list_item_1, android.R.id.text1); + ListView listView_alreadyConnectedDevs = (ListView)findViewById(R.id.listView_AlreadyConnectedDevices); + listView_alreadyConnectedDevs.setAdapter(adapter_connDevNameList); + listView_alreadyConnectedDevs.setOnItemClickListener(new OnItemClickListener() + { + //Return the id of the selected already connected device + @Override + public void onItemClick(AdapterView parent, View view, int pos, long id) + { + requestConnectToResult(mAlreadyConnectedDeviceInfos.get(pos)); + } + }); + + + //Setup found devices display list + adapter_devNameList = new ArrayAdapter(this, android.R.layout.simple_list_item_1, android.R.id.text1); + ListView listView_Devices = (ListView)findViewById(R.id.listView_FoundDevices); + listView_Devices.setAdapter(adapter_devNameList); + listView_Devices.setOnItemClickListener(new OnItemClickListener() + { + //Return the id of the selected already connected device + @Override + public void onItemClick(AdapterView parent, View view, int pos, long id) + { + requestConnectToResult(mScannedDeviceInfos.get(pos)); + } + }); + } + + /** + * Requests access to the given search result. + * @param asyncScanResultDeviceInfo The search result to attempt to connect to. + */ + protected void requestConnectToResult(final AsyncScanResultDeviceInfo asyncScanResultDeviceInfo) + { + //Inform the user we are connecting + runOnUiThread(new Runnable() + { + public void run() + { + mTextView_Status.setText("Connecting to " + asyncScanResultDeviceInfo.getDeviceDisplayName()); + releaseHandle = hrScanCtrl.requestDeviceAccess(asyncScanResultDeviceInfo, + new IPluginAccessResultReceiver() + { + @Override + public void onResultReceived(AntPlusHeartRatePcc result, + RequestAccessResult resultCode, DeviceState initialDeviceState) + { + if(resultCode == RequestAccessResult.SEARCH_TIMEOUT) + { + //On a connection timeout the scan automatically resumes, so we inform the user, and go back to scanning + runOnUiThread(new Runnable() + { + public void run() + { + Toast.makeText(Activity_AsyncScanHeartRateSampler.this, "Timed out attempting to connect, try again", Toast.LENGTH_LONG).show(); + mTextView_Status.setText("Scanning for heart rate devices asynchronously..."); + } + }); + } + else + { + //Otherwise the results, including SUCCESS, behave the same as + base_IPluginAccessResultReceiver.onResultReceived(result, resultCode, initialDeviceState); + hrScanCtrl = null; + } + } + }, base_IDeviceStateChangeReceiver); + } + }); + } + + /** + * Requests the asynchronous scan controller + */ + @Override + protected void requestAccessToPcc() + { + initScanDisplay(); + hrScanCtrl = AntPlusHeartRatePcc.requestAsyncScanController(this, 0, + new IAsyncScanResultReceiver() + { + @Override + public void onSearchStopped(RequestAccessResult reasonStopped) + { + //The triggers calling this function use the same codes and require the same actions as those received by the standard access result receiver + base_IPluginAccessResultReceiver.onResultReceived(null, reasonStopped, DeviceState.DEAD); + } + + @Override + public void onSearchResult(final AsyncScanResultDeviceInfo deviceFound) + { + for(AsyncScanResultDeviceInfo i: mScannedDeviceInfos) + { + //The current implementation of the async scan will reset it's ignore list every 30s, + //So we have to handle checking for duplicates in our list if we run longer than that + if(i.getAntDeviceNumber() == deviceFound.getAntDeviceNumber()) + { + //Found already connected device, ignore + return; + } + } + + //We split up devices already connected to the plugin from un-connected devices to make this information more visible to the user, + //since the user most likely wants to be aware of which device they are already using in another app + if(deviceFound.isAlreadyConnected()) + { + mAlreadyConnectedDeviceInfos.add(deviceFound); + runOnUiThread(new Runnable() + { + @Override + public void run() + { + if(adapter_connDevNameList.isEmpty()) //connected device category is invisible unless there are some present + { + findViewById(R.id.listView_AlreadyConnectedDevices).setVisibility(View.VISIBLE); + findViewById(R.id.textView_AlreadyConnectedTitle).setVisibility(View.VISIBLE); + } + adapter_connDevNameList.add(deviceFound.getDeviceDisplayName()); + adapter_connDevNameList.notifyDataSetChanged(); + } + }); + } + else + { + mScannedDeviceInfos.add(deviceFound); + runOnUiThread(new Runnable() + { + @Override + public void run() + { + adapter_devNameList.add(deviceFound.getDeviceDisplayName()); + adapter_devNameList.notifyDataSetChanged(); + } + }); + } + } + }); + } + + + + /** + * Ensures our controller is closed whenever we reset + */ + @Override + protected void handleReset() + { + if(hrScanCtrl != null) + { + hrScanCtrl.closeScanController(); + hrScanCtrl = null; + } + super.handleReset(); + } + + /** + * Ensures our controller is closed whenever we exit + */ + @Override + protected void onDestroy() + { + if(hrScanCtrl != null) + { + hrScanCtrl.closeScanController(); + hrScanCtrl = null; + } + super.onDestroy(); + } +} diff --git a/app/src/main/java/com/rubenvandeven/heartbeatstreamer/heartrate/Activity_HeartRateDisplayBase.java b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/heartrate/Activity_HeartRateDisplayBase.java new file mode 100644 index 0000000..3fe5207 --- /dev/null +++ b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/heartrate/Activity_HeartRateDisplayBase.java @@ -0,0 +1,453 @@ +/* +This software is subject to the license described in the License.txt file +included with this software distribution. You may not use this file except in compliance +with this license. + +Copyright (c) Dynastream Innovations Inc. 2013 +All rights reserved. + */ + +package com.rubenvandeven.heartbeatstreamer.heartrate; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.TextView; +import android.widget.Toast; + +import com.dsi.ant.plugins.antplus.pcc.AntPlusHeartRatePcc; +import com.dsi.ant.plugins.antplus.pcc.AntPlusHeartRatePcc.DataState; +import com.dsi.ant.plugins.antplus.pcc.AntPlusHeartRatePcc.ICalculatedRrIntervalReceiver; +import com.dsi.ant.plugins.antplus.pcc.AntPlusHeartRatePcc.IHeartRateDataReceiver; +import com.dsi.ant.plugins.antplus.pcc.AntPlusHeartRatePcc.IPage4AddtDataReceiver; +import com.dsi.ant.plugins.antplus.pcc.AntPlusHeartRatePcc.RrFlag; +import com.dsi.ant.plugins.antplus.pcc.defines.DeviceState; +import com.dsi.ant.plugins.antplus.pcc.defines.EventFlag; +import com.dsi.ant.plugins.antplus.pcc.defines.RequestAccessResult; +import com.dsi.ant.plugins.antplus.pccbase.AntPlusCommonPcc.IRssiReceiver; +import com.dsi.ant.plugins.antplus.pccbase.PccReleaseHandle; +import com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc.IDeviceStateChangeReceiver; +import com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc.IPluginAccessResultReceiver; +import com.dsi.ant.plugins.antplus.pccbase.AntPlusLegacyCommonPcc.ICumulativeOperatingTimeReceiver; +import com.dsi.ant.plugins.antplus.pccbase.AntPlusLegacyCommonPcc.IManufacturerAndSerialReceiver; +import com.dsi.ant.plugins.antplus.pccbase.AntPlusLegacyCommonPcc.IVersionAndModelReceiver; +import com.rubenvandeven.heartbeatstreamer.R; + +import java.math.BigDecimal; +import java.util.EnumSet; + +/** + * Base class to connects to Heart Rate Plugin and display all the event data. + */ +public abstract class Activity_HeartRateDisplayBase extends Activity +{ + protected abstract void requestAccessToPcc(); + + AntPlusHeartRatePcc hrPcc = null; + protected PccReleaseHandle releaseHandle = null; + + TextView tv_status; + + TextView tv_estTimestamp; + + TextView tv_rssi; + + TextView tv_computedHeartRate; + TextView tv_heartBeatCounter; + TextView tv_heartBeatEventTime; + + TextView tv_manufacturerSpecificByte; + TextView tv_previousHeartBeatEventTime; + + TextView tv_calculatedRrInterval; + + TextView tv_cumulativeOperatingTime; + + TextView tv_manufacturerID; + TextView tv_serialNumber; + + TextView tv_hardwareVersion; + TextView tv_softwareVersion; + TextView tv_modelNumber; + + TextView tv_dataStatus; + TextView tv_rrFlag; + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + handleReset(); + } + + /** + * Resets the PCC connection to request access again and clears any existing display data. + */ + protected void handleReset() + { + //Release the old access if it exists + if(releaseHandle != null) + { + releaseHandle.close(); + } + + requestAccessToPcc(); + } + + protected void showDataDisplay(String status) + { + setContentView(R.layout.activity_heart_rate); + + tv_status = (TextView)findViewById(R.id.textView_Status); + + tv_estTimestamp = (TextView)findViewById(R.id.textView_EstTimestamp); + + tv_rssi = (TextView)findViewById(R.id.textView_Rssi); + + tv_computedHeartRate = (TextView)findViewById(R.id.textView_ComputedHeartRate); + tv_heartBeatCounter = (TextView)findViewById(R.id.textView_HeartBeatCounter); + tv_heartBeatEventTime = (TextView)findViewById(R.id.textView_HeartBeatEventTime); + + tv_manufacturerSpecificByte = (TextView)findViewById(R.id.textView_ManufacturerSpecificByte); + tv_previousHeartBeatEventTime = (TextView)findViewById(R.id.textView_PreviousHeartBeatEventTime); + + tv_calculatedRrInterval = (TextView)findViewById(R.id.textView_CalculatedRrInterval); + + tv_cumulativeOperatingTime = (TextView)findViewById(R.id.textView_CumulativeOperatingTime); + + tv_manufacturerID = (TextView)findViewById(R.id.textView_ManufacturerID); + tv_serialNumber = (TextView)findViewById(R.id.textView_SerialNumber); + + tv_hardwareVersion = (TextView)findViewById(R.id.textView_HardwareVersion); + tv_softwareVersion = (TextView)findViewById(R.id.textView_SoftwareVersion); + tv_modelNumber = (TextView)findViewById(R.id.textView_ModelNumber); + + tv_dataStatus = (TextView)findViewById(R.id.textView_DataStatus); + tv_rrFlag = (TextView)findViewById(R.id.textView_rRFlag); + + //Reset the text display + tv_status.setText(status); + + tv_estTimestamp.setText("---"); + + tv_rssi.setText("---"); + + tv_computedHeartRate.setText("---"); + tv_heartBeatCounter.setText("---"); + tv_heartBeatEventTime.setText("---"); + + tv_manufacturerSpecificByte.setText("---"); + tv_previousHeartBeatEventTime.setText("---"); + + tv_calculatedRrInterval.setText("---"); + + tv_cumulativeOperatingTime.setText("---"); + + tv_manufacturerID.setText("---"); + tv_serialNumber.setText("---"); + + tv_hardwareVersion.setText("---"); + tv_softwareVersion.setText("---"); + tv_modelNumber.setText("---"); + tv_dataStatus.setText("---"); + tv_rrFlag.setText("---"); + } + + /** + * Switches the active view to the data display and subscribes to all the data events + */ + public void subscribeToHrEvents() + { + hrPcc.subscribeHeartRateDataEvent(new IHeartRateDataReceiver() + { + @Override + public void onNewHeartRateData(final long estTimestamp, EnumSet eventFlags, + final int computedHeartRate, final long heartBeatCount, + final BigDecimal heartBeatEventTime, final DataState dataState) + { + // Mark heart rate with asterisk if zero detected + final String textHeartRate = String.valueOf(computedHeartRate) + + ((DataState.ZERO_DETECTED.equals(dataState)) ? "*" : ""); + + // Mark heart beat count and heart beat event time with asterisk if initial value + final String textHeartBeatCount = String.valueOf(heartBeatCount) + + ((DataState.INITIAL_VALUE.equals(dataState)) ? "*" : ""); + final String textHeartBeatEventTime = String.valueOf(heartBeatEventTime) + + ((DataState.INITIAL_VALUE.equals(dataState)) ? "*" : ""); + + runOnUiThread(new Runnable() + { + @Override + public void run() + { + tv_estTimestamp.setText(String.valueOf(estTimestamp)); + + tv_computedHeartRate.setText(textHeartRate); + tv_heartBeatCounter.setText(textHeartBeatCount); + tv_heartBeatEventTime.setText(textHeartBeatEventTime); + + tv_dataStatus.setText(dataState.toString()); + } + }); + } + }); + + hrPcc.subscribePage4AddtDataEvent(new IPage4AddtDataReceiver() + { + @Override + public void onNewPage4AddtData(final long estTimestamp, final EnumSet eventFlags, + final int manufacturerSpecificByte, + final BigDecimal previousHeartBeatEventTime) + { + runOnUiThread(new Runnable() + { + @Override + public void run() + { + tv_estTimestamp.setText(String.valueOf(estTimestamp)); + + tv_manufacturerSpecificByte.setText(String.format("0x%02X", manufacturerSpecificByte)); + tv_previousHeartBeatEventTime.setText(String.valueOf(previousHeartBeatEventTime)); + } + }); + } + }); + + hrPcc.subscribeCumulativeOperatingTimeEvent(new ICumulativeOperatingTimeReceiver() + { + @Override + public void onNewCumulativeOperatingTime(final long estTimestamp, final EnumSet eventFlags, final long cumulativeOperatingTime) + { + runOnUiThread(new Runnable() + { + @Override + public void run() + { + tv_estTimestamp.setText(String.valueOf(estTimestamp)); + + tv_cumulativeOperatingTime.setText(String.valueOf(cumulativeOperatingTime)); + } + }); + } + }); + + hrPcc.subscribeManufacturerAndSerialEvent(new IManufacturerAndSerialReceiver() + { + @Override + public void onNewManufacturerAndSerial(final long estTimestamp, final EnumSet eventFlags, final int manufacturerID, + final int serialNumber) + { + runOnUiThread(new Runnable() + { + @Override + public void run() + { + tv_estTimestamp.setText(String.valueOf(estTimestamp)); + + tv_manufacturerID.setText(String.valueOf(manufacturerID)); + tv_serialNumber.setText(String.valueOf(serialNumber)); + } + }); + } + }); + + hrPcc.subscribeVersionAndModelEvent(new IVersionAndModelReceiver() + { + @Override + public void onNewVersionAndModel(final long estTimestamp, final EnumSet eventFlags, final int hardwareVersion, + final int softwareVersion, final int modelNumber) + { + runOnUiThread(new Runnable() + { + @Override + public void run() + { + tv_estTimestamp.setText(String.valueOf(estTimestamp)); + + tv_hardwareVersion.setText(String.valueOf(hardwareVersion)); + tv_softwareVersion.setText(String.valueOf(softwareVersion)); + tv_modelNumber.setText(String.valueOf(modelNumber)); + } + }); + } + }); + + hrPcc.subscribeCalculatedRrIntervalEvent(new ICalculatedRrIntervalReceiver() + { + @Override + public void onNewCalculatedRrInterval(final long estTimestamp, + EnumSet eventFlags, final BigDecimal rrInterval, final RrFlag flag) + { + runOnUiThread(new Runnable() + { + @Override + public void run() + { + tv_estTimestamp.setText(String.valueOf(estTimestamp)); + tv_rrFlag.setText(flag.toString()); + + // Mark RR with asterisk if source is not cached or page 4 + if (flag.equals(RrFlag.DATA_SOURCE_CACHED) + || flag.equals(RrFlag.DATA_SOURCE_PAGE_4)) + tv_calculatedRrInterval.setText(String.valueOf(rrInterval)); + else + tv_calculatedRrInterval.setText(String.valueOf(rrInterval) + "*"); + } + }); + } + }); + + hrPcc.subscribeRssiEvent(new IRssiReceiver() { + @Override + public void onRssiData(final long estTimestamp, final EnumSet evtFlags, final int rssi) { + runOnUiThread(new Runnable() { + @Override + public void run() { + tv_estTimestamp.setText(String.valueOf(estTimestamp)); + tv_rssi.setText(String.valueOf(rssi) + " dBm"); + } + }); + } + }); + } + + protected IPluginAccessResultReceiver base_IPluginAccessResultReceiver = + new IPluginAccessResultReceiver() + { + //Handle the result, connecting to events on success or reporting failure to user. + @Override + public void onResultReceived(AntPlusHeartRatePcc result, RequestAccessResult resultCode, + DeviceState initialDeviceState) + { + showDataDisplay("Connecting..."); + switch(resultCode) + { + case SUCCESS: + hrPcc = result; + tv_status.setText(result.getDeviceName() + ": " + initialDeviceState); + subscribeToHrEvents(); + if(!result.supportsRssi()) tv_rssi.setText("N/A"); + break; + case CHANNEL_NOT_AVAILABLE: + Toast.makeText(Activity_HeartRateDisplayBase.this, "Channel Not Available", Toast.LENGTH_SHORT).show(); + tv_status.setText("Error. Do Menu->Reset."); + break; + case ADAPTER_NOT_DETECTED: + Toast.makeText(Activity_HeartRateDisplayBase.this, "ANT Adapter Not Available. Built-in ANT hardware or external adapter required.", Toast.LENGTH_SHORT).show(); + tv_status.setText("Error. Do Menu->Reset."); + break; + case BAD_PARAMS: + //Note: Since we compose all the params ourself, we should never see this result + Toast.makeText(Activity_HeartRateDisplayBase.this, "Bad request parameters.", Toast.LENGTH_SHORT).show(); + tv_status.setText("Error. Do Menu->Reset."); + break; + case OTHER_FAILURE: + Toast.makeText(Activity_HeartRateDisplayBase.this, "RequestAccess failed. See logcat for details.", Toast.LENGTH_SHORT).show(); + tv_status.setText("Error. Do Menu->Reset."); + break; + case DEPENDENCY_NOT_INSTALLED: + tv_status.setText("Error. Do Menu->Reset."); + AlertDialog.Builder adlgBldr = new AlertDialog.Builder(Activity_HeartRateDisplayBase.this); + adlgBldr.setTitle("Missing Dependency"); + adlgBldr.setMessage("The required service\n\"" + AntPlusHeartRatePcc.getMissingDependencyName() + "\"\n was not found. You need to install the ANT+ Plugins service or you may need to update your existing version if you already have it. Do you want to launch the Play Store to get it?"); + adlgBldr.setCancelable(true); + adlgBldr.setPositiveButton("Go to Store", new OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + Intent startStore = null; + startStore = new Intent(Intent.ACTION_VIEW,Uri.parse("market://details?id=" + AntPlusHeartRatePcc.getMissingDependencyPackageName())); + startStore.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + Activity_HeartRateDisplayBase.this.startActivity(startStore); + } + }); + adlgBldr.setNegativeButton("Cancel", new OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + dialog.dismiss(); + } + }); + + final AlertDialog waitDialog = adlgBldr.create(); + waitDialog.show(); + break; + case USER_CANCELLED: + tv_status.setText("Cancelled. Do Menu->Reset."); + break; + case UNRECOGNIZED: + Toast.makeText(Activity_HeartRateDisplayBase.this, + "Failed: UNRECOGNIZED. PluginLib Upgrade Required?", + Toast.LENGTH_SHORT).show(); + tv_status.setText("Error. Do Menu->Reset."); + break; + default: + Toast.makeText(Activity_HeartRateDisplayBase.this, "Unrecognized result: " + resultCode, Toast.LENGTH_SHORT).show(); + tv_status.setText("Error. Do Menu->Reset."); + break; + } + } + }; + + //Receives state changes and shows it on the status display line + protected IDeviceStateChangeReceiver base_IDeviceStateChangeReceiver = + new IDeviceStateChangeReceiver() + { + @Override + public void onDeviceStateChange(final DeviceState newDeviceState) + { + runOnUiThread(new Runnable() + { + @Override + public void run() + { + tv_status.setText(hrPcc.getDeviceName() + ": " + newDeviceState); + } + }); + + + } + }; + + @Override + protected void onDestroy() + { + if(releaseHandle != null) + { + releaseHandle.close(); + } + super.onDestroy(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.activity_heart_rate, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch(item.getItemId()) + { + case R.id.menu_reset: + handleReset(); + tv_status.setText("Resetting..."); + return true; + default: + return super.onOptionsItemSelected(item); + } + } +} diff --git a/app/src/main/java/com/rubenvandeven/heartbeatstreamer/heartrate/Activity_SearchUiHeartRateSampler.java b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/heartrate/Activity_SearchUiHeartRateSampler.java new file mode 100644 index 0000000..8c97d89 --- /dev/null +++ b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/heartrate/Activity_SearchUiHeartRateSampler.java @@ -0,0 +1,49 @@ +/* +This software is subject to the license described in the License.txt file +included with this software distribution. You may not use this file except in compliance +with this license. + +Copyright (c) Dynastream Innovations Inc. 2013 +All rights reserved. +*/ + +package com.rubenvandeven.heartbeatstreamer.heartrate; + +import android.content.Intent; +import android.os.Bundle; + +import com.rubenvandeven.heartbeatstreamer.multidevicesearch.Activity_MultiDeviceSearchSampler; +import com.dsi.ant.plugins.antplus.pcc.AntPlusHeartRatePcc; +import com.dsi.ant.plugins.antplus.pccbase.MultiDeviceSearch.MultiDeviceSearchResult; + +/** + * Requests access to the heart rate using the plugin automatic search activity. + */ +public class Activity_SearchUiHeartRateSampler extends Activity_HeartRateDisplayBase +{ + @Override + protected void onCreate(Bundle savedInstanceState) + { + showDataDisplay("Connecting..."); + super.onCreate(savedInstanceState); + } + + @Override + protected void requestAccessToPcc() + { + Intent intent = getIntent(); + if (intent.hasExtra(Activity_MultiDeviceSearchSampler.EXTRA_KEY_MULTIDEVICE_SEARCH_RESULT)) + { + // device has already been selected through the multi-device search + MultiDeviceSearchResult result = intent + .getParcelableExtra(Activity_MultiDeviceSearchSampler.EXTRA_KEY_MULTIDEVICE_SEARCH_RESULT); + releaseHandle = AntPlusHeartRatePcc.requestAccess(this, result.getAntDeviceNumber(), 0, + base_IPluginAccessResultReceiver, base_IDeviceStateChangeReceiver); + } else + { + // starts the plugins UI search + releaseHandle = AntPlusHeartRatePcc.requestAccess(this, this, + base_IPluginAccessResultReceiver, base_IDeviceStateChangeReceiver); + } + } +} diff --git a/app/src/main/java/com/rubenvandeven/heartbeatstreamer/multidevicesearch/Activity_MultiDeviceFilter.java b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/multidevicesearch/Activity_MultiDeviceFilter.java new file mode 100644 index 0000000..7ad02c4 --- /dev/null +++ b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/multidevicesearch/Activity_MultiDeviceFilter.java @@ -0,0 +1,206 @@ +/* +This software is subject to the license described in the License.txt file +included with this software distribution. You may not use this file except in compliance +with this license. + +Copyright (c) Dynastream Innovations Inc. 2014 +All rights reserved. + */ + +package com.rubenvandeven.heartbeatstreamer.multidevicesearch; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.util.SparseArray; +import android.util.SparseBooleanArray; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.Toast; + +import com.dsi.ant.plugins.antplus.pcc.AntPlusHeartRatePcc; +import com.dsi.ant.plugins.antplus.pcc.defines.DeviceType; +import com.dsi.ant.plugins.antplus.pcc.defines.RequestAccessResult; +import com.rubenvandeven.heartbeatstreamer.R; + +import java.util.EnumSet; + +/** + * Starts the search activity after allowing user to select desired device types + */ +public class Activity_MultiDeviceFilter extends Activity +{ + Context mContext; + ListView mListView; + Button mSearch; + Button mSelectAll; + + ArrayAdapter mDeviceTypeListAdapter; + SparseArray mDeviceTypeList = new SparseArray(); + SparseBooleanArray mIsChecked; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_multidevice_filter); + + mContext = getApplicationContext(); + mSearch = (Button) findViewById(R.id.button_StartMultiDeviceSearch); + mSelectAll = (Button) findViewById(R.id.button_SelectAll); + + mListView = (ListView) findViewById(R.id.listView_MultiDeviceFilter); + mListView.setItemsCanFocus(false); + mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + + mDeviceTypeListAdapter = new ArrayAdapter(this, + android.R.layout.simple_list_item_checked, getResources().getStringArray( + R.array.device_types)); + mListView.setAdapter(mDeviceTypeListAdapter); + + mIsChecked = mListView.getCheckedItemPositions(); + + // Add all device types + int i = 0; + mDeviceTypeList.put(i++, DeviceType.BIKE_POWER); + mDeviceTypeList.put(i++, DeviceType.CONTROLLABLE_DEVICE); + mDeviceTypeList.put(i++, DeviceType.FITNESS_EQUIPMENT); + mDeviceTypeList.put(i++, DeviceType.BLOOD_PRESSURE); + mDeviceTypeList.put(i++, DeviceType.GEOCACHE); + mDeviceTypeList.put(i++, DeviceType.ENVIRONMENT); + mDeviceTypeList.put(i++, DeviceType.WEIGHT_SCALE); + mDeviceTypeList.put(i++, DeviceType.HEARTRATE); + mDeviceTypeList.put(i++, DeviceType.BIKE_SPDCAD); + mDeviceTypeList.put(i++, DeviceType.BIKE_CADENCE); + mDeviceTypeList.put(i++, DeviceType.BIKE_SPD); + mDeviceTypeList.put(i++, DeviceType.STRIDE_SDM); + + mSearch.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + EnumSet set = EnumSet.noneOf(DeviceType.class); + + for (int j = 0; j < mIsChecked.size(); j++) + { + int key = mIsChecked.keyAt(j); + if (mIsChecked.get(key)) + set.add(mDeviceTypeList.get(key)); + } + + if (set.isEmpty()) + { + Toast.makeText(mContext, "Please select device type(s) to filter on.", + Toast.LENGTH_SHORT).show(); + + } else + { + Intent i = new Intent(mContext, Activity_MultiDeviceSearchSampler.class); + Bundle args = new Bundle(); + args.putSerializable(Activity_MultiDeviceSearchSampler.FILTER_KEY, set); + i.putExtra(Activity_MultiDeviceSearchSampler.BUNDLE_KEY, args); + // Listen for search stopped results + startActivityForResult(i, Activity_MultiDeviceSearchSampler.RESULT_SEARCH_STOPPED); + } + } + }); + + mSelectAll.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View arg0) + { + for (int i = 0; i < mListView.getCount(); i++) + { + mListView.setItemChecked(i, true); + } + } + }); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + if(resultCode == Activity_MultiDeviceSearchSampler.RESULT_SEARCH_STOPPED) + { + RequestAccessResult result = RequestAccessResult.getValueFromInt(data.getIntExtra(Activity_MultiDeviceSearchSampler.EXTRA_KEY_MULTIDEVICE_SEARCH_RESULT, 0)); + switch(result) + { + case SUCCESS: + // Do nothing on success + break; + case CHANNEL_NOT_AVAILABLE: + Toast.makeText(this, "Channel Not Available", Toast.LENGTH_SHORT).show(); + break; + case ADAPTER_NOT_DETECTED: + Toast.makeText( + this, + "ANT Adapter Not Available. Built-in ANT hardware or external adapter required.", + Toast.LENGTH_SHORT).show(); + break; + case BAD_PARAMS: + // Note: Since we compose all the params ourself, we should + // never see this result + Toast.makeText(this, "Bad request parameters.", Toast.LENGTH_SHORT).show(); + break; + case OTHER_FAILURE: + Toast.makeText(this, "RequestAccess failed. See logcat for details.", + Toast.LENGTH_SHORT).show(); + break; + case DEPENDENCY_NOT_INSTALLED: + AlertDialog.Builder adlgBldr = new AlertDialog.Builder(this); + adlgBldr.setTitle("Missing Dependency"); + adlgBldr.setMessage("The required service\n\"" + + AntPlusHeartRatePcc.getMissingDependencyName() + + "\"\n was not found. You need to install the ANT+ Plugins service or you may need to update your existing version if you already have it. Do you want to launch the Play Store to get it?"); + adlgBldr.setCancelable(true); + adlgBldr.setPositiveButton("Go to Store", new android.content.DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + Intent startStore = null; + startStore = new Intent( + Intent.ACTION_VIEW, + Uri.parse("market://details?id=" + + AntPlusHeartRatePcc.getMissingDependencyPackageName())); + startStore.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + mContext.startActivity(startStore); + } + }); + adlgBldr.setNegativeButton("Cancel", new android.content.DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + dialog.dismiss(); + } + }); + + final AlertDialog waitDialog = adlgBldr.create(); + waitDialog.show(); + break; + case USER_CANCELLED: + break; + case UNRECOGNIZED: + Toast.makeText(this, "Failed: UNRECOGNIZED. PluginLib Upgrade Required?", + Toast.LENGTH_SHORT).show(); + break; + default: + Toast.makeText(this, "Unrecognized result: " + result, Toast.LENGTH_SHORT) + .show(); + break; + } + } + } + +} diff --git a/app/src/main/java/com/rubenvandeven/heartbeatstreamer/multidevicesearch/Activity_MultiDeviceSearchSampler.java b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/multidevicesearch/Activity_MultiDeviceSearchSampler.java new file mode 100644 index 0000000..9bea9d9 --- /dev/null +++ b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/multidevicesearch/Activity_MultiDeviceSearchSampler.java @@ -0,0 +1,262 @@ +/* +This software is subject to the license described in the License.txt file +included with this software distribution. You may not use this file except in compliance +with this license. + +Copyright (c) Dynastream Innovations Inc. 2014 +All rights reserved. + */ + +package com.rubenvandeven.heartbeatstreamer.multidevicesearch; + +import java.util.ArrayList; +import java.util.EnumSet; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import com.rubenvandeven.heartbeatstreamer.R; +import com.rubenvandeven.heartbeatstreamer.heartrate.Activity_SearchUiHeartRateSampler; +import com.dsi.ant.plugins.antplus.pcc.MultiDeviceSearch; +import com.dsi.ant.plugins.antplus.pcc.MultiDeviceSearch.RssiSupport; +import com.dsi.ant.plugins.antplus.pcc.defines.DeviceType; +import com.dsi.ant.plugins.antplus.pcc.defines.RequestAccessResult; +import com.dsi.ant.plugins.antplus.pccbase.MultiDeviceSearch.MultiDeviceSearchResult; + +/** + * Searches for multiple devices on the same channel using the multi-device + * search interface + */ +public class Activity_MultiDeviceSearchSampler extends Activity +{ + /** + * Relates a MultiDeviceSearchResult with an RSSI value + */ + public class MultiDeviceSearchResultWithRSSI + { + public MultiDeviceSearchResult mDevice; + public int mRSSI = Integer.MIN_VALUE; + } + public static final String EXTRA_KEY_MULTIDEVICE_SEARCH_RESULT = "com.rubenvandeven.heartbeatstreamer.multidevicesearch.result"; + public static final String BUNDLE_KEY = "com.rubenvandeven.heartbeatstreamer.multidevicesearch.bundle"; + public static final String FILTER_KEY = "com.rubenvandeven.heartbeatstreamer.multidevicesearch.filter"; + public static final int RESULT_SEARCH_STOPPED = RESULT_FIRST_USER; + + Context mContext; + TextView mStatus; + + ListView mFoundDevicesList; + ArrayList mFoundDevices = new ArrayList(); + ArrayAdapter_MultiDeviceSearchResult mFoundAdapter; + + ListView mConnectedDevicesList; + ArrayList mConnectedDevices = new ArrayList(); + ArrayAdapter_MultiDeviceSearchResult mConnectedAdapter; + + MultiDeviceSearch mSearch; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_multidevice_scan); + + mContext = getApplicationContext(); + mStatus = (TextView) findViewById(R.id.textView_Status); + + mFoundDevicesList = (ListView) findViewById(R.id.listView_FoundDevices); + + mFoundAdapter = new ArrayAdapter_MultiDeviceSearchResult(this, mFoundDevices); + mFoundDevicesList.setAdapter(mFoundAdapter); + + mFoundDevicesList.setOnItemClickListener(new OnItemClickListener() + { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) + { + launchConnection(mFoundAdapter.getItem(position).mDevice); + } + }); + + mConnectedDevicesList = (ListView) findViewById(R.id.listView_AlreadyConnectedDevices); + + mConnectedAdapter = new ArrayAdapter_MultiDeviceSearchResult(this, mConnectedDevices); + mConnectedDevicesList.setAdapter(mConnectedAdapter); + + mConnectedDevicesList.setOnItemClickListener(new OnItemClickListener() + { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) + { + launchConnection(mConnectedAdapter.getItem(position).mDevice); + } + }); + + Intent i = getIntent(); + Bundle args = i.getBundleExtra(BUNDLE_KEY); + @SuppressWarnings("unchecked") + EnumSet devices = (EnumSet) args.getSerializable(FILTER_KEY); + + // start the multi-device search + mSearch = new MultiDeviceSearch(this, devices, mCallback, mRssiCallback); + } + + @Override + public void onDestroy() + { + super.onDestroy(); + + // close and clean-up the multi-device search + mSearch.close(); + } + + public void launchConnection(MultiDeviceSearchResult result) + { + Class activity = null; + switch (result.getAntDeviceType()) + { + case HEARTRATE: + activity = Activity_SearchUiHeartRateSampler.class; + break; + case BIKE_CADENCE: + case BIKE_POWER: + case BIKE_SPD: + case BIKE_SPDCAD: + case BLOOD_PRESSURE: + case ENVIRONMENT: + case WEIGHT_SCALE: + case STRIDE_SDM: + case FITNESS_EQUIPMENT: + case GEOCACHE: + case CONTROLLABLE_DEVICE: + Toast.makeText(this, "Not currently supported", Toast.LENGTH_SHORT).show(); + break; + case UNKNOWN: + break; + default: + break; + } + if (activity != null) + { + Intent intent = new Intent(this, activity); + intent.putExtra(EXTRA_KEY_MULTIDEVICE_SEARCH_RESULT, result); + startActivity(intent); + finish(); + } + } + + /** + * Callbacks from the multi-device search interface + */ + private MultiDeviceSearch.SearchCallbacks mCallback = new MultiDeviceSearch.SearchCallbacks() + { + /** + * Called when a device is found. Display found devices in connected and + * found lists + */ + public void onDeviceFound(final MultiDeviceSearchResult deviceFound) + { + final MultiDeviceSearchResultWithRSSI result = new MultiDeviceSearchResultWithRSSI(); + result.mDevice = deviceFound; + + // We split up devices already connected to the plugin from + // un-connected devices to make this information more visible to the + // user, since the user most likely wants to be aware of which + // device they are already using in another app + if (deviceFound.isAlreadyConnected()) + { + runOnUiThread(new Runnable() + { + @Override + public void run() + { + // connected device category is invisible unless there + // are some present + if (mConnectedAdapter.isEmpty()) + { + findViewById(R.id.textView_AlreadyConnectedTitle).setVisibility( + View.VISIBLE); + mConnectedDevicesList.setVisibility(View.VISIBLE); + } + + mConnectedAdapter.add(result); + mConnectedAdapter.notifyDataSetChanged(); + } + }); + } + else + { + runOnUiThread(new Runnable() + { + @Override + public void run() + { + mFoundAdapter.add(result); + mFoundAdapter.notifyDataSetChanged(); + } + }); + } + } + + /** + * The search has been stopped unexpectedly + */ + public void onSearchStopped(RequestAccessResult reason) + { + Intent result = new Intent(); + result.putExtra(EXTRA_KEY_MULTIDEVICE_SEARCH_RESULT, reason.getIntValue()); + setResult(RESULT_SEARCH_STOPPED, result); + finish(); + } + + @Override + public void onSearchStarted(RssiSupport supportsRssi) { + if(supportsRssi == RssiSupport.UNAVAILABLE) + { + Toast.makeText(mContext, "Rssi information not available.", Toast.LENGTH_SHORT).show(); + } else if(supportsRssi == RssiSupport.UNKNOWN_OLDSERVICE) + { + Toast.makeText(mContext, "Rssi might be supported. Please upgrade the plugin service.", Toast.LENGTH_SHORT).show(); + } + } + }; + + /** + * Callback for RSSI data of previously found devices + */ + private MultiDeviceSearch.RssiCallback mRssiCallback = new MultiDeviceSearch.RssiCallback() + { + /** + * Receive an RSSI data update from a specific found device + */ + @Override + public void onRssiUpdate(final int resultId, final int rssi) + { + runOnUiThread(new Runnable() + { + @Override + public void run() + { + for (MultiDeviceSearchResultWithRSSI result : mFoundDevices) + { + if (result.mDevice.resultID == resultId) + { + result.mRSSI = rssi; + mFoundAdapter.notifyDataSetChanged(); + + break; + } + } + } + }); + } + }; +} diff --git a/app/src/main/java/com/rubenvandeven/heartbeatstreamer/multidevicesearch/ArrayAdapter_MultiDeviceSearchResult.java b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/multidevicesearch/ArrayAdapter_MultiDeviceSearchResult.java new file mode 100644 index 0000000..54c42a6 --- /dev/null +++ b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/multidevicesearch/ArrayAdapter_MultiDeviceSearchResult.java @@ -0,0 +1,111 @@ +/* +This software is subject to the license described in the License.txt file +included with this software distribution. You may not use this file except in compliance +with this license. + +Copyright (c) Dynastream Innovations Inc. 2014 +All rights reserved. + */ + +package com.rubenvandeven.heartbeatstreamer.multidevicesearch; + +import java.util.ArrayList; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.rubenvandeven.heartbeatstreamer.multidevicesearch.Activity_MultiDeviceSearchSampler.MultiDeviceSearchResultWithRSSI; +import com.rubenvandeven.heartbeatstreamer.R; + +/** + * Adapter that displays MultiDeviceSearchResultWithRSSI in a List View with + * layout R.layout.layout_multidevice_searchresult + */ +public class ArrayAdapter_MultiDeviceSearchResult extends + ArrayAdapter +{ + private static final int DEFAULT_MIN_RSSI = -1; + + private ArrayList mData; + private String[] mDeviceTypes; + private int mMinRSSI = DEFAULT_MIN_RSSI; + + public ArrayAdapter_MultiDeviceSearchResult(Context context, + ArrayList data) + { + super(context, R.layout.layout_multidevice_searchresult, data); + mData = data; + mDeviceTypes = context.getResources().getStringArray(R.array.device_types); + } + + /** + * Update the display with new data for the specified position + */ + public View getView(int position, View convertView, ViewGroup parent) + { + if (convertView == null) + { + LayoutInflater inflater = (LayoutInflater) getContext().getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + convertView = inflater.inflate(R.layout.layout_multidevice_searchresult, null); + } + + MultiDeviceSearchResultWithRSSI i = mData.get(position); + + if (i != null) + { + TextView tv_deviceType = (TextView) convertView + .findViewById(R.id.textView_multiDeviceType); + TextView tv_deviceName = (TextView) convertView + .findViewById(R.id.textView_multiDeviceName); + ProgressBar pb_RSSI = (ProgressBar) convertView + .findViewById(R.id.progressBar_multiDeviceRSSI); + + if (tv_deviceType != null) + { + tv_deviceType.setText(mDeviceTypes[i.mDevice.getAntDeviceType().ordinal()]); + } + if (tv_deviceName != null) + { + tv_deviceName.setText(i.mDevice.getDeviceDisplayName()); + } + + // only update once i.mRSSI value has been populated + if (pb_RSSI != null && i.mRSSI != Integer.MIN_VALUE) + { + // display RSSI data + if (pb_RSSI.getVisibility() != View.VISIBLE) + { + convertView.findViewById(R.id.label_RSSI).setVisibility(View.VISIBLE); + pb_RSSI.setVisibility(View.VISIBLE); + } + + // Device is nearest it can be, cap to zero + if (i.mRSSI >= 0) + { + i.mRSSI = 0; + } + + // 0 is farthest away, (- mMinRSSI) is nearest + int nearness = i.mRSSI - mMinRSSI; + + // find the new farthest + if (nearness < 0) + { + mMinRSSI = i.mRSSI; + nearness = 0; + } + + int display = 100 * nearness / -mMinRSSI; + pb_RSSI.setProgress(display); + } + } + + return convertView; + } +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..0d025f9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_async_scan.xml b/app/src/main/res/layout/activity_async_scan.xml new file mode 100644 index 0000000..6be3bb2 --- /dev/null +++ b/app/src/main/res/layout/activity_async_scan.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_heart_rate.xml b/app/src/main/res/layout/activity_heart_rate.xml new file mode 100644 index 0000000..9c283e2 --- /dev/null +++ b/app/src/main/res/layout/activity_heart_rate.xml @@ -0,0 +1,404 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..84f1951 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_multidevice_filter.xml b/app/src/main/res/layout/activity_multidevice_filter.xml new file mode 100644 index 0000000..cd10200 --- /dev/null +++ b/app/src/main/res/layout/activity_multidevice_filter.xml @@ -0,0 +1,54 @@ + + + + +