Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
12b97a6
Add 'Wear OS' menu option to HRSettingsActivity
roberi Jul 18, 2025
061a94c
Add skeleton implementation for WearHRProvider
roberi Jul 18, 2025
7be7a56
Enhance WearHRProvider with CapabilityClient discovery
roberi Jul 18, 2025
1fcf452
Declare heart rate monitoring capability
roberi Jul 18, 2025
bf01b28
Implement connect/disconnect logic in WearHRProvider
roberi Jul 18, 2025
d2d7d4b
Add helper method for posting actions to HRClient
roberi Jul 18, 2025
a618141
Add body sensors permission
roberi Jul 16, 2025
2b6b9d1
Implement service to receive HR control messages from phone
roberi Jul 18, 2025
6c9a642
Implement service to manage heart rate sensor
roberi Jul 16, 2025
ebc7a47
Initialize and manage HR sensor in HeartRateService
roberi Jul 16, 2025
a9c353b
Extract and log heart rate value from SensorEvent
roberi Jul 16, 2025
c080790
Allow phone to control HeartRateService start/stop
roberi Jul 19, 2025
f0cdf25
Send HR data to phone with accuracy filtering
roberi Jul 19, 2025
10ff19e
Enable WearHRProvider to receive HR data from Wear OS
roberi Jul 19, 2025
0636b25
Add BODY_SENSORS runtime permission check in HeartRateService
roberi Jul 25, 2025
60f799d
Improve error handling for Play Services and device scanning
roberi Jul 25, 2025
b887796
Remove <application> tag from hrdevice manifest
roberi Jul 26, 2025
6602c5b
Improve parsing of nested Android permissions
roberi May 25, 2026
9f156e6
Support READ_HEART_RATE permission for Wear OS 6+
roberi May 25, 2026
fee1319
Track battery level in HeartRateService
roberi May 25, 2026
9b4b9a2
Send Wear OS battery level to phone
roberi May 25, 2026
5841179
Receive Wear OS battery level in WearHRProvider
roberi Jun 5, 2026
8b406c9
Address Copilot review feedback
roberi Aug 14, 2026
6828ef1
Suppress WearRecents lint warning in launchPermissionActivity
roberi Aug 14, 2026
71b8bea
Run .\gradlew spotlessApply
roberi Aug 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/res/menu/hrsettings_menu.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@
android:orderInCategory="100"
android:title="@string/Experimental_HRM_devices" />

<item
android:id="@+id/menu_hrdevice_wear_os"
android:orderInCategory="100"
android:title="@string/Wear_OS_devices"
android:checkable="true"/>

<item
android:id="@+id/menu_hrdevice_mock"
android:checkable="true"
Expand Down
1 change: 1 addition & 0 deletions app/res/values/pref_keys.xml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
<string name="pref_bt_experimental">pref_bt_experimental</string>
<string name="pref_bt_mock">pref_bt_mock</string>
<string name="pref_bt_debug">pref_bt_debug</string>
<string name="pref_bt_wear_os">pref_bt_wear_os</string>

