Skip to content

Commit

Permalink
upgrade tooling, move analysis to separate thread, update layout
Browse files Browse the repository at this point in the history
  • Loading branch information
berdosi committed Oct 23, 2020
1 parent 6c7c134 commit 0649b35
Show file tree
Hide file tree
Showing 15 changed files with 226 additions and 97 deletions.
6 changes: 6 additions & 0 deletions .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 10 additions & 4 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions .idea/jarRepositories.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ android {
}
}
compileSdkVersion 29
buildToolsVersion '28.0.3'
buildToolsVersion '29.0.2'
defaultConfig {
applicationId "eu.berdosi.app.heartbeat"
minSdkVersion 21
Expand All @@ -31,10 +31,10 @@ android {

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
implementation 'com.google.android.material:material:1.2.1'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
4 changes: 2 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
package="eu.berdosi.app.heartbeat">

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<application
android:allowBackup="true"
Expand All @@ -16,7 +15,8 @@
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
android:theme="@style/AppTheme.NoActionBar"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

Expand Down
61 changes: 56 additions & 5 deletions app/src/main/java/eu/berdosi/app/heartbeat/MainActivity.java
Original file line number Diff line number Diff line change
@@ -1,31 +1,67 @@
package eu.berdosi.app.heartbeat;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.graphics.SurfaceTexture;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;

import android.os.Handler;
import android.os.Message;
import android.view.TextureView;
import android.view.Surface;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
import java.util.Date;

public class MainActivity extends Activity {
private final CameraService cameraService = new CameraService(this);

private boolean justShared = false;

@SuppressLint("HandlerLeak")
private final Handler mainHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);

if (msg.what == MESSAGE_UPDATE_REALTIME) {
((TextView) findViewById(R.id.textView)).setText(msg.obj.toString());
}

if (msg.what == MESSAGE_UPDATE_FINAL) {
((EditText) findViewById(R.id.editText)).setText(msg.obj.toString());

findViewById(R.id.floatingActionButton).setClickable(true);
}
}
};

private OutputAnalyzer analyzer;

public static final int MESSAGE_UPDATE_REALTIME = 1;
public static final int MESSAGE_UPDATE_FINAL = 2;

