Commit 03adf161 authored by toni's avatar toni
Browse files

Initial commit

parents
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<compositeConfiguration>
<compositeBuild compositeDefinitionSource="SCRIPT" />
</compositeConfiguration>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>
\ No newline at end of file
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "de.tonifetzer.sensorrecorder"
minSdkVersion 14
targetSdkVersion 28
versionCode 3
versionName "1.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.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'
}
# 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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.tonifetzer.sensorrecorder">
<!--<uses-feature android:name="android.hardware.type.watch" /> -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.BODY_SENSORS"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<application
android:allowBackup="true"
android:icon="@drawable/icon"
android:label="@string/app_name"
android:roundIcon="@drawable/icon"
android:supportsRtl="true"
android:theme="@style/AppTheme.NoActionBar">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<uses-library android:name="com.google.android.wearable"
android:required="false" />
</application>
</manifest>
\ No newline at end of file
package de.tonifetzer.sensorrecorder;
import android.content.Context;
import android.graphics.Color;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.TextView;
import android.widget.ToggleButton;
import java.util.concurrent.TimeUnit;
import de.tonifetzer.sensorrecorder.sensors.Logger;
import de.tonifetzer.sensorrecorder.sensors.MySensor;
import de.tonifetzer.sensorrecorder.sensors.PhoneSensors;
import de.tonifetzer.sensorrecorder.sensors.SensorType;
public class MainActivity extends AppCompatActivity {
private ToggleButton recButton;
private TextView txtViewTimeCounter;
private TextView txtViewFilename;
private TextView txtViewFilesize;
private PhoneSensors phoneSensors;
private final Logger dataLogger = new Logger(this);
private int loadCounter = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//this is a small hack to get a static context
MainActivity.context = getApplicationContext();
//init the sensors of the phone
phoneSensors = new PhoneSensors(this);
//find ui elements
recButton = findViewById(R.id.toggleButton);
txtViewTimeCounter = findViewById(R.id.textViewTimeCounter);
txtViewFilename = findViewById(R.id.textViewFilename);
txtViewFilesize = findViewById(R.id.textViewFilesize);
//set the listener
phoneSensors.setListener(new MySensor.SensorListener(){
@Override public void onData(final String csv) {}
@Override public void onData(final SensorType id, final String csv) {addDataToFile(id, csv); }
});
recButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(isChecked){
buttonView.setBackgroundColor(Color.parseColor("#FFCC0000"));
startRecording();
}
else{
buttonView.setBackgroundColor(Color.parseColor("#FF669900"));
stopRecording();
}
}
});
}
private void startRecording() {
phoneSensors.onResume(this);
loadCounter = 0;
dataLogger.start();
String path = dataLogger.getFile().getAbsolutePath();
txtViewFilename.setText(path.substring(path.length()-17));
}
private void stopRecording() {
phoneSensors.onPause(this);
dataLogger.stop();
}
private void addDataToFile(final SensorType id, final String csv) {
dataLogger.addCSV(id, csv);
runOnUiThread(new Runnable() {
@Override public void run() {
// dump buffer stats every x entries
if (++loadCounter % 250 == 0) {
//set filesize and buffer dump
final int kbPerMin = (int) (dataLogger.getTotalSize() / 1024 * 1000 * 60 / (System.currentTimeMillis() - dataLogger.getStartTS()));
txtViewFilesize.setText( (dataLogger.getCurrentSize() / 1024) + "kb, " + kbPerMin + "kb/min");
//set time (of course, this is not perfectly accurate, however for this purpose its okay)
long minutes = TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis() - dataLogger.getStartTS());
long seconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - dataLogger.getStartTS());
txtViewTimeCounter.setText(minutes + ":" + (seconds - (minutes * 60)));
}
}
});
}
//This is also part of the hack to get a static context
private static Context context;
public static Context getAppContext() {
return MainActivity.context;
}
}
package de.tonifetzer.sensorrecorder.sensors;
import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import java.io.File;
/**
* SDK save file class. Is able to open a folder on the device independent of the given
* device and android skd version.
*/
public class DataFolder {
private Context context;
private File folder;
public DataFolder(Context context, String folderName){
this.context = context;
// 1) try external data folder
folder = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), folderName);
if (isOK(folder)) {return;}
// 2) try sd-card folder
folder = new File(Environment.getExternalStorageDirectory() + "/" + folderName);
if (isOK(folder)) {return;}
// 3) try internal data folder
folder = new File(context.getApplicationInfo().dataDir);
if (isOK(folder)) {return;}
// all failed
throw new MyException("failed to create/access storage folder");
}
/** ensure the given folder is OK */
private static final boolean isOK(final File folder) {
folder.mkdirs();
final boolean ok = folder.exists() && folder.isDirectory();
if (ok) {
Log.d("dataFolder", "using: " + folder);
} else {
Log.d("dataFolder", "not OK: " + folder);
}
return ok;
}
public File getFolder(){
return folder;
}
}
package de.tonifetzer.sensorrecorder.sensors;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
/**
* log sensor data to file
*/
public final class Logger {
private static final int FLUSH_LIMIT = 2*1024*1024;
private StringBuilder sb = new StringBuilder();
private File file;
private FileOutputStream fos;
private Context context;
private int entries = 0;
private int sizeCurrent = 0;
private int sizeTotal = 0;
/** timestamp of logging start. all entries are relative to this one */
private long startTS = 0;
public Logger(Context context) {
this.context = context;
}
/** start logging (into RAM) */
public final void start() {
// start empty
sb.setLength(0);
entries = 0;
sizeTotal = 0;
sizeCurrent = 0;
// starting timestamp
startTS = System.currentTimeMillis();
// open the output-file immeditaly (to get permission errors)
// but do NOT yet write anything to the file
final DataFolder folder = new DataFolder(context, "sensorOutFiles");
file = new File(folder.getFolder(), startTS + ".csv");
try {
fos = new FileOutputStream(file);
Log.d("logger", "will write to: " + file.toString());
} catch (final Exception e) {
throw new MyException("error while opening log-file", e);
}
}
/** stop logging and flush RAM-data to the flash-chip */
public final void stop() {
synchronized (this) {
flush(true);
close();
}
}
public File getFile() {
return file;
}
public int getCurrentSize() {return sizeCurrent;}
public int getTotalSize() {return sizeTotal;}
public int getNumEntries() {return entries;}
/** add a new CSV entry for the given sensor number to the internal buffer */
public final void addCSV(final SensorType sensorNr, final String csv) {
synchronized (this) {
final long relTS = System.currentTimeMillis() - startTS;
sb.append(relTS); // relative timestamp (uses less space)
sb.append(';');
sb.append(sensorNr.id());
sb.append(';');
sb.append(csv);
sb.append('\n');
++entries;
sizeTotal += csv.length() + 10; // approx!
sizeCurrent = sb.length();
if (sb.length() > FLUSH_LIMIT) {flush(false);}
}
debug();
}
/** helper method for exception-less writing. DO NOT CALL DIRECTLY! */
private final void _write(final byte[] data) {
try {
fos.write(data);
Log.d("logger", "flushed " + data.length + " bytes to disk");
} catch (final Exception e) {
throw new RuntimeException("error while writing log-file", e);
}
}
/** helper-class for background writing */
class FlushAsync extends AsyncTask<byte[], Integer, Integer> {
@Override
protected final Integer doInBackground(byte[][] data) {
_write(data[0]);
return null;
}
};
/** flush current buffer-contents to disk */
private final void flush(boolean sync) {
// fetch current buffer contents to write and hereafter empty the buffer
// this action MUST be atomic, just like the add-method
byte[] data = null;
synchronized (this) {
data = sb.toString().getBytes(); // fetch data to write
sb.setLength(0); // reset the buffer
sizeCurrent = 0;
}
// write
if (sync) {
// write to disk using the current thread
_write(data);
} else {
// write to disk using a background-thread
new FlushAsync().execute(new byte[][] {data});
}
}
private final void close() {
try {
fos.close();
} catch (final Exception e) {
throw new MyException("error while writing log-file", e);
}
}
public final long getStartTS() {
return startTS;
}
int cnt = 0;
private final void debug() {
if (++cnt % 1000 == 0) {
Log.d("buffer", "size: " + sizeCurrent);
}
}
}
package de.tonifetzer.sensorrecorder.sensors;
import android.widget.Toast;
import de.tonifetzer.sensorrecorder.MainActivity;
/**
* Throws an exception into a Toast
*/
public class MyException extends RuntimeException {
MyException(final String err, final Throwable t) {
super(err, t);
Toast.makeText(MainActivity.getAppContext(), err, Toast.LENGTH_LONG).show();
}
MyException(final String err) {
super(err);
Toast.makeText(MainActivity.getAppContext(), err, Toast.LENGTH_LONG).show();
}
}
package de.tonifetzer.sensorrecorder.sensors;
import android.app.Activity;
/**
* base-class for all Sensors
*/
public abstract class MySensor {
/** listen for sensor events */
public interface SensorListener {
public void onData(final String csv);
/** received data from the given sensor */
public void onData(final SensorType id, final String csv);
}
/** the listener to inform (if any) */
protected SensorListener listener = null;
/** start the sensor */
public abstract void onResume(final Activity act);
/** stop the sensor */
public abstract void onPause(final Activity act);
/** attach the given listener to the sensor */
public void setListener(final SensorListener listener) {this.listener = listener;}
}
package de.tonifetzer.sensorrecorder.sensors;
import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Build;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* all available sensors
* and what to do within one class
*
*/
public class PhoneSensors extends MySensor implements SensorEventListener{
private static final int SENSOR_TYPE_HEARTRATE = 65562;
private SensorManager sensorManager;
private Sensor acc;
private Sensor grav;
private Sensor lin_acc;
private Sensor gyro;
private Sensor magnet;
private Sensor press;
private Sensor ori;
private Sensor heart;
private Sensor humidity;
private Sensor rotationVector;
private Sensor light;
private Sensor temperature;
/** local gravity copy (needed for orientation matrix) */
private float[] mGravity = new float[3];
/** local geomagnetic copy (needed for orientation matrix) */
private float[] mGeomagnetic = new float[3];
/** ctor */
public PhoneSensors(final Activity act){
// fetch the sensor manager from the activity
sensorManager = (