<string name="pref_hrz_age">hrzAge</string>
<string name="pref_hrz_sex">hrzSex</string>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/org/runnerup/view/HRSettingsActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ public void onDestroy() {
put(R.id.menu_hrdevice_paired_ble, R.string.pref_bt_paired_ble);
put(R.id.menu_hrdevice_experimental, R.string.pref_bt_experimental);
put(R.id.menu_hrdevice_mock, R.string.pref_bt_mock);
put(R.id.menu_hrdevice_wear_os, R.string.pref_bt_wear_os);
put(R.id.menu_hrdevice_debug, R.string.pref_bt_debug);
}
};
Expand Down
20 changes: 20 additions & 0 deletions common/src/main/java/org/runnerup/common/util/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,10 @@ interface Intents {
String START_WORKOUT = BuildConfig.applicationIdFull + ".START_WORKOUT";
String PAUSE_WORKOUT = BuildConfig.applicationIdFull + ".PAUSE_WORKOUT";
String RESUME_WORKOUT = BuildConfig.applicationIdFull + ".RESUME_WORKOUT";

/** ID of the node (device) that sent the message/data */
String EXTRA_SOURCE_NODE_ID = BuildConfig.applicationIdFull + ".EXTRA_SOURCE_NODE_ID";

// Used from Wear: Request permission
String EXTRA_PERMISSION_TO_REQUEST =
BuildConfig.applicationIdFull + ".EXTRA_PERMISSION_TO_REQUEST";
Expand Down Expand Up @@ -260,6 +264,16 @@ interface Path {
String MSG_CMD_WORKOUT_RESUME = PREFIX + "/workout/resume";
String MSG_CMD_WORKOUT_NEW_LAP = PREFIX + "/workout/new_lap";
String MSG_CMD_WORKOUT_START = PREFIX + "/workout/start";

/* Msg: start/stop HR from phone to wear */
String MSG_CMD_HR_START = PREFIX + "/hr/start";
String MSG_CMD_HR_STOP = PREFIX + "/hr/stop";

/* Msg: Heart rate data from wear to phone */
String MSG_HEART_RATE = PREFIX + "/hr/data";

/* Msg: Battery level from wear to phone */
String MSG_BATTERY_LEVEL = PREFIX + "/battery/level";
}

interface RunInfo {
Expand All @@ -274,5 +288,11 @@ interface RunInfo {
interface TrackerState {
String STATE = "state";
}

interface Capability {
// Important: The wear app must advertise this exact same capability name in its
// res/values/wear.xml file.
String HEART_RATE_PROVIDER = "runnerup_hr_provider";
}
}
}
1 change: 1 addition & 0 deletions common/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@
<string name="Experimental_HRM_devices">Experimental HRM devices</string>
<string name="Paired_BLE_devices">Paired BLE devices</string>
<string name="Experimental_features">Experimental features</string>
<string name="Wear_OS_devices">Wear OS devices</string>
<string name="Mock_HRM_devices">Mock HRM devices</string>
<string name="Debug_HRM">Debug HRM</string>
<string name="About_RunnerUp">About RunnerUp</string>
Expand Down
6 changes: 0 additions & 6 deletions hrdevice/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,4 @@
android:name="android.permission.BLUETOOTH_CONNECT"
android:minSdkVersion="31" />

<application
android:allowBackup="true"
android:label="@string/app_name">

</application>

</manifest>
3 changes: 3 additions & 0 deletions hrdevice/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,11 @@ repositories {
}

dependencies {
implementation project(':common')

implementation "androidx.appcompat:appcompat:${rootProject.ext.appcompat_version}"
implementation "androidx.preference:preference:${rootProject.ext.preference_version}"
implementation "com.google.android.gms:play-services-wearable:${rootProject.ext.googlePlayServicesWearableVersion}"

if (rootProject.ext.antPlusLibName) {
implementation(name: rootProject.ext.antPlusLibName, ext: 'aar')
Expand Down
3 changes: 1 addition & 2 deletions hrdevice/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<resources>
<string name="app_name">HRDevice</string>

<string name="pref_bt_paired_ble">pref_bt_paired_ble</string>
<string name="pref_bt_experimental">pref_bt_experimental</string>
<string name="pref_bt_mock">pref_bt_mock</string>
<string name="pref_bt_wear_os">pref_bt_wear_os</string>
</resources>
10 changes: 10 additions & 0 deletions hrdevice/src/org/runnerup/hr/HRManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ private static HRProvider getHRProviderImpl(Context ctx, String src) {
return new Bt20Base.StHRMv1(ctx);
}

if (src.contentEquals(WearHRProvider.NAME)) {
if (!WearHRProvider.checkLibrary(ctx)) return null;
return new WearHRProvider(ctx);
}

if (src.contentEquals(MockHRProvider.NAME)) {
return new MockHRProvider(ctx);
}
Expand All @@ -99,6 +104,7 @@ public static List<HRProvider> getHRProviderList(Context ctx) {
Resources res = ctx.getResources();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
boolean experimental = prefs.getBoolean(res.getString(R.string.pref_bt_experimental), false);
boolean wear_os = prefs.getBoolean(res.getString(R.string.pref_bt_wear_os), false);
boolean mock = prefs.getBoolean(res.getString(R.string.pref_bt_mock), false);

List<HRProvider> providers = new ArrayList<>();
Expand All @@ -125,6 +131,10 @@ public static List<HRProvider> getHRProviderList(Context ctx) {
}
}

if (wear_os && WearHRProvider.checkLibrary(ctx)) {
providers.add(new WearHRProvider(ctx));
}

if (mock) {
providers.add(new MockHRProvider(ctx));
}
Expand Down
Loading