@Override
protected void onResume() {
super.onResume();

analyzer = new OutputAnalyzer(this, findViewById(R.id.graphTextureView));
analyzer = new OutputAnalyzer(this, findViewById(R.id.graphTextureView), mainHandler);

TextureView cameraTextureView = findViewById(R.id.textureView2);

SurfaceTexture previewSurfaceTexture = cameraTextureView.getSurfaceTexture();
if (previewSurfaceTexture != null) {

// justShared is set if one clicks the share button.
if ((previewSurfaceTexture != null) && !justShared) {
// this first appears when we close the application and switch back - TextureView isn't quite ready at the first onResume.
Surface previewSurface = new Surface(previewSurfaceTexture);

Expand All @@ -39,19 +75,34 @@ protected void onPause() {
super.onPause();
cameraService.stop();
if (analyzer != null ) analyzer.stop();
analyzer = new OutputAnalyzer(this, findViewById(R.id.graphTextureView));
analyzer = new OutputAnalyzer(this, findViewById(R.id.graphTextureView), mainHandler);
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

int MY_PERMISSIONS_REQUEST_READ_CONTACTS = 1;
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);

}

public void onClickShareButton(View view) {
final Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_SUBJECT, String.format(getString(R.string.output_header_template), new Date()));
intent.putExtra(
Intent.EXTRA_TEXT,
String.format(
getString(R.string.output_body_template),
((TextView) findViewById(R.id.textView)).getText(),
((EditText) findViewById(R.id.editText)).getText()));

justShared = true;
startActivity(Intent.createChooser(intent, getString(R.string.send_output_to)));
}
}
10 changes: 10 additions & 0 deletions app/src/main/java/eu/berdosi/app/heartbeat/MeasureStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ class MeasureStore {
private final CopyOnWriteArrayList<Measurement<Integer>> measurements = new CopyOnWriteArrayList<>();
private int minimum = 2147483647;
private int maximum = -2147483648;

/**
* The latest N measurements are always averaged in order to smooth the values before it is
* analyzed.
*
* This value may need to be experimented with - it is better on the class level than putting it
* into local scope
*/
@SuppressWarnings("FieldCanBeLocal")
private final int rollingAverageSize = 4;

void add(int measurement) {
Expand Down Expand Up @@ -36,6 +45,7 @@ CopyOnWriteArrayList<Measurement<Float>> getStdValues() {
return stdValues;
}

@SuppressWarnings("SameParameterValue") // this parameter can be set at OutputAnalyzer
CopyOnWriteArrayList<Measurement<Integer>> getLastStdValues(int count) {
if (count < measurements.size()) {
return new CopyOnWriteArrayList<>(measurements.subList(measurements.size() - 1 - count, measurements.size() - 1));
Expand Down
32 changes: 23 additions & 9 deletions app/src/main/java/eu/berdosi/app/heartbeat/OutputAnalyzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.CountDownTimer;
import android.os.Handler;
import android.os.Message;
import android.view.TextureView;
import android.widget.EditText;
import android.widget.TextView;

import java.util.Locale;
import java.util.concurrent.CopyOnWriteArrayList;
Expand All @@ -28,10 +28,12 @@ class OutputAnalyzer {

private CountDownTimer timer;

private final Handler mainHandler;

OutputAnalyzer(Activity activity, TextureView graphTextureView) {
OutputAnalyzer(Activity activity, TextureView graphTextureView, Handler mainHandler) {
this.activity = activity;
this.chartDrawer = new ChartDrawer(graphTextureView);
this.mainHandler = mainHandler;
}

private boolean detectValley() {
Expand All @@ -40,15 +42,15 @@ private boolean detectValley() {
if (subList.size() < valleyDetectionWindowSize) {
return false;
} else {
Integer referenceValue = subList.get((int) Math.ceil(valleyDetectionWindowSize / 2)).measurement;
Integer referenceValue = subList.get((int) Math.ceil(valleyDetectionWindowSize / 2f)).measurement;

for (Measurement<Integer> measurement : subList) {
if (measurement.measurement < referenceValue) return false;
}

// filter out consecutive measurements due to too high measurement rate
return (!subList.get((int) Math.ceil(valleyDetectionWindowSize / 2)).measurement.equals(
subList.get((int) Math.ceil(valleyDetectionWindowSize / 2) - 1).measurement));
return (!subList.get((int) Math.ceil(valleyDetectionWindowSize / 2f)).measurement.equals(
subList.get((int) Math.ceil(valleyDetectionWindowSize / 2f) - 1).measurement));
}
}

Expand All @@ -62,6 +64,7 @@ void measurePulse(TextureView textureView, CameraService cameraService) {
detectedValleys = 0;

timer = new CountDownTimer(measurementLength, measurementInterval) {

@Override
public void onTick(long millisUntilFinished) {
// skip the first measurements, which are broken by exposure metering
Expand Down Expand Up @@ -98,7 +101,8 @@ public void onTick(long millisUntilFinished) {
: (60f * (detectedValleys - 1) / (Math.max(1, (valleys.get(valleys.size() - 1) - valleys.get(0)) / 1000f))),
detectedValleys,
1f * (measurementLength - millisUntilFinished - clipLength) / 1000f);
((TextView) activity.findViewById(R.id.textView)).setText(currentValue);

sendMessage(MainActivity.MESSAGE_UPDATE_REALTIME, currentValue);
}

// draw the chart on a separate thread.
Expand All @@ -120,7 +124,7 @@ public void onFinish() {
detectedValleys - 1,
1f * (valleys.get(valleys.size() - 1) - valleys.get(0)) / 1000f);

((TextView) activity.findViewById(R.id.textView)).setText(currentValue);
sendMessage(MainActivity.MESSAGE_UPDATE_REALTIME, currentValue);

StringBuilder returnValueSb = new StringBuilder();
returnValueSb.append(currentValue);
Expand Down Expand Up @@ -156,13 +160,16 @@ public void onFinish() {
returnValueSb.append(activity.getString(R.string.row_separator));
}

returnValueSb.append(activity.getString(R.string.output_detected_peaks_header));
returnValueSb.append(activity.getString(R.string.row_separator));

// add detected valleys location
for (long tick : valleys) {
returnValueSb.append(tick);
returnValueSb.append(activity.getString(R.string.row_separator));
}

((EditText) activity.findViewById(R.id.editText)).setText(returnValueSb.toString());
sendMessage(MainActivity.MESSAGE_UPDATE_FINAL, returnValueSb.toString());

cameraService.stop();
}
Expand All @@ -176,4 +183,11 @@ void stop() {
timer.cancel();
}
}

void sendMessage(int what, Object message) {
Message msg = new Message();
msg.what = what;
msg.obj = message;
mainHandler.sendMessage(msg);
}
}
Loading

0 comments on commit 0649b35

Please sign in to comment.