From 5ea6350cd38174d56c5056671d48131ead7e6989 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Tue, 13 Oct 2015 19:55:01 -0700 Subject: [PATCH 01/61] 0.4.2 builds in adnrodid studio --- .gitignore | 32 +++ README | 17 -- ant.properties | 19 -- build.gradle | 15 ++ build.xml | 83 ------- custom_rules.xml | 94 -------- default.properties | 12 -- funf_v4/build.gradle | 24 +++ {libs => funf_v4/libs}/gson-2.1-javadoc.jar | Bin {libs => funf_v4/libs}/gson-2.1-sources.jar | Bin {libs => funf_v4/libs}/gson-2.1.jar | Bin funf_v4/lint.xml | 3 + .../src/main/AndroidManifest.xml | 0 .../gson/internal/bind/JsonTreeReader.java | 0 .../gson/internal/bind/JsonTreeWriter.java | 0 .../java}/edu/mit/media/funf/FunfManager.java | 0 .../java}/edu/mit/media/funf/Launcher.java | 0 .../java}/edu/mit/media/funf/Schedule.java | 0 .../mit/media/funf/config/ConfigUpdater.java | 0 .../mit/media/funf/config/Configurable.java | 0 .../ConfigurableTypeAdapterFactory.java | 0 .../ContextInjectorTypeAdapaterFactory.java | 0 .../DefaultRuntimeTypeAdapterFactory.java | 0 .../config/DefaultScheduleSerializer.java | 0 .../media/funf/config/HttpConfigUpdater.java | 0 .../config/RuntimeTypeAdapterFactory.java | 0 .../config/SingletonTypeAdapterFactory.java | 0 .../mit/media/funf/data/DataNormalizer.java | 0 .../media/funf/json/BundleTypeAdapter.java | 0 .../edu/mit/media/funf/json/IJsonArray.java | 0 .../edu/mit/media/funf/json/IJsonObject.java | 0 .../edu/mit/media/funf/json/JsonUtils.java | 0 .../java}/edu/mit/media/funf/math/FFT.java | 0 .../java}/edu/mit/media/funf/math/MFCC.java | 0 .../java}/edu/mit/media/funf/math/Matrix.java | 0 .../java}/edu/mit/media/funf/math/Window.java | 0 .../media/funf/pipeline/BasicPipeline.java | 0 .../edu/mit/media/funf/pipeline/Pipeline.java | 0 .../media/funf/pipeline/PipelineFactory.java | 0 .../java}/edu/mit/media/funf/probe/Probe.java | 0 .../builtin/AccelerometerFeaturesProbe.java | 0 .../builtin/AccelerometerSensorProbe.java | 0 .../funf/probe/builtin/AccountsProbe.java | 0 .../funf/probe/builtin/ActivityProbe.java | 0 .../funf/probe/builtin/AndroidInfoProbe.java | 0 .../funf/probe/builtin/ApplicationsProbe.java | 0 .../probe/builtin/AudioFeaturesProbe.java | 0 .../funf/probe/builtin/AudioMediaProbe.java | 0 .../funf/probe/builtin/BatteryProbe.java | 0 .../funf/probe/builtin/BluetoothProbe.java | 0 .../probe/builtin/BrowserBookmarksProbe.java | 0 .../probe/builtin/BrowserSearchesProbe.java | 0 .../funf/probe/builtin/CallLogProbe.java | 0 .../funf/probe/builtin/CellTowerProbe.java | 0 .../funf/probe/builtin/ContactProbe.java | 0 .../probe/builtin/ContentProviderProbe.java | 0 .../builtin/DatedContentProviderProbe.java | 0 .../probe/builtin/GravitySensorProbe.java | 0 .../probe/builtin/GyroscopeSensorProbe.java | 0 .../funf/probe/builtin/HardwareInfoProbe.java | 0 .../funf/probe/builtin/ImageMediaProbe.java | 0 .../funf/probe/builtin/ImpulseProbe.java | 0 .../funf/probe/builtin/LightSensorProbe.java | 0 .../LinearAccelerationSensorProbe.java | 0 .../funf/probe/builtin/LocationProbe.java | 0 .../builtin/MagneticFieldSensorProbe.java | 0 .../probe/builtin/OrientationSensorProbe.java | 0 .../probe/builtin/PressureSensorProbe.java | 0 .../media/funf/probe/builtin/ProbeKeys.java | 0 .../probe/builtin/ProcessStatisticsProbe.java | 0 .../probe/builtin/ProximitySensorProbe.java | 0 .../builtin/RotationVectorSensorProbe.java | 0 .../builtin/RunningApplicationsProbe.java | 0 .../media/funf/probe/builtin/ScreenProbe.java | 0 .../media/funf/probe/builtin/SensorProbe.java | 0 .../funf/probe/builtin/ServicesProbe.java | 0 .../probe/builtin/SimpleLocationProbe.java | 0 .../media/funf/probe/builtin/SimpleProbe.java | 0 .../media/funf/probe/builtin/SmsProbe.java | 0 .../funf/probe/builtin/TelephonyProbe.java | 0 .../probe/builtin/TemperatureSensorProbe.java | 0 .../funf/probe/builtin/TimeOffsetProbe.java | 0 .../funf/probe/builtin/VideoMediaProbe.java | 0 .../media/funf/probe/builtin/WifiProbe.java | 0 .../mit/media/funf/security/Base64Coder.java | 0 .../edu/mit/media/funf/security/HashUtil.java | 0 .../edu/mit/media/funf/security/MD5.java | 0 .../mit/media/funf/security/RSAEncode.java | 0 .../media/funf/storage/BackedUpArchive.java | 0 .../funf/storage/CompositeFileArchive.java | 0 .../media/funf/storage/DefaultArchive.java | 0 .../media/funf/storage/DirectoryCleaner.java | 0 .../mit/media/funf/storage/FileArchive.java | 0 .../mit/media/funf/storage/FileCopier.java | 0 .../funf/storage/FileDirectoryArchive.java | 0 .../mit/media/funf/storage/HttpArchive.java | 0 .../funf/storage/NameValueDatabaseHelper.java | 0 .../media/funf/storage/RemoteFileArchive.java | 0 .../mit/media/funf/storage/UploadService.java | 0 .../mit/media/funf/time/DecimalTimeUnit.java | 0 .../edu/mit/media/funf/time/NtpMessage.java | 0 .../edu/mit/media/funf/time/TimeUnit.java | 0 .../edu/mit/media/funf/time/TimeUtil.java | 0 .../mit/media/funf/util/AnnotationUtil.java | 0 .../edu/mit/media/funf/util/ArrayUtil.java | 0 .../mit/media/funf/util/AsyncSharedPrefs.java | 0 .../edu/mit/media/funf/util/BundleUtil.java | 0 .../edu/mit/media/funf/util/EqualsUtil.java | 0 .../edu/mit/media/funf/util/FileUtil.java | 0 .../edu/mit/media/funf/util/HashCodeUtil.java | 0 .../java}/edu/mit/media/funf/util/IOUtil.java | 0 .../edu/mit/media/funf/util/LockUtil.java | 0 .../edu/mit/media/funf/util/LogUtil.java | 0 .../mit/media/funf/util/NameGenerator.java | 0 .../edu/mit/media/funf/util/StringUtil.java | 0 .../edu/mit/media/funf/util/UuidUtil.java | 0 .../src/main/res}/values/strings.xml | 0 gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 49896 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 164 ++++++++++++++ gradlew.bat | 90 ++++++++ import-summary.txt | 124 +++++++++++ libs/GSON_LICENSE | 203 ------------------ libs/gson-2.1.jar.orig | Bin 180110 -> 0 bytes lint.xml | 3 - proguard-project.txt | 21 -- proguard.cfg | 40 ---- project.properties | 12 -- settings.gradle | 1 + src/edu/mit/media/funf/probe/.DS_Store | Bin 6148 -> 0 bytes test/.classpath | 9 - test/.gitignore | 3 - test/.project | 40 ---- test/AndroidManifest.xml | 110 ---------- test/ant.properties | 18 -- test/build.xml | 85 -------- test/default.properties | 4 - test/local.properties | 10 - test/old/AccountsProbeTest.java | 27 --- test/old/ActivityProbeTest.java | 77 ------- test/old/AndroidInfoProbeTest.java | 48 ----- test/old/ApplicationsProbeTest.java | 51 ----- test/old/AudioFilesProbeTest.java | 57 ----- test/old/BatteryProbeTest.java | 41 ---- test/old/BluetoothProbeTest.java | 39 ---- test/old/BrowserBookmarksProbeTest.java | 45 ---- test/old/BrowserSearchesProbeTest.java | 45 ---- test/old/CallLogProbeTest.java | 45 ---- test/old/CellProbeTest.java | 45 ---- test/old/ContactProbeTest.java | 101 --------- test/old/GravitySensorProbeTest.java | 68 ------ test/old/HardwareInfoProbeTest.java | 53 ----- test/old/ImagesProbeTest.java | 60 ------ test/old/LightSensorProbeTest.java | 59 ----- test/old/LocationProbeTest.java | 59 ----- test/old/ProbeUtilsTest.java | 47 ---- test/old/ProcessStatisticsProbeTest.java | 27 --- test/old/RunningApplicationsProbeTest.java | 41 ---- test/old/SMSProbeTest.java | 59 ----- test/old/ServicesProbeTest.java | 27 --- test/old/TelephonyProbeTest.java | 67 ------ test/old/TemperatureSensorProbeTest.java | 50 ----- test/old/TimeOffsetProbeTest.java | 42 ---- test/old/VideosProbeTest.java | 54 ----- test/old/WifiProbeTest.java | 53 ----- test/proguard-project.txt | 20 -- test/proguard.cfg | 40 ---- test/project.properties | 12 -- test/res/.gitignore | 2 - .../mit/media/funf/AsyncSharedPrefsTest.java | 130 ----------- .../edu/mit/media/funf/FunfManagerTest.java | 136 ------------ test/src/edu/mit/media/funf/TestPipeline.java | 117 ---------- .../funf/config/TestConfigurableParsing.java | 119 ---------- .../funf/pipeline/BasicPipelineTest.java | 58 ----- .../mit/media/funf/probe/AnnotationsTest.java | 105 --------- .../edu/mit/media/funf/probe/ProbeTest.java | 120 ----------- .../mit/media/funf/probe/ProbeTestCase.java | 31 --- .../funf/probe/builtin/ContactProbeTest.java | 37 ---- .../builtin/RunningApplicationsProbeTest.java | 36 ---- .../funf/probe/builtin/ServicesProbeTest.java | 37 ---- .../probe/builtin/TelephonyProbeTest.java | 37 ---- .../probe/builtin/TestAllBuiltinProbes.java | 151 ------------- .../probe/builtin/TestLocationProbes.java | 109 ---------- .../funf/storage/DefaultArchiveTest.java | 34 --- .../funf/storage/PrefsWriteSpeedTest.java | 50 ----- .../mit/media/funf/tests/ExampleService.java | 27 --- .../edu/mit/media/funf/tests/SensorTest.java | 107 --------- 187 files changed, 459 insertions(+), 3685 deletions(-) delete mode 100644 README delete mode 100644 ant.properties create mode 100644 build.gradle delete mode 100644 build.xml delete mode 100644 custom_rules.xml delete mode 100644 default.properties create mode 100644 funf_v4/build.gradle rename {libs => funf_v4/libs}/gson-2.1-javadoc.jar (100%) rename {libs => funf_v4/libs}/gson-2.1-sources.jar (100%) rename {libs => funf_v4/libs}/gson-2.1.jar (100%) create mode 100644 funf_v4/lint.xml rename AndroidManifest.xml => funf_v4/src/main/AndroidManifest.xml (100%) rename {src => funf_v4/src/main/java}/com/google/gson/internal/bind/JsonTreeReader.java (100%) rename {src => funf_v4/src/main/java}/com/google/gson/internal/bind/JsonTreeWriter.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/FunfManager.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/Launcher.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/Schedule.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/config/ConfigUpdater.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/config/Configurable.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/config/ConfigurableTypeAdapterFactory.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/config/ContextInjectorTypeAdapaterFactory.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/config/DefaultRuntimeTypeAdapterFactory.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/config/DefaultScheduleSerializer.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/config/HttpConfigUpdater.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/config/RuntimeTypeAdapterFactory.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/config/SingletonTypeAdapterFactory.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/data/DataNormalizer.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/json/BundleTypeAdapter.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/json/IJsonArray.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/json/IJsonObject.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/json/JsonUtils.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/math/FFT.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/math/MFCC.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/math/Matrix.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/math/Window.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/pipeline/BasicPipeline.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/pipeline/Pipeline.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/pipeline/PipelineFactory.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/Probe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/AccelerometerFeaturesProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/AccelerometerSensorProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/AccountsProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/ActivityProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/AndroidInfoProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/ApplicationsProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/AudioFeaturesProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/AudioMediaProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/BatteryProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/BluetoothProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/BrowserBookmarksProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/BrowserSearchesProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/CallLogProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/CellTowerProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/ContactProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/ContentProviderProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/DatedContentProviderProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/GravitySensorProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/GyroscopeSensorProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/HardwareInfoProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/ImageMediaProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/ImpulseProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/LightSensorProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/LinearAccelerationSensorProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/LocationProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/MagneticFieldSensorProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/OrientationSensorProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/PressureSensorProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/ProbeKeys.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/ProcessStatisticsProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/ProximitySensorProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/RotationVectorSensorProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/RunningApplicationsProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/ScreenProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/SensorProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/ServicesProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/SimpleLocationProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/SimpleProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/SmsProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/TelephonyProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/TemperatureSensorProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/TimeOffsetProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/VideoMediaProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/probe/builtin/WifiProbe.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/security/Base64Coder.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/security/HashUtil.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/security/MD5.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/security/RSAEncode.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/storage/BackedUpArchive.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/storage/CompositeFileArchive.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/storage/DefaultArchive.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/storage/DirectoryCleaner.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/storage/FileArchive.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/storage/FileCopier.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/storage/FileDirectoryArchive.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/storage/HttpArchive.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/storage/NameValueDatabaseHelper.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/storage/RemoteFileArchive.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/storage/UploadService.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/time/DecimalTimeUnit.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/time/NtpMessage.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/time/TimeUnit.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/time/TimeUtil.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/util/AnnotationUtil.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/util/ArrayUtil.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/util/AsyncSharedPrefs.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/util/BundleUtil.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/util/EqualsUtil.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/util/FileUtil.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/util/HashCodeUtil.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/util/IOUtil.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/util/LockUtil.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/util/LogUtil.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/util/NameGenerator.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/util/StringUtil.java (100%) rename {src => funf_v4/src/main/java}/edu/mit/media/funf/util/UuidUtil.java (100%) rename {res => funf_v4/src/main/res}/values/strings.xml (100%) create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 import-summary.txt delete mode 100644 libs/GSON_LICENSE delete mode 100644 libs/gson-2.1.jar.orig delete mode 100644 lint.xml delete mode 100644 proguard-project.txt delete mode 100644 proguard.cfg delete mode 100644 project.properties create mode 100644 settings.gradle delete mode 100644 src/edu/mit/media/funf/probe/.DS_Store delete mode 100644 test/.classpath delete mode 100644 test/.gitignore delete mode 100644 test/.project delete mode 100644 test/AndroidManifest.xml delete mode 100644 test/ant.properties delete mode 100644 test/build.xml delete mode 100644 test/default.properties delete mode 100644 test/local.properties delete mode 100644 test/old/AccountsProbeTest.java delete mode 100644 test/old/ActivityProbeTest.java delete mode 100644 test/old/AndroidInfoProbeTest.java delete mode 100644 test/old/ApplicationsProbeTest.java delete mode 100644 test/old/AudioFilesProbeTest.java delete mode 100644 test/old/BatteryProbeTest.java delete mode 100644 test/old/BluetoothProbeTest.java delete mode 100644 test/old/BrowserBookmarksProbeTest.java delete mode 100644 test/old/BrowserSearchesProbeTest.java delete mode 100644 test/old/CallLogProbeTest.java delete mode 100644 test/old/CellProbeTest.java delete mode 100644 test/old/ContactProbeTest.java delete mode 100644 test/old/GravitySensorProbeTest.java delete mode 100644 test/old/HardwareInfoProbeTest.java delete mode 100644 test/old/ImagesProbeTest.java delete mode 100644 test/old/LightSensorProbeTest.java delete mode 100644 test/old/LocationProbeTest.java delete mode 100644 test/old/ProbeUtilsTest.java delete mode 100644 test/old/ProcessStatisticsProbeTest.java delete mode 100644 test/old/RunningApplicationsProbeTest.java delete mode 100644 test/old/SMSProbeTest.java delete mode 100644 test/old/ServicesProbeTest.java delete mode 100644 test/old/TelephonyProbeTest.java delete mode 100644 test/old/TemperatureSensorProbeTest.java delete mode 100644 test/old/TimeOffsetProbeTest.java delete mode 100644 test/old/VideosProbeTest.java delete mode 100644 test/old/WifiProbeTest.java delete mode 100644 test/proguard-project.txt delete mode 100644 test/proguard.cfg delete mode 100644 test/project.properties delete mode 100644 test/res/.gitignore delete mode 100644 test/src/edu/mit/media/funf/AsyncSharedPrefsTest.java delete mode 100644 test/src/edu/mit/media/funf/FunfManagerTest.java delete mode 100644 test/src/edu/mit/media/funf/TestPipeline.java delete mode 100644 test/src/edu/mit/media/funf/config/TestConfigurableParsing.java delete mode 100644 test/src/edu/mit/media/funf/pipeline/BasicPipelineTest.java delete mode 100644 test/src/edu/mit/media/funf/probe/AnnotationsTest.java delete mode 100644 test/src/edu/mit/media/funf/probe/ProbeTest.java delete mode 100644 test/src/edu/mit/media/funf/probe/ProbeTestCase.java delete mode 100644 test/src/edu/mit/media/funf/probe/builtin/ContactProbeTest.java delete mode 100644 test/src/edu/mit/media/funf/probe/builtin/RunningApplicationsProbeTest.java delete mode 100644 test/src/edu/mit/media/funf/probe/builtin/ServicesProbeTest.java delete mode 100644 test/src/edu/mit/media/funf/probe/builtin/TelephonyProbeTest.java delete mode 100644 test/src/edu/mit/media/funf/probe/builtin/TestAllBuiltinProbes.java delete mode 100644 test/src/edu/mit/media/funf/probe/builtin/TestLocationProbes.java delete mode 100644 test/src/edu/mit/media/funf/storage/DefaultArchiveTest.java delete mode 100644 test/src/edu/mit/media/funf/storage/PrefsWriteSpeedTest.java delete mode 100644 test/src/edu/mit/media/funf/tests/ExampleService.java delete mode 100644 test/src/edu/mit/media/funf/tests/SensorTest.java diff --git a/.gitignore b/.gitignore index 8448160..a1c7ce3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,35 @@ bin/* gen/* *.log local.properties +# Built application files +*.apk +*.ap_ + +# Files for the Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ +.DS_Store +.idea +*.iml diff --git a/README b/README deleted file mode 100644 index 416c257..0000000 --- a/README +++ /dev/null @@ -1,17 +0,0 @@ -= Funf - Open Sensing Framework = - -== Building the project == -The Funf project can be used as an Android library, or can be packaged as a jar file. -The typical method of using Funf is by using the Funf jar file. To build the funf jar -file, use the 'release' target of the Ant build script. (Make sure you have ant 1.8.* -or later installed.) Then, copy the jar file (bin/funf.jar) to the libs directory of your project. -The Android build scripts (Command line Ant or in Eclipse) will take care of compiling -the jar. - -If you think that you will be making frequent changes to the Funf library, you may -want to integrate Funf as a library project. -To use it as an Android library, add this project as a library dependency of your -Android application project. See the following links for more details. -http://developer.android.com/guide/developing/projects/projects-eclipse.html#ReferencingLibraryProject -or -http://developer.android.com/guide/developing/projects/projects-cmdline.html#ReferencingLibraryProject \ No newline at end of file diff --git a/ant.properties b/ant.properties deleted file mode 100644 index 0c276ff..0000000 --- a/ant.properties +++ /dev/null @@ -1,19 +0,0 @@ -# This file is used to override default values used by the Ant build system. -# -# This file must be checked in Version Control Systems, as it is -# integral to the build system of your project. - -# This file is only used by the Ant script. - -# You can use this to override default values such as -# 'source.dir' for the location of your java source folder and -# 'out.dir' for the location of your output folder. - -# You can also use it define how the release builds are signed by declaring -# the following properties: -# 'key.store' for the location of your keystore and -# 'key.alias' for the name of the key to use. -# The password will be asked during the build when you use the 'release' target. - -keystore=../funf.keystore -keystore.alias=funf diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..6a5c233 --- /dev/null +++ b/build.gradle @@ -0,0 +1,15 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.3.0' + } +} + +allprojects { + repositories { + jcenter() + } +} diff --git a/build.xml b/build.xml deleted file mode 100644 index 30ee4ca..0000000 --- a/build.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/custom_rules.xml b/custom_rules.xml deleted file mode 100644 index 8faf0ef..0000000 --- a/custom_rules.xml +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/default.properties b/default.properties deleted file mode 100644 index 62106dd..0000000 --- a/default.properties +++ /dev/null @@ -1,12 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system use, -# "build.properties", and override values to adapt the script to your -# project structure. - -android.library=true -# Project target. -target=android-7 diff --git a/funf_v4/build.gradle b/funf_v4/build.gradle new file mode 100644 index 0000000..0be7274 --- /dev/null +++ b/funf_v4/build.gradle @@ -0,0 +1,24 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 17 + buildToolsVersion "23.0.0" + + defaultConfig { + minSdkVersion 8 + targetSdkVersion 14 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } +} + +dependencies { + compile files('libs/gson-2.1-javadoc.jar') + compile files('libs/gson-2.1-sources.jar') + compile files('libs/gson-2.1.jar') +} diff --git a/libs/gson-2.1-javadoc.jar b/funf_v4/libs/gson-2.1-javadoc.jar similarity index 100% rename from libs/gson-2.1-javadoc.jar rename to funf_v4/libs/gson-2.1-javadoc.jar diff --git a/libs/gson-2.1-sources.jar b/funf_v4/libs/gson-2.1-sources.jar similarity index 100% rename from libs/gson-2.1-sources.jar rename to funf_v4/libs/gson-2.1-sources.jar diff --git a/libs/gson-2.1.jar b/funf_v4/libs/gson-2.1.jar similarity index 100% rename from libs/gson-2.1.jar rename to funf_v4/libs/gson-2.1.jar diff --git a/funf_v4/lint.xml b/funf_v4/lint.xml new file mode 100644 index 0000000..8423c0e --- /dev/null +++ b/funf_v4/lint.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/AndroidManifest.xml b/funf_v4/src/main/AndroidManifest.xml similarity index 100% rename from AndroidManifest.xml rename to funf_v4/src/main/AndroidManifest.xml diff --git a/src/com/google/gson/internal/bind/JsonTreeReader.java b/funf_v4/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java similarity index 100% rename from src/com/google/gson/internal/bind/JsonTreeReader.java rename to funf_v4/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java diff --git a/src/com/google/gson/internal/bind/JsonTreeWriter.java b/funf_v4/src/main/java/com/google/gson/internal/bind/JsonTreeWriter.java similarity index 100% rename from src/com/google/gson/internal/bind/JsonTreeWriter.java rename to funf_v4/src/main/java/com/google/gson/internal/bind/JsonTreeWriter.java diff --git a/src/edu/mit/media/funf/FunfManager.java b/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java similarity index 100% rename from src/edu/mit/media/funf/FunfManager.java rename to funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java diff --git a/src/edu/mit/media/funf/Launcher.java b/funf_v4/src/main/java/edu/mit/media/funf/Launcher.java similarity index 100% rename from src/edu/mit/media/funf/Launcher.java rename to funf_v4/src/main/java/edu/mit/media/funf/Launcher.java diff --git a/src/edu/mit/media/funf/Schedule.java b/funf_v4/src/main/java/edu/mit/media/funf/Schedule.java similarity index 100% rename from src/edu/mit/media/funf/Schedule.java rename to funf_v4/src/main/java/edu/mit/media/funf/Schedule.java diff --git a/src/edu/mit/media/funf/config/ConfigUpdater.java b/funf_v4/src/main/java/edu/mit/media/funf/config/ConfigUpdater.java similarity index 100% rename from src/edu/mit/media/funf/config/ConfigUpdater.java rename to funf_v4/src/main/java/edu/mit/media/funf/config/ConfigUpdater.java diff --git a/src/edu/mit/media/funf/config/Configurable.java b/funf_v4/src/main/java/edu/mit/media/funf/config/Configurable.java similarity index 100% rename from src/edu/mit/media/funf/config/Configurable.java rename to funf_v4/src/main/java/edu/mit/media/funf/config/Configurable.java diff --git a/src/edu/mit/media/funf/config/ConfigurableTypeAdapterFactory.java b/funf_v4/src/main/java/edu/mit/media/funf/config/ConfigurableTypeAdapterFactory.java similarity index 100% rename from src/edu/mit/media/funf/config/ConfigurableTypeAdapterFactory.java rename to funf_v4/src/main/java/edu/mit/media/funf/config/ConfigurableTypeAdapterFactory.java diff --git a/src/edu/mit/media/funf/config/ContextInjectorTypeAdapaterFactory.java b/funf_v4/src/main/java/edu/mit/media/funf/config/ContextInjectorTypeAdapaterFactory.java similarity index 100% rename from src/edu/mit/media/funf/config/ContextInjectorTypeAdapaterFactory.java rename to funf_v4/src/main/java/edu/mit/media/funf/config/ContextInjectorTypeAdapaterFactory.java diff --git a/src/edu/mit/media/funf/config/DefaultRuntimeTypeAdapterFactory.java b/funf_v4/src/main/java/edu/mit/media/funf/config/DefaultRuntimeTypeAdapterFactory.java similarity index 100% rename from src/edu/mit/media/funf/config/DefaultRuntimeTypeAdapterFactory.java rename to funf_v4/src/main/java/edu/mit/media/funf/config/DefaultRuntimeTypeAdapterFactory.java diff --git a/src/edu/mit/media/funf/config/DefaultScheduleSerializer.java b/funf_v4/src/main/java/edu/mit/media/funf/config/DefaultScheduleSerializer.java similarity index 100% rename from src/edu/mit/media/funf/config/DefaultScheduleSerializer.java rename to funf_v4/src/main/java/edu/mit/media/funf/config/DefaultScheduleSerializer.java diff --git a/src/edu/mit/media/funf/config/HttpConfigUpdater.java b/funf_v4/src/main/java/edu/mit/media/funf/config/HttpConfigUpdater.java similarity index 100% rename from src/edu/mit/media/funf/config/HttpConfigUpdater.java rename to funf_v4/src/main/java/edu/mit/media/funf/config/HttpConfigUpdater.java diff --git a/src/edu/mit/media/funf/config/RuntimeTypeAdapterFactory.java b/funf_v4/src/main/java/edu/mit/media/funf/config/RuntimeTypeAdapterFactory.java similarity index 100% rename from src/edu/mit/media/funf/config/RuntimeTypeAdapterFactory.java rename to funf_v4/src/main/java/edu/mit/media/funf/config/RuntimeTypeAdapterFactory.java diff --git a/src/edu/mit/media/funf/config/SingletonTypeAdapterFactory.java b/funf_v4/src/main/java/edu/mit/media/funf/config/SingletonTypeAdapterFactory.java similarity index 100% rename from src/edu/mit/media/funf/config/SingletonTypeAdapterFactory.java rename to funf_v4/src/main/java/edu/mit/media/funf/config/SingletonTypeAdapterFactory.java diff --git a/src/edu/mit/media/funf/data/DataNormalizer.java b/funf_v4/src/main/java/edu/mit/media/funf/data/DataNormalizer.java similarity index 100% rename from src/edu/mit/media/funf/data/DataNormalizer.java rename to funf_v4/src/main/java/edu/mit/media/funf/data/DataNormalizer.java diff --git a/src/edu/mit/media/funf/json/BundleTypeAdapter.java b/funf_v4/src/main/java/edu/mit/media/funf/json/BundleTypeAdapter.java similarity index 100% rename from src/edu/mit/media/funf/json/BundleTypeAdapter.java rename to funf_v4/src/main/java/edu/mit/media/funf/json/BundleTypeAdapter.java diff --git a/src/edu/mit/media/funf/json/IJsonArray.java b/funf_v4/src/main/java/edu/mit/media/funf/json/IJsonArray.java similarity index 100% rename from src/edu/mit/media/funf/json/IJsonArray.java rename to funf_v4/src/main/java/edu/mit/media/funf/json/IJsonArray.java diff --git a/src/edu/mit/media/funf/json/IJsonObject.java b/funf_v4/src/main/java/edu/mit/media/funf/json/IJsonObject.java similarity index 100% rename from src/edu/mit/media/funf/json/IJsonObject.java rename to funf_v4/src/main/java/edu/mit/media/funf/json/IJsonObject.java diff --git a/src/edu/mit/media/funf/json/JsonUtils.java b/funf_v4/src/main/java/edu/mit/media/funf/json/JsonUtils.java similarity index 100% rename from src/edu/mit/media/funf/json/JsonUtils.java rename to funf_v4/src/main/java/edu/mit/media/funf/json/JsonUtils.java diff --git a/src/edu/mit/media/funf/math/FFT.java b/funf_v4/src/main/java/edu/mit/media/funf/math/FFT.java similarity index 100% rename from src/edu/mit/media/funf/math/FFT.java rename to funf_v4/src/main/java/edu/mit/media/funf/math/FFT.java diff --git a/src/edu/mit/media/funf/math/MFCC.java b/funf_v4/src/main/java/edu/mit/media/funf/math/MFCC.java similarity index 100% rename from src/edu/mit/media/funf/math/MFCC.java rename to funf_v4/src/main/java/edu/mit/media/funf/math/MFCC.java diff --git a/src/edu/mit/media/funf/math/Matrix.java b/funf_v4/src/main/java/edu/mit/media/funf/math/Matrix.java similarity index 100% rename from src/edu/mit/media/funf/math/Matrix.java rename to funf_v4/src/main/java/edu/mit/media/funf/math/Matrix.java diff --git a/src/edu/mit/media/funf/math/Window.java b/funf_v4/src/main/java/edu/mit/media/funf/math/Window.java similarity index 100% rename from src/edu/mit/media/funf/math/Window.java rename to funf_v4/src/main/java/edu/mit/media/funf/math/Window.java diff --git a/src/edu/mit/media/funf/pipeline/BasicPipeline.java b/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java similarity index 100% rename from src/edu/mit/media/funf/pipeline/BasicPipeline.java rename to funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java diff --git a/src/edu/mit/media/funf/pipeline/Pipeline.java b/funf_v4/src/main/java/edu/mit/media/funf/pipeline/Pipeline.java similarity index 100% rename from src/edu/mit/media/funf/pipeline/Pipeline.java rename to funf_v4/src/main/java/edu/mit/media/funf/pipeline/Pipeline.java diff --git a/src/edu/mit/media/funf/pipeline/PipelineFactory.java b/funf_v4/src/main/java/edu/mit/media/funf/pipeline/PipelineFactory.java similarity index 100% rename from src/edu/mit/media/funf/pipeline/PipelineFactory.java rename to funf_v4/src/main/java/edu/mit/media/funf/pipeline/PipelineFactory.java diff --git a/src/edu/mit/media/funf/probe/Probe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/Probe.java similarity index 100% rename from src/edu/mit/media/funf/probe/Probe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/Probe.java diff --git a/src/edu/mit/media/funf/probe/builtin/AccelerometerFeaturesProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/AccelerometerFeaturesProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/AccelerometerFeaturesProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/AccelerometerFeaturesProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/AccelerometerSensorProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/AccelerometerSensorProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/AccelerometerSensorProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/AccelerometerSensorProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/AccountsProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/AccountsProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/AccountsProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/AccountsProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/ActivityProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ActivityProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/ActivityProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ActivityProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/AndroidInfoProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/AndroidInfoProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/AndroidInfoProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/AndroidInfoProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/ApplicationsProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ApplicationsProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/ApplicationsProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ApplicationsProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/AudioFeaturesProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/AudioFeaturesProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/AudioFeaturesProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/AudioFeaturesProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/AudioMediaProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/AudioMediaProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/AudioMediaProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/AudioMediaProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/BatteryProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BatteryProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/BatteryProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BatteryProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/BluetoothProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/BluetoothProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/BrowserBookmarksProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BrowserBookmarksProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/BrowserBookmarksProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BrowserBookmarksProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/BrowserSearchesProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BrowserSearchesProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/BrowserSearchesProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BrowserSearchesProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/CallLogProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/CallLogProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/CallLogProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/CallLogProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/CellTowerProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/CellTowerProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/CellTowerProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/CellTowerProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/ContactProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ContactProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/ContactProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ContactProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/ContentProviderProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ContentProviderProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/ContentProviderProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ContentProviderProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/DatedContentProviderProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/DatedContentProviderProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/DatedContentProviderProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/DatedContentProviderProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/GravitySensorProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/GravitySensorProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/GravitySensorProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/GravitySensorProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/GyroscopeSensorProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/GyroscopeSensorProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/GyroscopeSensorProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/GyroscopeSensorProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/HardwareInfoProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/HardwareInfoProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/HardwareInfoProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/HardwareInfoProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/ImageMediaProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ImageMediaProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/ImageMediaProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ImageMediaProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/ImpulseProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ImpulseProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/ImpulseProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ImpulseProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/LightSensorProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/LightSensorProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/LightSensorProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/LightSensorProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/LinearAccelerationSensorProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/LinearAccelerationSensorProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/LinearAccelerationSensorProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/LinearAccelerationSensorProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/LocationProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/LocationProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/LocationProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/LocationProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/MagneticFieldSensorProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/MagneticFieldSensorProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/MagneticFieldSensorProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/MagneticFieldSensorProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/OrientationSensorProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/OrientationSensorProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/OrientationSensorProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/OrientationSensorProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/PressureSensorProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/PressureSensorProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/PressureSensorProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/PressureSensorProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/ProbeKeys.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ProbeKeys.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/ProbeKeys.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ProbeKeys.java diff --git a/src/edu/mit/media/funf/probe/builtin/ProcessStatisticsProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ProcessStatisticsProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/ProcessStatisticsProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ProcessStatisticsProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/ProximitySensorProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ProximitySensorProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/ProximitySensorProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ProximitySensorProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/RotationVectorSensorProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/RotationVectorSensorProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/RotationVectorSensorProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/RotationVectorSensorProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/RunningApplicationsProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/RunningApplicationsProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/RunningApplicationsProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/RunningApplicationsProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/ScreenProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ScreenProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/ScreenProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ScreenProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/SensorProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/SensorProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/SensorProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/SensorProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/ServicesProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ServicesProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/ServicesProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/ServicesProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/SimpleLocationProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/SimpleLocationProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/SimpleLocationProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/SimpleLocationProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/SimpleProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/SimpleProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/SimpleProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/SimpleProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/SmsProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/SmsProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/SmsProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/SmsProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/TelephonyProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/TelephonyProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/TelephonyProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/TelephonyProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/TemperatureSensorProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/TemperatureSensorProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/TemperatureSensorProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/TemperatureSensorProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/TimeOffsetProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/TimeOffsetProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/TimeOffsetProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/TimeOffsetProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/VideoMediaProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/VideoMediaProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/VideoMediaProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/VideoMediaProbe.java diff --git a/src/edu/mit/media/funf/probe/builtin/WifiProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/WifiProbe.java similarity index 100% rename from src/edu/mit/media/funf/probe/builtin/WifiProbe.java rename to funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/WifiProbe.java diff --git a/src/edu/mit/media/funf/security/Base64Coder.java b/funf_v4/src/main/java/edu/mit/media/funf/security/Base64Coder.java similarity index 100% rename from src/edu/mit/media/funf/security/Base64Coder.java rename to funf_v4/src/main/java/edu/mit/media/funf/security/Base64Coder.java diff --git a/src/edu/mit/media/funf/security/HashUtil.java b/funf_v4/src/main/java/edu/mit/media/funf/security/HashUtil.java similarity index 100% rename from src/edu/mit/media/funf/security/HashUtil.java rename to funf_v4/src/main/java/edu/mit/media/funf/security/HashUtil.java diff --git a/src/edu/mit/media/funf/security/MD5.java b/funf_v4/src/main/java/edu/mit/media/funf/security/MD5.java similarity index 100% rename from src/edu/mit/media/funf/security/MD5.java rename to funf_v4/src/main/java/edu/mit/media/funf/security/MD5.java diff --git a/src/edu/mit/media/funf/security/RSAEncode.java b/funf_v4/src/main/java/edu/mit/media/funf/security/RSAEncode.java similarity index 100% rename from src/edu/mit/media/funf/security/RSAEncode.java rename to funf_v4/src/main/java/edu/mit/media/funf/security/RSAEncode.java diff --git a/src/edu/mit/media/funf/storage/BackedUpArchive.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/BackedUpArchive.java similarity index 100% rename from src/edu/mit/media/funf/storage/BackedUpArchive.java rename to funf_v4/src/main/java/edu/mit/media/funf/storage/BackedUpArchive.java diff --git a/src/edu/mit/media/funf/storage/CompositeFileArchive.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/CompositeFileArchive.java similarity index 100% rename from src/edu/mit/media/funf/storage/CompositeFileArchive.java rename to funf_v4/src/main/java/edu/mit/media/funf/storage/CompositeFileArchive.java diff --git a/src/edu/mit/media/funf/storage/DefaultArchive.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/DefaultArchive.java similarity index 100% rename from src/edu/mit/media/funf/storage/DefaultArchive.java rename to funf_v4/src/main/java/edu/mit/media/funf/storage/DefaultArchive.java diff --git a/src/edu/mit/media/funf/storage/DirectoryCleaner.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/DirectoryCleaner.java similarity index 100% rename from src/edu/mit/media/funf/storage/DirectoryCleaner.java rename to funf_v4/src/main/java/edu/mit/media/funf/storage/DirectoryCleaner.java diff --git a/src/edu/mit/media/funf/storage/FileArchive.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/FileArchive.java similarity index 100% rename from src/edu/mit/media/funf/storage/FileArchive.java rename to funf_v4/src/main/java/edu/mit/media/funf/storage/FileArchive.java diff --git a/src/edu/mit/media/funf/storage/FileCopier.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/FileCopier.java similarity index 100% rename from src/edu/mit/media/funf/storage/FileCopier.java rename to funf_v4/src/main/java/edu/mit/media/funf/storage/FileCopier.java diff --git a/src/edu/mit/media/funf/storage/FileDirectoryArchive.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/FileDirectoryArchive.java similarity index 100% rename from src/edu/mit/media/funf/storage/FileDirectoryArchive.java rename to funf_v4/src/main/java/edu/mit/media/funf/storage/FileDirectoryArchive.java diff --git a/src/edu/mit/media/funf/storage/HttpArchive.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java similarity index 100% rename from src/edu/mit/media/funf/storage/HttpArchive.java rename to funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java diff --git a/src/edu/mit/media/funf/storage/NameValueDatabaseHelper.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/NameValueDatabaseHelper.java similarity index 100% rename from src/edu/mit/media/funf/storage/NameValueDatabaseHelper.java rename to funf_v4/src/main/java/edu/mit/media/funf/storage/NameValueDatabaseHelper.java diff --git a/src/edu/mit/media/funf/storage/RemoteFileArchive.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/RemoteFileArchive.java similarity index 100% rename from src/edu/mit/media/funf/storage/RemoteFileArchive.java rename to funf_v4/src/main/java/edu/mit/media/funf/storage/RemoteFileArchive.java diff --git a/src/edu/mit/media/funf/storage/UploadService.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/UploadService.java similarity index 100% rename from src/edu/mit/media/funf/storage/UploadService.java rename to funf_v4/src/main/java/edu/mit/media/funf/storage/UploadService.java diff --git a/src/edu/mit/media/funf/time/DecimalTimeUnit.java b/funf_v4/src/main/java/edu/mit/media/funf/time/DecimalTimeUnit.java similarity index 100% rename from src/edu/mit/media/funf/time/DecimalTimeUnit.java rename to funf_v4/src/main/java/edu/mit/media/funf/time/DecimalTimeUnit.java diff --git a/src/edu/mit/media/funf/time/NtpMessage.java b/funf_v4/src/main/java/edu/mit/media/funf/time/NtpMessage.java similarity index 100% rename from src/edu/mit/media/funf/time/NtpMessage.java rename to funf_v4/src/main/java/edu/mit/media/funf/time/NtpMessage.java diff --git a/src/edu/mit/media/funf/time/TimeUnit.java b/funf_v4/src/main/java/edu/mit/media/funf/time/TimeUnit.java similarity index 100% rename from src/edu/mit/media/funf/time/TimeUnit.java rename to funf_v4/src/main/java/edu/mit/media/funf/time/TimeUnit.java diff --git a/src/edu/mit/media/funf/time/TimeUtil.java b/funf_v4/src/main/java/edu/mit/media/funf/time/TimeUtil.java similarity index 100% rename from src/edu/mit/media/funf/time/TimeUtil.java rename to funf_v4/src/main/java/edu/mit/media/funf/time/TimeUtil.java diff --git a/src/edu/mit/media/funf/util/AnnotationUtil.java b/funf_v4/src/main/java/edu/mit/media/funf/util/AnnotationUtil.java similarity index 100% rename from src/edu/mit/media/funf/util/AnnotationUtil.java rename to funf_v4/src/main/java/edu/mit/media/funf/util/AnnotationUtil.java diff --git a/src/edu/mit/media/funf/util/ArrayUtil.java b/funf_v4/src/main/java/edu/mit/media/funf/util/ArrayUtil.java similarity index 100% rename from src/edu/mit/media/funf/util/ArrayUtil.java rename to funf_v4/src/main/java/edu/mit/media/funf/util/ArrayUtil.java diff --git a/src/edu/mit/media/funf/util/AsyncSharedPrefs.java b/funf_v4/src/main/java/edu/mit/media/funf/util/AsyncSharedPrefs.java similarity index 100% rename from src/edu/mit/media/funf/util/AsyncSharedPrefs.java rename to funf_v4/src/main/java/edu/mit/media/funf/util/AsyncSharedPrefs.java diff --git a/src/edu/mit/media/funf/util/BundleUtil.java b/funf_v4/src/main/java/edu/mit/media/funf/util/BundleUtil.java similarity index 100% rename from src/edu/mit/media/funf/util/BundleUtil.java rename to funf_v4/src/main/java/edu/mit/media/funf/util/BundleUtil.java diff --git a/src/edu/mit/media/funf/util/EqualsUtil.java b/funf_v4/src/main/java/edu/mit/media/funf/util/EqualsUtil.java similarity index 100% rename from src/edu/mit/media/funf/util/EqualsUtil.java rename to funf_v4/src/main/java/edu/mit/media/funf/util/EqualsUtil.java diff --git a/src/edu/mit/media/funf/util/FileUtil.java b/funf_v4/src/main/java/edu/mit/media/funf/util/FileUtil.java similarity index 100% rename from src/edu/mit/media/funf/util/FileUtil.java rename to funf_v4/src/main/java/edu/mit/media/funf/util/FileUtil.java diff --git a/src/edu/mit/media/funf/util/HashCodeUtil.java b/funf_v4/src/main/java/edu/mit/media/funf/util/HashCodeUtil.java similarity index 100% rename from src/edu/mit/media/funf/util/HashCodeUtil.java rename to funf_v4/src/main/java/edu/mit/media/funf/util/HashCodeUtil.java diff --git a/src/edu/mit/media/funf/util/IOUtil.java b/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java similarity index 100% rename from src/edu/mit/media/funf/util/IOUtil.java rename to funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java diff --git a/src/edu/mit/media/funf/util/LockUtil.java b/funf_v4/src/main/java/edu/mit/media/funf/util/LockUtil.java similarity index 100% rename from src/edu/mit/media/funf/util/LockUtil.java rename to funf_v4/src/main/java/edu/mit/media/funf/util/LockUtil.java diff --git a/src/edu/mit/media/funf/util/LogUtil.java b/funf_v4/src/main/java/edu/mit/media/funf/util/LogUtil.java similarity index 100% rename from src/edu/mit/media/funf/util/LogUtil.java rename to funf_v4/src/main/java/edu/mit/media/funf/util/LogUtil.java diff --git a/src/edu/mit/media/funf/util/NameGenerator.java b/funf_v4/src/main/java/edu/mit/media/funf/util/NameGenerator.java similarity index 100% rename from src/edu/mit/media/funf/util/NameGenerator.java rename to funf_v4/src/main/java/edu/mit/media/funf/util/NameGenerator.java diff --git a/src/edu/mit/media/funf/util/StringUtil.java b/funf_v4/src/main/java/edu/mit/media/funf/util/StringUtil.java similarity index 100% rename from src/edu/mit/media/funf/util/StringUtil.java rename to funf_v4/src/main/java/edu/mit/media/funf/util/StringUtil.java diff --git a/src/edu/mit/media/funf/util/UuidUtil.java b/funf_v4/src/main/java/edu/mit/media/funf/util/UuidUtil.java similarity index 100% rename from src/edu/mit/media/funf/util/UuidUtil.java rename to funf_v4/src/main/java/edu/mit/media/funf/util/UuidUtil.java diff --git a/res/values/strings.xml b/funf_v4/src/main/res/values/strings.xml similarity index 100% rename from res/values/strings.xml rename to funf_v4/src/main/res/values/strings.xml diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..8c0fb64a8698b08ecc4158d828ca593c4928e9dd GIT binary patch literal 49896 zcmagFb986H(k`5d^NVfUwr$(C?M#x1ZQHiZiEVpg+jrjgoQrerx!>1o_ul)D>ebz~ zs=Mmxr&>W81QY-S1PKWQ%N-;H^tS;2*XwVA`dej1RRn1z<;3VgfE4~kaG`A%QSPsR z#ovnZe+tS9%1MfeDyz`RirvdjPRK~p(#^q2(^5@O&NM19EHdvN-A&StN>0g6QA^VN z0Gx%Gq#PD$QMRFzmK+utjS^Y1F0e8&u&^=w5K<;4Rz|i3A=o|IKLY+g`iK6vfr9?+ z-`>gmU&i?FGSL5&F?TXFu`&Js6h;15QFkXp2M1H9|Eq~bpov-GU(uz%mH0n55wUl- zv#~ccAz`F5wlQ>e_KlJS3@{)B?^v*EQM=IxLa&76^y51a((wq|2-`qON>+4dLc{Oo z51}}o^Zen(oAjxDK7b++9_Yg`67p$bPo3~BCpGM7uAWmvIhWc5Gi+gQZ|Pwa-Gll@<1xmcPy z|NZmu6m)g5Ftu~BG&Xdxclw7Cij{xbBMBn-LMII#Slp`AElb&2^Hw+w>(3crLH!;I zN+Vk$D+wP1#^!MDCiad@vM>H#6+`Ct#~6VHL4lzmy;lSdk>`z6)=>Wh15Q2)dQtGqvn0vJU@+(B5{MUc*qs4!T+V=q=wy)<6$~ z!G>e_4dN@lGeF_$q9`Ju6Ncb*x?O7=l{anm7Eahuj_6lA{*#Gv*TaJclevPVbbVYu z(NY?5q+xxbO6%g1xF0r@Ix8fJ~u)VRUp`S%&rN$&e!Od`~s+64J z5*)*WSi*i{k%JjMSIN#X;jC{HG$-^iX+5f5BGOIHWAl*%15Z#!xntpk($-EGKCzKa zT7{siZ9;4TICsWQ$pu&wKZQTCvpI$Xvzwxoi+XkkpeE&&kFb!B?h2hi%^YlXt|-@5 zHJ~%AN!g_^tmn1?HSm^|gCE#!GRtK2(L{9pL#hp0xh zME}|DB>(5)`iE7CM)&_+S}-Bslc#@B5W4_+k4Cp$l>iVyg$KP>CN?SVGZ(&02>iZK zB<^HP$g$Lq*L$BWd?2(F?-MUbNWTJVQdW7$#8a|k_30#vHAD1Z{c#p;bETk0VnU5A zBgLe2HFJ3032$G<`m*OB!KM$*sdM20jm)It5OSru@tXpK5LT>#8)N!*skNu1$TpIw zufjjdp#lyH5bZ%|Iuo|iu9vG1HrIVWLH>278xo>aVBkPN3V$~!=KnlXQ4eDqS7%E% zQ!z^$Q$b^6Q)g#cLpwur(|<0gWHo6A6jc;n`t(V9T;LzTAU{IAu*uEQ%Ort1k+Kn+f_N`9|bxYC+~Z1 zCC1UCWv*Orx$_@ydv9mIe(liLfOr7mhbV@tKw{6)q^1DH1nmvZ0cj215R<~&I<4S| zgnr;9Cdjqpz#o8i0CQjtl`}{c*P)aSdH|abxGdrR)-3z+02-eX(k*B)Uqv6~^nh** z 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/import-summary.txt b/import-summary.txt new file mode 100644 index 0000000..c936ca9 --- /dev/null +++ b/import-summary.txt @@ -0,0 +1,124 @@ +ECLIPSE ANDROID PROJECT IMPORT SUMMARY +====================================== + +Ignored Files: +-------------- +The following files were *not* copied into the new Gradle project; you +should evaluate whether these are still needed in your project and if +so manually move them: + +* .DS_Store +* .gitignore +* README +* ant.properties +* build.xml +* custom_rules.xml +* default.properties +* proguard-project.txt +* proguard.cfg +* test/ +* test/.classpath +* test/.gitignore +* test/.project +* test/AndroidManifest.xml +* test/ant.properties +* test/build.xml +* test/default.properties +* test/local.properties +* test/old/ +* test/old/AccountsProbeTest.java +* test/old/ActivityProbeTest.java +* test/old/AndroidInfoProbeTest.java +* test/old/ApplicationsProbeTest.java +* test/old/AudioFilesProbeTest.java +* test/old/BatteryProbeTest.java +* test/old/BluetoothProbeTest.java +* test/old/BrowserBookmarksProbeTest.java +* test/old/BrowserSearchesProbeTest.java +* test/old/CallLogProbeTest.java +* test/old/CellProbeTest.java +* test/old/ContactProbeTest.java +* test/old/GravitySensorProbeTest.java +* test/old/HardwareInfoProbeTest.java +* test/old/ImagesProbeTest.java +* test/old/LightSensorProbeTest.java +* test/old/LocationProbeTest.java +* test/old/ProbeUtilsTest.java +* test/old/ProcessStatisticsProbeTest.java +* test/old/RunningApplicationsProbeTest.java +* test/old/SMSProbeTest.java +* test/old/ServicesProbeTest.java +* test/old/TelephonyProbeTest.java +* test/old/TemperatureSensorProbeTest.java +* test/old/TimeOffsetProbeTest.java +* test/old/VideosProbeTest.java +* test/old/WifiProbeTest.java +* test/proguard-project.txt +* test/proguard.cfg +* test/project.properties +* test/res/ +* test/res/.gitignore +* test/src/ +* test/src/edu/ +* test/src/edu/mit/ +* test/src/edu/mit/media/ +* test/src/edu/mit/media/funf/ +* test/src/edu/mit/media/funf/AsyncSharedPrefsTest.java +* test/src/edu/mit/media/funf/FunfManagerTest.java +* test/src/edu/mit/media/funf/TestPipeline.java +* test/src/edu/mit/media/funf/config/ +* test/src/edu/mit/media/funf/config/TestConfigurableParsing.java +* test/src/edu/mit/media/funf/pipeline/ +* test/src/edu/mit/media/funf/pipeline/BasicPipelineTest.java +* test/src/edu/mit/media/funf/probe/ +* test/src/edu/mit/media/funf/probe/AnnotationsTest.java +* test/src/edu/mit/media/funf/probe/ProbeTest.java +* test/src/edu/mit/media/funf/probe/ProbeTestCase.java +* test/src/edu/mit/media/funf/probe/builtin/ +* test/src/edu/mit/media/funf/probe/builtin/ContactProbeTest.java +* test/src/edu/mit/media/funf/probe/builtin/RunningApplicationsProbeTest.java +* test/src/edu/mit/media/funf/probe/builtin/ServicesProbeTest.java +* test/src/edu/mit/media/funf/probe/builtin/TelephonyProbeTest.java +* test/src/edu/mit/media/funf/probe/builtin/TestAllBuiltinProbes.java +* test/src/edu/mit/media/funf/probe/builtin/TestLocationProbes.java +* test/src/edu/mit/media/funf/storage/ +* test/src/edu/mit/media/funf/storage/DefaultArchiveTest.java +* test/src/edu/mit/media/funf/storage/PrefsWriteSpeedTest.java +* test/src/edu/mit/media/funf/tests/ +* test/src/edu/mit/media/funf/tests/ExampleService.java +* test/src/edu/mit/media/funf/tests/SensorTest.java + +Moved Files: +------------ +Android Gradle projects use a different directory structure than ADT +Eclipse projects. Here's how the projects were restructured: + +* AndroidManifest.xml => app/src/main/AndroidManifest.xml +* libs/gson-2.1-javadoc.jar => app/libs/gson-2.1-javadoc.jar +* libs/gson-2.1-sources.jar => app/libs/gson-2.1-sources.jar +* libs/gson-2.1.jar => app/libs/gson-2.1.jar +* lint.xml => app/lint.xml +* res/ => app/src/main/res/ +* src/ => app/src/main/java/ +* src/.DS_Store => app/src/main/resources/.DS_Store +* src/com/.DS_Store => app/src/main/resources/com/.DS_Store +* src/edu/.DS_Store => app/src/main/resources/edu/.DS_Store +* src/edu/mit/.DS_Store => app/src/main/resources/edu/mit/.DS_Store +* src/edu/mit/media/.DS_Store => app/src/main/resources/edu/mit/media/.DS_Store +* src/edu/mit/media/funf/.DS_Store => app/src/main/resources/edu/mit/media/funf/.DS_Store +* src/edu/mit/media/funf/probe/.DS_Store => app/src/main/resources/edu/mit/media/funf/probe/.DS_Store + +Next Steps: +----------- +You can now build the project. The Gradle project needs network +connectivity to download dependencies. + +Bugs: +----- +If for some reason your project does not build, and you determine that +it is due to a bug or limitation of the Eclipse to Gradle importer, +please file a bug at http://b.android.com with category +Component-Tools. + +(This import summary is for your information only, and can be deleted +after import once you are satisfied with the results.) diff --git a/libs/GSON_LICENSE b/libs/GSON_LICENSE deleted file mode 100644 index 892eaed..0000000 --- a/libs/GSON_LICENSE +++ /dev/null @@ -1,203 +0,0 @@ -Google Gson - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2008-2011 Google Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/libs/gson-2.1.jar.orig b/libs/gson-2.1.jar.orig deleted file mode 100644 index b85f091a0d2e76b75babc21bdbe05ef118089910..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 180110 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
  • -UuCUewHwHK7&mZOv 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 z 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 JL;Eby{tu!-Czk*K diff --git a/lint.xml b/lint.xml deleted file mode 100644 index ee0eead..0000000 --- a/lint.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/proguard-project.txt b/proguard-project.txt deleted file mode 100644 index af94546..0000000 --- a/proguard-project.txt +++ /dev/null @@ -1,21 +0,0 @@ -# To enable ProGuard in your project, edit project.properties -# to define the proguard.config property as described in that file. -# -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# 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 *; -#} --dontoptimize \ No newline at end of file diff --git a/proguard.cfg b/proguard.cfg deleted file mode 100644 index b1cdf17..0000000 --- a/proguard.cfg +++ /dev/null @@ -1,40 +0,0 @@ --optimizationpasses 5 --dontusemixedcaseclassnames --dontskipnonpubliclibraryclasses --dontpreverify --verbose --optimizations !code/simplification/arithmetic,!field/*,!class/merging/* - --keep public class * extends android.app.Activity --keep public class * extends android.app.Application --keep public class * extends android.app.Service --keep public class * extends android.content.BroadcastReceiver --keep public class * extends android.content.ContentProvider --keep public class * extends android.app.backup.BackupAgentHelper --keep public class * extends android.preference.Preference --keep public class com.android.vending.licensing.ILicensingService - --keepclasseswithmembernames class * { - native ; -} - --keepclasseswithmembers class * { - public (android.content.Context, android.util.AttributeSet); -} - --keepclasseswithmembers class * { - public (android.content.Context, android.util.AttributeSet, int); -} - --keepclassmembers class * extends android.app.Activity { - public void *(android.view.View); -} - --keepclassmembers enum * { - public static **[] values(); - public static ** valueOf(java.lang.String); -} - --keep class * implements android.os.Parcelable { - public static final android.os.Parcelable$Creator *; -} diff --git a/project.properties b/project.properties deleted file mode 100644 index 0e58ae1..0000000 --- a/project.properties +++ /dev/null @@ -1,12 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system use, -# "ant.properties", and override values to adapt the script to your -# project structure. - -android.library=true -# Project target. -target=android-17 diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..4b8cafa --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':funf_v4' diff --git a/src/edu/mit/media/funf/probe/.DS_Store b/src/edu/mit/media/funf/probe/.DS_Store deleted file mode 100644 index 60f632c745411d07db7bcbee5b0b114b0f2c8cd0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}T>S5T0$TO;8~hp~uB@krqqagNG0*cobUb!HOnCXdpDDN$8@SmqA=H z&~J3n+M5tSjw_EpuRjSe@)|7TD9t*ZH&LrM_V$~WZCi)4X|xEFwU^|JFufknll3?X z?*o5zqwLJj^3?NJDsts#bBri^k|Z(OGm|}y!fbx)v^JYzzu%VKUcYF|&0x@J%ih_r zC~V8Jofqr)G;G`Ft;Gg2zzi@0%m6d+Ed%Blu@1h?-~cng z4E$FHXn&BXgs#C-quM&KQBMHG2D+7?O>YURkp^9ZrACaP2wjS(ONDu22wjeTY4Tiy zrAA#2Ld}fhn3+EwFG9_ZeyP$yxEi@-2AF|e2I{)&(D{FXzf9#Lf478t%m6d+#~2We z(Rk$HN13zr%jeNqD^c%INhq%*0|o7!O8|CgA8Dzej!UvJ&ox+Tq*2JP$`Sb@pa|iH J8TbJP-T?sGL!$ry diff --git a/test/.classpath b/test/.classpath deleted file mode 100644 index ff4f2a3..0000000 --- a/test/.classpath +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/test/.gitignore b/test/.gitignore deleted file mode 100644 index 862bb42..0000000 --- a/test/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -bin/* -gen/* -*.log \ No newline at end of file diff --git a/test/.project b/test/.project deleted file mode 100644 index b380917..0000000 --- a/test/.project +++ /dev/null @@ -1,40 +0,0 @@ - - - funf-old-test - - - - - - com.android.ide.eclipse.adt.ResourceManagerBuilder - - - - - com.android.ide.eclipse.adt.PreCompilerBuilder - - - - - org.eclipse.jdt.core.javabuilder - - - - - com.android.ide.eclipse.adt.ApkBuilder - - - - - - com.android.ide.eclipse.adt.AndroidNature - org.eclipse.jdt.core.javanature - - - - funf_src - 2 - _android_funf_3022c7/src - - - diff --git a/test/AndroidManifest.xml b/test/AndroidManifest.xml deleted file mode 100644 index 0b018b6..0000000 --- a/test/AndroidManifest.xml +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/ant.properties b/test/ant.properties deleted file mode 100644 index da0e730..0000000 --- a/test/ant.properties +++ /dev/null @@ -1,18 +0,0 @@ -# This file is used to override default values used by the Ant build system. -# -# This file must be checked in Version Control Systems, as it is -# integral to the build system of your project. - -# This file is only used by the Ant script. - -# You can use this to override default values such as -# 'source.dir' for the location of your java source folder and -# 'out.dir' for the location of your output folder. - -# You can also use it define how the release builds are signed by declaring -# the following properties: -# 'key.store' for the location of your keystore and -# 'key.alias' for the name of the key to use. -# The password will be asked during the build when you use the 'release' target. - -tested.project.dir=../ diff --git a/test/build.xml b/test/build.xml deleted file mode 100644 index f619669..0000000 --- a/test/build.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/default.properties b/test/default.properties deleted file mode 100644 index 372b115..0000000 --- a/test/default.properties +++ /dev/null @@ -1,4 +0,0 @@ -android.library=true -android.library.reference.1=.. -# Project target. -target=android-7 diff --git a/test/local.properties b/test/local.properties deleted file mode 100644 index e6c6c17..0000000 --- a/test/local.properties +++ /dev/null @@ -1,10 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must *NOT* be checked in Version Control Systems, -# as it contains information specific to your local configuration. - -# location of the SDK. This is only used by Ant -# For customization when using a Version Control System, please read the -# header note. -sdk.dir=/usr/local/Cellar/android-sdk/r21 diff --git a/test/old/AccountsProbeTest.java b/test/old/AccountsProbeTest.java deleted file mode 100644 index 9b74cb2..0000000 --- a/test/old/AccountsProbeTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package edu.mit.media.funf.probe.builtin; - -import android.os.Bundle; -import android.util.Log; - -import com.google.gson.Gson; - -import edu.mit.media.funf.probe.ProbeTestCase; -import edu.mit.media.funf.probe.Probe.Parameter; -import edu.mit.media.funf.tests.JsonUtils; - -public class AccountsProbeTest extends ProbeTestCase { - - public AccountsProbeTest() { - super(AccountsProbe.class); - } - - public void testData() { - Bundle params = new Bundle(); - params.putLong(Parameter.Builtin.PERIOD.name, 10L); - startProbe(params); - - Bundle data = getData(10); - Gson gson = JsonUtils.getGson(); - Log.i(TAG, "ServicesProbe DATA: " + gson.toJson(data)); - } -} diff --git a/test/old/ActivityProbeTest.java b/test/old/ActivityProbeTest.java deleted file mode 100644 index 19b7fec..0000000 --- a/test/old/ActivityProbeTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import java.util.List; - -import android.os.Bundle; -import android.util.Log; -import edu.mit.media.funf.probe.Probe; -import edu.mit.media.funf.probe.ProbeTestCase; -import edu.mit.media.funf.probe.Probe.Parameter; - -public class ActivityProbeTest extends ProbeTestCase { - - public ActivityProbeTest() { - super(ActivityProbe.class); - } - - - - @Override - protected List> getProbesAffected() { - List> list = super.getProbesAffected(); - list.add(AccelerometerSensorProbe.class); - return list; - } - - - - public void testData() throws InterruptedException { - Bundle params = new Bundle(); - params.putLong(Parameter.Builtin.DURATION.name, 10L); - params.putLong(Parameter.Builtin.PERIOD.name, 0L); - startProbe(params); - Bundle data = getData(30); - assertTrue(data.containsKey(ActivityProbe.TOTAL_INTERVALS)); - assertTrue(data.containsKey(ActivityProbe.LOW_ACTIVITY_INTERVALS)); - assertTrue(data.containsKey(ActivityProbe.HIGH_ACTIVITY_INTERVALS)); - Log.i(TAG,"T: " + data.getInt(ActivityProbe.TOTAL_INTERVALS) + " L:" + data.getInt(ActivityProbe.LOW_ACTIVITY_INTERVALS) + " H:" + data.getInt(ActivityProbe.HIGH_ACTIVITY_INTERVALS)); - Thread.sleep(4000); - } - - public void testWithAccelerometerBroadcast() throws InterruptedException { - - Bundle params = new Bundle(); - params.putLong(Parameter.Builtin.DURATION.name, 4L); - params.putLong(Parameter.Builtin.PERIOD.name, 0L); - - startProbe(AccelerometerSensorProbe.class, params); - params.putLong(Parameter.Builtin.DURATION.name, 2L); - startProbe(params); - - Bundle data = getData(20); - assertTrue(data.containsKey(ActivityProbe.TOTAL_INTERVALS)); - assertTrue(data.containsKey(ActivityProbe.LOW_ACTIVITY_INTERVALS)); - assertTrue(data.containsKey(ActivityProbe.HIGH_ACTIVITY_INTERVALS)); - } -} diff --git a/test/old/AndroidInfoProbeTest.java b/test/old/AndroidInfoProbeTest.java deleted file mode 100644 index 48b2cfd..0000000 --- a/test/old/AndroidInfoProbeTest.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import edu.mit.media.funf.probe.ProbeTestCase; -import android.os.Bundle; - -public class AndroidInfoProbeTest extends ProbeTestCase { - - public AndroidInfoProbeTest() { - super(AndroidInfoProbe.class); - } - - public void testData() { - Bundle params = new Bundle(); - startProbe(params); - Bundle data = getData(5); - String[] keysToCheck = new String[] { - "FIRMWARE_VERSION", - "BUILD_NUMBER", - "SDK", - }; - for (String key : keysToCheck) { - assertNotNull(data.get(key)); - System.out.println(key + ": " + String.valueOf(data.get(key))); - } - } - -} diff --git a/test/old/ApplicationsProbeTest.java b/test/old/ApplicationsProbeTest.java deleted file mode 100644 index 288feea..0000000 --- a/test/old/ApplicationsProbeTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import edu.mit.media.funf.probe.Probe; -import edu.mit.media.funf.probe.ProbeTestCase; -import android.os.Bundle; - -public class ApplicationsProbeTest extends ProbeTestCase { - - public ApplicationsProbeTest() { - super(ApplicationsProbe.class); - } - - public void testProbe() { - Bundle params = new Bundle(); - startProbe(params); - Bundle data = getData(2); - assertNotNull(data.get("INSTALLED_APPLICATIONS")); - assertNotNull(data.get("UNINSTALLED_APPLICATIONS")); - } - - public void testRunOnceProbe() { - Bundle params = new Bundle(); - params.putLong(Probe.Parameter.Builtin.PERIOD.name, 0L); - startProbe(params); - Bundle data = getData(2); - assertNotNull(data.get("INSTALLED_APPLICATIONS")); - assertNotNull(data.get("UNINSTALLED_APPLICATIONS")); - shouldNotReturnData(5); - } -} diff --git a/test/old/AudioFilesProbeTest.java b/test/old/AudioFilesProbeTest.java deleted file mode 100644 index dd66e3e..0000000 --- a/test/old/AudioFilesProbeTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import java.util.ArrayList; - -import android.os.Bundle; -import android.os.Parcelable; -import android.util.Log; -import edu.mit.media.funf.probe.ProbeTestCase; -import edu.mit.media.funf.probe.Probe.Parameter; - -public class AudioFilesProbeTest extends ProbeTestCase { - - public AudioFilesProbeTest() { - super(AudioFilesProbe.class); - } - - public void testData() throws InterruptedException { - Bundle params = new Bundle(); - params.putLong(Parameter.Builtin.PERIOD.name, 0L); - startProbe(params); - Bundle data = getData(10); - ArrayList audioFiles = data.getParcelableArrayList(AudioFilesProbe.AUDIO_FILES); - assertNotNull(audioFiles); - assertTrue(audioFiles.size() > 0); - Log.i(TAG, "Audio files: " + audioFiles.size()); - - // Running again should return an empty result - startProbe(params); - data = getData(10); - audioFiles = data.getParcelableArrayList(AudioFilesProbe.AUDIO_FILES); - assertNotNull(audioFiles); - Log.i(TAG, "Audio files: " + audioFiles.size()); - assertTrue(audioFiles.isEmpty()); - } - -} diff --git a/test/old/BatteryProbeTest.java b/test/old/BatteryProbeTest.java deleted file mode 100644 index 333573b..0000000 --- a/test/old/BatteryProbeTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import edu.mit.media.funf.probe.ProbeTestCase; -import android.os.BatteryManager; -import android.os.Bundle; - -public class BatteryProbeTest extends ProbeTestCase { - - public BatteryProbeTest() { - super(BatteryProbe.class); - } - - public void testProbe() { - Bundle params = new Bundle(); - startProbe(params); - Bundle data = getData(20); - assertNotNull(data.get(BatteryManager.EXTRA_LEVEL)); - } - -} diff --git a/test/old/BluetoothProbeTest.java b/test/old/BluetoothProbeTest.java deleted file mode 100644 index 0bf0148..0000000 --- a/test/old/BluetoothProbeTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import edu.mit.media.funf.probe.ProbeTestCase; -import android.os.Bundle; - -public class BluetoothProbeTest extends ProbeTestCase { - - public BluetoothProbeTest() { - super(BluetoothProbe.class); - } - - public void testBluetoothProbe() { - Bundle params = new Bundle(); - startProbe(params); - Bundle data = getData(20); - assertNotNull(data.get("DEVICES")); - } -} diff --git a/test/old/BrowserBookmarksProbeTest.java b/test/old/BrowserBookmarksProbeTest.java deleted file mode 100644 index c138ed9..0000000 --- a/test/old/BrowserBookmarksProbeTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import android.os.Bundle; -import edu.mit.media.funf.probe.ProbeTestCase; -import edu.mit.media.funf.probe.Probe.Parameter; -import edu.mit.media.funf.probe.builtin.ProbeKeys.BaseProbeKeys; -import edu.mit.media.funf.probe.builtin.ProbeKeys.BrowserBookmarksKeys; - -public class BrowserBookmarksProbeTest extends ProbeTestCase { - - public BrowserBookmarksProbeTest() { - super(BrowserBookmarksProbe.class); - } - - public void testProbe() { - Bundle params = new Bundle(); - params.putLong(Parameter.Builtin.PERIOD.name, 0L); - startProbe(params); - Bundle data = getData(5); - assertNotNull(data.get(BaseProbeKeys.TIMESTAMP)); - assertNotNull(data.get(BrowserBookmarksKeys.BOOKMARKS)); - } - -} diff --git a/test/old/BrowserSearchesProbeTest.java b/test/old/BrowserSearchesProbeTest.java deleted file mode 100644 index bff5dec..0000000 --- a/test/old/BrowserSearchesProbeTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import android.os.Bundle; -import edu.mit.media.funf.probe.ProbeTestCase; -import edu.mit.media.funf.probe.Probe.Parameter; -import edu.mit.media.funf.probe.builtin.ProbeKeys.BaseProbeKeys; -import edu.mit.media.funf.probe.builtin.ProbeKeys.BrowserSearchesKeys; - -public class BrowserSearchesProbeTest extends ProbeTestCase { - - public BrowserSearchesProbeTest() { - super(BrowserSearchesProbe.class); - } - - public void testProbe() { - Bundle params = new Bundle(); - params.putLong(Parameter.Builtin.PERIOD.name, 0L); - startProbe(params); - Bundle data = getData(10); - assertNotNull(data.get(BaseProbeKeys.TIMESTAMP)); - assertNotNull(data.get(BrowserSearchesKeys.SEARCHES)); - } - -} diff --git a/test/old/CallLogProbeTest.java b/test/old/CallLogProbeTest.java deleted file mode 100644 index 14ec0bd..0000000 --- a/test/old/CallLogProbeTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import android.os.Bundle; -import edu.mit.media.funf.probe.ProbeTestCase; -import edu.mit.media.funf.probe.Probe.Parameter; -import edu.mit.media.funf.probe.builtin.ProbeKeys.BaseProbeKeys; -import edu.mit.media.funf.probe.builtin.ProbeKeys.CallLogKeys; - -public class CallLogProbeTest extends ProbeTestCase { - - public CallLogProbeTest() { - super(CallLogProbe.class); - } - - public void testProbe() { - Bundle params = new Bundle(); - params.putLong(Parameter.Builtin.PERIOD.name, 0L); - startProbe(params); - Bundle data = getData(10); - assertNotNull(data.get(BaseProbeKeys.TIMESTAMP)); - assertNotNull(data.get(CallLogKeys.CALLS)); - } - -} diff --git a/test/old/CellProbeTest.java b/test/old/CellProbeTest.java deleted file mode 100644 index f9ed498..0000000 --- a/test/old/CellProbeTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import android.os.Bundle; -import edu.mit.media.funf.Utils; -import edu.mit.media.funf.probe.ProbeTestCase; -import edu.mit.media.funf.probe.Probe.Parameter; -import edu.mit.media.funf.probe.builtin.ProbeKeys.BaseProbeKeys; - -public class CellProbeTest extends ProbeTestCase { - - public CellProbeTest() { - super(CellProbe.class); - } - - public void testProbe() { - Bundle params = new Bundle(); - params.putLong(Parameter.Builtin.PERIOD.name, 0L); - startProbe(params); - Bundle data = getData(5); - assertNotNull(data.get(BaseProbeKeys.TIMESTAMP)); - System.out.println(Utils.join(Utils.getValues(data).keySet(), ", ")); - } - -} diff --git a/test/old/ContactProbeTest.java b/test/old/ContactProbeTest.java deleted file mode 100644 index af7ff6a..0000000 --- a/test/old/ContactProbeTest.java +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import java.util.ArrayList; - -import junit.framework.AssertionFailedError; -import android.os.Bundle; -import android.os.Parcelable; -import android.provider.ContactsContract.Data; -import android.util.Log; -import edu.mit.media.funf.Utils; -import edu.mit.media.funf.probe.ProbeTestCase; -import edu.mit.media.funf.probe.Probe.Parameter; -import edu.mit.media.funf.probe.builtin.ProbeKeys.BaseProbeKeys; - -public class ContactProbeTest extends ProbeTestCase { - - public ContactProbeTest() { - super(ContactProbe.class); - } - - public void testProbe() { - Bundle params = new Bundle(); - params.putLong(Parameter.Builtin.PERIOD.name, 0L); - params.putBoolean(ContactProbe.FULL_PARAM.getName(), true); - startProbe(params); - Bundle data = getData(30); - assertNotNull(data.get(BaseProbeKeys.TIMESTAMP)); - ArrayList contactData = data.getParcelableArrayList(ContactProbe.CONTACT_DATA); - assertNotNull(contactData); - int count = 1; - while(data != null) { - try { - data = getData(5); - count ++; - } catch (AssertionFailedError e) { - assertTrue(contactData.size() > 0); - Log.i("ContactProbeTest", "Contact keys: " + Utils.join(Utils.getValues(data).keySet(), ", ")); - for (Parcelable dataRow : contactData) { - Bundle b = (Bundle)dataRow; - Log.i("ContactProbeTest", "Data keys: " + b.getString(Data.MIMETYPE) + ":" + String.valueOf(b.get(Data.DATA1)) + " - Others: " + Utils.join(Utils.getValues(b).keySet(), ", ")); - } - data = null; - } - } - Log.i("ContactProbeTest", "Count: " + String.valueOf(count)); - } - - public void testFullParameter() { - // Run a full scan - Bundle params = new Bundle(); - params.putLong(Parameter.Builtin.PERIOD.name, 0L); - params.putBoolean(ContactProbe.FULL_PARAM.getName(), true); - startProbe(params); - Bundle data = getData(30); - boolean hasAtLeastTwo = false; - while (data != null) { - // Get the rest of the data - try { - data = getData(5); - hasAtLeastTwo = true; - } catch (AssertionFailedError e) { - data = null; - } - } - assertTrue(hasAtLeastTwo); - - // Run a non-full scan - params.putBoolean(ContactProbe.FULL_PARAM.getName(), false); - startProbe(params); - try { - data = getData(30); // Unfortunately we have to wait, since full scans take a while to return anything - ArrayList contactData = data.getParcelableArrayList(ContactProbe.CONTACT_DATA); - Log.i(TAG, "Data: " + (contactData == null ? "" : contactData.size())); - fail("Should not get any contacts for a non-full scan after a full scan"); - } catch (AssertionFailedError e) { - // Success - } - } - -} diff --git a/test/old/GravitySensorProbeTest.java b/test/old/GravitySensorProbeTest.java deleted file mode 100644 index c85f8f4..0000000 --- a/test/old/GravitySensorProbeTest.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import android.os.Bundle; -import edu.mit.media.funf.probe.ProbeTestCase; -import edu.mit.media.funf.probe.Probe.Parameter; - -public class GravitySensorProbeTest extends ProbeTestCase { - - public GravitySensorProbeTest() { - super(GravitySensorProbe.class); - } - - - public void testData() throws InterruptedException { - Bundle params = new Bundle(); - params.putLong(Parameter.Builtin.DURATION.name, 3L); - params.putLong(Parameter.Builtin.PERIOD.name, 10L); - startProbe(params); - for (int i=0; i<3; i++) { - Bundle data = getData(10); - assertTrue(data.containsKey("SENSOR")); - assertTrue(data.containsKey("EVENT_TIMESTAMP")); - assertTrue(data.containsKey("ACCURACY")); - assertTrue(data.containsKey("X")); - assertTrue(data.containsKey("Y")); - assertTrue(data.containsKey("Z")); - assertTrue(data.containsKey("TIMESTAMP")); - long[] eventTimestamp = data.getLongArray("EVENT_TIMESTAMP"); - int[] accuracy = data.getIntArray("ACCURACY"); - float[] x = data.getFloatArray("X"); - float[] y = data.getFloatArray("Y"); - float[] z = data.getFloatArray("Z"); - int numEvents = eventTimestamp.length; - assertEquals(numEvents, accuracy.length); - assertEquals(numEvents, x.length); - assertEquals(numEvents, y.length); - assertEquals(numEvents, z.length); -// System.out.println("@" + data.getLong("TIMESTAMP") + " - " + -// "X:" + data.getFloat("X") + -// "Y:" + data.getFloat("Y") + -// "Z:" + data.getFloat("Z") -// ); - } - } - - -} diff --git a/test/old/HardwareInfoProbeTest.java b/test/old/HardwareInfoProbeTest.java deleted file mode 100644 index 5665af8..0000000 --- a/test/old/HardwareInfoProbeTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import android.os.Bundle; -import android.util.Log; -import edu.mit.media.funf.probe.ProbeTestCase; - -public class HardwareInfoProbeTest extends ProbeTestCase { - - public HardwareInfoProbeTest() { - super(HardwareInfoProbe.class); - } - - public void testData() { - Bundle params = new Bundle(); - startProbe(params); - Bundle data = getData(5); - String[] keysToCheck = new String[] { - "WIFI_MAC", - "ANDROID_ID", - //"BLUETOOTH_MAC", Does not exist on some phones (Cliq) - "BRAND", - "MODEL", - "DEVICE_ID" - }; - Log.i(TAG, String.valueOf(data)); - for (String key : keysToCheck) { - assertNotNull("Key does not exist: " + key, data.getString(key)); - System.out.println(key + ": " + data.getString(key)); - } - } - -} diff --git a/test/old/ImagesProbeTest.java b/test/old/ImagesProbeTest.java deleted file mode 100644 index 106232f..0000000 --- a/test/old/ImagesProbeTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import java.util.ArrayList; - -import android.os.Bundle; -import android.os.Parcelable; -import android.util.Log; -import edu.mit.media.funf.probe.ProbeTestCase; -import edu.mit.media.funf.probe.Probe.Parameter; - -public class ImagesProbeTest extends ProbeTestCase { - - public ImagesProbeTest() { - super(ImagesProbe.class); - } - - public void testData() throws InterruptedException { - Log.i("edu.mit.media.funf.probe.builtin.ImagesProbe", "___________________START_________________"); - Bundle params = new Bundle(); - params.putLong(Parameter.Builtin.PERIOD.name, 0L); - startProbe(params); - Bundle data = getData(20); - ArrayList images = data.getParcelableArrayList(ImagesProbe.IMAGES); - assertNotNull(images); - assertTrue(images.size() > 0); - - Thread.sleep(5000L); - clearData(); - - // Running again should return an empty result - startProbe(params); - data = getData(10); - images = data.getParcelableArrayList(ImagesProbe.IMAGES); - assertNotNull(images); - Log.i(TAG, "Number of images: " + images.size()); - assertTrue(images.isEmpty()); - } - -} diff --git a/test/old/LightSensorProbeTest.java b/test/old/LightSensorProbeTest.java deleted file mode 100644 index cd402de..0000000 --- a/test/old/LightSensorProbeTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import android.os.Bundle; -import edu.mit.media.funf.probe.ProbeTestCase; -import edu.mit.media.funf.probe.Probe.Parameter; - -public class LightSensorProbeTest extends ProbeTestCase { - - public LightSensorProbeTest() { - super(LightSensorProbe.class); - } - - - public void testData() throws InterruptedException { - Bundle params = new Bundle(); - params.putLong(Parameter.Builtin.DURATION.name, 10L); - params.putLong(Parameter.Builtin.PERIOD.name, 10L); - startProbe(params); - - Bundle data = getData(10); - assertTrue(data.containsKey("SENSOR")); - assertTrue(data.containsKey("EVENT_TIMESTAMP")); - assertTrue(data.containsKey("ACCURACY")); - assertTrue(data.containsKey("LUX")); - assertTrue(data.containsKey("TIMESTAMP")); - long[] eventTimestamp = data.getLongArray("EVENT_TIMESTAMP"); - int[] accuracy = data.getIntArray("ACCURACY"); - float[] lux = data.getFloatArray("LUX"); - int numEvents = eventTimestamp.length; - assertEquals(numEvents, accuracy.length); - assertEquals(numEvents, lux.length); - System.out.println("@" + data.getLong("TIMESTAMP") + " - " + - "LUX Count:" + lux.length - ); - } - - -} diff --git a/test/old/LocationProbeTest.java b/test/old/LocationProbeTest.java deleted file mode 100644 index 074dc04..0000000 --- a/test/old/LocationProbeTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import android.location.Location; -import android.os.Bundle; -import edu.mit.media.funf.probe.ProbeTestCase; -import edu.mit.media.funf.probe.Probe.Parameter; - -public class LocationProbeTest extends ProbeTestCase { - - private static final int FUDGE_FACTOR = 10; - - - public LocationProbeTest() { - super(LocationProbe.class); - } - - public void testData() { - Bundle params = new Bundle(); - params.putLong(Parameter.Builtin.DURATION.name, 30L); - startProbe(params); - Bundle data = getData(30 + FUDGE_FACTOR); - Location location = (Location)data.get("LOCATION"); - assertNotNull(location); - System.out.println("Accuracy: " + String.valueOf(location.getAccuracy())); - } - - public void testReturningCachedLocation() { - Bundle params = new Bundle(); - params.putLong(Parameter.Builtin.DURATION.name, 1L); - startProbe(params); - Bundle data = getData(1 + FUDGE_FACTOR); - Location location = (Location)data.get("LOCATION"); - assertNotNull(location); - System.out.println("Accuracy: " + String.valueOf(location.getAccuracy())); - } - - -} diff --git a/test/old/ProbeUtilsTest.java b/test/old/ProbeUtilsTest.java deleted file mode 100644 index 00614ce..0000000 --- a/test/old/ProbeUtilsTest.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import junit.framework.TestCase; -import android.content.Intent; -import android.location.Location; -import android.os.Bundle; - -public class ProbeUtilsTest extends TestCase { - - - public void testIntentUrls() { - Intent i = new Intent("test.action"); - i.putExtra("TEST_PARAM1", true); - i.putExtra("TEST_PARAM2", "(&U#ALAN"); - i.putExtra("TEST_PARAM6", new boolean[] {true, false, true}); - i.putExtra("TEST_PARAM7", new float[] {1, 0, 32.34f}); - i.putExtra("TEST_PARAM8", 2); - i.putExtra("TEST_PARAM3", new Location("ASDF")); - Bundle b = new Bundle(); - b.putFloat("TEST_PARAM4", 23423.34f); - i.putExtra("TEST_PARAM5", b); - System.out.println(i.toUri(Intent.URI_INTENT_SCHEME)); - - // No arrays or parcelables show up - } -} diff --git a/test/old/ProcessStatisticsProbeTest.java b/test/old/ProcessStatisticsProbeTest.java deleted file mode 100644 index 8b19453..0000000 --- a/test/old/ProcessStatisticsProbeTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package edu.mit.media.funf.probe.builtin; - -import android.os.Bundle; -import android.util.Log; - -import com.google.gson.Gson; - -import edu.mit.media.funf.probe.ProbeTestCase; -import edu.mit.media.funf.probe.Probe.Parameter; -import edu.mit.media.funf.tests.JsonUtils; - -public class ProcessStatisticsProbeTest extends ProbeTestCase { - - public ProcessStatisticsProbeTest() { - super(ProcessStatisticsProbe.class); - } - - public void testData() { - Bundle params = new Bundle(); - params.putLong(Parameter.Builtin.PERIOD.name, 10L); - startProbe(params); - - Bundle data = getData(10); - Gson gson = JsonUtils.getGson(); - Log.i(TAG, "ProcessStatisticsProbe DATA: " + gson.toJson(data)); - } -} diff --git a/test/old/RunningApplicationsProbeTest.java b/test/old/RunningApplicationsProbeTest.java deleted file mode 100644 index fe0f132..0000000 --- a/test/old/RunningApplicationsProbeTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import android.os.Bundle; -import edu.mit.media.funf.opp.OppProbe; -import edu.mit.media.funf.probe.ProbeTestCase; - -public class RunningApplicationsProbeTest extends ProbeTestCase { - - public RunningApplicationsProbeTest() { - super(RunningApplicationsProbe.class); - } - - public void testProbe() { - Bundle params = new Bundle(); - params.putString(OppProbe.ReservedParamaters.PACKAGE.name, getTestRequester()); - startProbe(params); - Bundle data = getData(20); - assertNotNull(data.get("RUNNING_TASKS")); - } -} diff --git a/test/old/SMSProbeTest.java b/test/old/SMSProbeTest.java deleted file mode 100644 index 68a9121..0000000 --- a/test/old/SMSProbeTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import java.util.ArrayList; - -import android.os.Bundle; -import android.os.Parcelable; -import android.util.Log; -import edu.mit.media.funf.probe.ProbeTestCase; -import edu.mit.media.funf.probe.Probe.Parameter; - -public class SMSProbeTest extends ProbeTestCase { - - public SMSProbeTest() { - super(SMSProbe.class); - } - - public void testData() throws InterruptedException { - Bundle params = new Bundle(); - params.putLong(Parameter.Builtin.PERIOD.name, 0L); - startProbe(params); - Bundle data = getData(10); - ArrayList messages = data.getParcelableArrayList(SMSProbe.MESSAGES); - assertNotNull(messages); - assertTrue(messages.size() > 0); - - Thread.sleep(20000L); - clearData(); - - // Running again should return an empty result - startProbe(params); - data = getData(20); - messages = data.getParcelableArrayList(SMSProbe.MESSAGES); - assertNotNull(messages); - Log.i(TAG, "SMS size: " + messages.size()); - assertTrue(messages.isEmpty()); - } - -} diff --git a/test/old/ServicesProbeTest.java b/test/old/ServicesProbeTest.java deleted file mode 100644 index 6513cf0..0000000 --- a/test/old/ServicesProbeTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package edu.mit.media.funf.probe.builtin; - -import android.os.Bundle; -import android.util.Log; - -import com.google.gson.Gson; - -import edu.mit.media.funf.probe.ProbeTestCase; -import edu.mit.media.funf.probe.Probe.Parameter; -import edu.mit.media.funf.tests.JsonUtils; - -public class ServicesProbeTest extends ProbeTestCase { - - public ServicesProbeTest() { - super(ServicesProbe.class); - } - - public void testData() { - Bundle params = new Bundle(); - params.putLong(Parameter.Builtin.PERIOD.name, 10L); - startProbe(params); - - Bundle data = getData(10); - Gson gson = JsonUtils.getGson(); - Log.i(TAG, "ServicesProbe DATA: " + gson.toJson(data)); - } -} diff --git a/test/old/TelephonyProbeTest.java b/test/old/TelephonyProbeTest.java deleted file mode 100644 index e647ce1..0000000 --- a/test/old/TelephonyProbeTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import edu.mit.media.funf.probe.ProbeTestCase; -import android.os.Bundle; - -public class TelephonyProbeTest extends ProbeTestCase { - - public TelephonyProbeTest() { - super(TelephonyProbe.class); - } - - public void testData() { - Bundle params = new Bundle(); - startProbe(params); - Bundle data = getData(5); - String[] stringKeys = new String[] { - "DEVICE_ID", - "DEVICE_SOFTWARE_VERSION", - "LINE_1_NUMBER", - "NETWORK_COUNTRY_ISO", - "NETWORK_OPERATOR", - "NETWORK_OPERATOR_NAME", - "SIM_COUNTRY_ISO", - "SIM_OPERATOR", - "SIM_OPERATOR_NAME", - "SIM_SERIAL_NUMBER", - "SUBSCRIBER_ID", - "VOICEMAIL_ALPHA_TAG", - "VOICEMAIL_NUMBER", - }; - String[] otherKeys = new String[] { - "CALL_STATE", - "NETWORK_TYPE", - "PHONE_TYPE", - "SIM_STATE", - "HAS_ICC_CARD" - }; - for (String key : stringKeys) { - assertNotNull(data.getString(key)); - } - for (String key : otherKeys) { - assertNotNull(data.get(key)); - } - } - -} diff --git a/test/old/TemperatureSensorProbeTest.java b/test/old/TemperatureSensorProbeTest.java deleted file mode 100644 index 775ecfe..0000000 --- a/test/old/TemperatureSensorProbeTest.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import junit.framework.AssertionFailedError; -import android.os.Bundle; -import edu.mit.media.funf.probe.ProbeTestCase; -import edu.mit.media.funf.probe.Probe.Parameter; - -public class TemperatureSensorProbeTest extends ProbeTestCase { - - public TemperatureSensorProbeTest() { - super(TemperatureSensorProbe.class); - } - - - public void testData() throws InterruptedException { - Bundle params = new Bundle(); - params.putLong(Parameter.Builtin.DURATION.name, 10L); - params.putLong(Parameter.Builtin.PERIOD.name, 10L); - startProbe(params); - try { - Bundle data = getData(10); - fail("Should only fail if temperature probe is present on device."); - } catch (AssertionFailedError e) { - // Uncommon probe should not start - } - } - - -} diff --git a/test/old/TimeOffsetProbeTest.java b/test/old/TimeOffsetProbeTest.java deleted file mode 100644 index 02731f0..0000000 --- a/test/old/TimeOffsetProbeTest.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import android.os.Bundle; -import edu.mit.media.funf.probe.ProbeTestCase; -import edu.mit.media.funf.probe.Probe.Parameter; - -public class TimeOffsetProbeTest extends ProbeTestCase { - - public TimeOffsetProbeTest() { - super(TimeOffsetProbe.class); - } - - public void testProbe() { - Bundle params = new Bundle(); - params.putLong(Parameter.Builtin.PERIOD.name, 0L); - startProbe(params); - Bundle data = getData(20); - assertNotNull(data.get(TimeOffsetProbe.TIME_OFFSET)); - //System.out.println("Time Offset:" + data.getDouble(NtpProbe.TIME_OFFSET)); - } -} diff --git a/test/old/VideosProbeTest.java b/test/old/VideosProbeTest.java deleted file mode 100644 index fe23c34..0000000 --- a/test/old/VideosProbeTest.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import java.util.ArrayList; - -import android.os.Bundle; -import android.os.Parcelable; -import edu.mit.media.funf.probe.ProbeTestCase; -import edu.mit.media.funf.probe.Probe.Parameter; - -public class VideosProbeTest extends ProbeTestCase { - - public VideosProbeTest() { - super(VideosProbe.class); - } - - public void testData() { - Bundle params = new Bundle(); - params.putLong(Parameter.Builtin.PERIOD.name, 0L); - startProbe(params); - Bundle data = getData(10); - ArrayList videos = data.getParcelableArrayList(VideosProbe.VIDEOS); - assertNotNull(videos); - assertTrue(videos.size() > 0); - - // Running again should return an empty result - startProbe(params); - data = getData(10); - videos = data.getParcelableArrayList(VideosProbe.VIDEOS); - assertNotNull(videos); - assertTrue(videos.isEmpty()); - } - -} diff --git a/test/old/WifiProbeTest.java b/test/old/WifiProbeTest.java deleted file mode 100644 index e27a1ac..0000000 --- a/test/old/WifiProbeTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.probe.builtin; - -import java.util.ArrayList; - -import android.net.wifi.ScanResult; -import android.os.Bundle; -import android.os.Parcelable; -import edu.mit.media.funf.opp.OppProbe; -import edu.mit.media.funf.probe.ProbeTestCase; - -public class WifiProbeTest extends ProbeTestCase { - - private static final int FUDGE_FACTOR = 20; - - public WifiProbeTest() { - super(WifiProbe.class); - } - - public void testWifiProbe() throws InterruptedException { - Bundle params = new Bundle(); - params.putString(OppProbe.ReservedParamaters.PACKAGE.name, getTestRequester()); - startProbe(params); - Bundle data = getData(10 + FUDGE_FACTOR); - ArrayList scanResults = data.getParcelableArrayList(WifiProbe.SCAN_RESULTS); - assertNotNull(scanResults); - assertFalse(scanResults.isEmpty()); - for (Parcelable result : scanResults) { - System.out.println("SSID: " + ((ScanResult)result).SSID); - } - } - -} diff --git a/test/proguard-project.txt b/test/proguard-project.txt deleted file mode 100644 index f2fe155..0000000 --- a/test/proguard-project.txt +++ /dev/null @@ -1,20 +0,0 @@ -# To enable ProGuard in your project, edit project.properties -# to define the proguard.config property as described in that file. -# -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# 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 *; -#} diff --git a/test/proguard.cfg b/test/proguard.cfg deleted file mode 100644 index 49cc4ec..0000000 --- a/test/proguard.cfg +++ /dev/null @@ -1,40 +0,0 @@ --optimizationpasses 5 --dontusemixedcaseclassnames --dontskipnonpubliclibraryclasses --dontpreverify --verbose --optimizations !code/simplification/arithmetic,!field/*,!class/merging/* - --keep public class * extends android.app.Activity --keep public class * extends android.app.Application --keep public class * extends android.app.Service --keep public class * extends android.content.BroadcastReceiver --keep public class * extends android.content.ContentProvider --keep public class * extends android.app.backup.BackupAgentHelper --keep public class * extends android.preference.Preference --keep public class com.android.vending.licensing.ILicensingService - --keepclasseswithmembers class * { - native ; -} - --keepclasseswithmembers class * { - public (android.content.Context, android.util.AttributeSet); -} - --keepclasseswithmembers class * { - public (android.content.Context, android.util.AttributeSet, int); -} - --keepclassmembers class * extends android.app.Activity { - public void *(android.view.View); -} - --keepclassmembers enum * { - public static **[] values(); - public static ** valueOf(java.lang.String); -} - --keep class * implements android.os.Parcelable { - public static final android.os.Parcelable$Creator *; -} diff --git a/test/project.properties b/test/project.properties deleted file mode 100644 index d08f89f..0000000 --- a/test/project.properties +++ /dev/null @@ -1,12 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system use, -# "ant.properties", and override values to adapt the script to your -# project structure. - -android.library.reference.1=.. -# Project target. -target=android-17 diff --git a/test/res/.gitignore b/test/res/.gitignore deleted file mode 100644 index d6b7ef3..0000000 --- a/test/res/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/test/src/edu/mit/media/funf/AsyncSharedPrefsTest.java b/test/src/edu/mit/media/funf/AsyncSharedPrefsTest.java deleted file mode 100644 index dad6c2e..0000000 --- a/test/src/edu/mit/media/funf/AsyncSharedPrefsTest.java +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf; - -import edu.mit.media.funf.util.StringUtil; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.SharedPreferences.OnSharedPreferenceChangeListener; -import android.test.AndroidTestCase; -import static edu.mit.media.funf.util.AsyncSharedPrefs.async; - -public class AsyncSharedPrefsTest extends AndroidTestCase { - - public static final String TEST_PREFS = "TEST"; - - private SharedPreferences innerPrefs; - private SharedPreferences prefs; - - @Override - protected void setUp() throws Exception { - super.setUp(); - System.out.println("Clearing prefs"); - innerPrefs = getContext().getSharedPreferences(TEST_PREFS, Context.MODE_PRIVATE); - prefs = async(getContext().getSharedPreferences(TEST_PREFS, Context.MODE_PRIVATE)); - prefs.edit().clear().commit(); - } - - - public void testEdit() { - prefs.edit().putBoolean("test1", true).commit(); - assertTrue(prefs.getBoolean("test1", false)); - assertTrue((Boolean)prefs.getAll().get("test1")); - - prefs.edit().putFloat("test2", 1.2f).commit(); - assertEquals(1.2f, prefs.getFloat("test2", 0.0f)); - - prefs.edit().putInt("test3", 1).commit(); - assertEquals(1, prefs.getInt("test3", 0)); - - prefs.edit().putLong("test4", 1L).commit(); - assertEquals(1L, prefs.getLong("test4", 0L)); - - prefs.edit().putString("test5", "test").commit(); - assertEquals("test", prefs.getString("test5", "")); - - System.out.println("Entries: " + StringUtil.join(prefs.getAll().keySet(), ", ")); - - assertEquals(5, prefs.getAll().size()); - } - - public void testEditMultiple() { - prefs.edit().putBoolean("boolean", false).putString("string", "value").putInt("int", 3).commit(); - assertEquals(false, prefs.getBoolean("boolean", true)); - assertEquals("value", prefs.getString("string", "")); - assertEquals(3, prefs.getInt("int", 0)); - } - - public void testNoCommit() { - prefs.edit().putBoolean("boolean", true).commit(); - SharedPreferences.Editor editor = prefs.edit().putBoolean("boolean", false); // no commit - assertEquals(true, prefs.getBoolean("boolean", false)); - editor.commit(); - } - - public void testDiskPersistenceDelay() throws InterruptedException { - prefs.edit().putBoolean("boolean", true).commit(); - assertEquals(false, innerPrefs.getBoolean("boolean", false)); // not committed to disk yet - assertEquals(true, prefs.getBoolean("boolean", false)); - Thread.sleep(1000); - assertEquals(true, innerPrefs.getBoolean("boolean", false)); // committed later - } - - private String keyUpdated, valueUpdated; - public void testListeners() throws InterruptedException { - keyUpdated = valueUpdated = null; - OnSharedPreferenceChangeListener listener = new OnSharedPreferenceChangeListener() { - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - assertSame(prefs, sharedPreferences); - keyUpdated = key; - valueUpdated = sharedPreferences.getString(key, null); - } - }; - prefs.registerOnSharedPreferenceChangeListener(listener); - - prefs.edit().putString("listener_test", "text_value").commit(); - Thread.sleep(1000L); - assertEquals("listener_test", keyUpdated); - assertEquals("text_value", valueUpdated); - - prefs.unregisterOnSharedPreferenceChangeListener(listener); - prefs.edit().putString("listener_test", "another_value").commit(); - keyUpdated = valueUpdated = null; - - Thread.sleep(1000L); - assertNull(keyUpdated); - } - - public void testMultipleConsecutiveCommits() throws InterruptedException { - prefs.edit().putString("test", "1").commit(); - prefs.edit().putString("test", "2").commit(); - prefs.edit().putString("test", "3").commit(); - prefs.edit().putString("test", "4").commit(); - prefs.edit().putString("test", "5").commit(); - prefs.edit().putString("test", "6").commit(); - prefs.edit().putString("test", "7").commit(); - assertEquals("7", prefs.getString("test", "")); - Thread.sleep(1000L); - assertEquals("7", prefs.getString("test", "")); - } -} diff --git a/test/src/edu/mit/media/funf/FunfManagerTest.java b/test/src/edu/mit/media/funf/FunfManagerTest.java deleted file mode 100644 index 8ff6540..0000000 --- a/test/src/edu/mit/media/funf/FunfManagerTest.java +++ /dev/null @@ -1,136 +0,0 @@ -package edu.mit.media.funf; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.IBinder; -import android.test.AndroidTestCase; - -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; -import com.google.gson.JsonPrimitive; -import com.google.gson.internal.LazilyParsedNumber; - -import edu.mit.media.funf.json.IJsonArray; -import edu.mit.media.funf.json.IJsonObject; -import edu.mit.media.funf.json.JsonUtils; -import edu.mit.media.funf.probe.Probe; -import edu.mit.media.funf.probe.Probe.DataListener; -import edu.mit.media.funf.probe.builtin.AccelerometerFeaturesProbe; - -public class FunfManagerTest extends AndroidTestCase { - - private JsonParser parser = new JsonParser(); - private FunfManager manager; - private ServiceConnection conn = new ServiceConnection() { - - @Override - public void onServiceDisconnected(ComponentName name) { - // TODO Auto-generated method stub - - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - manager = ((FunfManager.LocalBinder)service).getManager(); - } - }; - - public class SampleDataListener implements DataListener { - - public BlockingQueue dataEvents = new LinkedBlockingQueue(); - public BlockingQueue completeEvents = new LinkedBlockingQueue(); - - @Override - public void onDataReceived(IJsonObject probeConfig, IJsonObject data) { - dataEvents.add(data); - } - - @Override - public void onDataCompleted(IJsonObject probeConfig, JsonElement checkpoint) { - completeEvents.add(probeConfig); - } - }; - - - - public SampleDataListener listener = new SampleDataListener(); - - @Override - protected void setUp() throws Exception { - super.setUp(); - listener = new SampleDataListener(); - getContext().bindService(new Intent(getContext(), FunfManager.class), conn, Context.BIND_AUTO_CREATE); - waitForServiceConnection(3000); - } - - public void waitForServiceConnection(long millisToWait) { - long time = System.currentTimeMillis(); - while (System.currentTimeMillis() < time + millisToWait) { - if (manager != null) { - break; - } else { - try { - Thread.sleep(100); - } - catch (InterruptedException e) { - } - } - } - } - - private final String probeConfig = "{\"@type\": \"" + AccelerometerFeaturesProbe.class.getName() + "\" " + - - "}"; - - public void testDataRequest() { - manager.requestData(listener, parser.parse(probeConfig)); - IJsonObject data = null; - try { - data = listener.dataEvents.poll(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - } - if (data == null) { - fail("Should have returned data within time alloted"); - } - - manager.unrequestData(listener, parser.parse(probeConfig)); - IJsonObject complete = null; - try { - complete = listener.completeEvents.poll(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - } - if (complete == null) { - fail("Should have completed within time alloted"); - } - } - - public void testIJsonObjectEqualsAndHash() { - JsonElement p1 = JsonUtils.immutable(new JsonPrimitive(new LazilyParsedNumber("1"))); - JsonElement p2 = JsonUtils.immutable(new JsonPrimitive(1)); - assertEquals(p1, p2); - assertEquals(p1.hashCode(), p2.hashCode()); - - Gson gson = manager.getGson(); - Probe probe = gson.fromJson(probeConfig, Probe.class); - IJsonObject o1 = (IJsonObject)JsonUtils.immutable(gson.toJsonTree(probe)); - IJsonArray a1 = o1.getAsJsonArray("freqBandEdges"); - IJsonArray a2 = (IJsonArray)JsonUtils.immutable(new JsonParser().parse(gson.toJson(a1))); - - assertEquals(a1, a2); - assertEquals(a1.hashCode(), a2.hashCode()); - IJsonObject o2 = (IJsonObject)JsonUtils.immutable(new JsonParser().parse(gson.toJson(o1))); - assertEquals(o1, o2); - assertEquals(o1.hashCode(), o2.hashCode()); - } - - public void testRegisterPipeline() { - - } -} diff --git a/test/src/edu/mit/media/funf/TestPipeline.java b/test/src/edu/mit/media/funf/TestPipeline.java deleted file mode 100644 index 7fb8a8b..0000000 --- a/test/src/edu/mit/media/funf/TestPipeline.java +++ /dev/null @@ -1,117 +0,0 @@ -package edu.mit.media.funf; - -import java.math.BigDecimal; -import java.util.LinkedList; -import java.util.Map; -import java.util.Queue; - -import android.test.AndroidTestCase; - -import com.google.gson.Gson; -import com.google.gson.JsonElement; - -import edu.mit.media.funf.config.Configurable; -import edu.mit.media.funf.pipeline.Pipeline; -import edu.mit.media.funf.pipeline.PipelineFactory; - -public class TestPipeline extends AndroidTestCase { - - private Gson gson; - private Queue actions; - - public static final String - CREATED = "created", - RAN = "ran", - DESTROYED = "destroyed"; - - @Override - protected void setUp() throws Exception { - super.setUp(); - gson = FunfManager.getGsonBuilder(getContext()).create(); - actions = new LinkedList(); - } - - public static class SamplePipeline implements Pipeline { - - public SamplePipeline() { - - } - - @Configurable - private Map schedules; - - @Configurable - private Object testComponent; - - private Object nonConfigurable; - - @Override - public void onCreate(FunfManager manager) { - // TODO Auto-generated method stub - - } - - @Override - public void onRun(String action, JsonElement config) { - // TODO Auto-generated method stub - - } - - @Override - public void onDestroy() { - // TODO Auto-generated method stub - - } - - public Map getSchedules() { - return schedules; - } - - @Override - public boolean isEnabled() { - // TODO Auto-generated method stub - return false; - } - } - - public String SAMPLE_PIPELINE_CONFIG = "{" + - "\"@type\": \"" + SamplePipeline.class.getName() + "\"," + - "\"" + PipelineFactory.SCHEDULES_FIELD_NAME + "\": {" + - "\"archive\": {" + - "\"interval\": 1," + - "\"opportunistic\": false" + - "}" + - "}," + - "\"testComponent\": {" + - "\"@schedule\": {" + - "\"duration\": 2" + - "}" + - "}," + - "\"nonConfigurable\": {" + - "\"@schedule\": {" + - "\"duration\": 2" + - "}" + - "}" + - "}"; - - - public void testPipelineLifecycle() { - - } - - public void testLoadPipeline() { - Pipeline pipeline = gson.fromJson(SAMPLE_PIPELINE_CONFIG, Pipeline.class); - assertTrue("Should respect runtime type.", pipeline instanceof SamplePipeline); - SamplePipeline samplePipeline = (SamplePipeline)pipeline; - Map schedules = samplePipeline.getSchedules(); - assertNotNull("Schedules should exists.", schedules); - assertTrue("Schedule should contain explicit action", schedules.containsKey("archive")); - Schedule archive = schedules.get("archive"); - assertEquals("Interval should have been set in schedule.", BigDecimal.ONE, archive.getInterval()); - assertEquals("Opportunistic should have been set in schedule.", false, archive.isOpportunistic()); - assertTrue("Schedule should contain annotation based action", schedules.containsKey("testComponent")); - Schedule testComponent = schedules.get("testComponent"); - assertEquals("Interval should have been set in schedule.", new BigDecimal(2), testComponent.getDuration()); - assertFalse("Schedule should not contain schedule for non configurable item.", schedules.containsKey("nonConfigurable")); - } -} diff --git a/test/src/edu/mit/media/funf/config/TestConfigurableParsing.java b/test/src/edu/mit/media/funf/config/TestConfigurableParsing.java deleted file mode 100644 index f5b8eae..0000000 --- a/test/src/edu/mit/media/funf/config/TestConfigurableParsing.java +++ /dev/null @@ -1,119 +0,0 @@ -package edu.mit.media.funf.config; - -import android.test.AndroidTestCase; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - -import edu.mit.media.funf.probe.Probe; - -public class TestConfigurableParsing extends AndroidTestCase { - - - public void testConfigurables() { - Gson gson = new GsonBuilder().registerTypeAdapterFactory( - new SingletonTypeAdapterFactory( - new DefaultRuntimeTypeAdapterFactory( - getContext(), - TestConfigurable.class, - Test1.class, - new ConfigurableTypeAdapterFactory()) - ) - ).create(); - Test1 test1 = gson.fromJson(Probe.DEFAULT_CONFIG, Test1.class); - assertEquals("Default was not set in configurable", 1, test1.overridden); - assertEquals("Default private field was not set in configurable", 2, test1.getPrivateField()); - JsonObject expectedConfig = new JsonObject(); - expectedConfig.addProperty("@type", Test1.class.getName()); - expectedConfig.addProperty("overridden", 1); - expectedConfig.addProperty("privateField", 2); - assertEquals("Configurable not serialized correctly", expectedConfig, gson.toJsonTree(test1)); - - test1 = gson.fromJson("{\"overridden\": 5, \"privateField\": 3}", Test1.class); - assertEquals("Specified config was not set in configurable", 5, test1.overridden); - assertEquals("Specified config on private field was not set in configurable", 3, test1.getPrivateField()); - - // Test runtime config - test1 = gson.fromJson("{\"@type\":\"" + Test2.class.getName() + "\", \"overridden\": 5, \"privateField\": 3}", Test1.class); - assertTrue("Runtime type not created from config", test1 instanceof Test2); - assertEquals("Specified config was not set in configurable", 5, test1.overridden); - assertEquals("Specified config was not set in configurable", "5", ((Test2)test1).overridden); - assertEquals("Specified config on private field was not set in configurable", 3, test1.getPrivateField()); - - - test1 = gson.fromJson("{\"notConfigurable\": \"yes\"}", Test1.class); - assertEquals("Specified config was not set in configurable", "no", test1.notConfigurable); - - // Test default is used if not specified - TestConfigurable test = gson.fromJson("{\"overridden\": 5, \"privateField\": 3}", TestConfigurable.class); - assertTrue("Runtime type not created from config", test instanceof Test1); - - // Test nested types - String nestedJson = "{\"@type\":\"" + Test2.class.getName() + "\", \"overridden\": 5, \"privateField\": 3, \"nested\": {\"@type\":\"" + Test2.class.getName() + "\", \"privateField\": 5}}"; - test = gson.fromJson(nestedJson, TestConfigurable.class); - assertTrue("Runtime type not created from config", test instanceof Test2); - Test2 test2 = (Test2)test; - assertTrue("Runtime type not created from nested config", test2.nested instanceof Test2); - assertEquals("Specified config was not set in nested configurable", 5, test2.nested.getPrivateField()); - String expectedSerialized = "{\"@type\":\"edu.mit.media.funf.config.TestConfigurableParsing$Test2\",\"nested\":{\"@type\":\"edu.mit.media.funf.config.TestConfigurableParsing$Test2\",\"nested\":{\"@type\":\"edu.mit.media.funf.config.TestConfigurableParsing$Test1\",\"overridden\":1,\"privateField\":2},\"overridden\":1,\"privateField\":5},\"overridden\":5,\"privateField\":3}"; - assertEquals("Configurable not serialized to json correctly", new JsonParser().parse(expectedSerialized), gson.toJsonTree(test)); - - } - - public void testSingleton() { - Gson gson = new GsonBuilder().registerTypeAdapterFactory( - new SingletonTypeAdapterFactory( - new DefaultRuntimeTypeAdapterFactory( - getContext(), - TestConfigurable.class, - Test1.class, - new ConfigurableTypeAdapterFactory()) - ) - ).create(); - - TestConfigurable test1 = gson.fromJson(Probe.DEFAULT_CONFIG, Test1.class); - TestConfigurable test2 = gson.fromJson(Probe.DEFAULT_CONFIG, Test1.class); - TestConfigurable test3 = gson.fromJson(Probe.DEFAULT_CONFIG, TestConfigurable.class); - assertSame("Singleton Type Adapter should return identical object for identical config and runtime configurations", - test1, test2); - assertSame("Singleton Type Adapter should return identical object for identical config and runtime configurations", - test1, test3); - - test1 = gson.fromJson(Probe.DEFAULT_CONFIG, Test1.class); - test2 = gson.fromJson("{\"privateField\": 5}", Test1.class); - assertNotSame("Two different configurations should not be cached the same.", test1, test2); - - // Specifying default should not return different - /* TODO: this is the way it should work, but need to come up with a method for doing this that does not involve creating an instance to figure out if you need to create a new instance - test1 = gson.fromJson(Probe.DEFAULT_CONFIG, Test1.class); - test2 = gson.fromJson("{\"privateField\": 2}", Test1.class); - assertSame("Two configurations that produce the same runtime object should be the same.", test1, test2); - */ - } - - public interface TestConfigurable { - - } - - public static class Test1 implements TestConfigurable { - @Configurable - public int overridden = 1; - @Configurable - private int privateField = 2; - - public String notConfigurable = "no"; - - public int getPrivateField() { - return privateField; - } - } - - public static class Test2 extends Test1 { - @Configurable - public String overridden = "test"; - @Configurable - public Test1 nested = new Test1(); - } -} diff --git a/test/src/edu/mit/media/funf/pipeline/BasicPipelineTest.java b/test/src/edu/mit/media/funf/pipeline/BasicPipelineTest.java deleted file mode 100644 index f39a9cb..0000000 --- a/test/src/edu/mit/media/funf/pipeline/BasicPipelineTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package edu.mit.media.funf.pipeline; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.IBinder; -import android.test.AndroidTestCase; -import android.util.Log; -import edu.mit.media.funf.FunfManager; -import edu.mit.media.funf.tests.R; - -public class BasicPipelineTest extends AndroidTestCase { - - private FunfManager mgr; - private ServiceConnection conn = new ServiceConnection() { - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - mgr = ((FunfManager.LocalBinder)service).getManager(); - synchronized (s) { - s.notify(); - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - // TODO Auto-generated method stub - - } - - }; - - private Object s = new Object(); - - public void setUp() throws Exception { - super.setUp(); - getContext().bindService(new Intent(getContext(), FunfManager.class), conn, Context.BIND_AUTO_CREATE); - synchronized (s) { - s.wait(5000L); - } - } - - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - getContext().unbindService(conn); - } - - - public void testAsdf() { - String testPipelineConfig = getContext().getResources().getString(R.string.default_pipeline); - Pipeline pipeline = mgr.getGson().fromJson(testPipelineConfig, Pipeline.class); - Log.d("FunfTest", "PIPELINE: " + mgr.getGson().toJson(pipeline)); - pipeline.onCreate(mgr); - } -} diff --git a/test/src/edu/mit/media/funf/probe/AnnotationsTest.java b/test/src/edu/mit/media/funf/probe/AnnotationsTest.java deleted file mode 100644 index 02718da..0000000 --- a/test/src/edu/mit/media/funf/probe/AnnotationsTest.java +++ /dev/null @@ -1,105 +0,0 @@ -package edu.mit.media.funf.probe; - -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -import java.lang.annotation.Documented; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import android.net.Uri; -import android.test.AndroidTestCase; -import android.text.Annotation; - -import com.google.gson.Gson; -import com.google.gson.JsonObject; - -import edu.mit.media.funf.Schedule; - -public class AnnotationsTest extends AndroidTestCase { - - @Documented - @Retention(RUNTIME) - @Target(TYPE) - @Inherited - public @interface A { - String value() default "a"; - } - - @Documented - @Retention(RUNTIME) - @Target(TYPE) - @Inherited - public @interface B { - String value() default "b"; - } - - @A - public class Parent { - - } - - @B - public class Child1 extends Parent { - - } - - @A("test") - @B - public class Child2 extends Parent { - - } - - @A("interface") - public interface TestInterface { - - } - - public class Child3 extends Parent implements TestInterface { - - } - - public class Child4 extends Child2 implements TestInterface { - - } - - public class Child5 implements TestInterface { - - } - - public void testAnnotationInheritance() { - System.out.println(Parent.class.getAnnotations().length); - System.out.println(Child1.class.getAnnotations().length); - System.out.println(Child2.class.getAnnotations().length); - System.out.println(Child2.class.getAnnotation(A.class).value()); - System.out.println(Child3.class.getAnnotation(A.class).value()); - System.out.println(Child4.class.getAnnotation(A.class).value()); - System.out.println(Child5.class.getAnnotation(A.class).value()); - } - - public void testReflectionAccessToDefaults() throws IllegalAccessException, InstantiationException { - - Annotation test = (Annotation) Schedule.DefaultSchedule.class.newInstance(); - } - - public void testGsonKeyOrderStability() { - Gson gson = new Gson(); - JsonObject t1 = new JsonObject(); - t1.addProperty("test1", 1); - t1.addProperty("test2", 2); - t1.addProperty("test3", 3); - System.out.println(gson.toJson(t1)); - JsonObject t2 = new JsonObject(); - t2.addProperty("test3", 3); - t2.addProperty("test2", 2); - t2.addProperty("test1", 1); - System.out.println(gson.toJson(t2)); - } - - public void testUri() { - Uri test = Uri.parse("http://www.test.com/test"); - Uri test2 = test.buildUpon().appendPath("/another?query=value").build(); - System.out.println(test2.toString()); - } -} diff --git a/test/src/edu/mit/media/funf/probe/ProbeTest.java b/test/src/edu/mit/media/funf/probe/ProbeTest.java deleted file mode 100644 index 291d4aa..0000000 --- a/test/src/edu/mit/media/funf/probe/ProbeTest.java +++ /dev/null @@ -1,120 +0,0 @@ -package edu.mit.media.funf.probe; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; - -import android.test.AndroidTestCase; - -public class ProbeTest extends AndroidTestCase { - - private TestProbe testProbe; - - public class TestProbe extends Probe.Base implements Probe { - public BlockingQueue messageQueue = new LinkedBlockingQueue(); - - public static final String - ENABLED = "ENABLED", - DISABLED = "DISABLED", - STARTED = "STARTED", - STOPPED = "STOPPED"; - - - - - @Override - protected void onEnable() { - messageQueue.offer(ENABLED); - } - - @Override - protected void onStart() { - messageQueue.offer(STARTED); - } - - @Override - protected void onStop() { - messageQueue.offer(STOPPED); - } - - @Override - protected void onDisable() { - messageQueue.offer(DISABLED); - } - - - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - testProbe = new TestProbe(); - } - - @Override - protected void tearDown() throws Exception { - if (testProbe != null) { - testProbe.disable(); - } - super.tearDown(); - } - - - public void testFullStateFlow() { - assertEquals(Probe.State.DISABLED, testProbe.getState()); - testProbe.enable(); - assertStateChange(testProbe, Probe.State.ENABLED, TestProbe.ENABLED); - testProbe.start(); - assertStateChange(testProbe, Probe.State.RUNNING, TestProbe.STARTED); - testProbe.stop(); - assertStateChange(testProbe, Probe.State.ENABLED, TestProbe.STOPPED); - testProbe.disable(); - assertStateChange(testProbe, Probe.State.DISABLED, TestProbe.DISABLED); - } - - public void testIndempotence() { - assertEquals(Probe.State.DISABLED, testProbe.getState()); - testProbe.disable(); - assertStateChange(testProbe, Probe.State.DISABLED); - - // Test multiple times to ensure it doesn't call onEnabled - testProbe.enable(); - assertStateChange(testProbe, Probe.State.ENABLED, TestProbe.ENABLED); - testProbe.enable(); - assertStateChange(testProbe, Probe.State.ENABLED); - testProbe.enable(); - assertStateChange(testProbe, Probe.State.ENABLED); - - testProbe.disable(); - assertStateChange(testProbe, Probe.State.DISABLED, TestProbe.DISABLED); - } - - - /** - * Asserts that the current state is as defined at the end of the list of state changes. - * If no state changes are provided, it is assumed that there should NOT be a state change. - * @param theTestProbe - * @param correctState - * @param correctMessages - */ - private void assertStateChange(TestProbe theTestProbe, Probe.State correctState, String... correctMessages) { - try { - if (correctMessages == null) { - correctMessages = new String[] { null }; - } - for (String correctMessage : correctMessages) { - String message = testProbe.messageQueue.poll(100, TimeUnit.MILLISECONDS); - if (correctMessage == null) { - assertNull(message); - } else { - assertNotNull(message); - assertEquals(correctMessage, message); - } - } - assertEquals(correctState, testProbe.getState()); - } catch (InterruptedException e) { - fail(); - } - } - -} diff --git a/test/src/edu/mit/media/funf/probe/ProbeTestCase.java b/test/src/edu/mit/media/funf/probe/ProbeTestCase.java deleted file mode 100644 index 0e52c2a..0000000 --- a/test/src/edu/mit/media/funf/probe/ProbeTestCase.java +++ /dev/null @@ -1,31 +0,0 @@ -package edu.mit.media.funf.probe; - -import android.test.AndroidTestCase; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonObject; - -import edu.mit.media.funf.FunfManager; - - -public class ProbeTestCase extends AndroidTestCase { - - private Gson factory; - private Class probeClass; - - public ProbeTestCase(Class probeClass) { - this.probeClass = probeClass; - } - - public Gson getFactory() { - if (factory == null) { - factory = new GsonBuilder().registerTypeAdapterFactory(FunfManager.getProbeFactory(getContext())).create(); - } - return factory; - } - - public T getProbe(JsonObject config) { - return getFactory().fromJson(config, probeClass); - } -} diff --git a/test/src/edu/mit/media/funf/probe/builtin/ContactProbeTest.java b/test/src/edu/mit/media/funf/probe/builtin/ContactProbeTest.java deleted file mode 100644 index 3b86ade..0000000 --- a/test/src/edu/mit/media/funf/probe/builtin/ContactProbeTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package edu.mit.media.funf.probe.builtin; - -import android.util.Log; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import edu.mit.media.funf.json.IJsonObject; -import edu.mit.media.funf.probe.Probe.DataListener; -import edu.mit.media.funf.probe.ProbeTestCase; - -public class ContactProbeTest extends ProbeTestCase { - - public ContactProbeTest() { - super(ContactProbe.class); - } - - public static final String TAG = "Probes"; - - private DataListener listener = new DataListener() { - @Override - public void onDataReceived(IJsonObject completeProbeUri, IJsonObject data) { - Log.i(TAG, "DATA: " + completeProbeUri.toString() + " " + data.toString()); - } - - @Override - public void onDataCompleted(IJsonObject completeProbeUri, JsonElement checkpoint) { - Log.i(TAG, "COMPLETE: " + completeProbeUri.toString()); - } -}; - - public void testProbe() throws InterruptedException { - ContactProbe probe = getProbe(new JsonObject()); - probe.registerListener(listener); - Thread.sleep(100L); - } -} diff --git a/test/src/edu/mit/media/funf/probe/builtin/RunningApplicationsProbeTest.java b/test/src/edu/mit/media/funf/probe/builtin/RunningApplicationsProbeTest.java deleted file mode 100644 index 3441158..0000000 --- a/test/src/edu/mit/media/funf/probe/builtin/RunningApplicationsProbeTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package edu.mit.media.funf.probe.builtin; - -import android.util.Log; - -import com.google.gson.JsonElement; - -import edu.mit.media.funf.json.IJsonObject; -import edu.mit.media.funf.probe.Probe.DataListener; -import edu.mit.media.funf.probe.ProbeTestCase; - -public class RunningApplicationsProbeTest extends ProbeTestCase implements DataListener { - - public RunningApplicationsProbeTest() { - super(RunningApplicationsProbe.class); - } - - public void testApps() throws InterruptedException { - RunningApplicationsProbe probe = getProbe(null); - probe.registerListener(this); - Thread.sleep(100000L); - probe.unregisterListener(this); - - } - - - @Override - public void onDataReceived(IJsonObject completeProbeUri, IJsonObject data) { - Log.i("MyData", data.toString()); - } - - @Override - public void onDataCompleted(IJsonObject completeProbeUri, JsonElement checkpoint) { - // TODO Auto-generated method stub - - } -} diff --git a/test/src/edu/mit/media/funf/probe/builtin/ServicesProbeTest.java b/test/src/edu/mit/media/funf/probe/builtin/ServicesProbeTest.java deleted file mode 100644 index 35cb525..0000000 --- a/test/src/edu/mit/media/funf/probe/builtin/ServicesProbeTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package edu.mit.media.funf.probe.builtin; - -import android.util.Log; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import edu.mit.media.funf.json.IJsonObject; -import edu.mit.media.funf.probe.Probe.DataListener; -import edu.mit.media.funf.probe.ProbeTestCase; - -public class ServicesProbeTest extends ProbeTestCase { - - public ServicesProbeTest() { - super(ServicesProbe.class); - } - - public static final String TAG = "Probes"; - - private DataListener listener = new DataListener() { - @Override - public void onDataReceived(IJsonObject completeProbeUri, IJsonObject data) { - Log.i(TAG, "DATA: " + completeProbeUri.toString() + " " + data.toString()); - } - - @Override - public void onDataCompleted(IJsonObject completeProbeUri, JsonElement checkpoint) { - Log.i(TAG, "COMPLETE: " + completeProbeUri.toString()); - } -}; - - public void testProbe() throws InterruptedException { - ServicesProbe probe = getProbe(new JsonObject()); - probe.registerListener(listener); - Thread.sleep(100L); - } -} diff --git a/test/src/edu/mit/media/funf/probe/builtin/TelephonyProbeTest.java b/test/src/edu/mit/media/funf/probe/builtin/TelephonyProbeTest.java deleted file mode 100644 index 1da0f69..0000000 --- a/test/src/edu/mit/media/funf/probe/builtin/TelephonyProbeTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package edu.mit.media.funf.probe.builtin; - -import android.util.Log; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import edu.mit.media.funf.json.IJsonObject; -import edu.mit.media.funf.probe.Probe.DataListener; -import edu.mit.media.funf.probe.ProbeTestCase; - -public class TelephonyProbeTest extends ProbeTestCase { - - public TelephonyProbeTest() { - super(TelephonyProbe.class); - } - - public static final String TAG = "Probes"; - - private DataListener listener = new DataListener() { - @Override - public void onDataReceived(IJsonObject completeProbeUri, IJsonObject data) { - Log.i(TAG, "DATA: " + completeProbeUri.toString() + " " + data.toString()); - } - - @Override - public void onDataCompleted(IJsonObject completeProbeUri, JsonElement checkpoint) { - Log.i(TAG, "COMPLETE: " + completeProbeUri.toString()); - } -}; - - public void testProbe() throws InterruptedException { - TelephonyProbe probe = getProbe(new JsonObject()); - probe.registerListener(listener); - Thread.sleep(100L); - } -} diff --git a/test/src/edu/mit/media/funf/probe/builtin/TestAllBuiltinProbes.java b/test/src/edu/mit/media/funf/probe/builtin/TestAllBuiltinProbes.java deleted file mode 100644 index 9c21a00..0000000 --- a/test/src/edu/mit/media/funf/probe/builtin/TestAllBuiltinProbes.java +++ /dev/null @@ -1,151 +0,0 @@ -package edu.mit.media.funf.probe.builtin; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import android.os.Debug; -import android.test.AndroidTestCase; -import android.util.Log; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import edu.mit.media.funf.FunfManager; -import edu.mit.media.funf.json.IJsonObject; -import edu.mit.media.funf.probe.Probe; -import edu.mit.media.funf.probe.Probe.ContinuousProbe; -import edu.mit.media.funf.probe.Probe.DataListener; -import edu.mit.media.funf.probe.Probe.State; -import edu.mit.media.funf.probe.Probe.StateListener; - - -/** - * This class turns on and off all of the builtin probes. - * While it doesn't test any of the output, it does ensure that basic use of the probes does not crash the process. - * @author alangardner - * - */ -public class TestAllBuiltinProbes extends AndroidTestCase { - - public static final String TAG = "FunfTest"; - - private DataListener listener = new DataListener() { - @Override - public void onDataReceived(IJsonObject completeProbeUri, IJsonObject data) { - Log.i(TAG, "DATA: " + completeProbeUri.toString() + " " + data.toString()); - } - - @Override - public void onDataCompleted(IJsonObject completeProbeUri, JsonElement checkpoint) { - Log.i(TAG, "COMPLETE: " + completeProbeUri.toString()); - } - }; - - - - private StateListener stateListener = new StateListener() { - - @Override - public void onStateChanged(Probe probe, State previousState) { - Log.i(TAG, probe.getClass().getName() + ": " + probe.getState()); - Log.i(TAG, getGson().toJson(probe)); - } - - }; - - - private Gson gson; - public Gson getGson() { - if (gson == null) { - gson = new GsonBuilder().registerTypeAdapterFactory(FunfManager.getProbeFactory(getContext())).create(); - } - return gson; - } - - @SuppressWarnings("rawtypes") - public static final Class[] ALL_PROBES = { - AccelerometerFeaturesProbe.class, - AccelerometerSensorProbe.class, - ApplicationsProbe.class, - AudioFeaturesProbe.class, - AudioMediaProbe.class, - BatteryProbe.class, - BluetoothProbe.class, - BrowserBookmarksProbe.class, - BrowserSearchesProbe.class, - CallLogProbe.class, - CellTowerProbe.class, - ContactProbe.class, - GravitySensorProbe.class, - GyroscopeSensorProbe.class, - HardwareInfoProbe.class, - ImageMediaProbe.class, - LightSensorProbe.class, - LinearAccelerationSensorProbe.class, - LocationProbe.class, - MagneticFieldSensorProbe.class, - OrientationSensorProbe.class, - PressureSensorProbe.class, - ProcessStatisticsProbe.class, - ProximitySensorProbe.class, - RotationVectorSensorProbe.class, - RunningApplicationsProbe.class, - ServicesProbe.class, - SimpleLocationProbe.class, - ScreenProbe.class, - SmsProbe.class, - TelephonyProbe.class, - TemperatureSensorProbe.class, - TimeOffsetProbe.class, - VideoMediaProbe.class, - WifiProbe.class - }; - - - - @SuppressWarnings("unchecked") - public void testAll() throws ClassNotFoundException, IOException, InterruptedException { - Log.i(TAG,"Running"); - Debug.startMethodTracing("calc"); - List> allProbeClasses = Arrays.asList((Class[])ALL_PROBES); - - // Run one at a time - Gson gson = getGson(); - for (Class probeClass : allProbeClasses) { - JsonObject config = new JsonObject(); - config.addProperty("sensorDelay", SensorProbe.SENSOR_DELAY_NORMAL); - config.addProperty("asdf", 1); - config.addProperty("zzzz", "__"); - Probe probe = gson.fromJson(config, probeClass); - probe.addStateListener(stateListener); - probe.registerListener(listener); - Thread.sleep(100L); - if (probe instanceof ContinuousProbe) { - ((ContinuousProbe)probe).unregisterListener(listener); - } - } - // Run simultaneously - List probes = new ArrayList(); - for (Class probeClass : allProbeClasses) { - probes.add(gson.fromJson(Probe.DEFAULT_CONFIG, probeClass)); - } - for (Probe probe : probes) { - probe.addStateListener(stateListener); - probe.registerListener(listener); - } - Thread.sleep(10000L); - for (Probe probe : probes) { - if (probe instanceof ContinuousProbe) { - ((ContinuousProbe)probe).unregisterListener(listener); - } - } - - Thread.sleep(1000L); // Give probes time stop - - Debug.stopMethodTracing(); - } -} diff --git a/test/src/edu/mit/media/funf/probe/builtin/TestLocationProbes.java b/test/src/edu/mit/media/funf/probe/builtin/TestLocationProbes.java deleted file mode 100644 index 2413916..0000000 --- a/test/src/edu/mit/media/funf/probe/builtin/TestLocationProbes.java +++ /dev/null @@ -1,109 +0,0 @@ -package edu.mit.media.funf.probe.builtin; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import android.test.AndroidTestCase; -import android.util.Log; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import edu.mit.media.funf.FunfManager; -import edu.mit.media.funf.json.IJsonObject; -import edu.mit.media.funf.probe.Probe; -import edu.mit.media.funf.probe.Probe.ContinuousProbe; -import edu.mit.media.funf.probe.Probe.DataListener; -import edu.mit.media.funf.probe.Probe.State; -import edu.mit.media.funf.probe.Probe.StateListener; - - -public class TestLocationProbes extends AndroidTestCase { - - public static final String TAG = "FunfTest"; - - private DataListener listener = new DataListener() { - @Override - public void onDataReceived(IJsonObject completeProbeUri, IJsonObject data) { - Log.i(TAG, "DATA: " + completeProbeUri.toString() + " " + data.toString()); - } - - @Override - public void onDataCompleted(IJsonObject completeProbeUri, JsonElement checkpoint) { - Log.i(TAG, "COMPLETE: " + completeProbeUri.toString()); - } - }; - - private StateListener stateListener = new StateListener() { - - @Override - public void onStateChanged(Probe probe, State previousState) { - Log.i(TAG, probe.getClass().getName() + ": " + probe.getState()); - Log.i(TAG, getGson().toJson(probe)); - } - - }; - - private Gson gson; - public Gson getGson() { - if (gson == null) { - gson = new GsonBuilder().registerTypeAdapterFactory(FunfManager.getProbeFactory(getContext())).create(); - } - return gson; - } - - @SuppressWarnings("rawtypes") - public static final Class[] ALL_PROBES = { - LocationProbe.class, - SimpleLocationProbe.class - }; - - - @SuppressWarnings("unchecked") - public void testAll() throws ClassNotFoundException, IOException, InterruptedException { - Log.i(TAG,"Running"); - List> allProbeClasses = Arrays.asList((Class[])ALL_PROBES); - - // Run one at a time - Gson gson = getGson(); - for (Class probeClass : allProbeClasses) { - JsonObject config = new JsonObject(); - config.addProperty("maxWaitTime", 1); - config.addProperty("asdf", 1); - config.addProperty("zzzz", "__"); - Probe probe = gson.fromJson(config, probeClass); - probe.addStateListener(stateListener); - probe.registerListener(listener); - Thread.sleep(100L); - if (probe instanceof ContinuousProbe) { - ((ContinuousProbe)probe).unregisterListener(listener); - } - } - // Run simultaneously - List probes = new ArrayList(); - for (Class probeClass : allProbeClasses) { - JsonObject config = new JsonObject(); - config.addProperty("maxWaitTime", 8); - config.addProperty("asdf", 1); - config.addProperty("zzzz", "__"); - Probe probe = gson.fromJson(config, probeClass); - probes.add(probe); - } - for (Probe probe : probes) { - probe.addStateListener(stateListener); - probe.registerListener(listener); - } - Thread.sleep(10000L); - for (Probe probe : probes) { - if (probe instanceof ContinuousProbe) { - ((ContinuousProbe)probe).unregisterListener(listener); - } - } - - Thread.sleep(1000L); // Give probes time stop - } -} diff --git a/test/src/edu/mit/media/funf/storage/DefaultArchiveTest.java b/test/src/edu/mit/media/funf/storage/DefaultArchiveTest.java deleted file mode 100644 index 4d2547e..0000000 --- a/test/src/edu/mit/media/funf/storage/DefaultArchiveTest.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.storage; - -import android.test.AndroidTestCase; - -public class DefaultArchiveTest extends AndroidTestCase { - - - public void testEncryptionKeyGeneration() { - DefaultArchive testDbArchive = new DefaultArchive(getContext(), "testDb"); - testDbArchive.setEncryptionPassword("changeme".toCharArray()); - testDbArchive.setEncryptionPassword("test1234".toCharArray()); - } -} diff --git a/test/src/edu/mit/media/funf/storage/PrefsWriteSpeedTest.java b/test/src/edu/mit/media/funf/storage/PrefsWriteSpeedTest.java deleted file mode 100644 index 4a4007f..0000000 --- a/test/src/edu/mit/media/funf/storage/PrefsWriteSpeedTest.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Funf: Open Sensing Framework - * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. - * Acknowledgments: Alan Gardner - * Contact: nadav@media.mit.edu - * - * This file is part of Funf. - * - * Funf is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * Funf is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with Funf. If not, see . - */ -package edu.mit.media.funf.storage; - -import android.content.Context; -import android.content.SharedPreferences; -import android.os.StatFs; -import android.test.AndroidTestCase; -import android.util.Log; - -public class PrefsWriteSpeedTest extends AndroidTestCase { - - public static final String TAG = "FunfTest"; - - public void testSpeed() { - long now = System.currentTimeMillis(); - for (int i=0; i< 100; i++) { - SharedPreferences prefs = getContext().getSharedPreferences("ASDF", Context.MODE_PRIVATE); - prefs.edit().clear().putString("TEST_KEY", "TEST_VALUE").commit(); - } - long total = System.currentTimeMillis() - now; - Log.i(TAG, "Total time: " + total + "ms"); - } - - public void testBlockSize() { - StatFs stats = new StatFs("/"); - Log.i(TAG, "Root block size:" + stats.getBlockSize()); - stats.restat("/sdcard"); - Log.i(TAG, "SDCard block size:" + stats.getBlockSize()); - } -} diff --git a/test/src/edu/mit/media/funf/tests/ExampleService.java b/test/src/edu/mit/media/funf/tests/ExampleService.java deleted file mode 100644 index c34bafc..0000000 --- a/test/src/edu/mit/media/funf/tests/ExampleService.java +++ /dev/null @@ -1,27 +0,0 @@ -package edu.mit.media.funf.tests; - -import java.util.Map; - -import android.app.IntentService; -import android.content.Intent; -import android.util.Log; -import edu.mit.media.funf.util.BundleUtil; - -public class ExampleService extends IntentService { - - public ExampleService() { - super("ExampleService"); - } - - @Override - protected void onHandleIntent(Intent intent) { - Map values = BundleUtil.getValues(intent.getExtras()); - String entryString = "{"; - for (Map.Entry entry : values.entrySet()) { - entryString += "" + entry.getKey() + ": " + String.valueOf(entry.getValue()); - } - entryString += "}"; - Log.i("ExampleService", "Intent: " + intent.getAction() + " -> " + entryString); - } - -} diff --git a/test/src/edu/mit/media/funf/tests/SensorTest.java b/test/src/edu/mit/media/funf/tests/SensorTest.java deleted file mode 100644 index f2faf14..0000000 --- a/test/src/edu/mit/media/funf/tests/SensorTest.java +++ /dev/null @@ -1,107 +0,0 @@ -package edu.mit.media.funf.tests; - -import android.content.Context; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; -import android.test.AndroidTestCase; - -public class SensorTest extends AndroidTestCase { - - private long lastNanos = 0L; - private long lastNanosSum = 0L; - private long count = 0L; - - public void testSensorSpeed() throws InterruptedException { - for (int i = 0; i< 1000; i++) { - // calibrateNanosConversion(); - System.out.println(millisToSeconds(System.currentTimeMillis()) - uptimeNanosToTimestamp(System.nanoTime())); - } - System.out.println("---------------------------"); - - double lastNano = 0; - for (int i = 0; i< 20; i++) { - long nano = System.nanoTime(); - double diff = millisToSeconds(System.currentTimeMillis()) - uptimeNanosToTimestamp(nano); - System.out.println(diff); - System.out.println(uptimeNanosToTimestamp(nano) - uptimeNanosToTimestamp(lastNanos)); - lastNanos = nano; - System.out.println(millisToSeconds(System.currentTimeMillis()) + " " + uptimeNanosToTimestamp(System.nanoTime())); - } - SensorManager manager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE); - Sensor sensor = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); - - calibrateNanosConversion(); - SensorEventListener sensorListener = new SensorEventListener() { - - @Override - public void onSensorChanged(SensorEvent event) { - System.out.println("--------------------"); - long mili = System.currentTimeMillis(); - long nano = System.nanoTime(); - if (lastNanos == 0L) { - lastNanos = event.timestamp; - } - //calibrateNanosConversion(); - System.out.println("Event Seconds: " + uptimeNanosToTimestamp(event.timestamp)); - System.out.println("Nano Seconds: " + uptimeNanosToTimestamp(nano)); - System.out.println("Diff Seconds: " + (uptimeNanosToTimestamp(nano) - uptimeNanosToTimestamp(event.timestamp))); - System.out.println("Acc Period: " + (uptimeNanosToTimestamp(nano) - uptimeNanosToTimestamp(lastNanos))); - System.out.println("Mili Seconds: " + millisToSeconds(mili)); - lastNanosSum += nano - lastNanos; - count++; - System.out.println("Avg diff:" + (lastNanosSum / count)); - lastNanos = nano; - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - } - }; - manager.registerListener(sensorListener, sensor, SensorManager.SENSOR_DELAY_FASTEST); - Thread.sleep(30); - } - - public static double millisToSeconds(long millis) { - return ((double)millis)/1000; - } - - public static long secondsToMillis(double seconds) { - return (long)(seconds*1000); - } - - public static final long NANOS_IN_SECOND = 1000000000; // 10^9 - private static long referenceNanos; - private static long referenceMillis; - private static double secondsOffset; - - /** - * Aligns the nano seconds to the start of a new millisecond. - * This should be called whenever device wakes up from sleep. - */ - public static void calibrateNanosConversion() { - long originalMillis = System.currentTimeMillis(); - long updatedMillis = originalMillis; - while(originalMillis == updatedMillis) { - updatedMillis = System.currentTimeMillis(); - } - referenceNanos = System.nanoTime(); - referenceMillis = updatedMillis; - secondsOffset = millisToSeconds(referenceMillis) - (double)referenceNanos / (double)NANOS_IN_SECOND; - } - - public static double uptimeNanosToTimestamp(long nanos) { - long currentMillisAccordingToNanos = secondsToMillis(_uptimeNanosToTimestamp(System.nanoTime())); - if (Math.abs(currentMillisAccordingToNanos - System.currentTimeMillis()) > 1) { - calibrateNanosConversion(); - } - return _uptimeNanosToTimestamp(nanos); - } - - private static double _uptimeNanosToTimestamp(long nanos) { - return ((double)nanos / (double)NANOS_IN_SECOND) + secondsOffset; - } - -} - From d8494aa48a0e34937619cf01635f97688317109d Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sun, 30 Aug 2015 12:14:44 +0200 Subject: [PATCH 02/61] added build version information --- funf_v4/build.gradle | 58 +++++++++++++++++++ funf_v4/src/main/assets/build.info | 1 + .../java/edu/mit/media/funf/FunfManager.java | 27 ++++++++- 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 funf_v4/src/main/assets/build.info diff --git a/funf_v4/build.gradle b/funf_v4/build.gradle index 0be7274..d89a488 100644 --- a/funf_v4/build.gradle +++ b/funf_v4/build.gradle @@ -22,3 +22,61 @@ dependencies { compile files('libs/gson-2.1-sources.jar') compile files('libs/gson-2.1.jar') } + + +def getVersionName = {-> + println '*** getVersionName' + try { + def stdout = new ByteArrayOutputStream() + exec { + commandLine 'git', 'symbolic-ref', '--short', '-q', 'HEAD' + standardOutput = stdout + } + + def stdout2 = new ByteArrayOutputStream() + exec { + commandLine 'git', 'log', '-1', '--format=%h' + standardOutput = stdout2 + } + + def stdout3 = new ByteArrayOutputStream() + exec { + commandLine 'git', 'describe', '--tags', '--exact-match' + standardOutput = stdout3 + errorOutput = new ByteArrayOutputStream() + ignoreExitValue = true + } + + + def return_value = stdout3.toString().trim() + "-" +stdout.toString().trim() + "-" + stdout2.toString().trim() + println "return_value: "+return_value + + def assetsDir = android.sourceSets.main.assets.srcDirs.toArray()[0] + println "assetsDir: "+assetsDir + + def buildInfoFile = new File(assetsDir, 'build.info').getAbsolutePath() + println "buildInfoFile: "+buildInfoFile + + new File(buildInfoFile).write(return_value); + + + + return return_value + } + catch (ignored) { + return null; + } + + + + + + + +} + +android { + defaultConfig { + versionName getVersionName() + } +} \ No newline at end of file diff --git a/funf_v4/src/main/assets/build.info b/funf_v4/src/main/assets/build.info new file mode 100644 index 0000000..c68c5d3 --- /dev/null +++ b/funf_v4/src/main/assets/build.info @@ -0,0 +1 @@ +0.4.2-build_version-0c00822 \ No newline at end of file diff --git a/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java b/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java index 54eee78..5383c4f 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java @@ -25,6 +25,9 @@ import static edu.mit.media.funf.util.LogUtil.TAG; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; import java.lang.reflect.Type; import java.math.BigDecimal; import java.util.ArrayList; @@ -210,7 +213,29 @@ public void run() { registerPipeline(name, newPipeline); // Will unregister previous before running } } - + + public String getVersion() { + String version = "unknown"; + BufferedReader reader = null; + try{ + reader = new BufferedReader(new InputStreamReader(getAssets().open("build.info"))); + version = reader.readLine().trim(); + } catch (IOException e) { + + } + finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + + } + } + } + + return version; + } + public JsonObject getPipelineConfig(String name) { String configString = prefs.getString(name, null); Bundle metadata = getMetadata(); From de054043ca74b7fa861520516a7c89b6a4fb376f Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sun, 30 Aug 2015 12:25:19 +0200 Subject: [PATCH 03/61] updated build script to take most recent tag instead of exact match --- funf_v4/build.gradle | 2 +- funf_v4/src/main/assets/build.info | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/funf_v4/build.gradle b/funf_v4/build.gradle index d89a488..2438b99 100644 --- a/funf_v4/build.gradle +++ b/funf_v4/build.gradle @@ -41,7 +41,7 @@ def getVersionName = {-> def stdout3 = new ByteArrayOutputStream() exec { - commandLine 'git', 'describe', '--tags', '--exact-match' + commandLine 'git', 'describe', '--tags', '--abbrev=0' standardOutput = stdout3 errorOutput = new ByteArrayOutputStream() ignoreExitValue = true diff --git a/funf_v4/src/main/assets/build.info b/funf_v4/src/main/assets/build.info index c68c5d3..6225ef6 100644 --- a/funf_v4/src/main/assets/build.info +++ b/funf_v4/src/main/assets/build.info @@ -1 +1 @@ -0.4.2-build_version-0c00822 \ No newline at end of file +0.4.2-dev-bd36d66 \ No newline at end of file From 4f1b6ce92f294247710b8d72c8d1e9177a43033f Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sun, 30 Aug 2015 12:29:21 +0200 Subject: [PATCH 04/61] removed build.info from tracking --- .gitignore | 1 + funf_v4/src/main/assets/build.info | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 funf_v4/src/main/assets/build.info diff --git a/.gitignore b/.gitignore index a1c7ce3..7d5d0a2 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ proguard/ .DS_Store .idea *.iml +build.info diff --git a/funf_v4/src/main/assets/build.info b/funf_v4/src/main/assets/build.info deleted file mode 100644 index 6225ef6..0000000 --- a/funf_v4/src/main/assets/build.info +++ /dev/null @@ -1 +0,0 @@ -0.4.2-dev-bd36d66 \ No newline at end of file From fe61fde93c8a94790fe777bfda7dd76a4448d443 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sun, 30 Aug 2015 22:57:11 +0200 Subject: [PATCH 05/61] implemented gzip compression of the files, configurable in pipeline config --- .../media/funf/storage/DefaultArchive.java | 19 ++- .../mit/media/funf/storage/FileCopier.java | 151 +++++++++++++++++- 2 files changed, 160 insertions(+), 10 deletions(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/storage/DefaultArchive.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/DefaultArchive.java index afad56d..3377823 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/storage/DefaultArchive.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/storage/DefaultArchive.java @@ -32,6 +32,8 @@ import javax.crypto.spec.PBEKeySpec; import android.content.Context; +import android.util.Log; + import edu.mit.media.funf.Schedule.DefaultSchedule; import edu.mit.media.funf.config.Configurable; import edu.mit.media.funf.security.Base64Coder; @@ -67,6 +69,9 @@ public class DefaultArchive implements FileArchive { @Configurable protected String key; + + @Configurable + protected Boolean compress = false; protected Context context; @@ -161,8 +166,8 @@ protected FileArchive getDelegateArchive() { String rootSdCardPath = getPathOnSDCard(); FileArchive backupArchive = FileDirectoryArchive.getRollingFileArchive(new File(rootSdCardPath + "backup")); FileArchive mainArchive = new CompositeFileArchive( - getTimestampedDbFileArchive(new File(rootSdCardPath + "archive"), context, key), - getTimestampedDbFileArchive(context.getDir("funf_" + getCleanedName() + "_archive", Context.MODE_PRIVATE), context, key) + getTimestampedDbFileArchive(new File(rootSdCardPath + "archive"), context, key, compress), + getTimestampedDbFileArchive(context.getDir("funf_" + getCleanedName() + "_archive", Context.MODE_PRIVATE), context, key, compress) ); delegateArchive = new BackedUpArchive(mainArchive, backupArchive); } @@ -171,9 +176,15 @@ protected FileArchive getDelegateArchive() { return delegateArchive; } - static FileDirectoryArchive getTimestampedDbFileArchive(File archiveDir, Context context, SecretKey encryptionKey) { + static FileDirectoryArchive getTimestampedDbFileArchive(File archiveDir, Context context, SecretKey encryptionKey, Boolean compress) { NameGenerator nameGenerator = new CompositeNameGenerator(new SystemUniqueTimestampNameGenerator(context), new RequiredSuffixNameGenerator(".db")); - FileCopier copier = (encryptionKey == null) ? new FileCopier.SimpleFileCopier() : new FileCopier.EncryptedFileCopier(encryptionKey, DES_ENCRYPTION); + FileCopier copier = null; + if (compress) { + copier = (encryptionKey == null) ? new FileCopier.CompressedFileCopier() : new FileCopier.CompressedEncryptedFileCopier(encryptionKey, DES_ENCRYPTION); + } else { + copier = (encryptionKey == null) ? new FileCopier.SimpleFileCopier() : new FileCopier.EncryptedFileCopier(encryptionKey, DES_ENCRYPTION); + } + return new FileDirectoryArchive(archiveDir, nameGenerator, copier, new DirectoryCleaner.KeepAll()); } diff --git a/funf_v4/src/main/java/edu/mit/media/funf/storage/FileCopier.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/FileCopier.java index 9f48ae7..d3664d9 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/storage/FileCopier.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/storage/FileCopier.java @@ -25,14 +25,18 @@ +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.ObjectOutputStream; import java.io.OutputStream; import java.nio.channels.FileChannel; +import java.util.zip.GZIPOutputStream; import javax.crypto.Cipher; import javax.crypto.CipherOutputStream; @@ -136,14 +140,14 @@ public boolean copy(File sourceFile, File destinationFile) { CipherOutputStream co = null; try { in = new FileInputStream(sourceFile); - out = new FileOutputStream(destinationFile); + out = new FileOutputStream(destinationFile); co = new CipherOutputStream(out, ecipher); byte[] buf = new byte[128*4096]; - int len = 0; - while ((len = in.read(buf)) > 0) - { - co.write(buf, 0, len); - } + int len = 0; + while ((len = in.read(buf)) > 0) + { + co.write(buf, 0, len); + } } catch (FileNotFoundException e) { Log.e(TAG, "File not found", e); return false; @@ -160,4 +164,139 @@ public boolean copy(File sourceFile, File destinationFile) { } } + + public static class CompressedEncryptedFileCopier implements FileCopier { + public static final String TAG = CompressedEncryptedFileCopier.class.getName(); + private final SecretKey key; + private final String transformation; + + public CompressedEncryptedFileCopier(SecretKey key, String transformation) { + this.key = key; + this.transformation = transformation; + } + + private Cipher cipher; // Cache + protected Cipher getCipher() { + if (cipher == null) { + synchronized (this) { + if (cipher == null) { + try { + cipher = Cipher.getInstance(transformation); + cipher.init(Cipher.ENCRYPT_MODE, key); + } catch (Exception e) { + Log.e(TAG, "Error creating cipher", e); + } + } + } + } + return cipher; + } + + @Override + public boolean copy(File sourceFile, File destinationFile) { + Log.i(TAG, "compressing + encrypting + copying " + sourceFile.getPath() + " to " + destinationFile.getPath()); + + Cipher ecipher = getCipher(); + if (ecipher == null) { + return false; + } + + InputStream in = null; + OutputStream out = null; + GZIPOutputStream gzipOutputStream = null; + try { + in = new FileInputStream(sourceFile); + out = new ByteArrayOutputStream(); + gzipOutputStream = new GZIPOutputStream(out); + + byte[] buf = new byte[128*4096]; + int len = 0; + while ((len = in.read(buf)) > 0) { + Log.i(TAG, "compressing..."); + gzipOutputStream.write(buf, 0, len); + } + + } catch (FileNotFoundException e) { + Log.e(TAG, "File not found", e); + return false; + } catch (IOException e) { + Log.e(TAG, "IOException", e); + return false; + } finally { + IOUtil.close(in); + IOUtil.close(gzipOutputStream); + IOUtil.close(out); + } + + InputStream in2 = null; + OutputStream out2 = null; + CipherOutputStream co = null; + try { + in2 = new ByteArrayInputStream(((ByteArrayOutputStream)out).toByteArray()); + + out2 = new FileOutputStream(destinationFile); + co = new CipherOutputStream(out2, ecipher); + byte[] buf = new byte[128*4096]; + int len = 0; + while ((len = in2.read(buf)) > 0) + { + co.write(buf, 0, len); + } + } catch (FileNotFoundException e) { + Log.e(TAG, "File not found", e); + return false; + } catch (IOException e) { + Log.e(TAG, "IOException", e); + return false; + } finally { + IOUtil.close(in2); + IOUtil.close(co); + IOUtil.close(out2); + } + + Log.i(TAG, "done copy"); + return true; + } + } + + public static class CompressedFileCopier implements FileCopier { + public static final String TAG = CompressedEncryptedFileCopier.class.getName(); + + @Override + public boolean copy(File sourceFile, File destinationFile) { + Log.i(TAG, "compressing + copying " + sourceFile.getPath() + " to " + destinationFile.getPath()); + + + InputStream in = null; + OutputStream out = null; + GZIPOutputStream gzipOutputStream = null; + try { + in = new FileInputStream(sourceFile); + out = new FileOutputStream(destinationFile); + gzipOutputStream = new GZIPOutputStream(out); + + byte[] buf = new byte[128*4096]; + int len = 0; + while ((len = in.read(buf)) > 0) { + Log.i(TAG, "compressing..."); + gzipOutputStream.write(buf, 0, len); + } + + } catch (FileNotFoundException e) { + Log.e(TAG, "File not found", e); + return false; + } catch (IOException e) { + Log.e(TAG, "IOException", e); + return false; + } finally { + IOUtil.close(in); + IOUtil.close(gzipOutputStream); + IOUtil.close(out); + } + + Log.i(TAG, "done copy"); + return true; + } + } + } From adb023670effc04844c285c7e1402009c04513d0 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sun, 30 Aug 2015 23:14:08 +0200 Subject: [PATCH 06/61] updated fields for location probe --- .../edu/mit/media/funf/probe/builtin/LocationProbe.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/LocationProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/LocationProbe.java index 8a7b224..64180e9 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/LocationProbe.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/LocationProbe.java @@ -159,7 +159,13 @@ public boolean shouldSkipField(FieldAttributes f) { || name.equals("mLat2") || name.equals("mLon1") || name.equals("mLon2") - || name.equals("mLon2") + || name.equals("mLon2") + || name.equals("mHasSpeed") + || name.equals("mHasAccuracy") + || name.equals("mHasAltitude") + || name.equals("mHasBearing") + || name.equals("mHasSpeed") + || name.equals("mElapsedRealtimeNanos") ) ); } From cd5c86b7f42b4894e4be339731a67cbcc8f5caff Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Mon, 31 Aug 2015 11:55:20 +0200 Subject: [PATCH 07/61] switched to nicer stream chaining --- .../mit/media/funf/storage/FileCopier.java | 37 ++++--------------- 1 file changed, 7 insertions(+), 30 deletions(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/storage/FileCopier.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/FileCopier.java index d3664d9..334597b 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/storage/FileCopier.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/storage/FileCopier.java @@ -203,11 +203,13 @@ public boolean copy(File sourceFile, File destinationFile) { InputStream in = null; OutputStream out = null; + CipherOutputStream co = null; GZIPOutputStream gzipOutputStream = null; try { in = new FileInputStream(sourceFile); - out = new ByteArrayOutputStream(); - gzipOutputStream = new GZIPOutputStream(out); + out = new FileOutputStream(destinationFile); + co = new CipherOutputStream(out, ecipher); + gzipOutputStream = new GZIPOutputStream(co); byte[] buf = new byte[128*4096]; int len = 0; @@ -215,6 +217,7 @@ public boolean copy(File sourceFile, File destinationFile) { Log.i(TAG, "compressing..."); gzipOutputStream.write(buf, 0, len); } + gzipOutputStream.finish(); } catch (FileNotFoundException e) { Log.e(TAG, "File not found", e); @@ -224,34 +227,8 @@ public boolean copy(File sourceFile, File destinationFile) { return false; } finally { IOUtil.close(in); - IOUtil.close(gzipOutputStream); - IOUtil.close(out); - } - - InputStream in2 = null; - OutputStream out2 = null; - CipherOutputStream co = null; - try { - in2 = new ByteArrayInputStream(((ByteArrayOutputStream)out).toByteArray()); - - out2 = new FileOutputStream(destinationFile); - co = new CipherOutputStream(out2, ecipher); - byte[] buf = new byte[128*4096]; - int len = 0; - while ((len = in2.read(buf)) > 0) - { - co.write(buf, 0, len); - } - } catch (FileNotFoundException e) { - Log.e(TAG, "File not found", e); - return false; - } catch (IOException e) { - Log.e(TAG, "IOException", e); - return false; - } finally { - IOUtil.close(in2); IOUtil.close(co); - IOUtil.close(out2); + IOUtil.close(out); } Log.i(TAG, "done copy"); @@ -260,7 +237,7 @@ public boolean copy(File sourceFile, File destinationFile) { } public static class CompressedFileCopier implements FileCopier { - public static final String TAG = CompressedEncryptedFileCopier.class.getName(); + public static final String TAG = CompressedFileCopier.class.getName(); @Override public boolean copy(File sourceFile, File destinationFile) { From 0e425bac839e49449801fc1f77f859add8d15159 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Mon, 31 Aug 2015 15:48:44 +0200 Subject: [PATCH 08/61] wifi probe now includes exclusion and can emit scan started data --- .../media/funf/probe/builtin/WifiProbe.java | 45 ++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/WifiProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/WifiProbe.java index 5007be5..f603910 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/WifiProbe.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/WifiProbe.java @@ -25,8 +25,6 @@ -import java.util.List; - import android.Manifest; import android.content.BroadcastReceiver; import android.content.Context; @@ -37,11 +35,16 @@ import android.net.wifi.WifiManager.WifiLock; import android.util.Log; +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import java.util.List; + import edu.mit.media.funf.Schedule; +import edu.mit.media.funf.config.Configurable; import edu.mit.media.funf.probe.Probe.Base; import edu.mit.media.funf.probe.Probe.DisplayName; import edu.mit.media.funf.probe.Probe.RequiredFeatures; @@ -54,6 +57,9 @@ @DisplayName("Nearby Wifi Devices Probe") public class WifiProbe extends Base { + @Configurable + private boolean include_scan_started = true; + public static final String TSF = "tsf"; private static final String LOCK_KEY = WifiProbe.class.getName(); @@ -68,7 +74,7 @@ public void onReceive(Context context, Intent intent) { if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(intent.getAction())) { List results = wifiManager.getScanResults(); if (results != null) { - Gson gson = getGson(); + Gson gson = getGsonBuilder().addSerializationExclusionStrategy(new WifiExclusionStrategy()).create(); for (ScanResult result : results) { JsonObject data = gson.toJsonTree(result).getAsJsonObject(); if (data.has(TIMESTAMP)) { @@ -174,14 +180,28 @@ private void releaseWifiLock() { wifiLock = null; } } - + + private JsonObject createScanStartedData() { + JsonObject data = new JsonObject(); + data.addProperty("BSSID", "00:00:00:00:00:00"); + data.addProperty("SSID", "DUMMY_SCAN_STARTED"); + return data; + + } + + private void sendScanStartedData() { + JsonObject data = createScanStartedData(); + sendData(data); + } + private void runScan() { numberOfAttempts += 1; int state = wifiManager.getWifiState(); if (state == WifiManager.WIFI_STATE_ENABLED) { boolean successfulStart = wifiManager.startScan(); - if (successfulStart) { + if (successfulStart) { Log.i(LogUtil.TAG, "WIFI scan started succesfully"); + if (include_scan_started) sendScanStartedData(); } else { Log.e(LogUtil.TAG, "WIFI scan failed."); } @@ -196,4 +216,19 @@ private void runScan() { } } + + public class WifiExclusionStrategy implements ExclusionStrategy { + public boolean shouldSkipClass(Class cls) { + return false; + } + public boolean shouldSkipField(FieldAttributes f) { + String name = f.getName(); + return (f.getDeclaringClass() == ScanResult.class && + (name.equals("XXX") + //here we can remove fields from the scan result if we want to + ) + ); + } + + } } From 12b99c63714c1482fde5783e150633282ffa94ab Mon Sep 17 00:00:00 2001 From: RaduGatej Date: Mon, 31 Aug 2015 20:42:00 +0200 Subject: [PATCH 09/61] Merge pull request #2 from OpenSensing/compressed_archive implemented gzip compression of the files, configurable in pipeline config From 0f054f90b1d4feeb7dae8939ea18afe273c2b5bd Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Tue, 1 Sep 2015 12:25:51 +0200 Subject: [PATCH 10/61] empirically 30 seconds is not enough for a full BT scan in dense environment, changed to 60 --- .../java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java index 6f04989..5a51f34 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java @@ -50,7 +50,7 @@ public class BluetoothProbe extends Base implements PassiveProbe { @Configurable - private BigDecimal maxScanTime = BigDecimal.valueOf(30.0); + private BigDecimal maxScanTime = BigDecimal.valueOf(60.0); private BluetoothAdapter adapter; private BroadcastReceiver receiver = new BroadcastReceiver() { From 8993f149a414fa2bb545d28e7f1d39e6d9e86cfc Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Tue, 1 Sep 2015 13:02:43 +0200 Subject: [PATCH 11/61] bluetooth probe now includes exclusion and can emit scan started data --- .../funf/probe/builtin/BluetoothProbe.java | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java index 5a51f34..c9acf6b 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java @@ -34,6 +34,12 @@ import android.content.Intent; import android.content.IntentFilter; import android.util.Log; + +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; +import com.google.gson.Gson; +import com.google.gson.JsonObject; + import edu.mit.media.funf.Schedule; import edu.mit.media.funf.config.Configurable; import edu.mit.media.funf.probe.Probe.Base; @@ -51,6 +57,9 @@ public class BluetoothProbe extends Base implements PassiveProbe { @Configurable private BigDecimal maxScanTime = BigDecimal.valueOf(60.0); + + @Configurable + private boolean include_scan_started = true; private BluetoothAdapter adapter; private BroadcastReceiver receiver = new BroadcastReceiver() { @@ -58,7 +67,8 @@ public class BluetoothProbe extends Base implements PassiveProbe { public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (action.equals(BluetoothDevice.ACTION_FOUND)) { - sendData(getGson().toJsonTree(intent.getExtras()).getAsJsonObject()); + Gson gson = getGsonBuilder().addSerializationExclusionStrategy(new BluetoothExclusionStrategy()).create(); + sendData(gson.toJsonTree(intent.getExtras()).getAsJsonObject()); } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) { stop(); } @@ -78,11 +88,26 @@ public void onReceive(Context context, Intent intent) { } } }; + + private JsonObject createScanStartedData() { + JsonObject data = new JsonObject(); + JsonObject addressData = new JsonObject(); + addressData.addProperty("mAddress", "00:00:00:00:00:00"); + data.add("android.bluetooth.device.extra.DEVICE", addressData); + data.addProperty("android.bluetooth.device.extra.NAME", "DUMMY_SCAN_STARTED"); + return data; + } + + private void sendScanStartedData() { + JsonObject data = createScanStartedData(); + sendData(data); + } private boolean shouldDisableOnFinish = false; // Keeps track of previous bluetooth state private void startDiscovery() { if (adapter.isEnabled()) { adapter.startDiscovery(); + if (include_scan_started) sendScanStartedData(); } else { shouldDisableOnFinish = true; getContext().registerReceiver(stateChangedReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); @@ -135,6 +160,19 @@ protected void onDisable() { Log.w(TAG, getClass().getName() + "Broadcast receiver not registered.", e); } } - + + public class BluetoothExclusionStrategy implements ExclusionStrategy { + public boolean shouldSkipClass(Class cls) { + return false; + } + public boolean shouldSkipField(FieldAttributes f) { + String name = f.getName(); + return (f.getDeclaringClass() == BluetoothDevice.class && + (name.equals("XXX") + //here we can remove fields from the scan result if we want to + ) + ); + } + } } From 4f80206d623a6d0091bdaccd6919848524be672e Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Wed, 2 Sep 2015 09:57:50 +0200 Subject: [PATCH 12/61] added changelog --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0a9a6ae --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,30 @@ +# Change Log + +## [Unreleased](https://github.com/OpenSensing/funf-v4/tree/HEAD) + +[Full Changelog](https://github.com/OpenSensing/funf-v4/compare/0.4.2...HEAD) + +**Implemented enhancements:** + +- change archiving to gzipped json [\#1](https://github.com/OpenSensing/funf-v4/issues/1) + +**Merged pull requests:** + +- implemented gzip compression of the files, configurable in pipeline config [\#2](https://github.com/OpenSensing/funf-v4/pull/2) ([h0pbeat](https://github.com/h0pbeat)) + +## [0.4.2](https://github.com/OpenSensing/funf-v4/tree/0.4.2) (2015-08-18) +[Full Changelog](https://github.com/OpenSensing/funf-v4/compare/0.3.1...0.4.2) + +## [0.3.1](https://github.com/OpenSensing/funf-v4/tree/0.3.1) (2012-04-03) +[Full Changelog](https://github.com/OpenSensing/funf-v4/compare/0.3.0...0.3.1) + +## [0.3.0](https://github.com/OpenSensing/funf-v4/tree/0.3.0) (2011-11-03) +[Full Changelog](https://github.com/OpenSensing/funf-v4/compare/0.2.1...0.3.0) + +## [0.2.1](https://github.com/OpenSensing/funf-v4/tree/0.2.1) (2011-10-07) +[Full Changelog](https://github.com/OpenSensing/funf-v4/compare/0.2.0...0.2.1) + +## [0.2.0](https://github.com/OpenSensing/funf-v4/tree/0.2.0) (2011-10-05) + + +\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* From eaa5d0767bc959bb5ac452323aa948ec5dd9f9de Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Tue, 22 Sep 2015 17:32:30 +0200 Subject: [PATCH 13/61] Create LICENSE.md --- LICENSE.md | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 LICENSE.md diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. From 0f4fbb187f5a35954b34b52c4b190cd3b55f4ca2 Mon Sep 17 00:00:00 2001 From: RaduGatej Date: Tue, 22 Sep 2015 17:35:41 +0200 Subject: [PATCH 14/61] Merge pull request #13 from OpenSensing/license Added license file From 792e1cb993a4b590fc920fa45e1885576aaaa17a Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Wed, 23 Sep 2015 15:54:46 +0200 Subject: [PATCH 15/61] working on file uploading --- funf_v4/build.gradle | 13 ++ .../java/edu/mit/media/funf/FunfManager.java | 1 + .../mit/media/funf/storage/HttpArchive.java | 153 ++++-------------- 3 files changed, 49 insertions(+), 118 deletions(-) diff --git a/funf_v4/build.gradle b/funf_v4/build.gradle index 2438b99..bf4e56f 100644 --- a/funf_v4/build.gradle +++ b/funf_v4/build.gradle @@ -15,12 +15,25 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' } } + + packagingOptions { + exclude 'META-INF/LICENSE' + exclude 'META-INF/NOTICE' + exclude 'META-INF/DEPENDENCIES' + } } dependencies { compile files('libs/gson-2.1-javadoc.jar') compile files('libs/gson-2.1-sources.jar') compile files('libs/gson-2.1.jar') + compile('org.apache.httpcomponents:httpmime:4.3.6') { + exclude module: 'httpclient' + } + compile 'org.apache.httpcomponents:httpclient-android:4.3.5' + compile ('org.apache.httpcomponents:httpcore:4.4.1'){ + exclude group: 'org.apache.httpcomponents', module: 'httpclient' + } } diff --git a/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java b/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java index 5383c4f..22a4a57 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java @@ -209,6 +209,7 @@ public void run() { if (pipelineConfig == null) { unregisterPipeline(name); } else { + Log.i(TAG, pipelineConfig); Pipeline newPipeline = gson.fromJson(pipelineConfig, Pipeline.class); registerPipeline(name, newPipeline); // Will unregister previous before running } diff --git a/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java index 9a497af..0bb5d57 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java @@ -36,6 +36,17 @@ import android.net.NetworkInfo; import android.net.NetworkInfo.State; import android.util.Log; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.entity.mime.content.FileBody; +import org.apache.http.impl.client.DefaultHttpClient; + import edu.mit.media.funf.Schedule.DefaultSchedule; import edu.mit.media.funf.config.Configurable; import edu.mit.media.funf.util.IOUtil; @@ -70,6 +81,7 @@ public HttpArchive(Context context, final String uploadUrl) { public HttpArchive(Context context, final String uploadUrl, final String mimeType) { this.url = uploadUrl; this.mimeType = mimeType; + this.context = context; } public void setContext(Context context) { @@ -102,138 +114,43 @@ public String getId() { public boolean add(File file) { - /* - HttpClient httpclient = new DefaultHttpClient(); - try { - httpclient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1); - HttpPost httppost = new HttpPost(uploadUrl); - - httppost.setEntity(new FileEntity(file, mimeType)); - - Log.i(TAG, "executing request " + httppost.getRequestLine()); - HttpResponse response = httpclient.execute(httppost); - - HttpEntity resEntity = response.getEntity(); - if (resEntity == null) { - Log.i(TAG, "Null response entity."); - return false; - } - Log.i(TAG, "Response " + response.getStatusLine().getStatusCode() + ": " - + IOUtils.inputStreamToString(resEntity.getContent(), "UTF-8")); - } catch (ClientProtocolException e) { - Log.e(TAG, e.getLocalizedMessage()); - e.printStackTrace(); - return false; - } catch (IOException e) { - Log.e(TAG, e.getLocalizedMessage()); - e.printStackTrace(); - return false; - } finally { - httpclient.getConnectionManager().shutdown(); - } - return true; - */ return IOUtil.isValidUrl(url) ? uploadFile(file, url) : false; } /** - * Copied (and slightly modified) from Friends and Family + * Based on funf v3 from OpenSensing * @param file * @param uploadurl * @return */ public static boolean uploadFile(File file,String uploadurl) { - HttpURLConnection conn = null; - DataOutputStream dos = null; - //DataInputStream inStream = null; - - String lineEnd = "\r\n"; - String twoHyphens = "--"; - String boundary = "*****"; - - - int bytesRead, bytesAvailable, bufferSize; - byte[] buffer; - int maxBufferSize = 64*1024; //old value 1024*1024 + HttpClient httpClient = new DefaultHttpClient() ; + HttpPost httpPost = new HttpPost(uploadurl); - boolean isSuccess = true; - try - { - //------------------ CLIENT REQUEST - FileInputStream fileInputStream = null; - //Log.i("FNF","UploadService Runnable: 1"); - try { - fileInputStream = new FileInputStream(file); - }catch (FileNotFoundException e) { - e.printStackTrace(); - Log.e(LogUtil.TAG, "file not found"); - } - // open a URL connection to the Servlet - URL url = new URL(uploadurl); - // Open a HTTP connection to the URL - conn = (HttpURLConnection) url.openConnection(); - // Allow Inputs - conn.setDoInput(true); - // Allow Outputs - conn.setDoOutput(true); - // Don't use a cached copy. - conn.setUseCaches(false); - // set timeout - conn.setConnectTimeout(60000); - conn.setReadTimeout(60000); - // Use a post method. - conn.setRequestMethod("POST"); - conn.setRequestProperty("Connection", "Keep-Alive"); - conn.setRequestProperty("Content-Type", "multipart/form-data;boundary="+boundary); + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); + FileBody fileBody = new FileBody(file); + builder.addPart("uploadedfile", fileBody); + HttpEntity entity = builder.build(); - dos = new DataOutputStream( conn.getOutputStream() ); - dos.writeBytes(twoHyphens + boundary + lineEnd); - dos.writeBytes("Content-Disposition: form-data; name=\"uploadedfile\";filename=\"" + file.getName() +"\"" + lineEnd); - dos.writeBytes(lineEnd); + httpPost.setEntity(entity); + HttpResponse response = null; - //Log.i("FNF","UploadService Runnable:Headers are written"); - - // create a buffer of maximum size - bytesAvailable = fileInputStream.available(); - bufferSize = Math.min(bytesAvailable, maxBufferSize); - buffer = new byte[bufferSize]; - - // read file and write it into form... - bytesRead = fileInputStream.read(buffer, 0, bufferSize); - while (bytesRead > 0) - { - dos.write(buffer, 0, bufferSize); - bytesAvailable = fileInputStream.available(); - bufferSize = Math.min(bytesAvailable, maxBufferSize); - bytesRead = fileInputStream.read(buffer, 0, bufferSize); - } - - // send multipart form data necesssary after file data... - dos.writeBytes(lineEnd); - dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd); - - // close streams - //Log.i("FNF","UploadService Runnable:File is written"); - fileInputStream.close(); - dos.flush(); - dos.close(); - } - catch (Exception e) - { - Log.e("FNF", "UploadService Runnable:Client Request error", e); - isSuccess = false; - } - - //------------------ read the SERVER RESPONSE try { - if (conn.getResponseCode() != 200) { - isSuccess = false; - } + response = httpClient.execute(httpPost); + } catch (ClientProtocolException e) { + Log.e("ClientProtocolExc: "+e, e.getMessage()); + return false; } catch (IOException e) { - Log.e("FNF", "Connection error", e); - isSuccess = false; + Log.e("IOException : "+e, e.getMessage()); + return false; } - - return isSuccess; + if(response == null) { + return false; + } + if(response.getStatusLine().getStatusCode() == 200) { + return true; + } + return false; } } From b61579cbe69adf62ebd105c8f0340d3e91f59f1f Mon Sep 17 00:00:00 2001 From: RaduGatej Date: Wed, 23 Sep 2015 15:55:07 +0200 Subject: [PATCH 16/61] Merge pull request #3 from OpenSensing/probes_config_cleaning small cleaning and enhancements of the probes From 3886b76493504bef02bd09832f69d7b3c2f60a35 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Wed, 23 Sep 2015 18:22:23 +0200 Subject: [PATCH 17/61] Removed unnecessary dependencies --- funf_v4/build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/funf_v4/build.gradle b/funf_v4/build.gradle index bf4e56f..c557918 100644 --- a/funf_v4/build.gradle +++ b/funf_v4/build.gradle @@ -24,8 +24,6 @@ android { } dependencies { - compile files('libs/gson-2.1-javadoc.jar') - compile files('libs/gson-2.1-sources.jar') compile files('libs/gson-2.1.jar') compile('org.apache.httpcomponents:httpmime:4.3.6') { exclude module: 'httpclient' From ae977a4500ccb32b4cc6f0f8307d0bb7da4eab13 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Wed, 23 Sep 2015 18:22:58 +0200 Subject: [PATCH 18/61] Creating folder for assets if doesn't exist --- funf_v4/build.gradle | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/funf_v4/build.gradle b/funf_v4/build.gradle index c557918..cb52a1e 100644 --- a/funf_v4/build.gradle +++ b/funf_v4/build.gradle @@ -65,25 +65,23 @@ def getVersionName = {-> def assetsDir = android.sourceSets.main.assets.srcDirs.toArray()[0] println "assetsDir: "+assetsDir + def assetsDirObject = new File(assetsDir, '') + + if (!assetsDirObject.exists()) { + assetsDirObject.mkdirs(); + } + def buildInfoFile = new File(assetsDir, 'build.info').getAbsolutePath() println "buildInfoFile: "+buildInfoFile new File(buildInfoFile).write(return_value); - - return return_value } catch (ignored) { return null; } - - - - - - } android { From 87bf11fd9fc899fb9d64029d64524c9b1f5ef867 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Wed, 23 Sep 2015 18:23:30 +0200 Subject: [PATCH 19/61] Just import reshuffling --- .../java/edu/mit/media/funf/FunfManager.java | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java b/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java index 22a4a57..2aa14f3 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java @@ -23,21 +23,6 @@ */ package edu.mit.media.funf; -import static edu.mit.media.funf.util.LogUtil.TAG; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.lang.reflect.Type; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - import android.app.AlarmManager; import android.app.PendingIntent; import android.app.Service; @@ -66,6 +51,19 @@ import com.google.gson.JsonSerializer; import com.google.gson.TypeAdapterFactory; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + import edu.mit.media.funf.Schedule.BasicSchedule; import edu.mit.media.funf.Schedule.DefaultSchedule; import edu.mit.media.funf.config.ConfigUpdater; @@ -94,6 +92,8 @@ import edu.mit.media.funf.util.LogUtil; import edu.mit.media.funf.util.StringUtil; +import static edu.mit.media.funf.util.LogUtil.TAG; + public class FunfManager extends Service { public static final String @@ -209,7 +209,6 @@ public void run() { if (pipelineConfig == null) { unregisterPipeline(name); } else { - Log.i(TAG, pipelineConfig); Pipeline newPipeline = gson.fromJson(pipelineConfig, Pipeline.class); registerPipeline(name, newPipeline); // Will unregister previous before running } From fc77f8a36734d114920b85b0816e1847273399c3 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Wed, 23 Sep 2015 20:54:53 +0200 Subject: [PATCH 20/61] Better URL validation --- .../main/java/edu/mit/media/funf/util/IOUtil.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java b/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java index 6d4ecb3..69ddb0f 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java @@ -62,6 +62,7 @@ import android.net.Uri; import android.util.Log; +import android.util.Patterns; public class IOUtil { @@ -120,18 +121,7 @@ public static boolean close(Closeable stream) { public static boolean isValidUrl(String url) { Log.d(LogUtil.TAG, "Validating url"); - boolean isValidUrl = false; - if (url != null && !url.trim().equals("")) { - try { - Uri test = Uri.parse(url); - isValidUrl = test.getScheme() != null - && test.getScheme().startsWith("http") - && test.getHost() != null - && !test.getHost().trim().equals(""); - } catch (Exception e) { - Log.d(LogUtil.TAG, "Not valid", e); - } - } + boolean isValidUrl = Patterns.WEB_URL.matcher(url).matches(); Log.d(LogUtil.TAG, "Valid url? " + isValidUrl); return isValidUrl; } From 5d8748021b589023e8005bc377a1e76983faa715 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Wed, 23 Sep 2015 20:55:08 +0200 Subject: [PATCH 21/61] Additional URI check --- .../java/edu/mit/media/funf/storage/HttpArchive.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java index 0bb5d57..22c3f0d 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java @@ -29,6 +29,8 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import android.content.Context; @@ -125,7 +127,13 @@ public boolean add(File file) { */ public static boolean uploadFile(File file,String uploadurl) { HttpClient httpClient = new DefaultHttpClient() ; - HttpPost httpPost = new HttpPost(uploadurl); + HttpPost httpPost; + try { + httpPost = new HttpPost(new URI(uploadurl)); + } catch (URISyntaxException e) { + e.printStackTrace(); + return false; + } MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); From e03552ec0af03a6d6fe7fe4d45c1a1b39f3815d2 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Wed, 23 Sep 2015 21:08:23 +0200 Subject: [PATCH 22/61] Resetting number of failures on run not just on start --- .../main/java/edu/mit/media/funf/storage/UploadService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/storage/UploadService.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/UploadService.java index 8e7cb72..5240a2e 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/storage/UploadService.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/storage/UploadService.java @@ -95,6 +95,8 @@ public void stop() { public void run(final FileArchive archive, final RemoteFileArchive remoteArchive) { Log.i(LogUtil.TAG, "Running upload..."); + remoteArchiveFailures = new HashMap(); + fileFailures = new HashMap(); if (archive != null && remoteArchive != null) { if (lock == null) { lock = LockUtil.getWakeLock(context); @@ -145,7 +147,7 @@ protected void runArchive(FileArchive archive, RemoteFileArchive remoteArchive, } } else { Log.i(LogUtil.TAG, "Canceling upload. Remote archive '" + remoteArchive.getId() - + "' is not currently available."); + + "' is not currently available. " + numRemoteFailures); filesToUpload.remove(file); } } From 98d93c5866161f191178177c97879303f1dd0138 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Thu, 24 Sep 2015 22:04:27 +0200 Subject: [PATCH 23/61] Experimental change to InputStreamEntity This makes the uploader compatible with Dropbox API --- .../mit/media/funf/storage/HttpArchive.java | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java index 22c3f0d..1e5ce9f 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java @@ -44,6 +44,8 @@ import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.InputStreamEntity; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.entity.mime.content.FileBody; @@ -135,30 +137,29 @@ public static boolean uploadFile(File file,String uploadurl) { return false; } - MultipartEntityBuilder builder = MultipartEntityBuilder.create(); - builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); - FileBody fileBody = new FileBody(file); - builder.addPart("uploadedfile", fileBody); - HttpEntity entity = builder.build(); - httpPost.setEntity(entity); + InputStreamEntity reqEntity = null; HttpResponse response = null; - try { + reqEntity = new InputStreamEntity(new FileInputStream(file), -1); + reqEntity.setContentType("binary/octet-stream"); + reqEntity.setChunked(true); // Send in multiple parts if needed + httpPost.setEntity(reqEntity); response = httpClient.execute(httpPost); - } catch (ClientProtocolException e) { - Log.e("ClientProtocolExc: "+e, e.getMessage()); + } catch (FileNotFoundException e) { + e.printStackTrace(); return false; } catch (IOException e) { - Log.e("IOException : "+e, e.getMessage()); + e.printStackTrace(); return false; } - if(response == null) { + if (response == null) { return false; } - if(response.getStatusLine().getStatusCode() == 200) { + if (response.getStatusLine().getStatusCode() == 200) { return true; } + return false; } } From cf60d04a2a044e5740b5b9956337e48d2d26c873 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Mon, 28 Sep 2015 18:02:44 +0200 Subject: [PATCH 24/61] Formatting of url in config updater --- .../java/edu/mit/media/funf/config/HttpConfigUpdater.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/config/HttpConfigUpdater.java b/funf_v4/src/main/java/edu/mit/media/funf/config/HttpConfigUpdater.java index 4ed3f06..0792ed1 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/config/HttpConfigUpdater.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/config/HttpConfigUpdater.java @@ -18,9 +18,10 @@ public class HttpConfigUpdater extends ConfigUpdater { @Override public JsonObject getConfig() throws ConfigUpdateException { try { - String content = null; - if (IOUtil.isValidUrl(url)) { - content = IOUtil.httpGet(url, null); + String content = null; + String currentUrl = IOUtil.formatServerUrl(url, ""); + if (IOUtil.isValidUrl(currentUrl)) { + content = IOUtil.httpGet(currentUrl, null); } if (content == null) { throw new ConfigUpdateException("Unable to download configuration."); From c0141b4252a686e4b0d998af46333001c8b1fd9d Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Mon, 28 Sep 2015 18:03:24 +0200 Subject: [PATCH 25/61] Formatting of url, inserting filename and access token --- .../java/edu/mit/media/funf/util/IOUtil.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java b/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java index 69ddb0f..78258c0 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java @@ -51,6 +51,8 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; @@ -60,10 +62,14 @@ import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.HttpParams; +import android.content.Context; +import android.content.SharedPreferences; import android.net.Uri; import android.util.Log; import android.util.Patterns; +import edu.mit.media.funf.FunfManager; + public class IOUtil { public static String inputStreamToString(InputStream is, String encoding) throws IOException { @@ -125,4 +131,37 @@ public static boolean isValidUrl(String url) { Log.d(LogUtil.TAG, "Valid url? " + isValidUrl); return isValidUrl; } + + public static String formatServerUrl(String uploadurl, String filename) { + String accessToken = FunfManager.context.getSharedPreferences("funf_auth", Context.MODE_PRIVATE).getString(md5(uploadurl), ""); + + String formattedUploadUrl = uploadurl; + formattedUploadUrl = formattedUploadUrl.replace("$FILENAME$", filename); + formattedUploadUrl = formattedUploadUrl.replace("$ACCESS_TOKEN$", accessToken); + + return formattedUploadUrl; + } + + public static final String md5(final String s) { + try { + // Create MD5 Hash + MessageDigest digest = java.security.MessageDigest.getInstance("MD5"); + digest.update(s.getBytes()); + byte messageDigest[] = digest.digest(); + + // Create Hex String + StringBuffer hexString = new StringBuffer(); + for (int i = 0; i < messageDigest.length; i++) { + String h = Integer.toHexString(0xFF & messageDigest[i]); + while (h.length() < 2) + h = "0" + h; + hexString.append(h); + } + return hexString.toString(); + + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return ""; + } } From 377edaf9cbb5c887d205edd811e0967f480ee536 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Mon, 28 Sep 2015 18:05:07 +0200 Subject: [PATCH 26/61] Support for token setting and broadcast of authentication error --- .../java/edu/mit/media/funf/FunfManager.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java b/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java index 2aa14f3..4fb627f 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java @@ -89,6 +89,7 @@ import edu.mit.media.funf.storage.HttpArchive; import edu.mit.media.funf.storage.RemoteFileArchive; import edu.mit.media.funf.time.TimeUtil; +import edu.mit.media.funf.util.IOUtil; import edu.mit.media.funf.util.LogUtil; import edu.mit.media.funf.util.StringUtil; @@ -112,7 +113,9 @@ public class FunfManager extends Service { PROBE_ACTION_UNREGISTER = "unregister", PROBE_ACTION_REGISTER_PASSIVE = "register-passive", PROBE_ACTION_UNREGISTER_PASSIVE = "unregister-passive"; - + + public static Context context = null; + public static FunfManager funfManager = null; private Handler handler; private JsonParser parser; @@ -160,6 +163,8 @@ public void onCreate() { this.disabledPipelines = new HashMap(); this.disabledPipelineNames = new HashSet(Arrays.asList(prefs.getString(DISABLED_PIPELINE_LIST, "").split(","))); this.disabledPipelineNames.remove(""); // Remove the empty name, if no disabled pipelines exist + FunfManager.context = this; + funfManager = this; reload(); } @@ -735,7 +740,17 @@ private void cancelProbe(String probeConfig) { scheduler.cancel(PROBE_TYPE, getComponenentUri(probeConfig, PROBE_ACTION_REGISTER_PASSIVE)); scheduler.cancel(PROBE_TYPE, getComponenentUri(probeConfig, PROBE_ACTION_UNREGISTER_PASSIVE)); } - + + public void setAuthToken(String url, String accessToken) { + getSharedPreferences("funf_auth", MODE_PRIVATE).edit().putString(IOUtil.md5(url), accessToken).commit(); + } + + public void authError() { + Log.i(TAG, "sending authError broadcast"); + Intent intent = new Intent(context.getPackageName() + "." + "AUTHENTICATION_ERROR"); + sendBroadcast(intent); + } + //////////////////////////////////////////////////// @@ -812,7 +827,7 @@ public void set(String type, Uri componentAndAction, Schedule schedule) { } } - + // TODO: Feature to wait a certain amount of seconds after boot to begin // TODO: Feature to prevent too many things from running at once, w/ random backoff times @@ -837,4 +852,4 @@ public void set(String type, Uri componentAndAction, Schedule schedule) { // This allows separate probes if required // Allows creation of database services to be determined by pipeline } -} +} \ No newline at end of file From c0688b88418e1a2f61bdc27885cdee052ae538a5 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Mon, 28 Sep 2015 18:07:00 +0200 Subject: [PATCH 27/61] Added todo for the future --- .../main/java/edu/mit/media/funf/config/HttpConfigUpdater.java | 1 + 1 file changed, 1 insertion(+) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/config/HttpConfigUpdater.java b/funf_v4/src/main/java/edu/mit/media/funf/config/HttpConfigUpdater.java index 0792ed1..d8f0557 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/config/HttpConfigUpdater.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/config/HttpConfigUpdater.java @@ -20,6 +20,7 @@ public JsonObject getConfig() throws ConfigUpdateException { try { String content = null; String currentUrl = IOUtil.formatServerUrl(url, ""); + //TODO handling of auth error if (IOUtil.isValidUrl(currentUrl)) { content = IOUtil.httpGet(currentUrl, null); } From 2e8f0d9673e97d3c1771245d913b803d457c3c20 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Mon, 28 Sep 2015 18:07:27 +0200 Subject: [PATCH 28/61] File uploader now supports string formatting and auth error --- .../edu/mit/media/funf/storage/HttpArchive.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java index 1e5ce9f..a77bb92 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java @@ -37,6 +37,7 @@ import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.NetworkInfo.State; +import android.util.Base64; import android.util.Log; import org.apache.http.HttpEntity; @@ -51,6 +52,7 @@ import org.apache.http.entity.mime.content.FileBody; import org.apache.http.impl.client.DefaultHttpClient; +import edu.mit.media.funf.FunfManager; import edu.mit.media.funf.Schedule.DefaultSchedule; import edu.mit.media.funf.config.Configurable; import edu.mit.media.funf.util.IOUtil; @@ -118,7 +120,8 @@ public String getId() { public boolean add(File file) { - return IOUtil.isValidUrl(url) ? uploadFile(file, url) : false; + String currentUrl = IOUtil.formatServerUrl(url, file.getName()); + return IOUtil.isValidUrl(currentUrl) ? uploadFile(file, currentUrl) : false; } /** @@ -137,7 +140,6 @@ public static boolean uploadFile(File file,String uploadurl) { return false; } - InputStreamEntity reqEntity = null; HttpResponse response = null; try { @@ -159,6 +161,13 @@ public static boolean uploadFile(File file,String uploadurl) { if (response.getStatusLine().getStatusCode() == 200) { return true; } + if (response.getStatusLine().getStatusCode() == 401) { + //Auth error + Log.i(LogUtil.TAG, "Auth Error "+response.getStatusLine().getStatusCode()); + //TODO propagate auth error up, in OAuth2 context this will require re-auth from user + FunfManager.funfManager.authError(); + } + return false; } From d98624dbbc1e830b5cbf21181629a482ca8d27d0 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Mon, 28 Sep 2015 23:06:55 +0200 Subject: [PATCH 29/61] Just removing todo --- .../main/java/edu/mit/media/funf/config/HttpConfigUpdater.java | 1 - 1 file changed, 1 deletion(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/config/HttpConfigUpdater.java b/funf_v4/src/main/java/edu/mit/media/funf/config/HttpConfigUpdater.java index d8f0557..0792ed1 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/config/HttpConfigUpdater.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/config/HttpConfigUpdater.java @@ -20,7 +20,6 @@ public JsonObject getConfig() throws ConfigUpdateException { try { String content = null; String currentUrl = IOUtil.formatServerUrl(url, ""); - //TODO handling of auth error if (IOUtil.isValidUrl(currentUrl)) { content = IOUtil.httpGet(currentUrl, null); } From 5117a62eb2dbc42f7efc832c09397789625bdc34 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Mon, 28 Sep 2015 23:09:17 +0200 Subject: [PATCH 30/61] Saving token not for the exact url but for the host, so the config can also use it --- .../main/java/edu/mit/media/funf/FunfManager.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java b/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java index 4fb627f..c022032 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java @@ -56,6 +56,8 @@ import java.io.InputStreamReader; import java.lang.reflect.Type; import java.math.BigDecimal; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -486,7 +488,6 @@ public static SingletonTypeAdapterFactory getProbeFactory(Context context) { public void registerPipeline(String name, Pipeline pipeline) { synchronized (pipelines) { - Log.d(LogUtil.TAG, "Registering pipeline: " + name); unregisterPipeline(name); pipelines.put(name, pipeline); pipeline.onCreate(this); @@ -742,7 +743,15 @@ private void cancelProbe(String probeConfig) { } public void setAuthToken(String url, String accessToken) { - getSharedPreferences("funf_auth", MODE_PRIVATE).edit().putString(IOUtil.md5(url), accessToken).commit(); + URI uri = null; + try { + uri = new URI(url); + getSharedPreferences("funf_auth", MODE_PRIVATE).edit().putString(IOUtil.md5(uri.getHost()), accessToken).commit(); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + + } public void authError() { From 35cf8ba28f1219cd75e3afbc488d12e077d1fd2a Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Mon, 28 Sep 2015 23:12:42 +0200 Subject: [PATCH 31/61] httpGet with response code so we can handle auth errors --- .../java/edu/mit/media/funf/util/IOUtil.java | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java b/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java index 78258c0..fa389ce 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java @@ -46,6 +46,7 @@ import static edu.mit.media.funf.util.LogUtil.TAG; +import java.io.BufferedReader; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; @@ -54,11 +55,11 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; -import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.BasicResponseHandler; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.HttpParams; @@ -87,26 +88,44 @@ public static String inputStreamToString(InputStream is, String encoding) throws } public static String httpGet(String uri,HttpParams params){ - String responseBody=null; + HttpResponse response=null; HttpClient httpclient = new DefaultHttpClient(); StringBuilder uriBuilder = new StringBuilder(uri); HttpGet httpget = new HttpGet(uriBuilder.toString()); if (params != null) { httpget.setParams(params); } - ResponseHandler responseHandler = new BasicResponseHandler(); try { - responseBody = httpclient.execute(httpget, responseHandler); + response = httpclient.execute(httpget); + if (response.getStatusLine().getStatusCode() == 401) { + FunfManager.funfManager.authError(); + response = null; + } } catch (ClientProtocolException e) { Log.e(TAG, "HttpGet Error: ", e); responseBody=null; + response=null; } catch (IOException e) { Log.e(TAG, "HttpGet Error: ", e); responseBody=null; + response=null; } finally{ httpclient.getConnectionManager().shutdown(); } - return responseBody; + if (response != null) { + try { + StringBuilder sb = new StringBuilder(); + HttpEntity entity = response.getEntity(); + BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent()), 65728); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + return sb.toString(); + } catch (IOException e) {e.printStackTrace();} + catch (Exception e) {e.printStackTrace();} + } + return null; } /** From d3a70bd8222d8255b9b4e95f20bdadc51583c7bf Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Mon, 28 Sep 2015 23:13:00 +0200 Subject: [PATCH 32/61] Getting access token by host not by full url --- .../java/edu/mit/media/funf/util/IOUtil.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java b/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java index fa389ce..0e74abf 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java @@ -52,6 +52,8 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; +import java.net.URI; +import java.net.URISyntaxException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -103,11 +105,9 @@ public static String httpGet(String uri,HttpParams params){ } } catch (ClientProtocolException e) { Log.e(TAG, "HttpGet Error: ", e); - responseBody=null; response=null; } catch (IOException e) { Log.e(TAG, "HttpGet Error: ", e); - responseBody=null; response=null; } finally{ httpclient.getConnectionManager().shutdown(); @@ -152,7 +152,15 @@ public static boolean isValidUrl(String url) { } public static String formatServerUrl(String uploadurl, String filename) { - String accessToken = FunfManager.context.getSharedPreferences("funf_auth", Context.MODE_PRIVATE).getString(md5(uploadurl), ""); + + URI uri = null; + String accessToken = ""; + try { + uri = new URI(uploadurl); + accessToken = FunfManager.context.getSharedPreferences("funf_auth", Context.MODE_PRIVATE).getString(md5(uri.getHost()), ""); + } catch (URISyntaxException e) { + e.printStackTrace(); + } String formattedUploadUrl = uploadurl; formattedUploadUrl = formattedUploadUrl.replace("$FILENAME$", filename); @@ -180,6 +188,8 @@ public static final String md5(final String s) { } catch (NoSuchAlgorithmException e) { e.printStackTrace(); + } catch (NullPointerException e) { + e.printStackTrace(); } return ""; } From 0607f4d1e2f8b59a5b05e4554bd72742fcf1ed5c Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Mon, 5 Oct 2015 14:45:41 +0200 Subject: [PATCH 33/61] Little safeguards to file uploader --- .../src/main/java/edu/mit/media/funf/storage/HttpArchive.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java index a77bb92..4af9643 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java @@ -131,6 +131,8 @@ public boolean add(File file) { * @return */ public static boolean uploadFile(File file,String uploadurl) { + if (uploadurl == null) return false; + if (uploadurl.equals("")) return false; HttpClient httpClient = new DefaultHttpClient() ; HttpPost httpPost; try { From 27967d8e2324b67c529edbff9f8de27b4e21dba8 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Mon, 5 Oct 2015 14:46:22 +0200 Subject: [PATCH 34/61] Building proper file suffix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Depending if the file is compressed (“.gz”) and encrypted (“.enc”) a proper suffix is built. --- .../main/java/edu/mit/media/funf/storage/DefaultArchive.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/storage/DefaultArchive.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/DefaultArchive.java index 3377823..9774096 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/storage/DefaultArchive.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/storage/DefaultArchive.java @@ -177,7 +177,10 @@ protected FileArchive getDelegateArchive() { } static FileDirectoryArchive getTimestampedDbFileArchive(File archiveDir, Context context, SecretKey encryptionKey, Boolean compress) { - NameGenerator nameGenerator = new CompositeNameGenerator(new SystemUniqueTimestampNameGenerator(context), new RequiredSuffixNameGenerator(".db")); + String suffix = ".db"; + if (compress) suffix += ".gz"; + if (encryptionKey != null) suffix += ".enc"; + NameGenerator nameGenerator = new CompositeNameGenerator(new SystemUniqueTimestampNameGenerator(context), new RequiredSuffixNameGenerator(suffix)); FileCopier copier = null; if (compress) { copier = (encryptionKey == null) ? new FileCopier.CompressedFileCopier() : new FileCopier.CompressedEncryptedFileCopier(encryptionKey, DES_ENCRYPTION); From 5ee4f91fe7f769a2a2b65bb47c2a757402d857fc Mon Sep 17 00:00:00 2001 From: RaduGatej Date: Mon, 5 Oct 2015 17:23:51 +0200 Subject: [PATCH 35/61] Merge pull request #14 from OpenSensing/file_uploader File uploader From 570de17d7e8f8f839f07a14d566344aa3ef66c92 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Mon, 5 Oct 2015 17:36:04 +0200 Subject: [PATCH 36/61] Updated changelog --- CHANGELOG.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a9a6ae..ce1d044 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,26 @@ **Implemented enhancements:** -- change archiving to gzipped json [\#1](https://github.com/OpenSensing/funf-v4/issues/1) +- Add stub for exclusion of fields to Wifi probe [\#12](https://github.com/OpenSensing/funf-v4/issues/12) +- Add stub for exclusion of fields to Bluetooth probe [\#11](https://github.com/OpenSensing/funf-v4/issues/11) +- Add LGPL license file [\#10](https://github.com/OpenSensing/funf-v4/issues/10) +- Include datapoint when Wifi scan starts, even if no devices are found [\#9](https://github.com/OpenSensing/funf-v4/issues/9) +- Include datapoint when Bluetooth scan starts, even if no devices are found [\#8](https://github.com/OpenSensing/funf-v4/issues/8) +- Change BT probe default duration to 60s [\#7](https://github.com/OpenSensing/funf-v4/issues/7) +- Change file uploader to HttpClient [\#6](https://github.com/OpenSensing/funf-v4/issues/6) +- Add changelog [\#4](https://github.com/OpenSensing/funf-v4/issues/4) +- Option to compress archives \(using gzip\) [\#1](https://github.com/OpenSensing/funf-v4/issues/1) + +**Closed issues:** + +- Generate archive file extension based on the compression/encryption [\#15](https://github.com/OpenSensing/funf-v4/issues/15) **Merged pull requests:** +- File uploader [\#14](https://github.com/OpenSensing/funf-v4/pull/14) ([h0pbeat](https://github.com/h0pbeat)) +- Added license file [\#13](https://github.com/OpenSensing/funf-v4/pull/13) ([h0pbeat](https://github.com/h0pbeat)) +- added changelog [\#5](https://github.com/OpenSensing/funf-v4/pull/5) ([h0pbeat](https://github.com/h0pbeat)) +- small cleaning and enhancements of the probes [\#3](https://github.com/OpenSensing/funf-v4/pull/3) ([h0pbeat](https://github.com/h0pbeat)) - implemented gzip compression of the files, configurable in pipeline config [\#2](https://github.com/OpenSensing/funf-v4/pull/2) ([h0pbeat](https://github.com/h0pbeat)) ## [0.4.2](https://github.com/OpenSensing/funf-v4/tree/0.4.2) (2015-08-18) @@ -27,4 +43,4 @@ ## [0.2.0](https://github.com/OpenSensing/funf-v4/tree/0.2.0) (2011-10-05) -\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* +\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file From 6b22d496eb98703d829226dd913e040a76d0dac3 Mon Sep 17 00:00:00 2001 From: RaduGatej Date: Wed, 14 Oct 2015 00:36:44 +0200 Subject: [PATCH 37/61] Merge pull request #17 from OpenSensing/release_4-3 Release 4 3 From e04ed01a8181e239c231aaf293c5365fdd0db3cb Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sat, 17 Oct 2015 17:19:00 -0700 Subject: [PATCH 38/61] Geofencing can be now used to limit data collection --- .../edu/mit/media/funf/data/Geofencer.java | 155 ++++++++++++++++++ .../media/funf/pipeline/BasicPipeline.java | 11 +- 2 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 funf_v4/src/main/java/edu/mit/media/funf/data/Geofencer.java diff --git a/funf_v4/src/main/java/edu/mit/media/funf/data/Geofencer.java b/funf_v4/src/main/java/edu/mit/media/funf/data/Geofencer.java new file mode 100644 index 0000000..c1e293c --- /dev/null +++ b/funf_v4/src/main/java/edu/mit/media/funf/data/Geofencer.java @@ -0,0 +1,155 @@ +package edu.mit.media.funf.data; + +import android.content.SharedPreferences; +import android.util.Log; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import edu.mit.media.funf.FunfManager; +import edu.mit.media.funf.config.Configurable; +import edu.mit.media.funf.json.IJsonObject; +import edu.mit.media.funf.util.IOUtil; +import edu.mit.media.funf.util.LogUtil; + +/** + * Created by arks on 10/16/15. + */ +public class Geofencer { + + //TODO broadcast recording/not recording + + @Configurable + protected Integer version = 0; + + @Configurable + protected List fences = new ArrayList(); + + @Configurable + protected Double minLocationAccuracy = 80.0; + + @Configurable + protected Long minLocationFreshness = 1L; + + @Configurable + protected Long timeWindow = 10L; + + + private Set acceptedLocationProbes = + new HashSet(Arrays.asList("edu.mit.media.funf.probe.builtin.LocationProbe", "edu.mit.media.funf.probe.builtin.SimpleLocationProbe")); + + + + public Geofencer() { + Log.i(LogUtil.TAG, "Creating geofencer " + version); + } + + public boolean shouldSaveData(String name, IJsonObject data) { + if (acceptedLocationProbes.contains(name)) { + updateVisits(name, data); + } + + return checkFences(timestampToMillis(data.get("timestamp").getAsLong())); + } + + + private void updateVisits(String name, IJsonObject data) { + + if (name.equals("edu.mit.media.funf.probe.builtin.LocationProbe")) { + Double accuracy = data.get("mAccuracy").getAsDouble(); + Double lat = data.get("mLatitude").getAsDouble(); + Double lon = data.get("mLongitude").getAsDouble(); + Long timestamp = timestampToMillis(data.get("mTime").getAsLong()); + + //TODO discard mock locations + if (accuracy > minLocationAccuracy) return; + if ((System.currentTimeMillis()) > timestamp + minLocationFreshness*60*1000) return; + + //TODO graceful error handling + for (JsonElement fence: fences) { + JsonObject fenceObject = fence.getAsJsonObject(); + Double fenceLatitude = fenceObject.get("latitude").getAsDouble(); + Double fenceLongitude = fenceObject.get("longitude").getAsDouble(); + Double fenceRadius = fenceObject.get("radius").getAsDouble(); + + Double distance = haversine(lat, lon, fenceLatitude, fenceLongitude); + + if (distance < fenceRadius) { + updateVisit(fenceLatitude, fenceLongitude, fenceRadius, timestamp); + } + } + + } + } + + private void updateVisit(Double fenceLatitude, Double fenceLongitude, Double fenceRadius, Long timestamp) { + String fenceID = fenceToString(fenceLatitude, fenceLongitude, fenceRadius); + SharedPreferences sharedPreferences = FunfManager.context.getSharedPreferences("funf_geofence", FunfManager.context.MODE_PRIVATE); + + Long previousTimestamp = sharedPreferences.getLong(fenceID, 0); + + if (timestamp > previousTimestamp) { + sharedPreferences.edit().putLong(fenceID, timestamp).commit(); + } + + } + + private String fenceToString(Double fenceLatitude, Double fenceLongitude, Double fenceRadius) { + return IOUtil.md5(""+fenceLatitude + ";"+fenceLongitude+";"+fenceRadius); + } + + private boolean checkFences(Long timestamp) { + //TODO in the future this should keep the last 24h or so of the fence history so we can + //put data from the past + + if (fences.size() == 0) return true; + + for (JsonElement fence: fences) { + JsonObject fenceObject = fence.getAsJsonObject(); + Double fenceLatitude = fenceObject.get("latitude").getAsDouble(); + Double fenceLongitude = fenceObject.get("longitude").getAsDouble(); + Double fenceRadius = fenceObject.get("radius").getAsDouble(); + + String fenceID = fenceToString(fenceLatitude, fenceLongitude, fenceRadius); + SharedPreferences sharedPreferences = FunfManager.context.getSharedPreferences("funf_geofence", FunfManager.context.MODE_PRIVATE); + Long lastVisitTimestamp = sharedPreferences.getLong(fenceID, 0); + + if ((timestamp < (lastVisitTimestamp + timeWindow*60*1000)) && (timestamp > (lastVisitTimestamp - timeWindow*60*1000)) ) return true; + + } + + return false; + } + + public Integer getVersion() { + return this.version; + } + + private Long timestampToMillis(Long timestamp) { + if (timestamp < 9999999999L) return timestamp*1000; + return timestamp; + } + + private Double haversine(double lat1, double lon1, double lat2, double lon2) { + Double R = 6372.8; // In kilometers + Double dLat = Math.toRadians(lat2 - lat1); + Double dLon = Math.toRadians(lon2 - lon1); + lat1 = Math.toRadians(lat1); + lat2 = Math.toRadians(lat2); + + Double a = Math.pow(Math.sin(dLat / 2),2) + Math.pow(Math.sin(dLon / 2),2) * Math.cos(lat1) * Math.cos(lat2); + Double c = 2 * Math.asin(Math.sqrt(a)); + return R * c * 1000; + } + + public List getFences() { + return this.fences; + } + +} diff --git a/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java b/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java index 8a3f03d..ac803e0 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java @@ -44,6 +44,7 @@ import edu.mit.media.funf.config.ConfigUpdater; import edu.mit.media.funf.config.Configurable; import edu.mit.media.funf.config.RuntimeTypeAdapterFactory; +import edu.mit.media.funf.data.Geofencer; import edu.mit.media.funf.json.IJsonObject; import edu.mit.media.funf.probe.Probe.DataListener; import edu.mit.media.funf.probe.builtin.ProbeKeys; @@ -84,7 +85,10 @@ public class BasicPipeline implements Pipeline, DataListener { protected List data = new ArrayList(); @Configurable - protected Map schedules = new HashMap(); + protected Map schedules = new HashMap(); + + @Configurable + protected Geofencer geofence = new Geofencer(); private UploadService uploader; @@ -144,6 +148,7 @@ protected void runArchive() { } protected void writeData(String name, IJsonObject data) { + if (!geofence.shouldSaveData(name, data)) return; SQLiteDatabase db = databaseHelper.getWritableDatabase(); final double timestamp = data.get(ProbeKeys.BaseProbeKeys.TIMESTAMP).getAsDouble(); final String value = data.toString(); @@ -348,4 +353,8 @@ public void onDataCompleted(IJsonObject probeConfig, JsonElement checkpoint) { // TODO Figure out what to do with continuations of probes, if anything } + + public List getFences() { + return this.geofence.getFences(); + } } From f2d561fe0c259d178dc968d3971facf588de76f5 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sat, 17 Oct 2015 19:35:46 -0700 Subject: [PATCH 39/61] Broadcasting data collections status --- .../java/edu/mit/media/funf/FunfManager.java | 6 ++++ .../edu/mit/media/funf/data/Geofencer.java | 4 +++ .../media/funf/pipeline/BasicPipeline.java | 31 +++++++++++++++++-- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java b/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java index c022032..78fd5dc 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java @@ -760,6 +760,12 @@ public void authError() { sendBroadcast(intent); } + public void broadcastDataCollectionStatus(Integer status) { + Intent intent = new Intent(context.getPackageName() + "." + "DATA_COLLECTION_STATUS"); + intent.putExtra("status", status); + sendBroadcast(intent); + } + //////////////////////////////////////////////////// diff --git a/funf_v4/src/main/java/edu/mit/media/funf/data/Geofencer.java b/funf_v4/src/main/java/edu/mit/media/funf/data/Geofencer.java index c1e293c..6980b19 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/data/Geofencer.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/data/Geofencer.java @@ -58,6 +58,10 @@ public boolean shouldSaveData(String name, IJsonObject data) { return checkFences(timestampToMillis(data.get("timestamp").getAsLong())); } + public boolean shouldSaveData(Long timestamp) { + return checkFences(timestamp); + } + private void updateVisits(String name, IJsonObject data) { diff --git a/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java b/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java index ac803e0..288901b 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java @@ -25,8 +25,11 @@ import java.util.List; import java.util.Map; +import android.app.Notification; +import android.app.NotificationManager; import android.content.ContentValues; import android.content.Context; +import android.content.Intent; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; @@ -56,6 +59,8 @@ import edu.mit.media.funf.util.LogUtil; import edu.mit.media.funf.util.StringUtil; +import static edu.mit.media.funf.util.LogUtil.TAG; + public class BasicPipeline implements Pipeline, DataListener { public static final String @@ -91,7 +96,9 @@ public class BasicPipeline implements Pipeline, DataListener { protected Geofencer geofence = new Geofencer(); private UploadService uploader; - + + private Integer savingData = -1; + private boolean enabled; private FunfManager manager; private SQLiteOpenHelper databaseHelper = null; @@ -146,8 +153,28 @@ protected void runArchive() { reloadDbHelper(manager); databaseHelper.getWritableDatabase(); // Build new database } - + + private void broadcastDataCollection() { + Log.i(TAG, "broadcastDataCollectionStatus() "+savingData); + manager.broadcastDataCollectionStatus(savingData); + } + protected void writeData(String name, IJsonObject data) { + + if (geofence.shouldSaveData(System.currentTimeMillis())) { + if (savingData != 1) { + savingData = 1; + broadcastDataCollection(); + } + + } else { + if (savingData != 0) { + savingData = 0; + broadcastDataCollection(); + } + + } + if (!geofence.shouldSaveData(name, data)) return; SQLiteDatabase db = databaseHelper.getWritableDatabase(); final double timestamp = data.get(ProbeKeys.BaseProbeKeys.TIMESTAMP).getAsDouble(); From e1b41e62256f4f2772995d5067530cde5d8012e8 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Mon, 19 Oct 2015 14:13:36 -0700 Subject: [PATCH 40/61] Better state broadcasting --- .../mit/media/funf/pipeline/BasicPipeline.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java b/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java index 288901b..9554af8 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java @@ -94,6 +94,9 @@ public class BasicPipeline implements Pipeline, DataListener { @Configurable protected Geofencer geofence = new Geofencer(); + + @Configurable + protected boolean broadcastCollectionState = true; private UploadService uploader; @@ -155,27 +158,28 @@ protected void runArchive() { } private void broadcastDataCollection() { + if (!broadcastCollectionState) return; Log.i(TAG, "broadcastDataCollectionStatus() "+savingData); - manager.broadcastDataCollectionStatus(savingData); - } - - protected void writeData(String name, IJsonObject data) { if (geofence.shouldSaveData(System.currentTimeMillis())) { if (savingData != 1) { savingData = 1; - broadcastDataCollection(); + manager.broadcastDataCollectionStatus(savingData); } } else { if (savingData != 0) { savingData = 0; - broadcastDataCollection(); + manager.broadcastDataCollectionStatus(savingData); } - } + } + protected void writeData(String name, IJsonObject data) { + + broadcastDataCollection(); if (!geofence.shouldSaveData(name, data)) return; + SQLiteDatabase db = databaseHelper.getWritableDatabase(); final double timestamp = data.get(ProbeKeys.BaseProbeKeys.TIMESTAMP).getAsDouble(); final String value = data.toString(); From 0636c10b54f80b2c67d138ac0b37b8d4f8a91c1e Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Tue, 1 Dec 2015 09:36:58 -0800 Subject: [PATCH 41/61] Bluetooth Probe can be now configured to keep phone discoverable --- .../media/funf/probe/builtin/BluetoothProbe.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java index c9acf6b..3fd96fc 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java @@ -60,6 +60,9 @@ public class BluetoothProbe extends Base implements PassiveProbe { @Configurable private boolean include_scan_started = true; + + @Configurable + private boolean keepBluetoothVisible = false; private BluetoothAdapter adapter; private BroadcastReceiver receiver = new BroadcastReceiver() { @@ -150,6 +153,18 @@ protected void onStop() { adapter.disable(); shouldDisableOnFinish = false; } + if (keepBluetoothVisible) { + makeVisible(); + } + } + + private void makeVisible() { + if(adapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { + Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); + discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 0); + discoverableIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + this.getContext().startActivity(discoverableIntent); + } } @Override From db2868eadd7a9fa087f950f4096c8277382f41d7 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sun, 6 Dec 2015 13:48:59 -0800 Subject: [PATCH 42/61] Removed silly logging --- .../src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java | 1 - 1 file changed, 1 deletion(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java b/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java index 9554af8..29e6e69 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java @@ -159,7 +159,6 @@ protected void runArchive() { private void broadcastDataCollection() { if (!broadcastCollectionState) return; - Log.i(TAG, "broadcastDataCollectionStatus() "+savingData); if (geofence.shouldSaveData(System.currentTimeMillis())) { if (savingData != 1) { From 24a7d785042da506e6e87fe89901728dbfcaedef Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sun, 6 Dec 2015 13:51:57 -0800 Subject: [PATCH 43/61] Better handling of authentication and errors --- .../java/edu/mit/media/funf/FunfManager.java | 19 +++++++++++++++++-- .../media/funf/config/HttpConfigUpdater.java | 8 +++++++- .../mit/media/funf/storage/HttpArchive.java | 10 ++++++---- .../java/edu/mit/media/funf/util/IOUtil.java | 18 +++++++++++------- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java b/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java index 78fd5dc..55dd9b8 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java @@ -750,13 +750,28 @@ public void setAuthToken(String url, String accessToken) { } catch (URISyntaxException e) { e.printStackTrace(); } + } - + public String getAuthToken(String url) { + String authToken = ""; + try { + URI uri = new URI(url); + authToken = getSharedPreferences("funf_auth", MODE_PRIVATE).getString(IOUtil.md5(uri.getHost()), ""); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + return authToken; } public void authError() { - Log.i(TAG, "sending authError broadcast"); + authError("unknown", ""); + } + + public void authError(String action, String accessToken) { + Log.i(TAG, "sending authError broadcast "+action); Intent intent = new Intent(context.getPackageName() + "." + "AUTHENTICATION_ERROR"); + intent.putExtra("action", action); + intent.putExtra("accessToken", accessToken); sendBroadcast(intent); } diff --git a/funf_v4/src/main/java/edu/mit/media/funf/config/HttpConfigUpdater.java b/funf_v4/src/main/java/edu/mit/media/funf/config/HttpConfigUpdater.java index 0792ed1..5bd6254 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/config/HttpConfigUpdater.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/config/HttpConfigUpdater.java @@ -1,10 +1,15 @@ package edu.mit.media.funf.config; +import android.util.Log; + import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; +import edu.mit.media.funf.FunfManager; +import edu.mit.media.funf.pipeline.BasicPipeline; import edu.mit.media.funf.util.IOUtil; +import edu.mit.media.funf.util.LogUtil; /** * ConfigUpdater which does an Http get to the given url. @@ -20,8 +25,9 @@ public JsonObject getConfig() throws ConfigUpdateException { try { String content = null; String currentUrl = IOUtil.formatServerUrl(url, ""); + Log.i(LogUtil.TAG, "Config url: " + currentUrl); if (IOUtil.isValidUrl(currentUrl)) { - content = IOUtil.httpGet(currentUrl, null); + content = IOUtil.httpGet(currentUrl, null, BasicPipeline.ACTION_UPDATE, FunfManager.funfManager.getAuthToken(url)); } if (content == null) { throw new ConfigUpdateException("Unable to download configuration."); diff --git a/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java index 4af9643..fb18f95 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java @@ -51,10 +51,12 @@ import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.entity.mime.content.FileBody; import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.execchain.MainClientExec; import edu.mit.media.funf.FunfManager; import edu.mit.media.funf.Schedule.DefaultSchedule; import edu.mit.media.funf.config.Configurable; +import edu.mit.media.funf.pipeline.BasicPipeline; import edu.mit.media.funf.util.IOUtil; import edu.mit.media.funf.util.LogUtil; @@ -121,7 +123,8 @@ public String getId() { public boolean add(File file) { String currentUrl = IOUtil.formatServerUrl(url, file.getName()); - return IOUtil.isValidUrl(currentUrl) ? uploadFile(file, currentUrl) : false; + Log.i(LogUtil.TAG, "UPLOADER: "+currentUrl + " "+IOUtil.isValidUrl(currentUrl)); + return IOUtil.isValidUrl(currentUrl) ? uploadFile(file, currentUrl,FunfManager.funfManager.getAuthToken(url)) : false; } /** @@ -130,7 +133,7 @@ public boolean add(File file) { * @param uploadurl * @return */ - public static boolean uploadFile(File file,String uploadurl) { + public static boolean uploadFile(File file,String uploadurl,String accessToken) { if (uploadurl == null) return false; if (uploadurl.equals("")) return false; HttpClient httpClient = new DefaultHttpClient() ; @@ -166,8 +169,7 @@ public static boolean uploadFile(File file,String uploadurl) { if (response.getStatusLine().getStatusCode() == 401) { //Auth error Log.i(LogUtil.TAG, "Auth Error "+response.getStatusLine().getStatusCode()); - //TODO propagate auth error up, in OAuth2 context this will require re-auth from user - FunfManager.funfManager.authError(); + FunfManager.funfManager.authError(BasicPipeline.ACTION_UPLOAD, accessToken); } diff --git a/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java b/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java index 0e74abf..a1eea9a 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java @@ -88,8 +88,8 @@ public static String inputStreamToString(InputStream is, String encoding) throws } while (read>=0); return out.toString(); } - - public static String httpGet(String uri,HttpParams params){ + + public static String httpGet(String uri,HttpParams params,String action,String accessToken) { HttpResponse response=null; HttpClient httpclient = new DefaultHttpClient(); StringBuilder uriBuilder = new StringBuilder(uri); @@ -97,10 +97,10 @@ public static String httpGet(String uri,HttpParams params){ if (params != null) { httpget.setParams(params); } - try { - response = httpclient.execute(httpget); + try { + response = httpclient.execute(httpget); if (response.getStatusLine().getStatusCode() == 401) { - FunfManager.funfManager.authError(); + FunfManager.funfManager.authError(action,accessToken); response = null; } } catch (ClientProtocolException e) { @@ -110,7 +110,7 @@ public static String httpGet(String uri,HttpParams params){ Log.e(TAG, "HttpGet Error: ", e); response=null; } finally{ - httpclient.getConnectionManager().shutdown(); + httpclient.getConnectionManager().shutdown(); } if (response != null) { try { @@ -125,7 +125,11 @@ public static String httpGet(String uri,HttpParams params){ } catch (IOException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();} } - return null; + return null; + } + + public static String httpGet(String uri,HttpParams params){ + return httpGet(uri,params,"unknown",""); } /** From 7bc7ec5390b5aa58ad1c090c81b0da056f53f7e4 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sun, 6 Dec 2015 14:21:16 -0800 Subject: [PATCH 44/61] Better httpGet using HttpURLConnection --- .../java/edu/mit/media/funf/util/IOUtil.java | 74 +++++++------------ 1 file changed, 27 insertions(+), 47 deletions(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java b/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java index a1eea9a..80529a7 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java @@ -44,33 +44,26 @@ */ package edu.mit.media.funf.util; -import static edu.mit.media.funf.util.LogUtil.TAG; +import android.content.Context; +import android.util.Log; +import android.util.Patterns; + +import org.apache.http.params.HttpParams; -import java.io.BufferedReader; +import java.io.BufferedInputStream; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.params.HttpParams; - -import android.content.Context; -import android.content.SharedPreferences; -import android.net.Uri; -import android.util.Log; -import android.util.Patterns; - import edu.mit.media.funf.FunfManager; public class IOUtil { @@ -90,40 +83,27 @@ public static String inputStreamToString(InputStream is, String encoding) throws } public static String httpGet(String uri,HttpParams params,String action,String accessToken) { - HttpResponse response=null; - HttpClient httpclient = new DefaultHttpClient(); - StringBuilder uriBuilder = new StringBuilder(uri); - HttpGet httpget = new HttpGet(uriBuilder.toString()); - if (params != null) { - httpget.setParams(params); - } + //params are deprecated + + HttpURLConnection urlConnection = null; + int responseCode = 0; try { - response = httpclient.execute(httpget); - if (response.getStatusLine().getStatusCode() == 401) { - FunfManager.funfManager.authError(action,accessToken); - response = null; - } - } catch (ClientProtocolException e) { - Log.e(TAG, "HttpGet Error: ", e); - response=null; + URL url = new URL(uri); + urlConnection = (HttpURLConnection) url.openConnection(); + responseCode = urlConnection.getResponseCode(); + InputStream in = new BufferedInputStream(urlConnection.getInputStream()); + String returnValue = inputStreamToString(in, "UTF-8"); + return returnValue; + } catch (MalformedURLException e) { + e.printStackTrace(); } catch (IOException e) { - Log.e(TAG, "HttpGet Error: ", e); - response=null; - } finally{ - httpclient.getConnectionManager().shutdown(); + e.printStackTrace(); + if (responseCode == 401) FunfManager.funfManager.authError(action, accessToken); } - if (response != null) { - try { - StringBuilder sb = new StringBuilder(); - HttpEntity entity = response.getEntity(); - BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent()), 65728); - String line; - while ((line = reader.readLine()) != null) { - sb.append(line); - } - return sb.toString(); - } catch (IOException e) {e.printStackTrace();} - catch (Exception e) {e.printStackTrace();} + finally { + if (urlConnection != null) { + urlConnection.disconnect(); + } } return null; } From 136ac11f7e78889ee590788b53c586a4804929cc Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Fri, 11 Dec 2015 07:25:50 -0800 Subject: [PATCH 45/61] Pipeline can be now configured to use json files instead of sliqte3 --- .../media/funf/pipeline/BasicPipeline.java | 54 +++++++----- .../media/funf/storage/DatabaseHelper.java | 24 ++++++ .../funf/storage/JsonDatabaseHelper.java | 85 +++++++++++++++++++ .../funf/storage/NameValueDatabaseHelper.java | 41 +++++++-- 4 files changed, 175 insertions(+), 29 deletions(-) create mode 100644 funf_v4/src/main/java/edu/mit/media/funf/storage/DatabaseHelper.java create mode 100644 funf_v4/src/main/java/edu/mit/media/funf/storage/JsonDatabaseHelper.java diff --git a/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java b/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java index 29e6e69..1b4a173 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java @@ -51,8 +51,10 @@ import edu.mit.media.funf.json.IJsonObject; import edu.mit.media.funf.probe.Probe.DataListener; import edu.mit.media.funf.probe.builtin.ProbeKeys; +import edu.mit.media.funf.storage.DatabaseHelper; import edu.mit.media.funf.storage.DefaultArchive; import edu.mit.media.funf.storage.FileArchive; +import edu.mit.media.funf.storage.JsonDatabaseHelper; import edu.mit.media.funf.storage.NameValueDatabaseHelper; import edu.mit.media.funf.storage.RemoteFileArchive; import edu.mit.media.funf.storage.UploadService; @@ -97,6 +99,9 @@ public class BasicPipeline implements Pipeline, DataListener { @Configurable protected boolean broadcastCollectionState = true; + + @Configurable + protected String file_format = "sqlite"; //can be sqlite or json private UploadService uploader; @@ -104,7 +109,9 @@ public class BasicPipeline implements Pipeline, DataListener { private boolean enabled; private FunfManager manager; - private SQLiteOpenHelper databaseHelper = null; + //private SQLiteOpenHelper databaseHelper = null; + //private JsonDatabaseHelper databaseHelper = null; + private DatabaseHelper databaseHelper = null; private Looper looper; private Handler handler; private Handler.Callback callback = new Handler.Callback() { @@ -142,19 +149,23 @@ public boolean handleMessage(Message msg) { }; protected void reloadDbHelper(Context ctx) { - this.databaseHelper = new NameValueDatabaseHelper(ctx, StringUtil.simpleFilesafe(name), version); + if (this.file_format.equals("json")) { + this.databaseHelper = new JsonDatabaseHelper(ctx, StringUtil.simpleFilesafe(name), version); + this.databaseHelper.init(); + } else { + this.databaseHelper = new NameValueDatabaseHelper(ctx, StringUtil.simpleFilesafe(name), version); + this.databaseHelper.init(); + } + } protected void runArchive() { - SQLiteDatabase db = databaseHelper.getWritableDatabase(); - // TODO: add check to make sure this is not empty - File dbFile = new File(db.getPath()); - db.close(); + File dbFile = new File(this.databaseHelper.getPath()); + this.databaseHelper.finish(); if (archive.add(dbFile)) { dbFile.delete(); } reloadDbHelper(manager); - databaseHelper.getWritableDatabase(); // Build new database } private void broadcastDataCollection() { @@ -179,18 +190,15 @@ protected void writeData(String name, IJsonObject data) { broadcastDataCollection(); if (!geofence.shouldSaveData(name, data)) return; - SQLiteDatabase db = databaseHelper.getWritableDatabase(); final double timestamp = data.get(ProbeKeys.BaseProbeKeys.TIMESTAMP).getAsDouble(); - final String value = data.toString(); + final IJsonObject value = data; if (timestamp == 0L || name == null || value == null) { Log.e(LogUtil.TAG, "Unable to save data. Not all required values specified. " + timestamp + " " + name + " - " + value); + //TODO custom exception throw new SQLException("Not all required fields specified."); } - ContentValues cv = new ContentValues(); - cv.put(NameValueDatabaseHelper.COLUMN_NAME, name); - cv.put(NameValueDatabaseHelper.COLUMN_VALUE, value); - cv.put(NameValueDatabaseHelper.COLUMN_TIMESTAMP, timestamp); - db.insertOrThrow(NameValueDatabaseHelper.DATA_TABLE.name, "", cv); + + this.databaseHelper.insert(name, timestamp, value); } @@ -272,9 +280,9 @@ protected FunfManager getFunfManager() { } - public SQLiteDatabase getDb() { - return databaseHelper.getReadableDatabase(); - } + //public SQLiteDatabase getDb() { + // return databaseHelper.getReadableDatabase(); + //} public List getDataRequests() { return data == null ? null : Collections.unmodifiableList(data); @@ -360,14 +368,14 @@ public void setUploader(UploadService uploader) { } - public SQLiteOpenHelper getDatabaseHelper() { - return databaseHelper; - } + //public SQLiteOpenHelper getDatabaseHelper() { + // return databaseHelper; + //} - public void setDatabaseHelper(SQLiteOpenHelper databaseHelper) { - this.databaseHelper = databaseHelper; - } + //public void setDatabaseHelper(SQLiteOpenHelper databaseHelper) { + // this.databaseHelper = databaseHelper; + //} @Override diff --git a/funf_v4/src/main/java/edu/mit/media/funf/storage/DatabaseHelper.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/DatabaseHelper.java new file mode 100644 index 0000000..9afc96b --- /dev/null +++ b/funf_v4/src/main/java/edu/mit/media/funf/storage/DatabaseHelper.java @@ -0,0 +1,24 @@ +package edu.mit.media.funf.storage; + +import android.content.Context; + +import edu.mit.media.funf.json.IJsonObject; + +/** + * Created by astopczynski on 10/27/15. + */ +public interface DatabaseHelper { + + public static final String COLUMN_NAME = "name"; + public static final String COLUMN_TIMESTAMP = "timestamp"; + public static final String COLUMN_VALUE = "value"; + + public void init(); + + public String getPath(); + + public void finish(); + + public void insert(String name, double timestamp, IJsonObject value); + +} diff --git a/funf_v4/src/main/java/edu/mit/media/funf/storage/JsonDatabaseHelper.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/JsonDatabaseHelper.java new file mode 100644 index 0000000..9658002 --- /dev/null +++ b/funf_v4/src/main/java/edu/mit/media/funf/storage/JsonDatabaseHelper.java @@ -0,0 +1,85 @@ +package edu.mit.media.funf.storage; + +import android.content.ContentValues; +import android.content.Context; +import android.provider.Settings; +import android.util.Log; + +import com.google.gson.JsonObject; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.TimeZone; + +import edu.mit.media.funf.json.IJsonObject; +import edu.mit.media.funf.util.LogUtil; + +/** + * Created by astopczynski on 10/26/15. + */ +public class JsonDatabaseHelper implements DatabaseHelper{ + + public static final String COLUMN_TIME_OFFSET = "time_offset"; + + + Context context; + String databaseName; + static FileOutputStream fos = null; + + public JsonDatabaseHelper(Context context, String name, int version) { + this.context = context; + this.databaseName = name; + } + + public void init() { + this.createDatabaseFile(); + } + + private void createDatabaseFile() { + try { + fos = this.context.openFileOutput(this.databaseName, Context.MODE_WORLD_READABLE); + JsonObject dataObject = new JsonObject(); + dataObject.addProperty("ANDROID_ID", + Settings.Secure.getString( + this.context.getContentResolver(), Settings.Secure.ANDROID_ID + )); + dataObject.addProperty(this.COLUMN_TIME_OFFSET, + TimeZone.getDefault().getOffset(System.currentTimeMillis())/1000/60); + + fos.write((dataObject.toString() + "\n").getBytes()); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + } + + public void insert(String name, double timestamp, IJsonObject value) { + if (fos == null) { + createDatabaseFile(); + } + try { + JsonObject dataObject = new JsonObject(); + dataObject.addProperty(this.COLUMN_NAME, name); + dataObject.addProperty(this.COLUMN_TIMESTAMP, timestamp); + dataObject.add(this.COLUMN_VALUE, value); + fos.write((dataObject.toString() + "\n").getBytes()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void finish() { + try { + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public String getPath() { + return this.context.getFileStreamPath(this.databaseName).getPath(); + } +} diff --git a/funf_v4/src/main/java/edu/mit/media/funf/storage/NameValueDatabaseHelper.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/NameValueDatabaseHelper.java index d1fd52c..7dd4f9c 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/storage/NameValueDatabaseHelper.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/storage/NameValueDatabaseHelper.java @@ -29,20 +29,22 @@ import java.util.Locale; import java.util.UUID; +import android.content.ContentValues; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; + +import edu.mit.media.funf.json.IJsonObject; +import edu.mit.media.funf.probe.builtin.ProbeKeys; import edu.mit.media.funf.time.TimeUtil; import edu.mit.media.funf.util.StringUtil; import edu.mit.media.funf.util.UuidUtil; -public class NameValueDatabaseHelper extends SQLiteOpenHelper { +public class NameValueDatabaseHelper extends SQLiteOpenHelper implements DatabaseHelper { public static final int CURRENT_VERSION = 1; - public static final String COLUMN_NAME = "name"; - public static final String COLUMN_TIMESTAMP = "timestamp"; - public static final String COLUMN_VALUE = "value"; + public static final Table DATA_TABLE = new Table("data", Arrays.asList(new Column(COLUMN_NAME, "TEXT"), // ACTION from data broadcast new Column(COLUMN_TIMESTAMP, "FLOAT"), // TIMESTAMP in data broadcast @@ -84,8 +86,35 @@ public void onCreate(SQLiteDatabase db) { @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // Nothing yet - } - + } + + @Override + public void init() { + this.getWritableDatabase(); + } + + @Override + public String getPath() { + return this.getWritableDatabase().getPath(); + } + + @Override + public void insert(String name, double timestamp, IJsonObject value) { + SQLiteDatabase db = this.getWritableDatabase(); + final String valueString = value.toString(); + ContentValues cv = new ContentValues(); + cv.put(NameValueDatabaseHelper.COLUMN_NAME, name); + cv.put(NameValueDatabaseHelper.COLUMN_VALUE, valueString); + cv.put(NameValueDatabaseHelper.COLUMN_TIMESTAMP, timestamp); + db.insertOrThrow(NameValueDatabaseHelper.DATA_TABLE.name, "", cv); + + } + + @Override + public void finish() { + this.getWritableDatabase().close(); + } + // TODO: Consider moving these to an external utils class /** * Immutable Table Definition From cb129297d331ace94c89b54fc26042d6d606592c Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Fri, 11 Dec 2015 07:29:33 -0800 Subject: [PATCH 46/61] Added mcc and mnc, required for proper cell geolocation --- .../funf/probe/builtin/CellTowerProbe.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/CellTowerProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/CellTowerProbe.java index 7217d9e..4cc3afb 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/CellTowerProbe.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/CellTowerProbe.java @@ -55,10 +55,28 @@ private Bundle getData() { GsmCellLocation gsmLocation = (GsmCellLocation) location; gsmLocation.fillInNotifierBundle(data); data.putInt(TYPE, TelephonyManager.PHONE_TYPE_GSM); + String networkOperator = manager.getNetworkOperator(); + int mcc = 0; + int mnc = 0; + if (networkOperator != null && !networkOperator.isEmpty()) { + mcc = Integer.parseInt(networkOperator.substring(0, 3)); + mnc = Integer.parseInt(networkOperator.substring(3)); + } + data.putInt("mcc", mcc); + data.putInt("mnc", mnc); } else if (location instanceof CdmaCellLocation) { CdmaCellLocation cdmaLocation = (CdmaCellLocation) location; cdmaLocation.fillInNotifierBundle(data); data.putInt(TYPE, TelephonyManager.PHONE_TYPE_CDMA); + String networkOperator = manager.getNetworkOperator(); + int mcc = 0; + int mnc = 0; + if (networkOperator != null && !networkOperator.isEmpty()) { + mcc = Integer.parseInt(networkOperator.substring(0, 3)); + mnc = Integer.parseInt(networkOperator.substring(3)); + } + data.putInt("mcc", mcc); + data.putInt("mnc", mnc); } else { data.putInt(TYPE, TelephonyManager.PHONE_TYPE_NONE); } From 4dd88edf156ea2f9d9615f2f205ee486ac6e4438 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Fri, 11 Dec 2015 07:32:48 -0800 Subject: [PATCH 47/61] Changed default backup to much smaller and made json db --- .../java/edu/mit/media/funf/storage/FileDirectoryArchive.java | 2 +- .../java/edu/mit/media/funf/storage/JsonDatabaseHelper.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/storage/FileDirectoryArchive.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/FileDirectoryArchive.java index 82474bf..65661ab 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/storage/FileDirectoryArchive.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/storage/FileDirectoryArchive.java @@ -54,7 +54,7 @@ public static FileDirectoryArchive getSimpleFileArchive(File archiveDir) { } public static FileDirectoryArchive getRollingFileArchive(File archiveDir) { - return new FileDirectoryArchive(archiveDir, new NameGenerator.IdentityNameGenerator(), new FileCopier.SimpleFileCopier(), new DirectoryCleaner.KeepUnderPercentageOfDiskFree(0.5, 10000000)); + return new FileDirectoryArchive(archiveDir, new NameGenerator.IdentityNameGenerator(), new FileCopier.SimpleFileCopier(), new DirectoryCleaner.KeepMostRecent(20)); } diff --git a/funf_v4/src/main/java/edu/mit/media/funf/storage/JsonDatabaseHelper.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/JsonDatabaseHelper.java index 9658002..89cb05d 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/storage/JsonDatabaseHelper.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/storage/JsonDatabaseHelper.java @@ -38,7 +38,7 @@ public void init() { private void createDatabaseFile() { try { - fos = this.context.openFileOutput(this.databaseName, Context.MODE_WORLD_READABLE); + fos = this.context.openFileOutput(this.databaseName, Context.MODE_PRIVATE); JsonObject dataObject = new JsonObject(); dataObject.addProperty("ANDROID_ID", Settings.Secure.getString( From 254a4e8f45ccb4f4628ede592c1ef3611016c732 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Fri, 18 Dec 2015 11:07:18 -0800 Subject: [PATCH 48/61] device can now have a unique ID attached to BT name --- .../media/funf/probe/builtin/BluetoothProbe.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java index 3fd96fc..8b475a4 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java @@ -63,6 +63,9 @@ public class BluetoothProbe extends Base implements PassiveProbe { @Configurable private boolean keepBluetoothVisible = false; + + @Configurable + private String bluetooth_id = ""; private BluetoothAdapter adapter; private BroadcastReceiver receiver = new BroadcastReceiver() { @@ -153,6 +156,9 @@ protected void onStop() { adapter.disable(); shouldDisableOnFinish = false; } + if (!this.bluetooth_id.isEmpty()) { + setBluetoothId(); + } if (keepBluetoothVisible) { makeVisible(); } @@ -167,6 +173,13 @@ private void makeVisible() { } } + private void setBluetoothId() { + if (this.bluetooth_id.isEmpty()) return; + String currentBluetoothName = adapter.getName(); + if (currentBluetoothName.contains(this.bluetooth_id)) return; + adapter.setName(currentBluetoothName + "$" + this.bluetooth_id + "$"); + } + @Override protected void onDisable() { try { From 0da44581766892f9713a7aefe3d73220a5bd6500 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sun, 20 Dec 2015 09:14:24 -0800 Subject: [PATCH 49/61] Added descriptions to probes --- .../edu/mit/media/funf/probe/builtin/HardwareInfoProbe.java | 2 ++ .../java/edu/mit/media/funf/probe/builtin/TimeOffsetProbe.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/HardwareInfoProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/HardwareInfoProbe.java index ec5f1fa..0718b45 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/HardwareInfoProbe.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/HardwareInfoProbe.java @@ -31,9 +31,11 @@ import android.provider.Settings.Secure; import android.telephony.TelephonyManager; import edu.mit.media.funf.Schedule; +import edu.mit.media.funf.probe.Probe; import edu.mit.media.funf.probe.Probe.RequiredPermissions; import edu.mit.media.funf.probe.builtin.ProbeKeys.HardwareInfoKeys; +@Probe.DisplayName("Basic information about this device (e.g. brand, model, hardware ids)") @Schedule.DefaultSchedule(interval=604800) @RequiredPermissions({android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.BLUETOOTH, android.Manifest.permission.READ_PHONE_STATE}) public class HardwareInfoProbe extends ImpulseProbe implements HardwareInfoKeys { diff --git a/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/TimeOffsetProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/TimeOffsetProbe.java index 247703b..babcdc1 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/TimeOffsetProbe.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/TimeOffsetProbe.java @@ -37,6 +37,7 @@ import com.google.gson.JsonObject; import edu.mit.media.funf.config.Configurable; +import edu.mit.media.funf.probe.Probe; import edu.mit.media.funf.probe.Probe.Base; import edu.mit.media.funf.probe.builtin.ProbeKeys.TimeOffsetKeys; import edu.mit.media.funf.time.NtpMessage; @@ -49,6 +50,7 @@ * @author alangardner * */ +@Probe.DisplayName("Status of time synchronization") public class TimeOffsetProbe extends Base implements TimeOffsetKeys { private static final BigDecimal SECONDS_1900_TO_1970 = new BigDecimal(2208988800L); From 231f30f54e8240eebdd3562036cc93465c52dbb2 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sun, 20 Dec 2015 09:20:28 -0800 Subject: [PATCH 50/61] Changed order of BT operations, just in case --- .../edu/mit/media/funf/probe/builtin/BluetoothProbe.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java index 8b475a4..b325461 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/probe/builtin/BluetoothProbe.java @@ -152,13 +152,13 @@ protected void onStop() { if (adapter.isDiscovering()) { adapter.cancelDiscovery(); } + if (!this.bluetooth_id.isEmpty()) { + setBluetoothId(); + } if (shouldDisableOnFinish) { adapter.disable(); shouldDisableOnFinish = false; } - if (!this.bluetooth_id.isEmpty()) { - setBluetoothId(); - } if (keepBluetoothVisible) { makeVisible(); } From 49b4d4e6ef78c6f892d26f06c22997103c19d81c Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sun, 20 Dec 2015 09:24:13 -0800 Subject: [PATCH 51/61] Broadcasting 500 error in case application wants to react to it --- funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java | 8 ++++++++ .../main/java/edu/mit/media/funf/storage/HttpArchive.java | 4 ++++ funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java | 1 + 3 files changed, 13 insertions(+) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java b/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java index 55dd9b8..9913503 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java @@ -781,6 +781,14 @@ public void broadcastDataCollectionStatus(Integer status) { sendBroadcast(intent); } + public void broadcastServerError(String action, String accessToken) { + Log.i(TAG, "sending serverError broadcast"); + Intent intent = new Intent(context.getPackageName() + "." + "SERVER_ERROR"); + intent.putExtra("action", action); + intent.putExtra("accessToken", accessToken); + sendBroadcast(intent); + } + //////////////////////////////////////////////////// diff --git a/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java index fb18f95..60370b3 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java @@ -171,6 +171,10 @@ public static boolean uploadFile(File file,String uploadurl,String accessToken) Log.i(LogUtil.TAG, "Auth Error "+response.getStatusLine().getStatusCode()); FunfManager.funfManager.authError(BasicPipeline.ACTION_UPLOAD, accessToken); } + if (response.getStatusLine().getStatusCode() == 500) { + //Server error + FunfManager.funfManager.broadcastServerError("SERVER_ERROR", accessToken); + } return false; diff --git a/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java b/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java index 80529a7..3067ddc 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/util/IOUtil.java @@ -99,6 +99,7 @@ public static String httpGet(String uri,HttpParams params,String action,String a } catch (IOException e) { e.printStackTrace(); if (responseCode == 401) FunfManager.funfManager.authError(action, accessToken); + if (responseCode == 500) FunfManager.funfManager.broadcastServerError("SERVER_ERROR", accessToken); } finally { if (urlConnection != null) { From 057e0a89ffecf28b073db5a7f482cd341c97a2a8 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sun, 20 Dec 2015 09:29:39 -0800 Subject: [PATCH 52/61] Support for setting and getting application strings --- .../java/edu/mit/media/funf/FunfManager.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java b/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java index 9913503..d38270d 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/FunfManager.java @@ -789,6 +789,23 @@ public void broadcastServerError(String action, String accessToken) { sendBroadcast(intent); } + public void setApplicationVersion(String version) { + try { + getSharedPreferences("funf_version", MODE_PRIVATE).edit().putString("application_version", version).commit(); + } catch (NullPointerException e) { + e.printStackTrace(); + } + } + + public String getApplicationVersion() { + try { + return getSharedPreferences("funf_version", MODE_PRIVATE).getString("application_version", "unknown"); + } catch (NullPointerException e) { + e.printStackTrace(); + return "unknown"; + } + } + //////////////////////////////////////////////////// From 83c49a8f556acec21bd0b85f9bc4c49e7a6c5a51 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sun, 20 Dec 2015 09:31:24 -0800 Subject: [PATCH 53/61] Protection against overwriting existing archive files. Just in case. --- .../edu/mit/media/funf/storage/FileDirectoryArchive.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/storage/FileDirectoryArchive.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/FileDirectoryArchive.java index 65661ab..edb2d9b 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/storage/FileDirectoryArchive.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/storage/FileDirectoryArchive.java @@ -61,7 +61,14 @@ public static FileDirectoryArchive getRollingFileArchive(File archiveDir) { @Override public boolean add(File item) { this.archiveDir.mkdirs(); - File archiveFile = new File(archiveDir, nameGenerator.generateName(item.getName())); + String filename = nameGenerator.generateName(item.getName()); + String appendix = ""; + File archiveFile = new File(archiveDir, filename); + while (archiveFile.exists()) { + appendix += "_"; + filename = nameGenerator.generateName(appendix + item.getName()); + archiveFile = new File(archiveDir, filename); + } boolean result = fileCopier.copy(item, archiveFile); cleaner.clean(archiveDir); return result; From dfe27acb8c309517f100daf6454fa41aa1649054 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sun, 20 Dec 2015 09:33:20 -0800 Subject: [PATCH 54/61] Saving funf version and application version in the database file --- .../edu/mit/media/funf/storage/JsonDatabaseHelper.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/storage/JsonDatabaseHelper.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/JsonDatabaseHelper.java index 89cb05d..cd7b028 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/storage/JsonDatabaseHelper.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/storage/JsonDatabaseHelper.java @@ -45,7 +45,15 @@ private void createDatabaseFile() { this.context.getContentResolver(), Settings.Secure.ANDROID_ID )); dataObject.addProperty(this.COLUMN_TIME_OFFSET, - TimeZone.getDefault().getOffset(System.currentTimeMillis())/1000/60); + TimeZone.getDefault().getOffset(System.currentTimeMillis()) / 1000 / 60); + + try { + dataObject.addProperty("FUNF_VERSION", FunfManager.funfManager.getVersion()); + } catch (NullPointerException e) {} + + try { + dataObject.addProperty("APPLICATION_VERSION", FunfManager.funfManager.getApplicationVersion()); + } catch (NullPointerException e) {} fos.write((dataObject.toString() + "\n").getBytes()); } catch (FileNotFoundException e) { From 7162d9cb585d00ac43621d6dd43b6d768f652359 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sun, 20 Dec 2015 09:37:40 -0800 Subject: [PATCH 55/61] Fix for data being lost after a crash and support for temp file when the main file is not available during archiving. --- .../funf/storage/JsonDatabaseHelper.java | 75 ++++++++++++++++--- 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/storage/JsonDatabaseHelper.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/JsonDatabaseHelper.java index cd7b028..525ab8f 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/storage/JsonDatabaseHelper.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/storage/JsonDatabaseHelper.java @@ -1,19 +1,20 @@ package edu.mit.media.funf.storage; -import android.content.ContentValues; +import com.google.gson.JsonObject; + import android.content.Context; import android.provider.Settings; -import android.util.Log; - -import com.google.gson.JsonObject; -import java.io.FileNotFoundException; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStreamReader; import java.util.TimeZone; +import edu.mit.media.funf.FunfManager; import edu.mit.media.funf.json.IJsonObject; -import edu.mit.media.funf.util.LogUtil; /** * Created by astopczynski on 10/26/15. @@ -25,11 +26,14 @@ public class JsonDatabaseHelper implements DatabaseHelper{ Context context; String databaseName; + String tempFileName; static FileOutputStream fos = null; + private FileOutputStream tempFos = null; public JsonDatabaseHelper(Context context, String name, int version) { this.context = context; this.databaseName = name; + this.tempFileName = this.databaseName + "_temp"; } public void init() { @@ -38,7 +42,8 @@ public void init() { private void createDatabaseFile() { try { - fos = this.context.openFileOutput(this.databaseName, Context.MODE_PRIVATE); + fos = this.context.openFileOutput(this.databaseName, Context.MODE_APPEND); + JsonObject dataObject = new JsonObject(); dataObject.addProperty("ANDROID_ID", Settings.Secure.getString( @@ -56,8 +61,6 @@ private void createDatabaseFile() { } catch (NullPointerException e) {} fos.write((dataObject.toString() + "\n").getBytes()); - } catch (FileNotFoundException e) { - e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } @@ -68,12 +71,57 @@ public void insert(String name, double timestamp, IJsonObject value) { if (fos == null) { createDatabaseFile(); } + JsonObject dataObject = null; try { - JsonObject dataObject = new JsonObject(); + dataObject = new JsonObject(); dataObject.addProperty(this.COLUMN_NAME, name); dataObject.addProperty(this.COLUMN_TIMESTAMP, timestamp); dataObject.add(this.COLUMN_VALUE, value); fos.write((dataObject.toString() + "\n").getBytes()); + } catch (IOException e) { + e.printStackTrace(); + insertIntoTemp(dataObject); + } + } + + private void createTempFile() { + try { + tempFos = this.context.openFileOutput(this.tempFileName, Context.MODE_APPEND); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void finalizeTempFile() { + try { + if (tempFos != null) tempFos.close(); + (new File(this.tempFileName)).delete(); + } catch (IOException e) { + } + + } + + private void copyFromTempFile() { + try { + FileInputStream tempFis = this.context.openFileInput(this.tempFileName); + BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(tempFis, "UTF-8")); + while (true) { + String line = bufferedReader.readLine(); + if (line == null) break; + fos.write((line + "\n").getBytes()); + } + + } catch (IOException e) { + } + } + + private void insertIntoTemp(JsonObject dataObject) { + createTempFile(); + try { + if (dataObject != null) { + tempFos.write((dataObject.toString() + "\n").getBytes()); + } } catch (IOException e) { e.printStackTrace(); } @@ -81,7 +129,12 @@ public void insert(String name, double timestamp, IJsonObject value) { public void finish() { try { - fos.close(); + if (fos != null) { + copyFromTempFile(); + finalizeTempFile(); + fos.close(); + } + } catch (IOException e) { e.printStackTrace(); } From 3c72c7cc41625b0eabac794c844664cacba13326 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sun, 20 Dec 2015 09:41:08 -0800 Subject: [PATCH 56/61] Updated geofencer not to fire probes when outside of the fence --- .../edu/mit/media/funf/data/Geofencer.java | 193 +++++++++--------- .../media/funf/pipeline/BasicPipeline.java | 8 +- .../java/edu/mit/media/funf/probe/Probe.java | 12 ++ 3 files changed, 117 insertions(+), 96 deletions(-) diff --git a/funf_v4/src/main/java/edu/mit/media/funf/data/Geofencer.java b/funf_v4/src/main/java/edu/mit/media/funf/data/Geofencer.java index 6980b19..a933bf3 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/data/Geofencer.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/data/Geofencer.java @@ -23,137 +23,140 @@ */ public class Geofencer { - //TODO broadcast recording/not recording + @Configurable + protected Integer version = 0; - @Configurable - protected Integer version = 0; + @Configurable + protected List fences = new ArrayList(); - @Configurable - protected List fences = new ArrayList(); + @Configurable + protected Double minLocationAccuracy = 80.0; - @Configurable - protected Double minLocationAccuracy = 80.0; + @Configurable + protected Long minLocationFreshness = 1L; - @Configurable - protected Long minLocationFreshness = 1L; + @Configurable + protected Long timeWindow = 10L; - @Configurable - protected Long timeWindow = 10L; + private Set acceptedLocationProbes = + new HashSet(Arrays.asList("edu.mit.media.funf.probe.builtin.LocationProbe", "edu.mit.media.funf.probe.builtin.SimpleLocationProbe")); - private Set acceptedLocationProbes = - new HashSet(Arrays.asList("edu.mit.media.funf.probe.builtin.LocationProbe", "edu.mit.media.funf.probe.builtin.SimpleLocationProbe")); + public Geofencer() { + Log.i(LogUtil.TAG, "Creating geofencer " + version); + } - public Geofencer() { - Log.i(LogUtil.TAG, "Creating geofencer " + version); + public boolean shouldSaveData(String name, IJsonObject data) { + if (acceptedLocationProbes.contains(name)) { + updateVisits(name, data); } - public boolean shouldSaveData(String name, IJsonObject data) { - if (acceptedLocationProbes.contains(name)) { - updateVisits(name, data); - } - - return checkFences(timestampToMillis(data.get("timestamp").getAsLong())); - } - - public boolean shouldSaveData(Long timestamp) { - return checkFences(timestamp); - } + return checkFences(timestampToMillis(data.get("timestamp").getAsLong())); + } + public boolean shouldSaveData(Long timestamp) { + return checkFences(timestamp); + } - private void updateVisits(String name, IJsonObject data) { + public boolean shouldFire(String name) { + if (acceptedLocationProbes.contains(name)) return true; + return shouldSaveData(System.currentTimeMillis()); - if (name.equals("edu.mit.media.funf.probe.builtin.LocationProbe")) { - Double accuracy = data.get("mAccuracy").getAsDouble(); - Double lat = data.get("mLatitude").getAsDouble(); - Double lon = data.get("mLongitude").getAsDouble(); - Long timestamp = timestampToMillis(data.get("mTime").getAsLong()); + } - //TODO discard mock locations - if (accuracy > minLocationAccuracy) return; - if ((System.currentTimeMillis()) > timestamp + minLocationFreshness*60*1000) return; + private void updateVisits(String name, IJsonObject data) { - //TODO graceful error handling - for (JsonElement fence: fences) { - JsonObject fenceObject = fence.getAsJsonObject(); - Double fenceLatitude = fenceObject.get("latitude").getAsDouble(); - Double fenceLongitude = fenceObject.get("longitude").getAsDouble(); - Double fenceRadius = fenceObject.get("radius").getAsDouble(); + if (name.equals("edu.mit.media.funf.probe.builtin.LocationProbe")) { + Double accuracy = data.get("mAccuracy").getAsDouble(); + Double lat = data.get("mLatitude").getAsDouble(); + Double lon = data.get("mLongitude").getAsDouble(); + Long timestamp = timestampToMillis(data.get("mTime").getAsLong()); - Double distance = haversine(lat, lon, fenceLatitude, fenceLongitude); - - if (distance < fenceRadius) { - updateVisit(fenceLatitude, fenceLongitude, fenceRadius, timestamp); - } - } - - } - } + //TODO discard mock locations + if (accuracy > minLocationAccuracy) return; + if ((System.currentTimeMillis()) > timestamp + minLocationFreshness*60*1000) return; - private void updateVisit(Double fenceLatitude, Double fenceLongitude, Double fenceRadius, Long timestamp) { - String fenceID = fenceToString(fenceLatitude, fenceLongitude, fenceRadius); - SharedPreferences sharedPreferences = FunfManager.context.getSharedPreferences("funf_geofence", FunfManager.context.MODE_PRIVATE); + //TODO graceful error handling + for (JsonElement fence: fences) { + JsonObject fenceObject = fence.getAsJsonObject(); + Double fenceLatitude = fenceObject.get("latitude").getAsDouble(); + Double fenceLongitude = fenceObject.get("longitude").getAsDouble(); + Double fenceRadius = fenceObject.get("radius").getAsDouble(); - Long previousTimestamp = sharedPreferences.getLong(fenceID, 0); + Double distance = haversine(lat, lon, fenceLatitude, fenceLongitude); - if (timestamp > previousTimestamp) { - sharedPreferences.edit().putLong(fenceID, timestamp).commit(); + if (distance < fenceRadius) { + updateVisit(fenceLatitude, fenceLongitude, fenceRadius, timestamp); } + } } + } - private String fenceToString(Double fenceLatitude, Double fenceLongitude, Double fenceRadius) { - return IOUtil.md5(""+fenceLatitude + ";"+fenceLongitude+";"+fenceRadius); - } - - private boolean checkFences(Long timestamp) { - //TODO in the future this should keep the last 24h or so of the fence history so we can - //put data from the past + private void updateVisit(Double fenceLatitude, Double fenceLongitude, Double fenceRadius, Long timestamp) { + String fenceID = fenceToString(fenceLatitude, fenceLongitude, fenceRadius); + SharedPreferences sharedPreferences = FunfManager.context.getSharedPreferences("funf_geofence", FunfManager.context.MODE_PRIVATE); - if (fences.size() == 0) return true; + Long previousTimestamp = sharedPreferences.getLong(fenceID, 0); - for (JsonElement fence: fences) { - JsonObject fenceObject = fence.getAsJsonObject(); - Double fenceLatitude = fenceObject.get("latitude").getAsDouble(); - Double fenceLongitude = fenceObject.get("longitude").getAsDouble(); - Double fenceRadius = fenceObject.get("radius").getAsDouble(); + if (timestamp > previousTimestamp) { + sharedPreferences.edit().putLong(fenceID, timestamp).commit(); + } - String fenceID = fenceToString(fenceLatitude, fenceLongitude, fenceRadius); - SharedPreferences sharedPreferences = FunfManager.context.getSharedPreferences("funf_geofence", FunfManager.context.MODE_PRIVATE); - Long lastVisitTimestamp = sharedPreferences.getLong(fenceID, 0); + } - if ((timestamp < (lastVisitTimestamp + timeWindow*60*1000)) && (timestamp > (lastVisitTimestamp - timeWindow*60*1000)) ) return true; + private String fenceToString(Double fenceLatitude, Double fenceLongitude, Double fenceRadius) { + return IOUtil.md5(""+fenceLatitude + ";"+fenceLongitude+";"+fenceRadius); + } - } + private boolean checkFences(Long timestamp) { + //TODO in the future this should keep the last 24h or so of the fence history so we can + //put data from the past - return false; - } + if (fences.size() == 0) return true; - public Integer getVersion() { - return this.version; - } + for (JsonElement fence: fences) { + JsonObject fenceObject = fence.getAsJsonObject(); + Double fenceLatitude = fenceObject.get("latitude").getAsDouble(); + Double fenceLongitude = fenceObject.get("longitude").getAsDouble(); + Double fenceRadius = fenceObject.get("radius").getAsDouble(); - private Long timestampToMillis(Long timestamp) { - if (timestamp < 9999999999L) return timestamp*1000; - return timestamp; - } + String fenceID = fenceToString(fenceLatitude, fenceLongitude, fenceRadius); + SharedPreferences sharedPreferences = FunfManager.context.getSharedPreferences("funf_geofence", FunfManager.context.MODE_PRIVATE); + Long lastVisitTimestamp = sharedPreferences.getLong(fenceID, 0); - private Double haversine(double lat1, double lon1, double lat2, double lon2) { - Double R = 6372.8; // In kilometers - Double dLat = Math.toRadians(lat2 - lat1); - Double dLon = Math.toRadians(lon2 - lon1); - lat1 = Math.toRadians(lat1); - lat2 = Math.toRadians(lat2); + if ((timestamp < (lastVisitTimestamp + timeWindow*60*1000)) && (timestamp > (lastVisitTimestamp - timeWindow*60*1000)) ) return true; - Double a = Math.pow(Math.sin(dLat / 2),2) + Math.pow(Math.sin(dLon / 2),2) * Math.cos(lat1) * Math.cos(lat2); - Double c = 2 * Math.asin(Math.sqrt(a)); - return R * c * 1000; } - public List getFences() { - return this.fences; - } + return false; + } + + public Integer getVersion() { + return this.version; + } + + private Long timestampToMillis(Long timestamp) { + if (timestamp < 9999999999L) return timestamp*1000; + return timestamp; + } + + private Double haversine(double lat1, double lon1, double lat2, double lon2) { + Double R = 6372.8; // In kilometers + Double dLat = Math.toRadians(lat2 - lat1); + Double dLon = Math.toRadians(lon2 - lon1); + lat1 = Math.toRadians(lat1); + lat2 = Math.toRadians(lat2); + + Double a = Math.pow(Math.sin(dLat / 2),2) + Math.pow(Math.sin(dLon / 2),2) * Math.cos(lat1) * Math.cos(lat2); + Double c = 2 * Math.asin(Math.sqrt(a)); + return R * c * 1000; + } + + public List getFences() { + return this.fences; + } } diff --git a/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java b/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java index 1b4a173..7325376 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/pipeline/BasicPipeline.java @@ -105,6 +105,8 @@ public class BasicPipeline implements Pipeline, DataListener { private UploadService uploader; + public static BasicPipeline basicPipeline = null; + private Integer savingData = -1; private boolean enabled; @@ -204,6 +206,7 @@ protected void writeData(String name, IJsonObject data) { @Override public void onCreate(FunfManager manager) { + basicPipeline = this; if (archive == null) { archive = new DefaultArchive(manager, name); } @@ -259,7 +262,6 @@ public void onRun(String action, JsonElement config) { * @param config the configuration for the action */ protected void onBeforeRun(int action, JsonElement config) { - } /** @@ -395,4 +397,8 @@ public void onDataCompleted(IJsonObject probeConfig, JsonElement checkpoint) { public List getFences() { return this.geofence.getFences(); } + + public Geofencer getGeofence() { + return this.geofence; + } } diff --git a/funf_v4/src/main/java/edu/mit/media/funf/probe/Probe.java b/funf_v4/src/main/java/edu/mit/media/funf/probe/Probe.java index eab8ee4..a628219 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/probe/Probe.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/probe/Probe.java @@ -41,6 +41,7 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManager; +import android.util.Log; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -51,14 +52,17 @@ import edu.mit.media.funf.FunfManager; import edu.mit.media.funf.Schedule.DefaultSchedule; import edu.mit.media.funf.data.DataNormalizer; +import edu.mit.media.funf.data.Geofencer; import edu.mit.media.funf.json.BundleTypeAdapter; import edu.mit.media.funf.json.IJsonObject; import edu.mit.media.funf.json.JsonUtils; +import edu.mit.media.funf.pipeline.BasicPipeline; import edu.mit.media.funf.probe.builtin.ProbeKeys.BaseProbeKeys; import edu.mit.media.funf.security.HashUtil; import edu.mit.media.funf.security.HashUtil.HashingType; import edu.mit.media.funf.time.TimeUtil; import edu.mit.media.funf.util.LockUtil; +import edu.mit.media.funf.util.LogUtil; public interface Probe { @@ -577,12 +581,20 @@ private void ensureLooperThreadExists() { } } + private boolean shouldFire() { + Geofencer geofence = null; + if (BasicPipeline.basicPipeline != null) geofence = BasicPipeline.basicPipeline.getGeofence(); + if (geofence != null && !geofence.shouldFire(this.getClass().getName())) return false; + return true; + } + protected final void enable() { ensureLooperThreadExists(); handler.sendMessage(handler.obtainMessage(ENABLE_MESSAGE)); } protected final void start() { + if (!shouldFire()) return; ensureLooperThreadExists(); handler.sendMessage(handler.obtainMessage(START_MESSAGE)); } From 76f6a8ace3a0d54447e437ac63fd878a5239d261 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sun, 20 Dec 2015 09:43:23 -0800 Subject: [PATCH 57/61] Removed unused dependencies, woot --- funf_v4/build.gradle | 7 ------ .../mit/media/funf/storage/HttpArchive.java | 25 ++++++------------- 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/funf_v4/build.gradle b/funf_v4/build.gradle index cb52a1e..641e99d 100644 --- a/funf_v4/build.gradle +++ b/funf_v4/build.gradle @@ -25,13 +25,6 @@ android { dependencies { compile files('libs/gson-2.1.jar') - compile('org.apache.httpcomponents:httpmime:4.3.6') { - exclude module: 'httpclient' - } - compile 'org.apache.httpcomponents:httpclient-android:4.3.5' - compile ('org.apache.httpcomponents:httpcore:4.4.1'){ - exclude group: 'org.apache.httpcomponents', module: 'httpclient' - } } diff --git a/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java b/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java index 60370b3..e21ec08 100644 --- a/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java +++ b/funf_v4/src/main/java/edu/mit/media/funf/storage/HttpArchive.java @@ -23,35 +23,24 @@ */ package edu.mit.media.funf.storage; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; - import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.NetworkInfo.State; -import android.util.Base64; import android.util.Log; -import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; -import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.ContentType; import org.apache.http.entity.InputStreamEntity; -import org.apache.http.entity.mime.HttpMultipartMode; -import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.entity.mime.content.FileBody; import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.execchain.MainClientExec; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import edu.mit.media.funf.FunfManager; import edu.mit.media.funf.Schedule.DefaultSchedule; From 3fedc7026bfbaa7610bc332f4bc8db7c0c72c964 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sun, 20 Dec 2015 10:33:18 -0800 Subject: [PATCH 58/61] updated CHANGELOG --- CHANGELOG.md | 56 ++++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce1d044..4f058e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,46 +1,46 @@ # Change Log -## [Unreleased](https://github.com/OpenSensing/funf-v4/tree/HEAD) +## [Unreleased](https://github.com/OpenSensing/funf-core-android/tree/HEAD) -[Full Changelog](https://github.com/OpenSensing/funf-v4/compare/0.4.2...HEAD) +[Full Changelog](https://github.com/OpenSensing/funf-core-android/compare/0.4.3...HEAD) **Implemented enhancements:** -- Add stub for exclusion of fields to Wifi probe [\#12](https://github.com/OpenSensing/funf-v4/issues/12) -- Add stub for exclusion of fields to Bluetooth probe [\#11](https://github.com/OpenSensing/funf-v4/issues/11) -- Add LGPL license file [\#10](https://github.com/OpenSensing/funf-v4/issues/10) -- Include datapoint when Wifi scan starts, even if no devices are found [\#9](https://github.com/OpenSensing/funf-v4/issues/9) -- Include datapoint when Bluetooth scan starts, even if no devices are found [\#8](https://github.com/OpenSensing/funf-v4/issues/8) -- Change BT probe default duration to 60s [\#7](https://github.com/OpenSensing/funf-v4/issues/7) -- Change file uploader to HttpClient [\#6](https://github.com/OpenSensing/funf-v4/issues/6) -- Add changelog [\#4](https://github.com/OpenSensing/funf-v4/issues/4) -- Option to compress archives \(using gzip\) [\#1](https://github.com/OpenSensing/funf-v4/issues/1) +- Support for Bluetooth identifier [\#12](https://github.com/OpenSensing/funf-core-android/issues/12) +- Introduce JSON database file [\#11](https://github.com/OpenSensing/funf-core-android/issues/11) +- Improve HTTP get [\#10](https://github.com/OpenSensing/funf-core-android/issues/10) +- Communication of funf status [\#9](https://github.com/OpenSensing/funf-core-android/issues/9) +- Geofencing to limit data collection [\#8](https://github.com/OpenSensing/funf-core-android/issues/8) +- Bluetooth discoverability [\#1](https://github.com/OpenSensing/funf-core-android/issues/1) -**Closed issues:** +**Merged pull requests:** -- Generate archive file extension based on the compression/encryption [\#15](https://github.com/OpenSensing/funf-v4/issues/15) +- Json db [\#7](https://github.com/OpenSensing/funf-core-android/pull/7) ([h0pbeat](https://github.com/h0pbeat)) +- Geofencing [\#6](https://github.com/OpenSensing/funf-core-android/pull/6) ([h0pbeat](https://github.com/h0pbeat)) +- Bluetooth Probe can be now configured to keep phone discoverable [\#5](https://github.com/OpenSensing/funf-core-android/pull/5) ([h0pbeat](https://github.com/h0pbeat)) -**Merged pull requests:** +## [0.4.3](https://github.com/OpenSensing/funf-core-android/tree/0.4.3) (2015-10-16) +[Full Changelog](https://github.com/OpenSensing/funf-core-android/compare/0.4.2...0.4.3) + +## [0.4.2](https://github.com/OpenSensing/funf-core-android/tree/0.4.2) (2013-04-10) +[Full Changelog](https://github.com/OpenSensing/funf-core-android/compare/0.4.1...0.4.2) -- File uploader [\#14](https://github.com/OpenSensing/funf-v4/pull/14) ([h0pbeat](https://github.com/h0pbeat)) -- Added license file [\#13](https://github.com/OpenSensing/funf-v4/pull/13) ([h0pbeat](https://github.com/h0pbeat)) -- added changelog [\#5](https://github.com/OpenSensing/funf-v4/pull/5) ([h0pbeat](https://github.com/h0pbeat)) -- small cleaning and enhancements of the probes [\#3](https://github.com/OpenSensing/funf-v4/pull/3) ([h0pbeat](https://github.com/h0pbeat)) -- implemented gzip compression of the files, configurable in pipeline config [\#2](https://github.com/OpenSensing/funf-v4/pull/2) ([h0pbeat](https://github.com/h0pbeat)) +## [0.4.1](https://github.com/OpenSensing/funf-core-android/tree/0.4.1) (2013-04-06) +[Full Changelog](https://github.com/OpenSensing/funf-core-android/compare/0.4.0...0.4.1) -## [0.4.2](https://github.com/OpenSensing/funf-v4/tree/0.4.2) (2015-08-18) -[Full Changelog](https://github.com/OpenSensing/funf-v4/compare/0.3.1...0.4.2) +## [0.4.0](https://github.com/OpenSensing/funf-core-android/tree/0.4.0) (2013-03-27) +[Full Changelog](https://github.com/OpenSensing/funf-core-android/compare/0.3.1...0.4.0) -## [0.3.1](https://github.com/OpenSensing/funf-v4/tree/0.3.1) (2012-04-03) -[Full Changelog](https://github.com/OpenSensing/funf-v4/compare/0.3.0...0.3.1) +## [0.3.1](https://github.com/OpenSensing/funf-core-android/tree/0.3.1) (2012-04-03) +[Full Changelog](https://github.com/OpenSensing/funf-core-android/compare/0.3.0...0.3.1) -## [0.3.0](https://github.com/OpenSensing/funf-v4/tree/0.3.0) (2011-11-03) -[Full Changelog](https://github.com/OpenSensing/funf-v4/compare/0.2.1...0.3.0) +## [0.3.0](https://github.com/OpenSensing/funf-core-android/tree/0.3.0) (2011-11-03) +[Full Changelog](https://github.com/OpenSensing/funf-core-android/compare/0.2.1...0.3.0) -## [0.2.1](https://github.com/OpenSensing/funf-v4/tree/0.2.1) (2011-10-07) -[Full Changelog](https://github.com/OpenSensing/funf-v4/compare/0.2.0...0.2.1) +## [0.2.1](https://github.com/OpenSensing/funf-core-android/tree/0.2.1) (2011-10-07) +[Full Changelog](https://github.com/OpenSensing/funf-core-android/compare/0.2.0...0.2.1) -## [0.2.0](https://github.com/OpenSensing/funf-v4/tree/0.2.0) (2011-10-05) +## [0.2.0](https://github.com/OpenSensing/funf-core-android/tree/0.2.0) (2011-10-05) \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file From bff60cc09d4602f8bc698a44deee285ef6ae7c25 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sun, 20 Dec 2015 11:19:48 -0800 Subject: [PATCH 59/61] updated changelog --- CHANGELOG.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f058e6..4c20edf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,7 @@ # Change Log -## [Unreleased](https://github.com/OpenSensing/funf-core-android/tree/HEAD) - -[Full Changelog](https://github.com/OpenSensing/funf-core-android/compare/0.4.3...HEAD) +## [0.4.4](https://github.com/OpenSensing/funf-core-android/tree/0.4.4) (2015-12-20) +[Full Changelog](https://github.com/OpenSensing/funf-core-android/compare/0.4.3...0.4.4) **Implemented enhancements:** @@ -20,7 +19,10 @@ - Bluetooth Probe can be now configured to keep phone discoverable [\#5](https://github.com/OpenSensing/funf-core-android/pull/5) ([h0pbeat](https://github.com/h0pbeat)) ## [0.4.3](https://github.com/OpenSensing/funf-core-android/tree/0.4.3) (2015-10-16) -[Full Changelog](https://github.com/OpenSensing/funf-core-android/compare/0.4.2...0.4.3) +[Full Changelog](https://github.com/OpenSensing/funf-core-android/compare/v0.5.0RC1...0.4.3) + +## [v0.5.0RC1](https://github.com/OpenSensing/funf-core-android/tree/v0.5.0RC1) (2013-09-19) +[Full Changelog](https://github.com/OpenSensing/funf-core-android/compare/0.4.2...v0.5.0RC1) ## [0.4.2](https://github.com/OpenSensing/funf-core-android/tree/0.4.2) (2013-04-10) [Full Changelog](https://github.com/OpenSensing/funf-core-android/compare/0.4.1...0.4.2) From 6eedd9a6a10655f914abe7475f24b18a1a013103 Mon Sep 17 00:00:00 2001 From: h0pbeat Date: Sun, 20 Dec 2015 11:26:17 -0800 Subject: [PATCH 60/61] updated changelog --- CHANGELOG.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c20edf..735eae8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,30 @@ ## [0.4.3](https://github.com/OpenSensing/funf-core-android/tree/0.4.3) (2015-10-16) [Full Changelog](https://github.com/OpenSensing/funf-core-android/compare/v0.5.0RC1...0.4.3) +**Implemented enhancements:** + +- Add stub for exclusion of fields to Wifi probe [\#12](https://github.com/OpenSensing/funf-v4/issues/12) +- Add stub for exclusion of fields to Bluetooth probe [\#11](https://github.com/OpenSensing/funf-v4/issues/11) +- Add LGPL license file [\#10](https://github.com/OpenSensing/funf-v4/issues/10) +- Include datapoint when Wifi scan starts, even if no devices are found [\#9](https://github.com/OpenSensing/funf-v4/issues/9) +- Include datapoint when Bluetooth scan starts, even if no devices are found [\#8](https://github.com/OpenSensing/funf-v4/issues/8) +- Change BT probe default duration to 60s [\#7](https://github.com/OpenSensing/funf-v4/issues/7) +- Change file uploader to HttpClient [\#6](https://github.com/OpenSensing/funf-v4/issues/6) +- Add changelog [\#4](https://github.com/OpenSensing/funf-v4/issues/4) +- Option to compress archives \(using gzip\) [\#1](https://github.com/OpenSensing/funf-v4/issues/1) + +**Closed issues:** + +- Generate archive file extension based on the compression/encryption [\#15](https://github.com/OpenSensing/funf-v4/issues/15) + +**Merged pull requests:** + +- File uploader [\#14](https://github.com/OpenSensing/funf-v4/pull/14) ([h0pbeat](https://github.com/h0pbeat)) +- Added license file [\#13](https://github.com/OpenSensing/funf-v4/pull/13) ([h0pbeat](https://github.com/h0pbeat)) +- added changelog [\#5](https://github.com/OpenSensing/funf-v4/pull/5) ([h0pbeat](https://github.com/h0pbeat)) +- small cleaning and enhancements of the probes [\#3](https://github.com/OpenSensing/funf-v4/pull/3) ([h0pbeat](https://github.com/h0pbeat)) +- implemented gzip compression of the files, configurable in pipeline config [\#2](https://github.com/OpenSensing/funf-v4/pull/2) ([h0pbeat](https://github.com/h0pbeat)) + ## [v0.5.0RC1](https://github.com/OpenSensing/funf-core-android/tree/v0.5.0RC1) (2013-09-19) [Full Changelog](https://github.com/OpenSensing/funf-core-android/compare/0.4.2...v0.5.0RC1) @@ -45,4 +69,4 @@ ## [0.2.0](https://github.com/OpenSensing/funf-core-android/tree/0.2.0) (2011-10-05) -\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file +\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* From f2c00a29f1647572396505c7fbb115abb3f504e1 Mon Sep 17 00:00:00 2001 From: RaduGatej Date: Sun, 24 Apr 2016 22:49:23 +0200 Subject: [PATCH 61/61] Create README.md --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..077ea05 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# Funf Open Sensing Framework +Fork of the popular [Funf open sensing framework](https://github.com/funf-org/funf-core-android) which has been inactive for some years. +Currently developed for and used in data collection projects at Google and at the Technical University of Denmark. + +For details about architecture design, documentation as well as scripts for processing collected data, see the original repo's [wiki](https://github.com/funf-org/funf-core-android/wiki). + +# Improvements +Forked from Funf v0.4.2. See the [changelog](https://github.com/OpenSensing/funf-core-android/blob/master/CHANGELOG.md) for the full set of improvements over v0.4.2. Some of the more important ones include: +* Geofencing +* Gzipping of files before upload +* Token based authentication +* File storage in JSON format +* Gradle integration + +# How to build +Run ./gradlew assembleRelease in the project root to produce an .aar that you can use as a library in your Android app. + +# Usage +Funf is meant to be used as a dependency for another Android app that can provide user interaction and data collection configuration. See original project for a [tutorial](https://github.com/funf-org/funf-core-android/wiki/WifiScannerTutorial) on this. + + + +