diff options
author | Alexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com> | 2023-08-13 11:34:37 +0300 |
---|---|---|
committer | Alexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com> | 2023-08-13 11:34:37 +0300 |
commit | eb1d62a5b6da490fb7fea6414cfa78a8542b14cb (patch) | |
tree | 8a7788a4e3aa41761a5958d182f940ee86afab9e | |
parent | 8b2fa7a2ac97990930668c0a0a0bf8252c76ab35 (diff) |
Daily step counter
-rw-r--r-- | app/build.gradle | 28 | ||||
-rw-r--r-- | app/src/main/AndroidManifest.xml | 5 | ||||
-rw-r--r-- | app/src/main/java/com/clusterrr/hexeditorwatchface/HexWatchFace.java | 151 | ||||
-rw-r--r-- | app/src/main/res/xml/watch_face_info.xml | 5 | ||||
-rw-r--r-- | gradle.properties | 4 |
5 files changed, 120 insertions, 73 deletions
diff --git a/app/build.gradle b/app/build.gradle index c18b71c..76e5aac 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,14 +3,14 @@ plugins { } android { - compileSdk 32 + compileSdk 34 defaultConfig { applicationId "com.clusterrr.hexeditorwatchface" - minSdk 23 - targetSdk 32 - versionCode 18 - versionName "1.6" + minSdk 30 + targetSdk 34 + versionCode 19 + versionName "1.7" } buildTypes { @@ -30,13 +30,15 @@ android { } dependencies { - implementation 'androidx.recyclerview:recyclerview:1.2.1' - implementation 'com.google.android.support:wearable:2.8.1' - implementation 'com.google.android.gms:play-services-base:17.6.0' + implementation 'androidx.recyclerview:recyclerview:1.3.1' + implementation 'androidx.health:health-services-client:1.1.0-alpha01' + implementation 'com.google.android.support:wearable:2.9.0' + implementation 'com.google.android.gms:play-services-base:18.2.0' implementation 'androidx.palette:palette:1.0.0' - implementation 'androidx.appcompat:appcompat:1.4.1' - implementation 'androidx.preference:preference:1.1.1' - implementation 'com.google.android.material:material:1.4.0' - implementation 'androidx.wear:wear:1.2.0' - compileOnly 'com.google.android.wearable:wearable:2.8.1' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.preference:preference:1.2.1' + implementation 'com.google.android.material:material:1.9.0' + implementation 'androidx.wear:wear:1.3.0' + implementation 'com.google.guava:guava:29.0-android' + compileOnly 'com.google.android.wearable:wearable:2.9.0' }
\ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c003ccc..1792fa8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Required to act as a custom watch face. -->
<uses-permission android:name="android.permission.BODY_SENSORS" />
+ <uses-permission android:name="android.permission.BODY_SENSORS_BACKGROUND" />
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- Required for complications to receive complication data and open the provider chooser. -->
@@ -22,6 +23,10 @@ android:supportsRtl="true"
android:icon="@drawable/icon">
+ <property
+ android:name="com.google.wear.watchface.format.version"
+ android:value="1" />
+
<meta-data
android:name="com.google.android.wearable.standalone"
android:value="true" />
diff --git a/app/src/main/java/com/clusterrr/hexeditorwatchface/HexWatchFace.java b/app/src/main/java/com/clusterrr/hexeditorwatchface/HexWatchFace.java index e5afd18..14c01cc 100644 --- a/app/src/main/java/com/clusterrr/hexeditorwatchface/HexWatchFace.java +++ b/app/src/main/java/com/clusterrr/hexeditorwatchface/HexWatchFace.java @@ -1,5 +1,7 @@ package com.clusterrr.hexeditorwatchface; +import static kotlin.jvm.internal.Reflection.createKotlinClass; + import android.Manifest; import android.content.BroadcastReceiver; import android.content.Context; @@ -17,10 +19,29 @@ import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.health.connect.HealthConnectException; +import android.health.connect.ReadRecordsResponse; import android.os.BatteryManager; import android.os.Handler; import android.os.Message; +import androidx.annotation.NonNull; +import androidx.health.services.client.HealthServices; +import androidx.health.services.client.HealthServicesClient; +import androidx.health.services.client.PassiveListenerCallback; +import androidx.health.services.client.PassiveMonitoringClient; +import androidx.health.services.client.data.DataPoint; +import androidx.health.services.client.data.DataPointContainer; +import androidx.health.services.client.data.DataType; +import androidx.health.services.client.data.DeltaDataType; +import androidx.health.services.client.data.ExerciseType; +import androidx.health.services.client.data.IntervalDataPoint; +import androidx.health.services.client.data.PassiveListenerConfig; +import androidx.health.services.client.data.PassiveMonitoringCapabilities; +import androidx.health.services.client.data.UserActivityInfo; +import androidx.health.services.client.data.UserActivityState; + +import android.os.SystemClock; import android.support.wearable.watchface.CanvasWatchFaceService; import android.support.wearable.watchface.WatchFaceStyle; import android.util.Log; @@ -28,9 +49,16 @@ import android.view.SurfaceHolder; import androidx.core.content.ContextCompat; +import com.google.common.util.concurrent.ListenableFuture; + import java.lang.ref.WeakReference; +import java.time.Instant; import java.util.Arrays; import java.util.Calendar; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.TimeZone; import java.util.concurrent.TimeUnit; @@ -76,7 +104,7 @@ public class HexWatchFace extends CanvasWatchFaceService { } } - private class Engine extends CanvasWatchFaceService.Engine implements SensorEventListener { + private class Engine extends CanvasWatchFaceService.Engine implements SensorEventListener, PassiveListenerCallback { /* Handler to update the time once a second in interactive mode. */ private final Handler mUpdateTimeHandler = new EngineHandler(this); private Calendar mCalendar; @@ -108,7 +136,7 @@ public class HexWatchFace extends CanvasWatchFaceService { private int mTouchCount = 0; private SensorManager mSensorManager = null; private Sensor mHeartRateSensor = null; - private Sensor mStepCountSensor = null; + private PassiveMonitoringClient mStepPassiveMonitoringClient = null; private int mBackgroundMinX = 0; private int mBackgroundMinY = 0; private int mBackgroundMaxX = 0; @@ -144,10 +172,6 @@ public class HexWatchFace extends CanvasWatchFaceService { mLegendHeartRate = BitmapFactory.decodeResource(res, R.drawable.legend_heart_rate); mNumbers = new HexNumbers(res); mSensorManager = ((SensorManager)getSystemService(SENSOR_SERVICE)); - - if (mCalendar.get(Calendar.DAY_OF_MONTH) == prefs.getInt(getString(R.string.pref_steps_day), 0)) { - mStepCounter = prefs.getInt(getString(R.string.pref_today_step_last), 0); - } } @Override @@ -634,20 +658,29 @@ public class HexWatchFace extends CanvasWatchFaceService { /* && !mAmbient && isVisible() */) { // Enable step sensor if need - if (mStepCountSensor == null) { - mStepCountSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER); - mSensorManager.registerListener(this, mStepCountSensor, SensorManager.SENSOR_DELAY_NORMAL); + if (mStepPassiveMonitoringClient == null) { + HealthServicesClient healthServicesClient = HealthServices.getClient(getApplicationContext()); + mStepPassiveMonitoringClient = healthServicesClient.getPassiveMonitoringClient(); + + Set<DataType<?,?>> dataTypes = new HashSet<>(); + dataTypes.add(DataType.STEPS_DAILY); + PassiveListenerConfig passiveListenerConfig = PassiveListenerConfig.builder() + //.setShouldUserActivityInfoBeRequested(true) + .setDataTypes(dataTypes) + .build(); + mStepPassiveMonitoringClient.setPassiveListenerCallback(passiveListenerConfig, this); Log.i(TAG, "Step sensor enabled"); } - } else if (mStepCountSensor != null) + } else if (mStepPassiveMonitoringClient != null) { // Disable step sensor - mSensorManager.unregisterListener(this, mStepCountSensor); - mStepCountSensor = null; + mStepPassiveMonitoringClient.clearPassiveListenerCallbackAsync(); + mStepPassiveMonitoringClient = null; Log.i(TAG, "Step sensor Disabled"); } } + // Heart rate receiver @Override public void onSensorChanged(SensorEvent event) { //Log.d(TAG, "New sensor data: " + event.sensor.getType()); @@ -659,57 +692,59 @@ public class HexWatchFace extends CanvasWatchFaceService { //Log.d(TAG, "Heart rate: " + mHeartRate); } break; - case Sensor.TYPE_STEP_COUNTER: - SharedPreferences prefs = getApplicationContext().getSharedPreferences(getString(R.string.app_name), MODE_PRIVATE); - int steps; - try { - steps = (int) event.values[0]; - } - catch (Exception ex) { - steps = 0; - } - // It's a bit tricky because we can get steps since reboot only - int todayStepStart = prefs.getInt(getString(R.string.pref_today_step_start), 0); - if (steps >= 0 && ( - // Check if it's new day - (mCalendar.get(Calendar.DAY_OF_MONTH) != prefs.getInt(getString(R.string.pref_steps_day), 0)) - || (steps < todayStepStart)) // or value reset - ) { - // Store new day values - prefs.edit() - .putInt(getString(R.string.pref_steps_day), mCalendar.get(Calendar.DAY_OF_MONTH)) - .putInt(getString(R.string.pref_today_step_start), steps) - .apply(); - steps = 0; - } else { - // Calculate today steps - steps = Math.max(steps - todayStepStart, 0); - int last = prefs.getInt(getString(R.string.pref_today_step_last), 0); - if (steps < last) { - // Reboot? - Log.d(TAG, "Reboot? Recalculate todayStepStart from " + todayStepStart + " to todayStepStart-"+last); - todayStepStart -= last; - prefs.edit() - .putInt(getString(R.string.pref_steps_day), mCalendar.get(Calendar.DAY_OF_MONTH)) - .putInt(getString(R.string.pref_today_step_start), steps) - .apply(); - steps = Math.max(steps - todayStepStart, 0); - } - } - if (steps / STEPS_SAVE_INTERVAL != mStepCounter / STEPS_SAVE_INTERVAL) { - // Save last value every 10 steps - prefs.edit() - .putInt(getString(R.string.pref_today_step_last), steps) - .apply(); - } - mStepCounter = steps; - Log.d(TAG, "Steps: " + steps + ", today: " + mStepCounter); - break; } } @Override public void onAccuracyChanged(Sensor sensor, int i) { + // unused + } + + // Steps receiver + @Override + public void onNewDataPointsReceived(@NonNull DataPointContainer dataPoints) { + PassiveListenerCallback.super.onNewDataPointsReceived(dataPoints); + + List<IntervalDataPoint<Long>> dps = dataPoints.getData(DataType.STEPS_DAILY); + Instant bootInstant = Instant.ofEpochMilli(System.currentTimeMillis() - SystemClock.elapsedRealtime()); + + long ts = 0; + long steps = 0; + + if (!dps.isEmpty()) { + for (IntervalDataPoint<Long> dp : dps) + { + Instant endTime = dp.getEndInstant(bootInstant); + if (endTime.toEpochMilli() > ts) + { + ts = endTime.toEpochMilli(); + steps = dp.getValue(); + } + } + } + + mStepCounter = (int)steps; + Log.d(TAG, "Today steps: " + mStepCounter); + } + + @Override + public void onRegistered() { + PassiveListenerCallback.super.onRegistered(); + Log.d(TAG, "Step counter sensor registered"); + } + + @Override + public void onPermissionLost() { + PassiveListenerCallback.super.onPermissionLost(); + mStepPassiveMonitoringClient = null; + Log.e(TAG, "Step counter permission lost"); + } + + @Override + public void onRegistrationFailed(@NonNull Throwable throwable) { + PassiveListenerCallback.super.onRegistrationFailed(throwable); + mStepPassiveMonitoringClient = null; + Log.d(TAG, "Step counter sensor unregistered"); } } }
\ No newline at end of file diff --git a/app/src/main/res/xml/watch_face_info.xml b/app/src/main/res/xml/watch_face_info.xml new file mode 100644 index 0000000..d3d363c --- /dev/null +++ b/app/src/main/res/xml/watch_face_info.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<WatchFaceInfo xmlns:android="http://schemas.android.com/apk/res/android"> + <Preview android:value="@drawable/preview_notround" /> + <Editable android:value="true" /> +</WatchFaceInfo>
\ No newline at end of file diff --git a/gradle.properties b/gradle.properties index d1f95ee..37a4c64 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects @@ -21,4 +21,4 @@ android.useAndroidX=true android.nonTransitiveRClass=true android.defaults.buildfeatures.buildconfig=true android.nonFinalResIds=false -org.gradle.unsafe.configuration-cache=true
\ No newline at end of file +org.gradle.unsafe.configuration-cache=true |