diff --git a/.travis.yml b/.travis.yml
index c65a9c3..6d11e4c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,11 +3,11 @@ language: android
jdk: oraclejdk8
before_install:
- - wget http://services.gradle.org/distributions/gradle-2.14.1-bin.zip
- - unzip gradle-2.14.1-bin.zip
- - export GRADLE_HOME=$PWD/gradle-2.14.1
+ - wget http://services.gradle.org/distributions/gradle-3.3-bin.zip
+ - unzip gradle-3.3-bin.zip
+ - export GRADLE_HOME=$PWD/gradle-3.3
- export PATH=$GRADLE_HOME/bin:$PATH
- - ( sleep 5 && while [ 1 ]; do sleep 1; echo y; done ) | android update sdk -a --no-ui --filter tool,platform-tool,build-tools-25.0.0
+ - ( sleep 5 && while [ 1 ]; do sleep 1; echo y; done ) | android update sdk -a --no-ui --filter tool,platform-tool,build-tools-25.0.2
script:
- gradle assembleDebug
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 3b7277a..45147a5 100755
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,13 +1,18 @@
-
+
-
+
+
+
+
diff --git a/assets/html/passive_data_kit/generator_location_disclosure.html b/assets/html/passive_data_kit/generator_location_disclosure.html
new file mode 100755
index 0000000..583b4d7
--- /dev/null
+++ b/assets/html/passive_data_kit/generator_location_disclosure.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+ TODO: Write disclosure for use of location data...
+
+
+
+ Place completed file in assets/html/passive_data_kit/generator_location_disclosure.html
in your project to override this placeholder.
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 22ab1c0..7a80bca 100755
--- a/build.gradle
+++ b/build.gradle
@@ -9,7 +9,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.2.2'
+ classpath 'com.android.tools.build:gradle:2.3.0'
}
}
@@ -23,7 +23,7 @@ repositories {
android {
compileSdkVersion 25
- buildToolsVersion "25"
+ buildToolsVersion "25.0.2"
useLibrary 'org.apache.http.legacy'
@@ -51,25 +51,22 @@ android {
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
// testCompile 'junit:junit:4.12'
- compile 'com.android.support:appcompat-v7:25.0.1'
- compile 'com.android.support:recyclerview-v7:25.0.1'
- compile 'com.android.support:cardview-v7:25.0.1'
- compile 'com.google.android.gms:play-services-location:9.8.0'
- compile 'com.google.android.gms:play-services-maps:9.8.0'
- compile 'com.google.android.gms:play-services-nearby:9.8.0'
- compile 'com.google.android.gms:play-services-places:9.8.0'
- compile 'com.google.android.gms:play-services-awareness:9.8.0'
+ compile 'com.android.support:appcompat-v7:25.3.0'
+ compile 'com.android.support:recyclerview-v7:25.3.0'
+ compile 'com.android.support:cardview-v7:25.3.0'
+ compile 'com.google.android.gms:play-services-location:10.2.0'
+ compile 'com.google.android.gms:play-services-maps:10.2.0'
+ compile 'com.google.android.gms:play-services-nearby:10.2.0'
+ compile 'com.google.android.gms:play-services-places:10.2.0'
+ compile 'com.google.android.gms:play-services-awareness:10.2.0'
+ compile 'com.android.support:customtabs:23.3.0'
+ compile 'com.google.maps.android:android-maps-utils:0.4'
compile 'com.squareup.okhttp3:okhttp:3.2.0'
compile 'commons-io:commons-io:2.4'
compile 'commons-codec:commons-codec:1.10'
compile 'org.apache.commons:commons-lang3:3.4'
compile 'com.fasterxml.jackson.core:jackson-core:2.7.3'
- compile 'com.github.philjay:mpandroidchart:v3.0.0'
- compile 'com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-7'
-
- compile project(':libraries-base')
- compile project(':libraries-common')
- compile project(':libraries-controller')
+ compile 'com.github.philjay:mpandroidchart:v3.0.1'
}
buildTypes {
@@ -78,4 +75,4 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
-}
\ No newline at end of file
+}
diff --git a/gvr-android-sdk b/gvr-android-sdk
deleted file mode 160000
index 3360cbd..0000000
--- a/gvr-android-sdk
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 3360cbd21d8ba07c305fa379b905ff8c7632f6cc
diff --git a/res/drawable-hdpi/ic_button_disclosure_setting.png b/res/drawable-hdpi/ic_button_disclosure_setting.png
new file mode 100755
index 0000000..9628f97
Binary files /dev/null and b/res/drawable-hdpi/ic_button_disclosure_setting.png differ
diff --git a/res/drawable-hdpi/ic_pdk_diagnostic.png b/res/drawable-hdpi/ic_pdk_diagnostic.png
new file mode 100755
index 0000000..e80247a
Binary files /dev/null and b/res/drawable-hdpi/ic_pdk_diagnostic.png differ
diff --git a/res/drawable-hdpi/ic_text_messages.png b/res/drawable-hdpi/ic_text_messages.png
new file mode 100755
index 0000000..ef5d391
Binary files /dev/null and b/res/drawable-hdpi/ic_text_messages.png differ
diff --git a/res/drawable-hdpi/ic_withings_device.png b/res/drawable-hdpi/ic_withings_device.png
new file mode 100755
index 0000000..168df46
Binary files /dev/null and b/res/drawable-hdpi/ic_withings_device.png differ
diff --git a/res/drawable-mdpi/ic_button_disclosure_setting.png b/res/drawable-mdpi/ic_button_disclosure_setting.png
new file mode 100755
index 0000000..a252c98
Binary files /dev/null and b/res/drawable-mdpi/ic_button_disclosure_setting.png differ
diff --git a/res/drawable-mdpi/ic_pdk_diagnostic.png b/res/drawable-mdpi/ic_pdk_diagnostic.png
new file mode 100755
index 0000000..539cf8a
Binary files /dev/null and b/res/drawable-mdpi/ic_pdk_diagnostic.png differ
diff --git a/res/drawable-mdpi/ic_text_messages.png b/res/drawable-mdpi/ic_text_messages.png
new file mode 100755
index 0000000..cdabac0
Binary files /dev/null and b/res/drawable-mdpi/ic_text_messages.png differ
diff --git a/res/drawable-mdpi/ic_withings_device.png b/res/drawable-mdpi/ic_withings_device.png
new file mode 100755
index 0000000..5b184d8
Binary files /dev/null and b/res/drawable-mdpi/ic_withings_device.png differ
diff --git a/res/drawable-xhdpi/ic_button_disclosure_setting.png b/res/drawable-xhdpi/ic_button_disclosure_setting.png
new file mode 100755
index 0000000..c006523
Binary files /dev/null and b/res/drawable-xhdpi/ic_button_disclosure_setting.png differ
diff --git a/res/drawable-xhdpi/ic_pdk_diagnostic.png b/res/drawable-xhdpi/ic_pdk_diagnostic.png
new file mode 100755
index 0000000..c9ebaa0
Binary files /dev/null and b/res/drawable-xhdpi/ic_pdk_diagnostic.png differ
diff --git a/res/drawable-xhdpi/ic_text_messages.png b/res/drawable-xhdpi/ic_text_messages.png
new file mode 100755
index 0000000..a0a8ceb
Binary files /dev/null and b/res/drawable-xhdpi/ic_text_messages.png differ
diff --git a/res/drawable-xhdpi/ic_withings_device.png b/res/drawable-xhdpi/ic_withings_device.png
new file mode 100755
index 0000000..73aece2
Binary files /dev/null and b/res/drawable-xhdpi/ic_withings_device.png differ
diff --git a/res/drawable-xxhdpi/ic_button_disclosure_setting.png b/res/drawable-xxhdpi/ic_button_disclosure_setting.png
new file mode 100755
index 0000000..8310d1e
Binary files /dev/null and b/res/drawable-xxhdpi/ic_button_disclosure_setting.png differ
diff --git a/res/drawable-xxhdpi/ic_pdk_diagnostic.png b/res/drawable-xxhdpi/ic_pdk_diagnostic.png
new file mode 100755
index 0000000..62b411d
Binary files /dev/null and b/res/drawable-xxhdpi/ic_pdk_diagnostic.png differ
diff --git a/res/drawable-xxhdpi/ic_text_messages.png b/res/drawable-xxhdpi/ic_text_messages.png
new file mode 100755
index 0000000..31ceb29
Binary files /dev/null and b/res/drawable-xxhdpi/ic_text_messages.png differ
diff --git a/res/drawable-xxhdpi/ic_withings_device.png b/res/drawable-xxhdpi/ic_withings_device.png
new file mode 100755
index 0000000..5dd8a9d
Binary files /dev/null and b/res/drawable-xxhdpi/ic_withings_device.png differ
diff --git a/res/drawable-xxxhdpi/ic_button_disclosure_setting.png b/res/drawable-xxxhdpi/ic_button_disclosure_setting.png
new file mode 100755
index 0000000..b25919e
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_button_disclosure_setting.png differ
diff --git a/res/drawable-xxxhdpi/ic_pdk_diagnostic.png b/res/drawable-xxxhdpi/ic_pdk_diagnostic.png
new file mode 100755
index 0000000..ed16022
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_pdk_diagnostic.png differ
diff --git a/res/drawable-xxxhdpi/ic_text_messages.png b/res/drawable-xxxhdpi/ic_text_messages.png
new file mode 100755
index 0000000..352d5d5
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_text_messages.png differ
diff --git a/res/drawable-xxxhdpi/ic_withings_device.png b/res/drawable-xxxhdpi/ic_withings_device.png
new file mode 100755
index 0000000..2803e06
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_withings_device.png differ
diff --git a/res/drawable/ic_location_heatmap_marker.xml b/res/drawable/ic_location_heatmap_marker.xml
new file mode 100755
index 0000000..1df13e4
--- /dev/null
+++ b/res/drawable/ic_location_heatmap_marker.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/res/layout/card_diagnostic_action.xml b/res/layout/card_diagnostic_action.xml
index a3274e1..acfd651 100755
--- a/res/layout/card_diagnostic_action.xml
+++ b/res/layout/card_diagnostic_action.xml
@@ -1,20 +1,41 @@
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
-
+
+ android:layout_height="wrap_content">
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/card_generator_location_google.xml b/res/layout/card_generator_location_google.xml
index 716536d..979c932 100755
--- a/res/layout/card_generator_location_google.xml
+++ b/res/layout/card_generator_location_google.xml
@@ -2,6 +2,7 @@
@@ -42,24 +43,29 @@
android:textColor="@android:color/white"
android:layout_marginRight="8dp"/>
-
+ android:layout_height="200dp">
-
+
-
+ android:layout_gravity="bottom|right"
+ android:layout_marginRight="6dp"
+ android:layout_marginBottom="104dp" />
+
\ No newline at end of file
diff --git a/res/layout/card_generator_phone_calls.xml b/res/layout/card_generator_phone_calls.xml
index f66de04..2a2725b 100755
--- a/res/layout/card_generator_phone_calls.xml
+++ b/res/layout/card_generator_phone_calls.xml
@@ -41,7 +41,8 @@
android:textColor="@android:color/white"
android:layout_marginRight="8dp"/>
-
@@ -150,6 +151,12 @@
android:layout_marginBottom="8dp"/>
+
\ No newline at end of file
diff --git a/res/layout/card_generator_screen_state.xml b/res/layout/card_generator_screen_state.xml
index f09f805..7af3c2d 100755
--- a/res/layout/card_generator_screen_state.xml
+++ b/res/layout/card_generator_screen_state.xml
@@ -41,7 +41,8 @@
android:textColor="@android:color/white"
android:layout_marginRight="8dp"/>
-
@@ -138,6 +139,12 @@
+
\ No newline at end of file
diff --git a/res/layout/card_generator_text_messages.xml b/res/layout/card_generator_text_messages.xml
new file mode 100755
index 0000000..03c022d
--- /dev/null
+++ b/res/layout/card_generator_text_messages.xml
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/card_generator_withings_device.xml b/res/layout/card_generator_withings_device.xml
new file mode 100755
index 0000000..5eb8c89
--- /dev/null
+++ b/res/layout/card_generator_withings_device.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/dialog_location_randomized.xml b/res/layout/dialog_location_randomized.xml
new file mode 100755
index 0000000..58965d7
--- /dev/null
+++ b/res/layout/dialog_location_randomized.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/res/layout/dialog_location_user.xml b/res/layout/dialog_location_user.xml
new file mode 100755
index 0000000..17bbf31
--- /dev/null
+++ b/res/layout/dialog_location_user.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/res/layout/layout_data_disclosure_detail_pdk.xml b/res/layout/layout_data_disclosure_detail_pdk.xml
new file mode 100755
index 0000000..9cd2e4e
--- /dev/null
+++ b/res/layout/layout_data_disclosure_detail_pdk.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/res/layout/layout_data_disclosure_pdk.xml b/res/layout/layout_data_disclosure_pdk.xml
new file mode 100755
index 0000000..cc763d0
--- /dev/null
+++ b/res/layout/layout_data_disclosure_pdk.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/layout_diagnostics_pdk.xml b/res/layout/layout_diagnostics_pdk.xml
index e71a783..379c418 100755
--- a/res/layout/layout_diagnostics_pdk.xml
+++ b/res/layout/layout_diagnostics_pdk.xml
@@ -1,7 +1,16 @@
-
+ android:layout_height="match_parent">
+
+
+
diff --git a/res/layout/row_disclosure_action_pdk.xml b/res/layout/row_disclosure_action_pdk.xml
new file mode 100755
index 0000000..a5250e3
--- /dev/null
+++ b/res/layout/row_disclosure_action_pdk.xml
@@ -0,0 +1,19 @@
+
+
+
+
diff --git a/res/layout/row_disclosure_location_accuracy_pdk.xml b/res/layout/row_disclosure_location_accuracy_pdk.xml
new file mode 100755
index 0000000..78cd4cc
--- /dev/null
+++ b/res/layout/row_disclosure_location_accuracy_pdk.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
diff --git a/res/layout/row_generator_disclosure_generic.xml b/res/layout/row_generator_disclosure_generic.xml
new file mode 100755
index 0000000..200f44e
--- /dev/null
+++ b/res/layout/row_generator_disclosure_generic.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/menu/diagnostic_menu.xml b/res/menu/diagnostic_menu.xml
new file mode 100755
index 0000000..65f394a
--- /dev/null
+++ b/res/menu/diagnostic_menu.xml
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/res/values/databases.xml b/res/values/databases.xml
new file mode 100755
index 0000000..02d98e8
--- /dev/null
+++ b/res/values/databases.xml
@@ -0,0 +1,26 @@
+
+ CREATE TABLE metadata(key TEXT, value TEXT, last_updated INTEGER);
+
+
+ CREATE TABLE history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, latitude REAL, longitude REAL, altitude REAL, bearing REAL, speed REAL, provider TEXT, location_timestamp INTEGER, accuracy REAL);
+
+
+ CREATE TABLE history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, state TEXT);
+
+
+ CREATE TABLE history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, duration INTEGER, call_type TEXT, number TEXT, post_dial_digits TEXT, via_number TEXT, is_new INTEGER, pulled_externally INTEGER, country_iso TEXT, data_usage INTEGER, geocoded_location TEXT, is_video INTEGER, presentation TEXT, is_read INTEGER);
+
+
+ CREATE TABLE history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, direction TEXT, length INTEGER, body TEXT, number_name TEXT, number TEXT);
+
+
+ CREATE TABLE history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, event_name TEXT, event_details TEXT);
+
+
+ CREATE TABLE activity_measure_history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, date_start INTEGER, timezone TEXT, steps REAL, distance REAL, active_calories REAL, total_calories REAL, elevation REAL, soft_activity_duration REAL, moderate_activity_duration REAL, intense_activity_duration REAL);
+ CREATE TABLE body_measure_history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, measure_date INTEGER, measure_status TEXT, measure_category TEXT, measure_type TEXT, measure_value REAL);
+ CREATE TABLE intraday_activity_history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, activity_start REAL, activity_duration REAL, calories REAL, distance REAL, elevation_climbed REAL, steps REAL, swim_strokes REAL, pool_laps REAL);
+ CREATE TABLE sleep_measure_history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, start_date REAL, end_date REAL, state TEXT, measurement_device TEXT);
+ CREATE TABLE sleep_summary_history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, start_date REAL, end_date REAL, timezone TEXT, measurement_device TEXT, wake_duration REAL, light_sleep_duration REAL, deep_sleep_duration REAL, rem_sleep_duration REAL, wake_count INTEGER, to_sleep_duration REAL, to_wake_duration REAL);
+ CREATE TABLE workout_history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, start_date REAL, end_date REAL, measurement_device TEXT, workout_category TEXT, caolories REAL, effective_duration REAL, raw_data TEXT);
+
diff --git a/res/values/diagnostics.xml b/res/values/diagnostics.xml
index c0adb6a..026fce6 100755
--- a/res/values/diagnostics.xml
+++ b/res/values/diagnostics.xml
@@ -1,7 +1,23 @@
- Unable to connect to Microsoft Band.
+ Microsoft Band App Not Installed
+ Unable to connect to Microsoft Band. Please install the app and try again.
+
+ Microsoft Band Permission Required
Please grant the app sensor access on the Microsoft Band.
- Please grant the app permission to access location services on this device.
+
+ Activity Recognition Permission Required
Please grant the app permission to recognize your activity using data from this device.
+
+ Call Log Permission Required
Please grant the app permission to access the phone call logs on this device.
+
+ Text Messaging Permission Required
+ Please all the app permission to access your text messages to gather and report your messaging activity statistics.
+
+ Location Permission Required
+ Please grant the app permission to access location services on this device.
+
+ Access to Withing Account Required
+ Permission is required to fetch data from the Withings server. Please grant this application the requested permissions.
+
diff --git a/res/values/generators.xml b/res/values/generators.xml
index 166f4b6..934f041 100755
--- a/res/values/generators.xml
+++ b/res/values/generators.xml
@@ -3,9 +3,11 @@
- com.audacious_software.passive_data_kit.generators.device.Location
- com.audacious_software.passive_data_kit.generators.device.ScreenState
- com.audacious_software.passive_data_kit.generators.communication.PhoneCalls
- - com.audacious_software.passive_data_kit.generators.wearables.MicrosoftBand
- - com.audacious_software.passive_data_kit.generators.services.GoogleAwareness
- - com.audacious_software.passive_data_kit.generators.vr.DaydreamViewController
+ - com.audacious_software.passive_data_kit.generators.communication.TextMessages
+
+
+
+ - com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice
@@ -43,9 +45,59 @@
Device Location
Coordinates: %1$.4f, %2$.4f
+ Location Accuracy
+ Tap here to update the accuracy of your data.
+
+ Best Accuracy
+ Best Available Data From Location Hardware
+
+ Locally Randomized
+ Location Combined With Random Noise
+
+ User Provided
+ Static Location Provided By User
+
+ Disabled
+ App Does Not Use Location Data
+
+ Best Accuracy
+ This app will use the location hardware on this device to obtain the most accurate location readings.
+
+ Location Disabled
+ This app will not use your location, but use an placeholder instead.
+
+ Locally Randomized
+ Please enter the random distance (kilometers) to use to obfuscate your exact location:
+
+ User Provided
+ Please enter a postal code or city and province to use as your location:
+ Unable to find your location. Please enter your location another way and try again.
Screen State
+ On
+ Off
+ Doze
+ Unknown
+ Legend:
+ No screen state changes have been observed yet.
+
+
+ SMS Text Messages
+ Incoming
+ Outgoing
+ Other
+
+ #3F51B5
+ #4CAF50
+ #9E9E9E
+
+ Latest Text Message
+ Length
+ Direction
+ %1$d chars.
+
+ No text messages have been sent or received on this device.
Phone Calls
@@ -64,4 +116,8 @@
Direction
%1$.2f min.
+ No phone calls have been made or received on this device.
+
+
+ Withings Device
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 61e918e..73b220f 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -4,6 +4,7 @@
0.0.1
Unknown Version
Data Stream
+ Continue
- %d generator
@@ -14,10 +15,17 @@
Today
- On
- Off
- Doze
- Unknown
- Legend:
+ Never
+ Diagnostics
+ Diagnostics (%d)
+ Diagnostics
+
+ The app is set up correctly.\n\nNo further actions are needed.
+
+ Passive Data Disclosure
+ Select a disclosure item for more information…
+
+ Data Collection Description
+ Tap here to learn how this app uses your data.
diff --git a/src/com/audacious_software/passive_data_kit/Logger.java b/src/com/audacious_software/passive_data_kit/Logger.java
index b315574..9ea620e 100755
--- a/src/com/audacious_software/passive_data_kit/Logger.java
+++ b/src/com/audacious_software/passive_data_kit/Logger.java
@@ -2,7 +2,10 @@
import android.content.Context;
+import com.audacious_software.passive_data_kit.generators.diagnostics.AppEvent;
+
import java.util.HashMap;
+import java.util.Map;
/**
* Created by cjkarr on 4/3/2016.
@@ -10,8 +13,12 @@
public class Logger {
private Context mContext = null;
- public void log(String event, HashMap details) {
- // TODO
+ public void log(String event, Map details) {
+ if (details == null) {
+ details = new HashMap<>();
+ }
+
+ AppEvent.getInstance(this.mContext).logEvent(event, details);
}
private static class LoggerHolder {
diff --git a/src/com/audacious_software/passive_data_kit/PassiveDataKit.java b/src/com/audacious_software/passive_data_kit/PassiveDataKit.java
index 8a8efc1..285f97e 100755
--- a/src/com/audacious_software/passive_data_kit/PassiveDataKit.java
+++ b/src/com/audacious_software/passive_data_kit/PassiveDataKit.java
@@ -5,9 +5,13 @@
import com.audacious_software.passive_data_kit.diagnostics.DiagnosticAction;
import com.audacious_software.passive_data_kit.generators.Generators;
+import java.io.File;
import java.util.ArrayList;
public class PassiveDataKit {
+ private static final String STORAGE_PATH = "passive-data-kit";
+ private static final String GENERATORS_PATH = "generators";
+
private Context mContext = null;
private boolean mStarted = false;
@@ -30,6 +34,17 @@ public static ArrayList diagnostics(Context context)
return actions;
}
+ public static File getGeneratorsStorage(Context context) {
+ File path = new File(context.getFilesDir(), PassiveDataKit.STORAGE_PATH);
+ path = new File(path, PassiveDataKit.GENERATORS_PATH);
+
+ if (path.exists() == false) {
+ path.mkdirs();
+ }
+
+ return path;
+ }
+
private static class PassiveDataKitHolder {
public static PassiveDataKit instance = new PassiveDataKit();
}
diff --git a/src/com/audacious_software/passive_data_kit/PhoneUtililties.java b/src/com/audacious_software/passive_data_kit/PhoneUtililties.java
new file mode 100755
index 0000000..0213eb1
--- /dev/null
+++ b/src/com/audacious_software/passive_data_kit/PhoneUtililties.java
@@ -0,0 +1,30 @@
+package com.audacious_software.passive_data_kit;
+
+import android.content.Context;
+
+/**
+ * Created by cjkarr on 12/13/2016.
+ */
+
+public class PhoneUtililties {
+ public static String normalizedPhoneNumber(String phoneNumber)
+ {
+ if (phoneNumber == null) {
+ return null;
+ }
+
+ phoneNumber = phoneNumber.replaceAll("[^\\d.]", "");
+
+ while (phoneNumber.length() > 10) {
+ phoneNumber = phoneNumber.substring(1);
+ }
+
+ while (phoneNumber.length() < 10) {
+ phoneNumber += "0";
+ }
+
+
+ return phoneNumber;
+ }
+}
+
diff --git a/src/com/audacious_software/passive_data_kit/activities/DataDisclosureActivity.java b/src/com/audacious_software/passive_data_kit/activities/DataDisclosureActivity.java
new file mode 100755
index 0000000..70d41f3
--- /dev/null
+++ b/src/com/audacious_software/passive_data_kit/activities/DataDisclosureActivity.java
@@ -0,0 +1,44 @@
+package com.audacious_software.passive_data_kit.activities;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.MenuItem;
+import android.widget.FrameLayout;
+
+import com.audacious_software.passive_data_kit.activities.generators.GeneratorsAdapter;
+import com.audacious_software.pdk.passivedatakit.R;
+
+public class DataDisclosureActivity extends AppCompatActivity {
+ private GeneratorsAdapter mAdapter = null;
+
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ this.setContentView(R.layout.layout_data_disclosure_pdk);
+ this.getSupportActionBar().setTitle(R.string.title_data_disclosure);
+ this.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ FrameLayout dataView = (FrameLayout) this.findViewById(R.id.data_view);
+
+ this.mAdapter = new GeneratorsAdapter();
+ this.mAdapter.setContext(this.getApplicationContext());
+ this.mAdapter.setDataView(dataView);
+
+ RecyclerView listView = (RecyclerView) this.findViewById(R.id.list_view);
+
+ listView.setLayoutManager(new LinearLayoutManager(this));
+
+ listView.setAdapter(this.mAdapter);
+ }
+
+ public boolean onOptionsItemSelected(MenuItem item)
+ {
+ if (item.getItemId() == android.R.id.home)
+ {
+ this.finish();
+ }
+
+ return true;
+ }
+}
diff --git a/src/com/audacious_software/passive_data_kit/activities/DataDisclosureDetailActivity.java b/src/com/audacious_software/passive_data_kit/activities/DataDisclosureDetailActivity.java
new file mode 100755
index 0000000..aaf79bb
--- /dev/null
+++ b/src/com/audacious_software/passive_data_kit/activities/DataDisclosureDetailActivity.java
@@ -0,0 +1,123 @@
+package com.audacious_software.passive_data_kit.activities;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.FrameLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.audacious_software.passive_data_kit.Logger;
+import com.audacious_software.passive_data_kit.generators.Generator;
+import com.audacious_software.pdk.passivedatakit.R;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.List;
+
+public class DataDisclosureDetailActivity extends AppCompatActivity {
+ public static class Action {
+ public String title;
+ public String subtitle;
+
+ public View view;
+ }
+
+ public static final String GENERATOR_CLASS_NAME = "com.audacious_software.passive_data_kit.activities.DataDisclosureDetailActivity.GENERATOR_CLASS_NAME";
+
+ private Class extends Generator> mGeneratorClass = null;
+
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final DataDisclosureDetailActivity me = this;
+
+ this.setContentView(R.layout.layout_data_disclosure_detail_pdk);
+ this.getSupportActionBar().setSubtitle(R.string.title_data_disclosure);
+ this.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ try {
+ this.mGeneratorClass = (Class extends Generator>) Class.forName(this.getIntent().getStringExtra(DataDisclosureDetailActivity.GENERATOR_CLASS_NAME));
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+
+ if (this.mGeneratorClass != null) {
+ try {
+ Method getGeneratorTitle = this.mGeneratorClass.getDeclaredMethod("getGeneratorTitle", Context.class);
+
+ String title = (String) getGeneratorTitle.invoke(null, this);
+ this.getSupportActionBar().setTitle(title);
+
+ Method getDisclosureActions = this.mGeneratorClass.getDeclaredMethod("getDisclosureActions", Context.class);
+
+ final List actions = (List) getDisclosureActions.invoke(null, this);
+
+ ListView actionsList = (ListView) this.findViewById(R.id.disclosure_actions);
+ ArrayAdapter adapter = new ArrayAdapter(this, R.layout.row_disclosure_action_pdk, actions) {
+ public View getView (int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(me).inflate(R.layout.row_disclosure_action_pdk, null);
+ }
+
+ Action action = actions.get(position);
+
+ TextView title = (TextView) convertView.findViewById(R.id.action_title);
+ title.setText(action.title);
+
+ TextView description = (TextView) convertView.findViewById(R.id.action_description);
+ description.setText(action.subtitle);
+
+ return convertView;
+ }
+ };
+
+ actionsList.setAdapter(adapter);
+
+ actionsList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> adapterView, View view, int position, long l) {
+ Log.e("PDK", "TAPPED: " + position);
+
+ Action action = actions.get(position);
+
+ FrameLayout dataView = (FrameLayout) me.findViewById(R.id.data_view);
+ dataView.removeAllViews();
+
+ if (action.view != null) {
+ FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+
+ action.view.setLayoutParams(params);
+
+ dataView.addView(action.view);
+ }
+ }
+ });
+
+ actionsList.performItemClick(null, 0, 0);
+ } catch (NoSuchMethodException e1) {
+ Logger.getInstance(this).logThrowable(e1);
+ } catch (InvocationTargetException e1) {
+ Logger.getInstance(this).logThrowable(e1);
+ } catch (IllegalAccessException e1) {
+ Logger.getInstance(this).logThrowable(e1);
+ }
+ }
+ }
+
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ this.finish();
+ }
+
+ return true;
+ }
+
+}
diff --git a/src/com/audacious_software/passive_data_kit/activities/DataStreamActivity.java b/src/com/audacious_software/passive_data_kit/activities/DataStreamActivity.java
index 184173a..3e80bb0 100755
--- a/src/com/audacious_software/passive_data_kit/activities/DataStreamActivity.java
+++ b/src/com/audacious_software/passive_data_kit/activities/DataStreamActivity.java
@@ -6,21 +6,14 @@
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-import com.audacious_software.passive_data_kit.PassiveDataKit;
import com.audacious_software.passive_data_kit.activities.generators.DataPointsAdapter;
-import com.audacious_software.passive_data_kit.diagnostics.DiagnosticAction;
import com.audacious_software.passive_data_kit.generators.Generators;
import com.audacious_software.pdk.passivedatakit.R;
import java.util.ArrayList;
-public class DataStreamActivity extends AppCompatActivity implements Generators.NewDataPointListener {
+public class DataStreamActivity extends AppCompatActivity implements Generators.GeneratorUpdatedListener {
private DataPointsAdapter mAdapter = null;
protected void onCreate(Bundle savedInstanceState) {
@@ -30,6 +23,7 @@ protected void onCreate(Bundle savedInstanceState) {
this.getSupportActionBar().setSubtitle(this.getResources().getQuantityString(R.plurals.activity_data_stream_subtitle, 0, 0));
this.mAdapter = new DataPointsAdapter();
+ this.mAdapter.setContext(this.getApplicationContext());
RecyclerView listView = (RecyclerView) this.findViewById(R.id.list_view);
@@ -41,30 +35,37 @@ protected void onCreate(Bundle savedInstanceState) {
protected void onResume() {
super.onResume();
- Generators.getInstance(this).addNewDataPointListener(this);
+ Generators.getInstance(this).addNewGeneratorUpdatedListener(this);
- Generators.getInstance(this).broadcastLatestDataPoints();
+ final int count = this.mAdapter.getItemCount();
+
+ Handler mainHandler = new Handler(Looper.getMainLooper());
+
+ final DataStreamActivity me = this;
+
+ mainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ me.getSupportActionBar().setSubtitle(me.getResources().getQuantityString(R.plurals.activity_data_stream_subtitle, count, count));
+ }
+ });
}
protected void onPause() {
super.onPause();
- Generators.getInstance(this).removeNewDataPointListener(this);
+ Generators.getInstance(this).removeGeneratorUpdatedListener(this);
}
@Override
- public void onNewDataPoint(String identifier, Bundle data) {
- this.mAdapter.updateDataPoint(identifier, data);
-
- final int count = this.mAdapter.getItemCount();
-
- Handler mainHandler = new Handler(Looper.getMainLooper());
-
+ public void onGeneratorUpdated(String identifier, long timestamp, Bundle data) {
final DataStreamActivity me = this;
- mainHandler.post(new Runnable() {
+ this.runOnUiThread(new Runnable() {
@Override
public void run() {
+ me.mAdapter.notifyDataSetChanged();
+ int count = me.mAdapter.getItemCount();
me.getSupportActionBar().setSubtitle(me.getResources().getQuantityString(R.plurals.activity_data_stream_subtitle, count, count));
}
});
diff --git a/src/com/audacious_software/passive_data_kit/activities/DiagnosticsActivity.java b/src/com/audacious_software/passive_data_kit/activities/DiagnosticsActivity.java
index ce8a3d5..851103d 100755
--- a/src/com/audacious_software/passive_data_kit/activities/DiagnosticsActivity.java
+++ b/src/com/audacious_software/passive_data_kit/activities/DiagnosticsActivity.java
@@ -1,11 +1,14 @@
package com.audacious_software.passive_data_kit.activities;
+import android.app.Activity;
+import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
-import android.util.Log;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
@@ -17,6 +20,38 @@
import java.util.ArrayList;
public class DiagnosticsActivity extends AppCompatActivity {
+ public static void setUpDiagnositicsItem(Activity activity, Menu menu, boolean showAction) {
+ final ArrayList actions = PassiveDataKit.diagnostics(activity);
+
+ MenuItem item = menu.add(Menu.NONE, R.id.action_diagnostics, 0, activity.getString(R.string.action_diagnostics));
+
+ if (actions.size() > 0 && showAction) {
+ item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+
+ item.setIcon(R.drawable.ic_pdk_diagnostic);
+ item.setTitle("" + actions.size());
+ } else {
+ item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+
+ if (actions.size() > 0) {
+ item.setTitle(activity.getString(R.string.action_diagnostics_incomplete, actions.size()));
+ }
+ }
+ }
+
+ public static boolean diagnosticItemSelected(Activity activity, MenuItem item) {
+ int id = item.getItemId();
+
+ if (id == R.id.action_diagnostics) {
+ Intent diagnosticsIntent = new Intent(activity, DiagnosticsActivity.class);
+ activity.startActivity(diagnosticsIntent);
+
+ return true;
+ }
+
+ return false;
+ }
+
private class DiagnosticViewHolder extends RecyclerView.ViewHolder {
private View mView = null;
@@ -44,7 +79,10 @@ public void bindDiagnosticAction(DiagnosticAction action)
{
this.mAction = action;
- TextView message = (TextView) this.mView.findViewById(R.id.message_action);
+ TextView title = (TextView) this.mView.findViewById(R.id.action_title);
+ title.setText(action.getTitle());
+
+ TextView message = (TextView) this.mView.findViewById(R.id.action_message);
message.setText(action.getMessage());
}
}
@@ -52,34 +90,46 @@ public void bindDiagnosticAction(DiagnosticAction action)
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.layout_diagnostics_pdk);
+ this.getSupportActionBar().setTitle(R.string.title_pdk_diagnostics);
+ }
- final ArrayList actions = PassiveDataKit.diagnostics(this);
+ protected void onResume() {
+ super.onResume();
- Log.e("PDK", "ACTIONS COUNT: " + actions.size());
+ final ArrayList actions = PassiveDataKit.diagnostics(this);
RecyclerView listView = (RecyclerView) this.findViewById(R.id.list_view);
+ TextView emptyMessage = (TextView) this.findViewById(R.id.message_no_diagnostics);
- listView.setLayoutManager(new LinearLayoutManager(this));
+ if (actions.size() > 0) {
+ listView.setVisibility(View.VISIBLE);
+ emptyMessage.setVisibility(View.GONE);
- listView.setAdapter(new RecyclerView.Adapter() {
- @Override
- public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_diagnostic_action, parent, false);
+ listView.setLayoutManager(new LinearLayoutManager(this));
- return new DiagnosticViewHolder(v);
- }
+ listView.setAdapter(new RecyclerView.Adapter() {
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_diagnostic_action, parent, false);
- @Override
- public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
- DiagnosticViewHolder diagHolder = (DiagnosticViewHolder) holder;
+ return new DiagnosticViewHolder(v);
+ }
- diagHolder.bindDiagnosticAction(actions.get(position));
- }
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ DiagnosticViewHolder diagHolder = (DiagnosticViewHolder) holder;
- @Override
- public int getItemCount() {
- return actions.size();
- }
- });
+ diagHolder.bindDiagnosticAction(actions.get(position));
+ }
+
+ @Override
+ public int getItemCount() {
+ return actions.size();
+ }
+ });
+ } else {
+ listView.setVisibility(View.GONE);
+ emptyMessage.setVisibility(View.VISIBLE);
+ }
}
}
diff --git a/src/com/audacious_software/passive_data_kit/activities/OAuthResponseActivity.java b/src/com/audacious_software/passive_data_kit/activities/OAuthResponseActivity.java
new file mode 100755
index 0000000..3edcfa3
--- /dev/null
+++ b/src/com/audacious_software/passive_data_kit/activities/OAuthResponseActivity.java
@@ -0,0 +1,32 @@
+package com.audacious_software.passive_data_kit.activities;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+
+import com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice;
+import com.audacious_software.pdk.passivedatakit.R;
+
+/**
+ * Created by cjkarr on 3/18/2017.
+ */
+
+public class OAuthResponseActivity extends AppCompatActivity {
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ protected void onResume() {
+ super.onResume();
+
+ Uri u = this.getIntent().getData();
+
+ Log.e("PDK", "CALLBACK FROM OAUTH: " + u);
+ Log.e("PDK", "PATH: " + u.getPath());
+
+ if (u.getPath().startsWith(WithingsDevice.API_OAUTH_CALLBACK_PATH)) {
+ WithingsDevice.getInstance(this).finishAuthentication(u);
+ }
+ }
+}
diff --git a/src/com/audacious_software/passive_data_kit/activities/PdkActivity.java b/src/com/audacious_software/passive_data_kit/activities/PdkActivity.java
index d8ef541..b0a4a1e 100755
--- a/src/com/audacious_software/passive_data_kit/activities/PdkActivity.java
+++ b/src/com/audacious_software/passive_data_kit/activities/PdkActivity.java
@@ -1,6 +1,5 @@
package com.audacious_software.passive_data_kit.activities;
-import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
public abstract class PdkActivity extends AppCompatActivity
diff --git a/src/com/audacious_software/passive_data_kit/activities/generators/DataPointsAdapter.java b/src/com/audacious_software/passive_data_kit/activities/generators/DataPointsAdapter.java
index b711460..fd1679d 100755
--- a/src/com/audacious_software/passive_data_kit/activities/generators/DataPointsAdapter.java
+++ b/src/com/audacious_software/passive_data_kit/activities/generators/DataPointsAdapter.java
@@ -1,9 +1,6 @@
package com.audacious_software.passive_data_kit.activities.generators;
import android.content.Context;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
@@ -14,15 +11,17 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
public class DataPointsAdapter extends RecyclerView.Adapter {
- private ArrayList mDataPoints = new ArrayList<>();
+ private Context mContext = null;
@Override
public DataPointViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- Class extends Generator> generatorClass = Generators.getInstance(null).fetchCustomViewClass(viewType);
+ Class extends Generator> generatorClass = Generators.getInstance(this.mContext).fetchCustomViewClass(viewType);
try {
Method fetchView = generatorClass.getDeclaredMethod("fetchView", ViewGroup.class);
@@ -51,20 +50,24 @@ public DataPointViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
}
@Override
- public void onBindViewHolder(DataPointViewHolder holder, int position) {
- Bundle dataPoint = this.mDataPoints.get(position);
- Class extends Generator> generatorClass = Generators.getInstance(null).fetchCustomViewClass(dataPoint.getBundle(Generator.PDK_METADATA).getString(Generator.IDENTIFIER));
+ public void onBindViewHolder(final DataPointViewHolder holder, int position) {
+ List> activeGenerators = Generators.getInstance(holder.itemView.getContext()).activeGenerators();
+
+ this.sortGenerators(this.mContext, activeGenerators);
+
+ Class extends Generator> generatorClass = activeGenerators.get(position);
try {
- Method bindViewHolder = generatorClass.getDeclaredMethod("bindViewHolder", DataPointViewHolder.class, Bundle.class);
- bindViewHolder.invoke(null, holder, dataPoint);
+ Method bindViewHolder = generatorClass.getDeclaredMethod("bindViewHolder", DataPointViewHolder.class);
+ bindViewHolder.invoke(null, holder);
} catch (Exception e) {
e.printStackTrace();
try {
generatorClass = Generator.class;
- Method bindViewHolder = generatorClass.getDeclaredMethod("bindViewHolder", DataPointViewHolder.class, Bundle.class);
- bindViewHolder.invoke(null, holder, dataPoint);
+ Method bindViewHolder = generatorClass.getDeclaredMethod("bindViewHolder", DataPointViewHolder.class);
+
+ bindViewHolder.invoke(null, holder);
} catch (NoSuchMethodException e1) {
Logger.getInstance(holder.itemView.getContext()).logThrowable(e1);
} catch (InvocationTargetException e1) {
@@ -77,51 +80,63 @@ public void onBindViewHolder(DataPointViewHolder holder, int position) {
@Override
public int getItemCount() {
- return this.mDataPoints.size();
+ return Generators.getInstance(null).activeGenerators().size();
}
- public int getItemViewType (int position) {
- Bundle dataPoint = this.mDataPoints.get(position);
- Class extends Generator> generatorClass = Generators.getInstance(null).fetchCustomViewClass(dataPoint.getBundle(Generator.PDK_METADATA).getString(Generator.IDENTIFIER));
-
- return generatorClass.hashCode();
- }
-
- public void updateDataPoint(String identifier, Bundle data) {
- ArrayList toDelete = new ArrayList<>();
-
- Handler mainHandler = new Handler(Looper.getMainLooper());
- final DataPointsAdapter me = this;
-
- for (Bundle bundle : this.mDataPoints) {
- if (bundle.getBundle(Generator.PDK_METADATA).getString(Generator.IDENTIFIER).equals(identifier)) {
- toDelete.add(bundle);
+ private void sortGenerators(final Context context, List> generators) {
+ Collections.sort(generators, new Comparator>() {
+ @Override
+ public int compare(Class extends Generator> one, Class extends Generator> two) {
+ long oneUpdated = 0;
+
+ try {
+ Method oneGenerated = one.getDeclaredMethod("latestPointGenerated", Context.class);
+
+ oneUpdated = (long) oneGenerated.invoke(null, context);
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+
+ long twoUpdated = 0;
+
+ try {
+ Method twoGenerated = two.getDeclaredMethod("latestPointGenerated", Context.class);
+
+ twoUpdated = (long) twoGenerated.invoke(null, context);
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+
+ if (oneUpdated < twoUpdated) {
+ return 1;
+ } else if (oneUpdated > twoUpdated) {
+ return -1;
+ }
+
+ return 0;
}
- }
-
- Collections.reverse(toDelete);
+ });
+ }
- for (Bundle delete : toDelete) {
- final int position = this.mDataPoints.indexOf(delete);
+ public int getItemViewType (int position) {
+ List> activeGenerators = Generators.getInstance(this.mContext).activeGenerators();
- this.mDataPoints.remove(position);
+ this.sortGenerators(this.mContext, activeGenerators);
-// mainHandler.post(new Runnable() {
-// @Override
-// public void run() {
-// me.notifyItemRemoved(position);
-// }
-// });
- }
+ Class extends Generator> generatorClass = activeGenerators.get(position);
- this.mDataPoints.add(0, data);
+ return generatorClass.hashCode();
+ }
- mainHandler.post(new Runnable() {
- @Override
- public void run() {
-// me.notifyItemInserted(0);
- me.notifyDataSetChanged();
- }
- });
+ public void setContext(Context context) {
+ this.mContext = context;
}
}
diff --git a/src/com/audacious_software/passive_data_kit/activities/generators/GeneratorViewHolder.java b/src/com/audacious_software/passive_data_kit/activities/generators/GeneratorViewHolder.java
new file mode 100755
index 0000000..5726eb2
--- /dev/null
+++ b/src/com/audacious_software/passive_data_kit/activities/generators/GeneratorViewHolder.java
@@ -0,0 +1,10 @@
+package com.audacious_software.passive_data_kit.activities.generators;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+public class GeneratorViewHolder extends RecyclerView.ViewHolder {
+ public GeneratorViewHolder(View itemView) {
+ super(itemView);
+ }
+}
diff --git a/src/com/audacious_software/passive_data_kit/activities/generators/GeneratorsAdapter.java b/src/com/audacious_software/passive_data_kit/activities/generators/GeneratorsAdapter.java
new file mode 100755
index 0000000..adf3618
--- /dev/null
+++ b/src/com/audacious_software/passive_data_kit/activities/generators/GeneratorsAdapter.java
@@ -0,0 +1,129 @@
+package com.audacious_software.passive_data_kit.activities.generators;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import com.audacious_software.passive_data_kit.Logger;
+import com.audacious_software.passive_data_kit.activities.DataDisclosureDetailActivity;
+import com.audacious_software.passive_data_kit.generators.Generator;
+import com.audacious_software.passive_data_kit.generators.Generators;
+import com.audacious_software.pdk.passivedatakit.R;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class GeneratorsAdapter extends RecyclerView.Adapter {
+ private Context mContext = null;
+ private FrameLayout mDataView = null;
+
+ @Override
+ public GeneratorViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = Generator.fetchDisclosureView(parent);
+
+ return new GeneratorViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(final GeneratorViewHolder holder, int position) {
+ final GeneratorsAdapter me = this;
+
+ List> activeGenerators = Generators.getInstance(holder.itemView.getContext()).activeGenerators();
+
+ this.sortGenerators(this.mContext, activeGenerators);
+
+ Class extends Generator> generatorClass = activeGenerators.get(position);
+
+ Log.e("PDK", "GENERATOR CLASS: " + generatorClass);
+
+ try {
+ Method bindViewHolder = generatorClass.getDeclaredMethod("bindDisclosureViewHolder", GeneratorViewHolder.class);
+ bindViewHolder.invoke(null, holder);
+ } catch (Exception e) {
+// e.printStackTrace();
+ try {
+ generatorClass = Generator.class;
+
+ Method bindViewHolder = generatorClass.getDeclaredMethod("bindDisclosureViewHolder", GeneratorViewHolder.class);
+
+ bindViewHolder.invoke(null, holder);
+ } catch (NoSuchMethodException e1) {
+ Logger.getInstance(holder.itemView.getContext()).logThrowable(e1);
+ } catch (InvocationTargetException e1) {
+ Logger.getInstance(holder.itemView.getContext()).logThrowable(e1);
+ } catch (IllegalAccessException e1) {
+ Logger.getInstance(holder.itemView.getContext()).logThrowable(e1);
+ }
+ }
+
+ final Class extends Generator> finalClass = generatorClass;
+
+ holder.itemView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ me.mDataView.removeAllViews();
+
+ try {
+ Method bindViewHolder = finalClass.getDeclaredMethod("getDisclosureDataView", GeneratorViewHolder.class);
+
+ View dataView = (View) bindViewHolder.invoke(null, holder);
+ me.mDataView.addView(dataView);
+ } catch (NoSuchMethodException e1) {
+ Logger.getInstance(holder.itemView.getContext()).logThrowable(e1);
+ } catch (InvocationTargetException e1) {
+ Logger.getInstance(holder.itemView.getContext()).logThrowable(e1);
+ } catch (IllegalAccessException e1) {
+ Logger.getInstance(holder.itemView.getContext()).logThrowable(e1);
+ }
+ }
+ });
+
+ ImageView settingsButton = (ImageView) holder.itemView.findViewById(R.id.button_disclosure_item);
+
+ settingsButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(holder.itemView.getContext(), DataDisclosureDetailActivity.class);
+ intent.putExtra(DataDisclosureDetailActivity.GENERATOR_CLASS_NAME, finalClass.getCanonicalName());
+
+ holder.itemView.getContext().startActivity(intent);
+ }
+ });
+
+ }
+
+ @Override
+ public int getItemCount() {
+ return Generators.getInstance(null).activeGenerators().size();
+ }
+
+ private void sortGenerators(final Context context, List> generators) {
+ Collections.sort(generators, new Comparator>() {
+ @Override
+ public int compare(Class extends Generator> one, Class extends Generator> two) {
+ return one.getName().compareTo(two.getName());
+ }
+ });
+ }
+
+ public int getItemViewType (int position) {
+ return 0;
+ }
+
+ public void setContext(Context context) {
+ this.mContext = context;
+ }
+
+ public void setDataView(FrameLayout dataView) {
+ this.mDataView = dataView;
+ }
+}
diff --git a/src/com/audacious_software/passive_data_kit/diagnostics/DiagnosticAction.java b/src/com/audacious_software/passive_data_kit/diagnostics/DiagnosticAction.java
index c3869ff..d466198 100755
--- a/src/com/audacious_software/passive_data_kit/diagnostics/DiagnosticAction.java
+++ b/src/com/audacious_software/passive_data_kit/diagnostics/DiagnosticAction.java
@@ -8,8 +8,10 @@
public class DiagnosticAction {
private String mMessage = null;
private Runnable mAction = null;
+ private String mTitle = null;
- public DiagnosticAction(String message, Runnable action) {
+ public DiagnosticAction(String title, String message, Runnable action) {
+ this.mTitle = title;
this.mMessage = message;
this.mAction = action;
}
@@ -25,4 +27,8 @@ public void run() {
public String getMessage() {
return this.mMessage;
}
+
+ public String getTitle() {
+ return this.mTitle;
+ }
}
diff --git a/src/com/audacious_software/passive_data_kit/generators/Generator.java b/src/com/audacious_software/passive_data_kit/generators/Generator.java
index dedb648..66563c0 100755
--- a/src/com/audacious_software/passive_data_kit/generators/Generator.java
+++ b/src/com/audacious_software/passive_data_kit/generators/Generator.java
@@ -1,6 +1,10 @@
package com.audacious_software.passive_data_kit.generators;
+import android.content.ContentValues;
import android.content.Context;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -8,11 +12,13 @@
import android.widget.TextView;
import com.audacious_software.passive_data_kit.activities.generators.DataPointViewHolder;
+import com.audacious_software.passive_data_kit.activities.generators.GeneratorViewHolder;
import com.audacious_software.pdk.passivedatakit.R;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
+import java.util.List;
@SuppressWarnings("unused")
public abstract class Generator
@@ -25,6 +31,15 @@ public abstract class Generator
public static final String MEDIA_ATTACHMENT_KEY = "attachment";
public static final String MEDIA_CONTENT_TYPE_KEY = "attachment-type";
public static final String MEDIA_ATTACHMENT_GUID_KEY = "attachment-guid";
+ public static final String LATITUDE = "latitude";
+ public static final String LONGITUDE = "longitude";
+
+ private static final String TABLE_SQLITE_MASTER = "sqlite_master";
+
+ private static final String TABLE_METADATA = "metadata";
+ private static String TABLE_METADATA_LAST_UPDATED = "last_updated";
+ private static String TABLE_METADATA_KEY = "key";
+ private static String TABLE_METADATA_VALUE = "value";
protected Context mContext = null;
@@ -41,10 +56,6 @@ public static void stop(Context context) {
// Do nothing - override in subclasses.
}
- public static void broadcastLatestDataPoint(Context context) {
- // Do nothing - override in subclasses.
- }
-
public static boolean isEnabled(Context context)
{
return false;
@@ -55,12 +66,32 @@ public static boolean isRunning(Context context)
return false;
}
+ public static long latestPointGenerated(Context context) {
+ return 0;
+ }
+
public static View fetchView(ViewGroup parent) {
return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_generic, parent, false);
}
- public static void bindViewHolder(DataPointViewHolder holder, Bundle dataPoint) {
- String identifier = dataPoint.getBundle(Generator.PDK_METADATA).getString(Generator.IDENTIFIER);
+ public static void bindViewHolder(DataPointViewHolder holder) {
+ Class currentClass = new Object() { }.getClass().getEnclosingClass();
+
+ String identifier = currentClass.getCanonicalName();
+
+ TextView generatorLabel = (TextView) holder.itemView.findViewById(R.id.label_generator);
+
+ generatorLabel.setText(identifier);
+ }
+
+ public static View fetchDisclosureView(ViewGroup parent) {
+ return LayoutInflater.from(parent.getContext()).inflate(R.layout.row_generator_disclosure_generic, parent, false);
+ }
+
+ public static void bindDisclosureViewHolder(GeneratorViewHolder holder) {
+ Class currentClass = new Object() { }.getClass().getEnclosingClass();
+
+ String identifier = currentClass.getCanonicalName();
TextView generatorLabel = (TextView) holder.itemView.findViewById(R.id.label_generator);
@@ -87,4 +118,68 @@ public static String formatTimestamp(Context context, double timestamp) {
return context.getString(R.string.format_full_timestamp_pdk, date, time);
}
+
+ public abstract List fetchPayloads();
+
+ public Cursor queryHistory(String[] cols, String where, String[] args, String orderBy) {
+ Cursor c = new MatrixCursor(cols);
+
+ return c;
+ }
+
+ protected int getDatabaseVersion(SQLiteDatabase db) {
+ String where = "type = ? AND name = ?";
+ String[] args = { "table", Generator.TABLE_METADATA };
+
+ Cursor c = db.query(Generator.TABLE_SQLITE_MASTER, null, where, args, null, null, null);
+
+ if (c.getCount() > 0) {
+ // Do nothing - table exists...
+ } else {
+ db.execSQL(this.mContext.getString(R.string.pdk_generator_create_version_table));
+ }
+
+ c.close();
+
+ String versionWhere = Generator.TABLE_METADATA_KEY + " = ?";
+ String[] versionArgs = { "version" };
+
+ c = db.query(Generator.TABLE_METADATA, null, versionWhere, versionArgs, null, null, Generator.TABLE_METADATA_LAST_UPDATED + " DESC");
+
+ int version = 0;
+
+ if (c.moveToNext()) {
+ version = Integer.parseInt(c.getString(c.getColumnIndex(Generator.TABLE_METADATA_VALUE)));
+ }
+
+ c.close();
+
+ return version;
+ }
+
+ protected void setDatabaseVersion(SQLiteDatabase db, int newVersion) {
+ boolean keyExists = false;
+
+ String versionWhere = Generator.TABLE_METADATA_KEY + " = ?";
+ String[] versionArgs = { "version" };
+
+ Cursor c = db.query(Generator.TABLE_METADATA, null, versionWhere, versionArgs, null, null, Generator.TABLE_METADATA_LAST_UPDATED + " DESC");
+
+ if (c.getCount() > 0) {
+ keyExists = true;
+ }
+
+ c.close();
+
+ ContentValues values = new ContentValues();
+ values.put(Generator.TABLE_METADATA_KEY, "version");
+ values.put(Generator.TABLE_METADATA_VALUE, "" + newVersion);
+ values.put(Generator.TABLE_METADATA_LAST_UPDATED, System.currentTimeMillis());
+
+ if (keyExists) {
+ db.update(Generator.TABLE_METADATA, values, versionWhere, versionArgs);
+ } else {
+ db.insert(Generator.TABLE_METADATA, null, values);
+ }
+ }
}
diff --git a/src/com/audacious_software/passive_data_kit/generators/Generators.java b/src/com/audacious_software/passive_data_kit/generators/Generators.java
index fb9e412..14a4d26 100755
--- a/src/com/audacious_software/passive_data_kit/generators/Generators.java
+++ b/src/com/audacious_software/passive_data_kit/generators/Generators.java
@@ -11,6 +11,7 @@
import com.audacious_software.passive_data_kit.Logger;
import com.audacious_software.passive_data_kit.diagnostics.DiagnosticAction;
+import com.audacious_software.passive_data_kit.generators.diagnostics.AppEvent;
import com.audacious_software.pdk.passivedatakit.R;
import java.lang.reflect.InvocationTargetException;
@@ -19,22 +20,26 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
public class Generators {
private Context mContext = null;
private boolean mStarted = false;
+
private ArrayList mGenerators = new ArrayList<>();
private HashSet mActiveGenerators = new HashSet<>();
private SharedPreferences mSharedPreferences = null;
- private HashSet mNewDataPointListeners = new HashSet<>();
private HashMap> mGeneratorMap = new HashMap<>();
private SparseArray> mViewTypeMap = new SparseArray<>();
+ private HashSet mGeneratorUpdatedListeners = new HashSet<>();
public void start() {
if (!this.mStarted)
{
this.mGenerators.clear();
+ this.mGenerators.add(AppEvent.class.getCanonicalName());
+
for (String className : this.mContext.getResources().getStringArray(R.array.pdk_available_generators))
{
this.mGenerators.add(className);
@@ -148,27 +153,11 @@ public ArrayList diagnostics() {
return actions;
}
- public void transmitData(String identifier, Bundle data) {
- double now = (double) System.currentTimeMillis();
- now = now / 1000; // Convert to seconds...
-
- Bundle metadata = new Bundle();
- metadata.putString(Generator.IDENTIFIER, identifier);
- metadata.putDouble(Generator.TIMESTAMP, now);
- metadata.putString(Generator.GENERATOR, this.getGeneratorFullName(identifier));
- metadata.putString(Generator.SOURCE, this.getSource());
- data.putBundle(Generator.PDK_METADATA, metadata);
-
- for (Generators.NewDataPointListener listener : this.mNewDataPointListeners) {
- listener.onNewDataPoint(identifier, data);
- }
- }
-
- private String getSource() {
+ public String getSource() {
return "unknown-user-please-set-me";
}
- private String getGeneratorFullName(String identifier) {
+ public String getGeneratorFullName(String identifier) {
String pdkName = this.mContext.getString(R.string.pdk_name);
String pdkVersion = this.mContext.getString(R.string.pdk_version);
String appName = this.mContext.getString(this.mContext.getApplicationInfo().labelRes);
@@ -186,10 +175,6 @@ private String getGeneratorFullName(String identifier) {
return identifier + ": " + appName + "/" + version + " " + pdkName + "/" + pdkVersion;
}
- public void removeNewDataPointListener(Generators.NewDataPointListener listener) {
- this.mNewDataPointListeners.remove(listener);
- }
-
public void registerCustomViewClass(String identifier, Class extends Generator> generatorClass) {
this.mGeneratorMap.put(identifier, generatorClass);
this.mViewTypeMap.put(generatorClass.hashCode(), generatorClass);
@@ -215,29 +200,59 @@ public Class extends Generator> fetchCustomViewClass(int viewType) {
return generatorClass;
}
- public void broadcastLatestDataPoints() {
- for (String className : this.mGenerators)
- {
- try {
- Class extends Generator> generatorClass = (Class) Class.forName(className);
-
- Log.e("PDK", "CLASS " + generatorClass);
+ public Generator getGenerator(String className) {
+ Log.e("BB", "GENERATOR FIND START");
+ for (String name : this.mActiveGenerators) {
+ Log.e("BB", "GENERATOR NAME: " + name);
+ }
+ Log.e("BB", "GENERATOR FIND END");
- if (generatorClass != null) {
- Method broadcast = generatorClass.getDeclaredMethod("broadcastLatestDataPoint", Context.class);
+ if (this.mActiveGenerators.contains(className)) {
+ try {
+ Class probeClass = (Class) Class.forName(className);
- broadcast.invoke(null, this.mContext);
- }
+ Method getInstance = probeClass.getDeclaredMethod("getInstance", Context.class);
+ return (Generator) getInstance.invoke(null, this.mContext);
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
} catch (NoSuchMethodException e) {
-
- } catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ }
+
+ return null;
+ }
+
+ public List> activeGenerators() {
+ ArrayList> active = new ArrayList<>();
+
+ for (String className : this.mActiveGenerators) {
+ try {
+ active.add((Class extends Generator>) Class.forName(className));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
+
+ return active;
+ }
+
+ public void notifyGeneratorUpdated(String identifier, long timestamp, Bundle bundle) {
+ for (GeneratorUpdatedListener listener : this.mGeneratorUpdatedListeners) {
+ listener.onGeneratorUpdated(identifier, timestamp, bundle);
+ }
+ }
+
+ public void notifyGeneratorUpdated(String identifier, Bundle bundle) {
+ long timestamp = System.currentTimeMillis();
+
+ for (GeneratorUpdatedListener listener : this.mGeneratorUpdatedListeners) {
+ listener.onGeneratorUpdated(identifier, timestamp, bundle);
+ }
}
private static class GeneratorsHolder {
@@ -257,11 +272,15 @@ private void setContext(Context context) {
this.mContext = context.getApplicationContext();
}
- public void addNewDataPointListener(Generators.NewDataPointListener listener) {
- this.mNewDataPointListeners.add(listener);
+ public void addNewGeneratorUpdatedListener(Generators.GeneratorUpdatedListener listener) {
+ this.mGeneratorUpdatedListeners.add(listener);
+ }
+
+ public void removeGeneratorUpdatedListener(Generators.GeneratorUpdatedListener listener) {
+ this.mGeneratorUpdatedListeners.remove(listener);
}
- public interface NewDataPointListener {
- void onNewDataPoint(String identifier, Bundle data);
+ public interface GeneratorUpdatedListener {
+ void onGeneratorUpdated(String identifier, long timestamp, Bundle data);
}
}
diff --git a/src/com/audacious_software/passive_data_kit/generators/communication/PhoneCalls.java b/src/com/audacious_software/passive_data_kit/generators/communication/PhoneCalls.java
index 4294ab8..cea19b6 100755
--- a/src/com/audacious_software/passive_data_kit/generators/communication/PhoneCalls.java
+++ b/src/com/audacious_software/passive_data_kit/generators/communication/PhoneCalls.java
@@ -1,11 +1,13 @@
package com.audacious_software.passive_data_kit.generators.communication;
import android.Manifest;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
@@ -20,6 +22,7 @@
import android.view.ViewGroup;
import android.widget.TextView;
+import com.audacious_software.passive_data_kit.PassiveDataKit;
import com.audacious_software.passive_data_kit.activities.generators.DataPointViewHolder;
import com.audacious_software.passive_data_kit.activities.generators.RequestPermissionActivity;
import com.audacious_software.passive_data_kit.diagnostics.DiagnosticAction;
@@ -27,19 +30,17 @@
import com.audacious_software.passive_data_kit.generators.Generators;
import com.audacious_software.pdk.passivedatakit.R;
import com.github.mikephil.charting.charts.PieChart;
-import com.github.mikephil.charting.components.Description;
-import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.PieData;
import com.github.mikephil.charting.data.PieDataSet;
import com.github.mikephil.charting.data.PieEntry;
import com.github.mikephil.charting.formatter.IValueFormatter;
-import com.github.mikephil.charting.utils.ColorTemplate;
import com.github.mikephil.charting.utils.ViewPortHandler;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
+import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@@ -50,21 +51,6 @@ public class PhoneCalls extends Generator {
private static final String ENABLED = "com.audacious_software.passive_data_kit.generators.communication.PhoneCalls.ENABLED";
private static final boolean ENABLED_DEFAULT = true;
- private static final String SAMPLE_INTERVAL = "com.audacious_software.passive_data_kit.generators.communication.PhoneCalls.SAMPLE_INTERVAL";
- private static final long SAMPLE_INTERVAL_DEFAULT = 30000; // 300000;
-
- private static final String LAST_INCOMING_COUNT = "com.audacious_software.passive_data_kit.generators.communication.PhoneCalls.LAST_INCOMING_COUNT";
- private static final String LAST_OUTGOING_COUNT = "com.audacious_software.passive_data_kit.generators.communication.PhoneCalls.LAST_OUTGOING_COUNT";
- private static final String LAST_MISSED_COUNT = "com.audacious_software.passive_data_kit.generators.communication.PhoneCalls.LAST_MISSED_COUNT";
- private static final String LAST_TOTAL_COUNT = "com.audacious_software.passive_data_kit.generators.communication.PhoneCalls.LAST_TOTAL_COUNT";
-
- private static final String LAST_CALL_TIMESTAMP = "com.audacious_software.passive_data_kit.generators.communication.PhoneCalls.LAST_CALL_TIMESTAMP";
- private static final String LAST_CALL_DURATION = "com.audacious_software.passive_data_kit.generators.communication.PhoneCalls.LAST_CALL_DURATION";
- private static final String LAST_CALL_TYPE = "com.audacious_software.passive_data_kit.generators.communication.PhoneCalls.LAST_CALL_TYPE";
-
- private static final String LAST_SAMPLE = "com.audacious_software.passive_data_kit.generators.communication.PhoneCalls.LAST_SAMPLE";
- private static final long LAST_SAMPLE_DEFAULT = 0;
-
private static final String CALL_DATE_KEY = "call_timestamp";
private static final String CALL_DURATION_KEY = "duration";
private static final String CALL_IS_NEW_KEY = "is_new";
@@ -94,9 +80,33 @@ public class PhoneCalls extends Generator {
private static final String CALL_PRESENTATION_PAYPHONE = "payphone";
private static final String CALL_PRESENTATION_UNKNOWN = "unknown";
+ private static int DATABASE_VERSION = 2;
+
+ private static final String TABLE_HISTORY = "history";
+ private static final String HISTORY_OBSERVED = "observed";
+ private static final String HISTORY_DURATION = "duration";
+ private static final String HISTORY_NUMBER = "number";
+ private static final String HISTORY_IS_NEW = "is_new";
+ private static final String HISTORY_PULLED_EXTERNALLY = "pulled_externally";
+ private static final String HISTORY_POST_DIAL_DIGITS = "post_dial_digits";
+ private static final String HISTORY_COUNTRY_ISO = "country_iso";
+ private static final String HISTORY_DATA_USAGE = "data_usage";
+ private static final String HISTORY_GEOCODED_LOCATION = "geocoded_location";
+ private static final String HISTORY_VIDEO = "is_video";
+ private static final String HISTORY_VIA_NUMBER = "via_number";
+ private static final String HISTORY_PRESENTATION = "presentation";
+ private static final String HISTORY_IS_READ = "is_read";
+ private static final String HISTORY_CALL_TYPE = "call_type";
+
private static PhoneCalls sInstance = null;
private Handler mHandler = null;
+ private Context mContext = null;
+
+ private static final String DATABASE_PATH = "pdk-phone-calls.sqlite";
+
+ private SQLiteDatabase mDatabase = null;
+ private long mSampleInterval = 60000;
public static PhoneCalls getInstance(Context context) {
if (PhoneCalls.sInstance == null) {
@@ -108,6 +118,8 @@ public static PhoneCalls getInstance(Context context) {
public PhoneCalls(Context context) {
super(context);
+
+ this.mContext = context.getApplicationContext();
}
public static void start(final Context context) {
@@ -115,12 +127,14 @@ public static void start(final Context context) {
}
private void startGenerator() {
- Log.e("PDK", "START PHONE CALL GENERATOR");
-
final PhoneCalls me = this;
if (this.mHandler != null) {
- this.mHandler.getLooper().quitSafely();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ this.mHandler.getLooper().quitSafely();
+ } else {
+ this.mHandler.getLooper().quit();
+ }
this.mHandler = null;
}
@@ -128,9 +142,7 @@ private void startGenerator() {
final Runnable checkLogs = new Runnable() {
@Override
public void run() {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(me.mContext);
- SharedPreferences.Editor e = prefs.edit();
-
+ Log.e("PDK", "CHECK PHONE LOGS");
boolean approved = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@@ -143,156 +155,172 @@ public void run() {
Log.e("PDK", "TODO: Fetch Call Logs...");
- long now = System.currentTimeMillis();
-
if (approved) {
- long totalIncoming = prefs.getLong(PhoneCalls.LAST_INCOMING_COUNT, 0);
- long totalOutgoing = prefs.getLong(PhoneCalls.LAST_OUTGOING_COUNT, 0);
- long totalMissed = prefs.getLong(PhoneCalls.LAST_MISSED_COUNT, 0);
- long total = prefs.getLong(PhoneCalls.LAST_TOTAL_COUNT, 0);
+ long lastObserved = 0;
- long lastSample = prefs.getLong(PhoneCalls.LAST_SAMPLE, PhoneCalls.LAST_SAMPLE_DEFAULT);
+ Cursor lastCursor = me.mDatabase.query(PhoneCalls.TABLE_HISTORY, null, null, null, null, null, PhoneCalls.HISTORY_OBSERVED + " DESC");
- String where = CallLog.Calls.DATE + " > ?";
- String[] args = {"" + lastSample};
+ if (lastCursor.moveToNext()) {
+ lastObserved = lastCursor.getLong(lastCursor.getColumnIndex(PhoneCalls.HISTORY_OBSERVED));
+ }
- long latestCallTimestamp = -1;
- String latestCallType = null;
- long latestCallDuration = -1;
+ lastCursor.close();
- Cursor c = me.mContext.getContentResolver().query(CallLog.Calls.CONTENT_URI, null, where, args, CallLog.Calls.DATE);
+ String where = CallLog.Calls.DATE + " > ?";
+ String[] args = {"" + lastObserved};
- while (c.moveToNext()) {
- Bundle bundle = new Bundle();
+ Cursor c = me.mContext.getContentResolver().query(CallLog.Calls.CONTENT_URI, null, where, args, CallLog.Calls.DATE);
- bundle.putLong(PhoneCalls.CALL_DATE_KEY, c.getLong(c.getColumnIndex(CallLog.Calls.DATE)));
- bundle.putLong(PhoneCalls.CALL_DURATION_KEY, c.getLong(c.getColumnIndex(CallLog.Calls.DURATION)));
- bundle.putString(PhoneCalls.CALL_NUMBER_KEY, c.getString(c.getColumnIndex(CallLog.Calls.NUMBER)));
- bundle.putBoolean(PhoneCalls.CALL_IS_NEW_KEY, (c.getInt(c.getColumnIndex(CallLog.Calls.NEW)) != 0));
+ if (c != null) {
+ while (c.moveToNext()) {
+ ContentValues values = new ContentValues();
+ values.put(PhoneCalls.HISTORY_OBSERVED, c.getLong(c.getColumnIndex(CallLog.Calls.DATE)));
+ values.put(PhoneCalls.HISTORY_DURATION, c.getLong(c.getColumnIndex(CallLog.Calls.DURATION)));
+ values.put(PhoneCalls.HISTORY_NUMBER, c.getLong(c.getColumnIndex(CallLog.Calls.DURATION)));
+ values.put(PhoneCalls.HISTORY_IS_NEW, (c.getInt(c.getColumnIndex(CallLog.Calls.NEW)) != 0));
- int features = 0;
+ Bundle bundle = new Bundle();
+ bundle.putLong(PhoneCalls.CALL_DATE_KEY, c.getLong(c.getColumnIndex(CallLog.Calls.DATE)));
+ bundle.putLong(PhoneCalls.CALL_DURATION_KEY, c.getLong(c.getColumnIndex(CallLog.Calls.DURATION)));
+ bundle.putString(PhoneCalls.CALL_NUMBER_KEY, c.getString(c.getColumnIndex(CallLog.Calls.NUMBER)));
+ bundle.putBoolean(PhoneCalls.CALL_IS_NEW_KEY, (c.getInt(c.getColumnIndex(CallLog.Calls.NEW)) != 0));
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- features = c.getInt(c.getColumnIndex(CallLog.Calls.FEATURES));
- }
+ int features = 0;
- int typeInt = c.getInt(c.getColumnIndex(CallLog.Calls.TYPE));
- String type = PhoneCalls.CALL_TYPE_UNKNOWN;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ features = c.getInt(c.getColumnIndex(CallLog.Calls.FEATURES));
+ }
- switch(Build.VERSION.SDK_INT) {
- case 25:
- if (typeInt == CallLog.Calls.ANSWERED_EXTERNALLY_TYPE) {
- type = PhoneCalls.CALL_TYPE_ANSWERED_EXTERNALLY;
- }
+ int typeInt = c.getInt(c.getColumnIndex(CallLog.Calls.TYPE));
+ String type = PhoneCalls.CALL_TYPE_UNKNOWN;
+
+ switch (Build.VERSION.SDK_INT) {
+ case 25:
+ if (typeInt == CallLog.Calls.ANSWERED_EXTERNALLY_TYPE) {
+ type = PhoneCalls.CALL_TYPE_ANSWERED_EXTERNALLY;
+ }
+
+ bundle.putBoolean(PhoneCalls.CALL_PULLED_EXTERNALLY_KEY, ((features & CallLog.Calls.FEATURES_PULLED_EXTERNALLY) == CallLog.Calls.FEATURES_PULLED_EXTERNALLY));
+
+ values.put(PhoneCalls.HISTORY_PULLED_EXTERNALLY, ((features & CallLog.Calls.FEATURES_PULLED_EXTERNALLY) == CallLog.Calls.FEATURES_PULLED_EXTERNALLY));
+ case 24:
+ if (typeInt == CallLog.Calls.REJECTED_TYPE) {
+ type = PhoneCalls.CALL_TYPE_REJECTED;
+ } else if (typeInt == CallLog.Calls.BLOCKED_TYPE) {
+ type = PhoneCalls.CALL_TYPE_BLOCKED;
+ }
+
+ bundle.putString(PhoneCalls.CALL_POST_DIAL_DIGITS_KEY, c.getString(c.getColumnIndex(CallLog.Calls.POST_DIAL_DIGITS)));
+ bundle.putString(PhoneCalls.CALL_VIA_NUMBER_KEY, c.getString(c.getColumnIndex(CallLog.Calls.VIA_NUMBER)));
+
+ values.put(PhoneCalls.HISTORY_POST_DIAL_DIGITS, c.getString(c.getColumnIndex(CallLog.Calls.POST_DIAL_DIGITS)));
+ values.put(PhoneCalls.HISTORY_VIA_NUMBER, c.getString(c.getColumnIndex(CallLog.Calls.VIA_NUMBER)));
+ case 21:
+ if (typeInt == CallLog.Calls.VOICEMAIL_TYPE) {
+ type = PhoneCalls.CALL_TYPE_VOICEMAIL;
+ }
+
+ bundle.putString(PhoneCalls.CALL_COUNTRY_ISO_KEY, c.getString(c.getColumnIndex(CallLog.Calls.COUNTRY_ISO)));
+ bundle.putLong(PhoneCalls.CALL_DATA_USAGE_KEY, c.getLong(c.getColumnIndex(CallLog.Calls.DATA_USAGE)));
+ bundle.putString(PhoneCalls.CALL_GEOCODED_LOCATION_KEY, c.getString(c.getColumnIndex(CallLog.Calls.GEOCODED_LOCATION)));
+ bundle.putBoolean(PhoneCalls.CALL_VIDEO_KEY, ((features & CallLog.Calls.FEATURES_VIDEO) == CallLog.Calls.FEATURES_VIDEO));
+
+ values.put(PhoneCalls.HISTORY_COUNTRY_ISO, c.getString(c.getColumnIndex(CallLog.Calls.COUNTRY_ISO)));
+ values.put(PhoneCalls.HISTORY_DATA_USAGE, c.getLong(c.getColumnIndex(CallLog.Calls.DATA_USAGE)));
+ values.put(PhoneCalls.HISTORY_GEOCODED_LOCATION, c.getString(c.getColumnIndex(CallLog.Calls.GEOCODED_LOCATION)));
+ values.put(PhoneCalls.HISTORY_VIDEO, ((features & CallLog.Calls.FEATURES_VIDEO) == CallLog.Calls.FEATURES_VIDEO));
- bundle.putBoolean(PhoneCalls.CALL_PULLED_EXTERNALLY_KEY, ((features & CallLog.Calls.FEATURES_PULLED_EXTERNALLY) == CallLog.Calls.FEATURES_PULLED_EXTERNALLY));
- case 24:
- if (typeInt == CallLog.Calls.REJECTED_TYPE) {
- type = PhoneCalls.CALL_TYPE_REJECTED;
- } else if (typeInt == CallLog.Calls.BLOCKED_TYPE) {
- type = PhoneCalls.CALL_TYPE_BLOCKED;
- }
-
- bundle.putString(PhoneCalls.CALL_POST_DIAL_DIGITS_KEY, c.getString(c.getColumnIndex(CallLog.Calls.POST_DIAL_DIGITS)));
- bundle.putString(PhoneCalls.CALL_VIA_NUMBER_KEY, c.getString(c.getColumnIndex(CallLog.Calls.VIA_NUMBER)));
- case 21:
- if (typeInt == CallLog.Calls.VOICEMAIL_TYPE) {
- type = PhoneCalls.CALL_TYPE_VOICEMAIL;
- }
+// bundle.putString(PhoneCalls.CALL_TRANSCRIPTION_KEY, c.getString(c.getColumnIndex(CallLog.Calls.TRANSCRIPTION)));
+ case 19:
+ switch (c.getInt(c.getColumnIndex(CallLog.Calls.NUMBER_PRESENTATION))) {
+ case CallLog.Calls.PRESENTATION_ALLOWED:
+ bundle.putString(PhoneCalls.CALL_PRESENTATION_KEY, PhoneCalls.CALL_PRESENTATION_ALLOWED);
+ values.put(PhoneCalls.HISTORY_PRESENTATION, PhoneCalls.CALL_PRESENTATION_ALLOWED);
+ break;
+ case CallLog.Calls.PRESENTATION_RESTRICTED:
+ bundle.putString(PhoneCalls.CALL_PRESENTATION_KEY, PhoneCalls.CALL_PRESENTATION_RESTRICTED);
+ values.put(PhoneCalls.HISTORY_PRESENTATION, PhoneCalls.CALL_PRESENTATION_RESTRICTED);
+ break;
+ case CallLog.Calls.PRESENTATION_PAYPHONE:
+ bundle.putString(PhoneCalls.CALL_PRESENTATION_KEY, PhoneCalls.CALL_PRESENTATION_PAYPHONE);
+ values.put(PhoneCalls.HISTORY_PRESENTATION, PhoneCalls.CALL_PRESENTATION_PAYPHONE);
+ break;
+ case CallLog.Calls.PRESENTATION_UNKNOWN:
+ bundle.putString(PhoneCalls.CALL_PRESENTATION_KEY, PhoneCalls.CALL_PRESENTATION_UNKNOWN);
+ values.put(PhoneCalls.HISTORY_PRESENTATION, PhoneCalls.CALL_PRESENTATION_UNKNOWN);
+ break;
+ }
+ case 14:
+ bundle.putBoolean(PhoneCalls.CALL_IS_READ_KEY, (c.getInt(c.getColumnIndex(CallLog.Calls.IS_READ)) != 0));
+ values.put(PhoneCalls.HISTORY_IS_READ, (c.getInt(c.getColumnIndex(CallLog.Calls.IS_READ)) != 0));
+ case 1:
+ if (typeInt == CallLog.Calls.INCOMING_TYPE) {
+ type = PhoneCalls.CALL_TYPE_INCOMING;
+ } else if (typeInt == CallLog.Calls.OUTGOING_TYPE) {
+ type = PhoneCalls.CALL_TYPE_OUTGOING;
+ } else if (typeInt == CallLog.Calls.MISSED_TYPE) {
+ type = PhoneCalls.CALL_TYPE_MISSED;
+ }
+ }
- bundle.putString(PhoneCalls.CALL_COUNTRY_ISO_KEY, c.getString(c.getColumnIndex(CallLog.Calls.COUNTRY_ISO)));
- bundle.putLong(PhoneCalls.CALL_DATA_USAGE_KEY, c.getLong(c.getColumnIndex(CallLog.Calls.DATA_USAGE)));
- bundle.putString(PhoneCalls.CALL_GEOCODED_LOCATION_KEY, c.getString(c.getColumnIndex(CallLog.Calls.GEOCODED_LOCATION)));
+ bundle.putString(PhoneCalls.CALL_TYPE_KEY, type);
+ values.put(PhoneCalls.HISTORY_CALL_TYPE, type);
- bundle.putBoolean(PhoneCalls.CALL_VIDEO_KEY, ((features & CallLog.Calls.FEATURES_VIDEO) == CallLog.Calls.FEATURES_VIDEO));
+ String[] sensitiveFields = {
+ PhoneCalls.CALL_NUMBER_KEY,
+ PhoneCalls.CALL_POST_DIAL_DIGITS_KEY,
+ PhoneCalls.CALL_VIA_NUMBER_KEY,
+ };
-// bundle.putString(PhoneCalls.CALL_TRANSCRIPTION_KEY, c.getString(c.getColumnIndex(CallLog.Calls.TRANSCRIPTION)));
- case 19:
- switch (c.getInt(c.getColumnIndex(CallLog.Calls.NUMBER_PRESENTATION))) {
- case CallLog.Calls.PRESENTATION_ALLOWED:
- bundle.putString(PhoneCalls.CALL_PRESENTATION_KEY, PhoneCalls.CALL_PRESENTATION_ALLOWED);
- break;
- case CallLog.Calls.PRESENTATION_RESTRICTED:
- bundle.putString(PhoneCalls.CALL_PRESENTATION_KEY, PhoneCalls.CALL_PRESENTATION_RESTRICTED);
- break;
- case CallLog.Calls.PRESENTATION_PAYPHONE:
- bundle.putString(PhoneCalls.CALL_PRESENTATION_KEY, PhoneCalls.CALL_PRESENTATION_PAYPHONE);
- break;
- case CallLog.Calls.PRESENTATION_UNKNOWN:
- bundle.putString(PhoneCalls.CALL_PRESENTATION_KEY, PhoneCalls.CALL_PRESENTATION_UNKNOWN);
- break;
+ for (String field : sensitiveFields) {
+ if (bundle.containsKey(field)) {
+ bundle.putString(field, new String(Hex.encodeHex(DigestUtils.sha256(bundle.getString(field)))));
}
- case 14:
- bundle.putBoolean(PhoneCalls.CALL_IS_READ_KEY, (c.getInt(c.getColumnIndex(CallLog.Calls.IS_READ)) != 0));
- case 1:
- if (typeInt == CallLog.Calls.INCOMING_TYPE) {
- type = PhoneCalls.CALL_TYPE_INCOMING;
- totalIncoming += 1;
- } else if (typeInt == CallLog.Calls.OUTGOING_TYPE) {
- type = PhoneCalls.CALL_TYPE_OUTGOING;
- totalOutgoing += 1;
- } else if (typeInt == CallLog.Calls.MISSED_TYPE) {
- type = PhoneCalls.CALL_TYPE_MISSED;
- totalMissed += 1;
- }
- }
-
- bundle.putString(PhoneCalls.CALL_TYPE_KEY, type);
+ }
- if (bundle.getLong(PhoneCalls.CALL_DATE_KEY, 0) > latestCallTimestamp) {
- latestCallTimestamp = bundle.getLong(PhoneCalls.CALL_DATE_KEY, 0);
- latestCallDuration = bundle.getLong(PhoneCalls.CALL_DURATION_KEY, 0);
- latestCallType = type;
- }
+ String[] valueSensitiveFields = {
+ PhoneCalls.HISTORY_NUMBER,
+ PhoneCalls.HISTORY_POST_DIAL_DIGITS,
+ PhoneCalls.HISTORY_VIA_NUMBER,
+ };
- Log.e("PDK", "------");
- for (int i = 0; i < c.getColumnCount(); i++) {
- Log.e("PDK", "CALL LOG: " + c.getColumnName(i) + " --> " + c.getString(i));
- }
+ for (String field : valueSensitiveFields) {
+ if (values.containsKey(field)) {
+ values.put(field, new String(Hex.encodeHex(DigestUtils.sha256(values.getAsString(field)))));
+ }
+ }
- String[] sensitiveFields = {
- PhoneCalls.CALL_NUMBER_KEY,
- PhoneCalls.CALL_POST_DIAL_DIGITS_KEY,
- PhoneCalls.CALL_VIA_NUMBER_KEY
- };
+ me.mDatabase.insert(PhoneCalls.TABLE_HISTORY, null, values);
- for (String field : sensitiveFields) {
- if (bundle.containsKey(field)) {
- bundle.putString(field, new String(Hex.encodeHex(DigestUtils.sha256(bundle.getString(field)))));
- }
+ Generators.getInstance(me.mContext).notifyGeneratorUpdated(PhoneCalls.GENERATOR_IDENTIFIER, bundle);
}
- Generators.getInstance(me.mContext).transmitData(PhoneCalls.GENERATOR_IDENTIFIER, bundle);
+ c.close();
}
+ }
- Log.e("PDK", "------");
+ if (me.mHandler != null) {
+ me.mHandler.postDelayed(this, me.mSampleInterval);
+ }
+ }
+ };
- total += c.getCount();
+ File path = PassiveDataKit.getGeneratorsStorage(this.mContext);
- c.close();
+ path = new File(path, PhoneCalls.DATABASE_PATH);
- if (latestCallType != null) {
- e.putLong(PhoneCalls.LAST_CALL_TIMESTAMP, latestCallTimestamp);
- e.putLong(PhoneCalls.LAST_CALL_DURATION, latestCallDuration);
- e.putString(PhoneCalls.LAST_CALL_TYPE, latestCallType);
- }
+ this.mDatabase = SQLiteDatabase.openOrCreateDatabase(path, null);
- e.putLong(PhoneCalls.LAST_INCOMING_COUNT, totalIncoming);
- e.putLong(PhoneCalls.LAST_OUTGOING_COUNT, totalOutgoing);
- e.putLong(PhoneCalls.LAST_MISSED_COUNT, totalMissed);
- e.putLong(PhoneCalls.LAST_TOTAL_COUNT, total);
+ int version = this.getDatabaseVersion(this.mDatabase);
- e.putLong(PhoneCalls.LAST_SAMPLE, now);
- e.apply();
- }
-
- long sampleInterval = prefs.getLong(PhoneCalls.SAMPLE_INTERVAL, PhoneCalls.SAMPLE_INTERVAL_DEFAULT);
+ switch (version) {
+ case 0:
+ this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_phone_calls_create_history_table));
+ case 1:
+ this.mDatabase.delete(PhoneCalls.TABLE_HISTORY, null, null);
+ }
- if (me.mHandler != null) {
- me.mHandler.postDelayed(this, sampleInterval);
- }
- }
- };
+ this.setDatabaseVersion(this.mDatabase, PhoneCalls.DATABASE_VERSION);
Runnable r = new Runnable() {
@Override
@@ -340,7 +368,7 @@ public static ArrayList diagnostics(final Context context) {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CALL_LOG) != PackageManager.PERMISSION_GRANTED){
final Handler handler = new Handler(Looper.getMainLooper());
- actions.add(new DiagnosticAction(context.getString(R.string.diagnostic_call_log_permission_required), new Runnable() {
+ actions.add(new DiagnosticAction(context.getString(R.string.diagnostic_call_log_permission_required_title), context.getString(R.string.diagnostic_call_log_permission_required), new Runnable() {
@Override
public void run() {
@@ -363,92 +391,136 @@ public void run() {
return actions;
}
- public static void bindViewHolder(DataPointViewHolder holder, final Bundle dataPoint) {
+ public static void bindViewHolder(DataPointViewHolder holder) {
final Context context = holder.itemView.getContext();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- double timestamp = dataPoint.getBundle(Generator.PDK_METADATA).getDouble(Generator.TIMESTAMP);
+ long lastTimestamp = 0;
+ long lastDuration = 0;
+ String callType = null;
- TextView dateLabel = (TextView) holder.itemView.findViewById(R.id.generator_data_point_date);
+ long totalIncoming = 0;
+ long totalOutgoing = 0;
+ long totalMissed = 0;
+ long total = 0;
- dateLabel.setText(Generator.formatTimestamp(context, timestamp));
+ PhoneCalls generator = PhoneCalls.getInstance(holder.itemView.getContext());
- PieChart pieChart = (PieChart) holder.itemView.findViewById(R.id.chart_phone_calls);
- pieChart.getLegend().setEnabled(false);
+ Cursor c = generator.mDatabase.query(PhoneCalls.TABLE_HISTORY, null, null, null, null, null, PhoneCalls.HISTORY_OBSERVED + " DESC");
- pieChart.setEntryLabelColor(android.R.color.transparent);
- pieChart.getDescription().setEnabled(false);
- pieChart.setDrawHoleEnabled(false);
+ while (c.moveToNext()) {
+ if (lastTimestamp == 0) {
+ lastTimestamp = c.getLong(c.getColumnIndex(PhoneCalls.HISTORY_OBSERVED));
+ lastDuration = c.getLong(c.getColumnIndex(PhoneCalls.HISTORY_DURATION));
+ }
- long totalIncoming = prefs.getLong(PhoneCalls.LAST_INCOMING_COUNT, 0);
- long totalOutgoing = prefs.getLong(PhoneCalls.LAST_OUTGOING_COUNT, 0);
- long totalMissed = prefs.getLong(PhoneCalls.LAST_MISSED_COUNT, 0);
- long total = prefs.getLong(PhoneCalls.LAST_TOTAL_COUNT, 0);
+ total += 1;
- List entries = new ArrayList<>();
+ String type = c.getString(c.getColumnIndex(PhoneCalls.HISTORY_CALL_TYPE));
- if (totalIncoming > 0) {
- entries.add(new PieEntry(totalIncoming, context.getString(R.string.generator_phone_calls_incoming_label)));
- }
+ if (PhoneCalls.CALL_TYPE_INCOMING.equals(type)) {
+ totalIncoming += 1;
+ } else if (PhoneCalls.CALL_TYPE_OUTGOING.equals(type)) {
+ totalOutgoing += 1;
+ } else if (PhoneCalls.CALL_TYPE_MISSED.equals(type)) {
+ totalOutgoing += 1;
+ }
- if (totalOutgoing > 0) {
- entries.add(new PieEntry(totalOutgoing, context.getString(R.string.generator_phone_calls_outgoing_label)));
+ if (callType == null) {
+ callType = type;
+ }
}
- if (totalMissed > 0) {
- entries.add(new PieEntry(totalMissed, context.getString(R.string.generator_phone_calls_missed_label)));
- }
+ c.close();
- long other = total - (totalIncoming + totalOutgoing + totalMissed);
+ View cardContent = holder.itemView.findViewById(R.id.card_content);
+ View cardEmpty = holder.itemView.findViewById(R.id.card_empty);
+ TextView dateLabel = (TextView) holder.itemView.findViewById(R.id.generator_data_point_date);
- if (other > 0) {
- entries.add(new PieEntry(other, context.getString(R.string.generator_phone_calls_other_label)));
- }
+ if (total > 0) {
+ cardContent.setVisibility(View.VISIBLE);
+ cardEmpty.setVisibility(View.GONE);
- PieDataSet set = new PieDataSet(entries, " ");
+ dateLabel.setText(Generator.formatTimestamp(context, lastTimestamp));
- int[] colors = {
- R.color.generator_phone_call_incoming,
- R.color.generator_phone_call_outgoing,
- R.color.generator_phone_call_missed,
- R.color.generator_phone_call_other
- };
+ PieChart pieChart = (PieChart) holder.itemView.findViewById(R.id.chart_phone_calls);
+ pieChart.getLegend().setEnabled(false);
- set.setColors(colors, context);
+ pieChart.setEntryLabelColor(android.R.color.transparent);
+ pieChart.getDescription().setEnabled(false);
+ pieChart.setDrawHoleEnabled(false);
- PieData data = new PieData(set);
- data.setValueTextSize(14);
- data.setValueTypeface(Typeface.DEFAULT_BOLD);
- data.setValueTextColor(0xffffffff);
+ List entries = new ArrayList<>();
- data.setValueFormatter(new IValueFormatter() {
- @Override
- public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
- return "" + ((Float) value).intValue();
+ if (totalIncoming > 0) {
+ entries.add(new PieEntry(totalIncoming, context.getString(R.string.generator_phone_calls_incoming_label)));
+ }
+
+ if (totalOutgoing > 0) {
+ entries.add(new PieEntry(totalOutgoing, context.getString(R.string.generator_phone_calls_outgoing_label)));
+ }
+
+ if (totalMissed > 0) {
+ entries.add(new PieEntry(totalMissed, context.getString(R.string.generator_phone_calls_missed_label)));
}
- });
- pieChart.setData(data);
- pieChart.invalidate();
+ long other = total - (totalIncoming + totalOutgoing + totalMissed);
+
+ if (other > 0) {
+ entries.add(new PieEntry(other, context.getString(R.string.generator_phone_calls_other_label)));
+ }
+
+ PieDataSet set = new PieDataSet(entries, " ");
+
+ int[] colors = {
+ R.color.generator_phone_call_incoming,
+ R.color.generator_phone_call_outgoing,
+ R.color.generator_phone_call_missed,
+ R.color.generator_phone_call_other
+ };
+
+ set.setColors(colors, context);
+
+ PieData data = new PieData(set);
+ data.setValueTextSize(14);
+ data.setValueTypeface(Typeface.DEFAULT_BOLD);
+ data.setValueTextColor(0xffffffff);
+
+ data.setValueFormatter(new IValueFormatter() {
+ @Override
+ public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
+ return "" + ((Float) value).intValue();
+ }
+ });
+
+ pieChart.setData(data);
+ pieChart.invalidate();
- long latest = prefs.getLong(PhoneCalls.LAST_CALL_TIMESTAMP, 0);
- long duration = prefs.getLong(PhoneCalls.LAST_CALL_DURATION, 0);
- String direction = prefs.getString(PhoneCalls.LAST_CALL_TYPE, "");
+ TextView latestField = (TextView) holder.itemView.findViewById(R.id.field_latest_call);
+ TextView durationField = (TextView) holder.itemView.findViewById(R.id.field_duration);
+ TextView directionField = (TextView) holder.itemView.findViewById(R.id.field_direction);
- TextView latestField = (TextView) holder.itemView.findViewById(R.id.field_latest_call);
- TextView durationField = (TextView) holder.itemView.findViewById(R.id.field_duration);
- TextView directionField = (TextView) holder.itemView.findViewById(R.id.field_direction);
+ Date lateDate = new Date(lastTimestamp);
+ String day = android.text.format.DateFormat.getMediumDateFormat(context).format(lateDate);
+ String time = android.text.format.DateFormat.getTimeFormat(context).format(lateDate);
- Date lateDate = new Date(latest);
- String day = android.text.format.DateFormat.getMediumDateFormat(context).format(lateDate);
- String time = android.text.format.DateFormat.getTimeFormat(context).format(lateDate);
+ latestField.setText(context.getString(R.string.format_full_timestamp_pdk, day, time));
+ durationField.setText(context.getString(R.string.generator_phone_calls_duration_format, ((float) lastDuration) / 60));
+ directionField.setText(callType);
- latestField.setText(context.getString(R.string.format_full_timestamp_pdk, day, time));
- durationField.setText(context.getString(R.string.generator_phone_calls_duration_format, ((float) duration) / 60));
- directionField.setText(direction);
+ dateLabel.setText(Generator.formatTimestamp(context, lastTimestamp / 1000));
+ } else {
+ cardContent.setVisibility(View.GONE);
+ cardEmpty.setVisibility(View.VISIBLE);
- dateLabel.setText(Generator.formatTimestamp(context, latest / 1000));
+ dateLabel.setText(R.string.label_never_pdk);
+ }
+ }
+
+ @Override
+ public List fetchPayloads() {
+ return new ArrayList<>();
}
public static View fetchView(ViewGroup parent)
@@ -456,8 +528,19 @@ public static View fetchView(ViewGroup parent)
return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_phone_calls, parent, false);
}
- public static void broadcastLatestDataPoint(Context context) {
- Generators.getInstance(context).transmitData(PhoneCalls.GENERATOR_IDENTIFIER, new Bundle());
- }
+ public static long latestPointGenerated(Context context) {
+ long timestamp = 0;
+
+ PhoneCalls me = PhoneCalls.getInstance(context);
+ Cursor c = me.mDatabase.query(PhoneCalls.TABLE_HISTORY, null, null, null, null, null, PhoneCalls.HISTORY_OBSERVED + " DESC");
+
+ if (c.moveToNext()) {
+ timestamp = c.getLong(c.getColumnIndex(PhoneCalls.HISTORY_OBSERVED));
+ }
+
+ c.close();
+
+ return timestamp;
+ }
}
diff --git a/src/com/audacious_software/passive_data_kit/generators/communication/TextMessages.java b/src/com/audacious_software/passive_data_kit/generators/communication/TextMessages.java
new file mode 100755
index 0000000..f82e641
--- /dev/null
+++ b/src/com/audacious_software/passive_data_kit/generators/communication/TextMessages.java
@@ -0,0 +1,473 @@
+package com.audacious_software.passive_data_kit.generators.communication;
+
+import android.Manifest;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.graphics.Typeface;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.preference.PreferenceManager;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.audacious_software.passive_data_kit.PassiveDataKit;
+import com.audacious_software.passive_data_kit.PhoneUtililties;
+import com.audacious_software.passive_data_kit.activities.generators.DataPointViewHolder;
+import com.audacious_software.passive_data_kit.activities.generators.RequestPermissionActivity;
+import com.audacious_software.passive_data_kit.diagnostics.DiagnosticAction;
+import com.audacious_software.passive_data_kit.generators.Generator;
+import com.audacious_software.passive_data_kit.generators.Generators;
+import com.audacious_software.pdk.passivedatakit.R;
+import com.github.mikephil.charting.charts.PieChart;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.PieData;
+import com.github.mikephil.charting.data.PieDataSet;
+import com.github.mikephil.charting.data.PieEntry;
+import com.github.mikephil.charting.formatter.IValueFormatter;
+import com.github.mikephil.charting.utils.ViewPortHandler;
+
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.codec.digest.DigestUtils;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+
+public class TextMessages extends Generator {
+ private static final String GENERATOR_IDENTIFIER = "pdk-text-messages";
+
+ private static final String ENABLED = "com.audacious_software.passive_data_kit.generators.communication.TextMessages.ENABLED";
+ private static final boolean ENABLED_DEFAULT = true;
+
+ private static final Uri SMS_INBOX_URI = Uri.parse("content://sms/inbox");
+ private static final Uri SMS_SENT_URI = Uri.parse("content://sms/sent");
+
+ private static final String SMS_DATE = "date";
+ private static final String SMS_BODY = "body";
+ private static final String SMS_NUMBER_NAME = "person";
+ private static final String SMS_NUMBER = "address";
+ private static final String SMS_LENGTH = "length";
+ private static final String SMS_DIRECTION = "direction";
+
+ private static int DATABASE_VERSION = 1;
+
+ private static final String TABLE_HISTORY = "history";
+ private static final String HISTORY_OBSERVED = "observed";
+ private static final String HISTORY_DIRECTION = "direction";
+ private static final String HISTORY_LENGTH = "length";
+ private static final String HISTORY_BODY = "body";
+ private static final String HISTORY_NUMBER_NAME = "number_name";
+ private static final String HISTORY_NUMBER = "number";
+ private static final String HISTORY_DIRECTION_INCOMING = "incoming";
+ private static final String HISTORY_DIRECTION_OUTGOING = "outgoing";
+
+ private static TextMessages sInstance = null;
+
+ private Handler mHandler = null;
+ private Context mContext = null;
+
+ private static final String DATABASE_PATH = "pdk-text-messages.sqlite";
+
+ private SQLiteDatabase mDatabase = null;
+ private long mSampleInterval = 60000;
+
+ public static TextMessages getInstance(Context context) {
+ if (TextMessages.sInstance == null) {
+ TextMessages.sInstance = new TextMessages(context.getApplicationContext());
+ }
+
+ return TextMessages.sInstance;
+ }
+
+ public TextMessages(Context context) {
+ super(context);
+
+ this.mContext = context.getApplicationContext();
+ }
+
+ public static void start(final Context context) {
+ TextMessages.getInstance(context).startGenerator();
+ }
+
+ private void startGenerator() {
+ final TextMessages me = this;
+
+ if (this.mHandler != null) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ this.mHandler.getLooper().quitSafely();
+ } else {
+ this.mHandler.getLooper().quit();
+ }
+
+ this.mHandler = null;
+ }
+
+ final Runnable checkLogs = new Runnable() {
+ @Override
+ public void run() {
+
+ Log.e("PDK", "CHECK TEXT LOGS");
+
+ boolean approved = false;
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (ContextCompat.checkSelfPermission(me.mContext, Manifest.permission.READ_SMS) == PackageManager.PERMISSION_GRANTED){
+ approved = true;
+ }
+ } else {
+ approved = true;
+ }
+
+ if (approved) {
+ long lastObserved = 0;
+
+ Cursor lastCursor = me.mDatabase.query(TextMessages.TABLE_HISTORY, null, null, null, null, null, TextMessages.HISTORY_OBSERVED + " DESC");
+
+ if (lastCursor.moveToNext()) {
+ lastObserved = lastCursor.getLong(lastCursor.getColumnIndex(TextMessages.HISTORY_OBSERVED));
+ }
+
+ lastCursor.close();
+
+ ArrayList toTransmit = new ArrayList<>();
+
+ String where = TextMessages.SMS_DATE + " > ?";
+ String[] args = {"" + lastObserved};
+
+ Cursor c = me.mContext.getContentResolver().query(TextMessages.SMS_INBOX_URI, null, where, args, TextMessages.SMS_DATE);
+
+ while (c.moveToNext()) {
+ ContentValues values = new ContentValues();
+ values.put(TextMessages.HISTORY_OBSERVED, c.getLong(c.getColumnIndex(TextMessages.SMS_DATE)));
+
+ String body = c.getString(c.getColumnIndex(TextMessages.SMS_BODY));
+
+ values.put(TextMessages.HISTORY_LENGTH, body.length());
+ values.put(TextMessages.HISTORY_BODY, body);
+
+ String name = c.getString(c.getColumnIndex(TextMessages.SMS_NUMBER_NAME));
+ String number = c.getString(c.getColumnIndex(TextMessages.SMS_NUMBER));
+
+ if (name == null) {
+ name = number;
+ }
+
+ values.put(TextMessages.HISTORY_NUMBER_NAME, name);
+ values.put(TextMessages.HISTORY_NUMBER, number);
+
+ values.put(TextMessages.HISTORY_DIRECTION, TextMessages.HISTORY_DIRECTION_INCOMING);
+
+ toTransmit.add(values);
+ }
+
+ c.close();
+
+ c = me.mContext.getContentResolver().query(TextMessages.SMS_SENT_URI, null, where, args, TextMessages.SMS_DATE);
+
+ while (c.moveToNext()) {
+ ContentValues values = new ContentValues();
+ values.put(TextMessages.HISTORY_OBSERVED, c.getLong(c.getColumnIndex(TextMessages.SMS_DATE)));
+
+ String body = c.getString(c.getColumnIndex(TextMessages.SMS_BODY));
+
+ values.put(TextMessages.HISTORY_LENGTH, body.length());
+ values.put(TextMessages.HISTORY_BODY, body);
+
+ String name = c.getString(c.getColumnIndex(TextMessages.SMS_NUMBER_NAME));
+ String number = c.getString(c.getColumnIndex(TextMessages.SMS_NUMBER));
+
+ if (name == null) {
+ name = number;
+ }
+
+ values.put(TextMessages.HISTORY_NUMBER_NAME, name);
+ values.put(TextMessages.HISTORY_NUMBER, number);
+
+ values.put(TextMessages.HISTORY_DIRECTION, TextMessages.HISTORY_DIRECTION_OUTGOING);
+
+ toTransmit.add(values);
+ }
+
+ c.close();
+
+ Collections.sort(toTransmit, new Comparator() {
+ @Override
+ public int compare(ContentValues one, ContentValues two) {
+ Long oneTime = one.getAsLong(TextMessages.HISTORY_OBSERVED);
+ Long twoTime = two.getAsLong(TextMessages.HISTORY_OBSERVED);
+
+ return oneTime.compareTo(twoTime);
+ }
+ });
+
+ for (ContentValues values : toTransmit) {
+ String[] sensitiveFields = {
+ TextMessages.HISTORY_NUMBER_NAME,
+ TextMessages.HISTORY_NUMBER,
+ TextMessages.HISTORY_BODY,
+ };
+
+ for (String field : sensitiveFields) {
+ if (values.containsKey(field)) {
+ String value = values.getAsString(field);
+
+ if (TextMessages.HISTORY_NUMBER.equals(TextMessages.HISTORY_NUMBER)) {
+ value = PhoneUtililties.normalizedPhoneNumber(value);
+ }
+
+ values.put(field, new String(Hex.encodeHex(DigestUtils.sha256(value))));
+ }
+ }
+
+ Bundle bundle = new Bundle();
+ bundle.putLong(TextMessages.SMS_DATE, values.getAsLong(TextMessages.HISTORY_OBSERVED));
+ bundle.putInt(TextMessages.SMS_LENGTH, values.getAsInteger(TextMessages.HISTORY_LENGTH));
+ bundle.putString(TextMessages.SMS_NUMBER_NAME, values.getAsString(TextMessages.HISTORY_NUMBER_NAME));
+ bundle.putString(TextMessages.SMS_NUMBER, values.getAsString(TextMessages.HISTORY_NUMBER));
+ bundle.putString(TextMessages.SMS_DIRECTION, values.getAsString(TextMessages.HISTORY_DIRECTION));
+ bundle.putString(TextMessages.SMS_BODY, values.getAsString(TextMessages.HISTORY_BODY));
+
+ me.mDatabase.insert(TextMessages.TABLE_HISTORY, null, values);
+
+ Generators.getInstance(me.mContext).notifyGeneratorUpdated(TextMessages.GENERATOR_IDENTIFIER, bundle);
+ }
+ }
+
+ if (me.mHandler != null) {
+ me.mHandler.postDelayed(this, me.mSampleInterval);
+ }
+ }
+ };
+
+ File path = PassiveDataKit.getGeneratorsStorage(this.mContext);
+
+ path = new File(path, TextMessages.DATABASE_PATH);
+
+ this.mDatabase = SQLiteDatabase.openOrCreateDatabase(path, null);
+
+ int version = this.getDatabaseVersion(this.mDatabase);
+
+ switch (version) {
+ case 0:
+ this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_text_messages_create_history_table));
+ }
+
+ this.setDatabaseVersion(this.mDatabase, TextMessages.DATABASE_VERSION);
+
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ Looper.prepare();
+
+ me.mHandler = new Handler();
+
+ Looper.loop();
+ }
+ };
+
+ Thread t = new Thread(r);
+ t.start();
+
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ me.mHandler.post(checkLogs);
+
+ Generators.getInstance(this.mContext).registerCustomViewClass(TextMessages.GENERATOR_IDENTIFIER, TextMessages.class);
+ }
+
+ public static boolean isEnabled(Context context) {
+ SharedPreferences prefs = Generators.getInstance(context).getSharedPreferences(context);
+
+ return prefs.getBoolean(TextMessages.ENABLED, TextMessages.ENABLED_DEFAULT);
+ }
+
+ public static boolean isRunning(Context context) {
+ if (TextMessages.sInstance == null) {
+ return false;
+ }
+
+ return TextMessages.sInstance.mHandler != null;
+ }
+
+ public static ArrayList diagnostics(final Context context) {
+ ArrayList actions = new ArrayList<>();
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_SMS) != PackageManager.PERMISSION_GRANTED){
+ final Handler handler = new Handler(Looper.getMainLooper());
+
+ actions.add(new DiagnosticAction(context.getString(R.string.diagnostic_sms_log_permission_required_title), context.getString(R.string.diagnostic_sms_log_permission_required), new Runnable() {
+
+ @Override
+ public void run() {
+ handler.post(new Runnable() {
+
+ @Override
+ public void run() {
+ Intent intent = new Intent(context, RequestPermissionActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(RequestPermissionActivity.PERMISSION, Manifest.permission.READ_SMS);
+
+ context.startActivity(intent);
+ }
+ });
+ }
+ }));
+ }
+ }
+
+ return actions;
+ }
+
+ public static void bindViewHolder(DataPointViewHolder holder) {
+ final Context context = holder.itemView.getContext();
+
+ long lastTimestamp = 0;
+ int lastLength = 0;
+
+ long totalIncoming = 0;
+ long totalOutgoing = 0;
+ long total = 0;
+
+ TextMessages generator = TextMessages.getInstance(holder.itemView.getContext());
+ String lastDirection = null;
+
+ Cursor c = generator.mDatabase.query(TextMessages.TABLE_HISTORY, null, null, null, null, null, TextMessages.HISTORY_OBSERVED + " DESC");
+
+ while (c.moveToNext()) {
+ if (lastTimestamp == 0) {
+ lastTimestamp = c.getLong(c.getColumnIndex(TextMessages.HISTORY_OBSERVED));
+ lastDirection = c.getString(c.getColumnIndex(TextMessages.HISTORY_DIRECTION));
+ lastLength = c.getInt(c.getColumnIndex(TextMessages.HISTORY_LENGTH));
+ }
+
+ total += 1;
+
+ String direction = c.getString(c.getColumnIndex(TextMessages.HISTORY_DIRECTION));
+
+ if (TextMessages.HISTORY_DIRECTION_INCOMING.equals(direction)) {
+ totalIncoming += 1;
+ } else if (TextMessages.HISTORY_DIRECTION_OUTGOING.equals(direction)) {
+ totalOutgoing += 1;
+ }
+ }
+
+ c.close();
+
+ View cardContent = holder.itemView.findViewById(R.id.card_content);
+ View cardEmpty = holder.itemView.findViewById(R.id.card_empty);
+ TextView dateLabel = (TextView) holder.itemView.findViewById(R.id.generator_data_point_date);
+
+ if (total > 0) {
+ cardContent.setVisibility(View.VISIBLE);
+ cardEmpty.setVisibility(View.GONE);
+
+ dateLabel.setText(Generator.formatTimestamp(context, lastTimestamp));
+
+ PieChart pieChart = (PieChart) holder.itemView.findViewById(R.id.chart_text_messages);
+ pieChart.getLegend().setEnabled(false);
+
+ pieChart.setEntryLabelColor(android.R.color.transparent);
+ pieChart.getDescription().setEnabled(false);
+ pieChart.setDrawHoleEnabled(false);
+
+ List entries = new ArrayList<>();
+
+ if (totalIncoming > 0) {
+ entries.add(new PieEntry(totalIncoming, context.getString(R.string.generator_text_messages_incoming_label)));
+ }
+
+ if (totalOutgoing > 0) {
+ entries.add(new PieEntry(totalOutgoing, context.getString(R.string.generator_text_messages_outgoing_label)));
+ }
+
+ PieDataSet set = new PieDataSet(entries, " ");
+
+ int[] colors = {
+ R.color.generator_text_messages_incoming,
+ R.color.generator_text_messages_outgoing
+ };
+
+ set.setColors(colors, context);
+
+ PieData data = new PieData(set);
+ data.setValueTextSize(14);
+ data.setValueTypeface(Typeface.DEFAULT_BOLD);
+ data.setValueTextColor(0xffffffff);
+
+ data.setValueFormatter(new IValueFormatter() {
+ @Override
+ public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
+ return "" + ((Float) value).intValue();
+ }
+ });
+
+ pieChart.setData(data);
+ pieChart.invalidate();
+
+ TextView latestField = (TextView) holder.itemView.findViewById(R.id.field_latest_text_message);
+ TextView lengthField = (TextView) holder.itemView.findViewById(R.id.field_length);
+ TextView directionField = (TextView) holder.itemView.findViewById(R.id.field_direction);
+
+ Date lateDate = new Date(lastTimestamp);
+ String day = android.text.format.DateFormat.getMediumDateFormat(context).format(lateDate);
+ String time = android.text.format.DateFormat.getTimeFormat(context).format(lateDate);
+
+ latestField.setText(context.getString(R.string.format_full_timestamp_pdk, day, time));
+ lengthField.setText(context.getString(R.string.generator_text_messages_length_format, lastLength));
+ directionField.setText(lastDirection);
+
+ dateLabel.setText(Generator.formatTimestamp(context, lastTimestamp / 1000));
+ } else {
+ cardContent.setVisibility(View.GONE);
+ cardEmpty.setVisibility(View.VISIBLE);
+
+ dateLabel.setText(R.string.label_never_pdk);
+ }
+ }
+
+ @Override
+ public List fetchPayloads() {
+ return new ArrayList<>();
+ }
+
+ public static View fetchView(ViewGroup parent)
+ {
+ return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_text_messages, parent, false);
+ }
+
+ public static long latestPointGenerated(Context context) {
+ long timestamp = 0;
+
+ TextMessages me = TextMessages.getInstance(context);
+
+ Cursor c = me.mDatabase.query(TextMessages.TABLE_HISTORY, null, null, null, null, null, TextMessages.HISTORY_OBSERVED + " DESC");
+
+ if (c.moveToNext()) {
+ timestamp = c.getLong(c.getColumnIndex(TextMessages.HISTORY_OBSERVED));
+ }
+
+ c.close();
+
+ return timestamp;
+ }
+}
diff --git a/src/com/audacious_software/passive_data_kit/generators/device/Location.java b/src/com/audacious_software/passive_data_kit/generators/device/Location.java
index 814c5ad..058f4ec 100755
--- a/src/com/audacious_software/passive_data_kit/generators/device/Location.java
+++ b/src/com/audacious_software/passive_data_kit/generators/device/Location.java
@@ -1,26 +1,51 @@
package com.audacious_software.passive_data_kit.generators.device;
import android.Manifest;
+import android.app.Activity;
+import android.content.ContentValues;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
+import android.content.res.ColorStateList;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.location.Address;
+import android.location.Geocoder;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.preference.PreferenceManager;
-import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
+import android.support.v4.content.res.ResourcesCompat;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.widget.SwitchCompat;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.webkit.WebView;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.ListView;
import android.widget.TextView;
+import android.widget.Toast;
import com.audacious_software.passive_data_kit.DeviceInformation;
+import com.audacious_software.passive_data_kit.PassiveDataKit;
+import com.audacious_software.passive_data_kit.activities.DataDisclosureDetailActivity;
import com.audacious_software.passive_data_kit.activities.generators.DataPointViewHolder;
+import com.audacious_software.passive_data_kit.activities.generators.GeneratorViewHolder;
import com.audacious_software.passive_data_kit.activities.generators.RequestPermissionActivity;
import com.audacious_software.passive_data_kit.diagnostics.DiagnosticAction;
import com.audacious_software.passive_data_kit.generators.Generator;
@@ -35,16 +60,23 @@
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.MapView;
import com.google.android.gms.maps.OnMapReadyCallback;
-import com.google.android.gms.maps.UiSettings;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
+import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.MarkerOptions;
+import com.google.maps.android.ui.IconGenerator;
+import java.io.File;
+import java.io.IOException;
+import java.security.SecureRandom;
import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
@SuppressWarnings("unused")
public class Location extends Generator implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener {
private static final String GENERATOR_IDENTIFIER = "pdk-location";
+ private static final String DATABASE_PATH = "pdk-location.sqlite";
private static final String ENABLED = "com.audacious_software.passive_data_kit.generators.device.Location.ENABLED";
private static final boolean ENABLED_DEFAULT = true;
@@ -61,18 +93,43 @@ public class Location extends Generator implements GoogleApiClient.ConnectionCal
private static final String BEARING_KEY = "bearing";
private static final String SPEED_KEY = "speed";
private static final String EXTRAS_KEY = "extras";
+ private static final String SETTING_DISPLAY_HYBRID_MAP = "com.audacious_software.passive_data_kit.generators.device.Location.SETTING_DISPLAY_HYBRID_MAP";
+ private static final boolean SETTING_DISPLAY_HYBRID_MAP_DEFAULT = true;
- private static final String LAST_KNOWN_LATITUDE = "com.audacious_software.passive_data_kit.generators.device.Location.LAST_KNOWN_LATITUDE";
- private static final float LAST_KNOWN_LATITUDE_DEFAULT = 0;
+ private static String ACCURACY_MODE = "com.audacious_software.passive_data_kit.generators.device.Location.ACCURACY_MODE";
- private static final String LAST_KNOWN_LONGITUDE = "com.audacious_software.passive_data_kit.generators.device.Location.LAST_KNOWN_LONGITUDE";
- private static final float LAST_KNOWN_LONGITUDE_DEFAULT = 0;
+ private static int ACCURACY_BEST = 0;
+ private static int ACCURACY_RANDOMIZED = 1;
+ private static int ACCURACY_USER = 2;
+ private static int ACCURACY_DISABLED = 3;
- private static final String LAST_KNOWN_TIMESTAMP = "com.audacious_software.passive_data_kit.generators.device.Location.LAST_KNOWN_TIMESTAMP";;
+ private static final String ACCURACY_MODE_RANDOMIZED_RANGE = "com.audacious_software.passive_data_kit.generators.device.Location.ACCURACY_MODE_RANDOMIZED_RANGE";
+ private static final long ACCURACY_MODE_RANDOMIZED_RANGE_DEFAULT = 100;
+
+ private static final String ACCURACY_MODE_USER_LOCATION = "com.audacious_software.passive_data_kit.generators.device.Location.ACCURACY_MODE_USER_LOCATION";
+ private static final String ACCURACY_MODE_USER_LOCATION_DEFAULT = "Chicago, Illinois";
+
+ private static final String ACCURACY_MODE_USER_LOCATION_LATITUDE = "com.audacious_software.passive_data_kit.generators.device.Location.ACCURACY_MODE_USER_LOCATION_LATITUDE";
+ private static final String ACCURACY_MODE_USER_LOCATION_LONGITUDE = "com.audacious_software.passive_data_kit.generators.device.Location.ACCURACY_MODE_USER_LOCATION_LONGITUDE";
private static Location sInstance = null;
private GoogleApiClient mGoogleApiClient = null;
private android.location.Location mLastLocation = null;
+ private long mUpdateInterval = 60000;
+
+ private SQLiteDatabase mDatabase = null;
+ private static int DATABASE_VERSION = 1;
+
+ private static final String TABLE_HISTORY = "history";
+ public static final String HISTORY_OBSERVED = "observed";
+ public static final String HISTORY_LATITUDE = "latitude";
+ public static final String HISTORY_LONGITUDE = "longitude";
+ public static final String HISTORY_ALTITUDE = "altitude";
+ public static final String HISTORY_BEARING = "bearing";
+ public static final String HISTORY_SPEED = "speed";
+ public static final String HISTORY_PROVIDER = "provider";
+ public static final String HISTORY_LOCATION_TIMESTAMP = "location_timestamp";
+ public static final String HISTORY_ACCURACY = "accuracy";
public static Location getInstance(Context context) {
if (Location.sInstance == null) {
@@ -114,7 +171,6 @@ else if (Location.useGoogleLocationServices(me.mContext))
me.mGoogleApiClient = builder.build();
me.mGoogleApiClient.connect();
}
-
}
else
{
@@ -128,6 +184,31 @@ else if (Location.useGoogleLocationServices(me.mContext))
t.start();
Generators.getInstance(this.mContext).registerCustomViewClass(Location.GENERATOR_IDENTIFIER, Location.class);
+
+ File path = PassiveDataKit.getGeneratorsStorage(this.mContext);
+
+ path = new File(path, Location.DATABASE_PATH);
+
+ this.mDatabase = SQLiteDatabase.openOrCreateDatabase(path, null);
+
+ int version = this.getDatabaseVersion(this.mDatabase);
+
+ switch (version) {
+ case 0:
+ this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_location_create_history_table));
+ }
+
+ this.setDatabaseVersion(this.mDatabase, Location.DATABASE_VERSION);
+ }
+
+ private void stopGenerator() {
+ if (this.mGoogleApiClient != null) {
+ this.mGoogleApiClient.disconnect();
+ this.mGoogleApiClient = null;
+ }
+
+ this.mDatabase.close();
+ this.mDatabase = null;
}
public static boolean useGoogleLocationServices(Context context) {
@@ -183,7 +264,7 @@ private ArrayList runDiagostics() {
int permissionCheck = ContextCompat.checkSelfPermission(this.mContext, Manifest.permission.ACCESS_FINE_LOCATION);
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
- actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_location_permission), new Runnable() {
+ actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_location_permission_title), me.mContext.getString(R.string.diagnostic_missing_location_permission), new Runnable() {
@Override
public void run() {
@@ -206,13 +287,13 @@ public void run() {
return actions;
}
-
@Override
public void onConnected(Bundle bundle) {
final LocationRequest request = new LocationRequest();
request.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
- request.setInterval(60000);
+ request.setFastestInterval(this.mUpdateInterval);
+ request.setInterval(this.mUpdateInterval);
if (this.mGoogleApiClient != null && this.mGoogleApiClient.isConnected()) {
if (ContextCompat.checkSelfPermission(this.mContext, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
@@ -239,78 +320,139 @@ public void onLocationChanged(android.location.Location location) {
long now = System.currentTimeMillis();
- Bundle bundle = new Bundle();
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext);
+ int selected = prefs.getInt(Location.ACCURACY_MODE, Location.ACCURACY_BEST);
- bundle.putDouble(Location.LATITUDE_KEY, location.getLatitude());
- bundle.putDouble(Location.LONGITUDE_KEY, location.getLongitude());
- bundle.putDouble(Location.FIX_TIMESTAMP_KEY, ((double) location.getTime()) / 1000);
- bundle.putString(Location.PROVIDER_KEY, location.getProvider());
+ if (selected == Location.ACCURACY_RANDOMIZED) {
+ // http://gis.stackexchange.com/a/68275/10230
- this.mLastLocation = location;
+ double latitude = location.getLatitude();
+ double longitude = location.getLongitude();
- if (location.hasAccuracy()) {
- bundle.putFloat(Location.ACCURACY_KEY, location.getAccuracy());
+ double radius = prefs.getLong(Location.ACCURACY_MODE_RANDOMIZED_RANGE, Location.ACCURACY_MODE_RANDOMIZED_RANGE_DEFAULT);
+
+ double radiusInDegrees = radius / 111000;
+
+ Random r = new SecureRandom();
+
+ double u = r.nextDouble();
+ double v = r.nextDouble();
+
+ double w = radiusInDegrees * Math.sqrt(u);
+ double t = 2 * Math.PI * v;
+ double x = w * Math.cos(t);
+ double y = w * Math.sin(t);
+
+ // Adjust the x-coordinate for the shrinking of the east-west distances
+ longitude = longitude + (x / Math.cos(latitude));
+ latitude = y + latitude;
+
+ location.setLongitude(longitude);
+ location.setLatitude(latitude);
}
+ ContentValues values = new ContentValues();
+ values.put(Location.HISTORY_OBSERVED, System.currentTimeMillis());
+ values.put(Location.HISTORY_LATITUDE, location.getLatitude());
+ values.put(Location.HISTORY_LONGITUDE, location.getLongitude());
+ values.put(Location.HISTORY_PROVIDER, location.getProvider());
+ values.put(Location.HISTORY_LOCATION_TIMESTAMP, location.getTime());
+
+ Bundle updated = new Bundle();
+ updated.putLong(Location.HISTORY_OBSERVED, System.currentTimeMillis());
+ updated.putDouble(Location.HISTORY_LATITUDE, location.getLatitude());
+ updated.putDouble(Location.HISTORY_LONGITUDE, location.getLongitude());
+ updated.putString(Location.HISTORY_PROVIDER, location.getProvider());
+ updated.putLong(Location.HISTORY_LOCATION_TIMESTAMP, location.getTime());
+
+ Bundle metadata = new Bundle();
+ metadata.putDouble(Generator.LATITUDE, location.getLatitude());
+ metadata.putDouble(Generator.LONGITUDE, location.getLongitude());
+
+ updated.putBundle(Generator.PDK_METADATA, metadata);
+
if (location.hasAltitude()) {
- bundle.putDouble(Location.ALTITUDE_KEY, location.getAltitude());
+ values.put(Location.HISTORY_ALTITUDE, location.getAltitude());
+ updated.putDouble(Location.HISTORY_ALTITUDE, location.getAltitude());
}
if (location.hasBearing()) {
- bundle.putFloat(Location.BEARING_KEY, location.getBearing());
+ values.put(Location.HISTORY_BEARING, location.getBearing());
+ updated.putDouble(Location.HISTORY_BEARING, location.getBearing());
}
if (location.hasSpeed()) {
- bundle.putFloat(Location.SPEED_KEY, location.getSpeed());
+ values.put(Location.HISTORY_SPEED, location.getBearing());
+ updated.putDouble(Location.HISTORY_SPEED, location.getBearing());
}
- Bundle extras = location.getExtras();
-
- if (extras != null) {
- bundle.putBundle(Location.EXTRAS_KEY, extras);
+ if (location.hasAccuracy()) {
+ values.put(Location.HISTORY_ACCURACY, location.getAccuracy());
+ updated.putDouble(Location.HISTORY_ACCURACY, location.getAccuracy());
}
- Generators.getInstance(this.mContext).transmitData(Location.GENERATOR_IDENTIFIER, bundle);
+ this.mDatabase.insert(Location.TABLE_HISTORY, null, values);
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext);
- SharedPreferences.Editor e = prefs.edit();
+ Generators.getInstance(this.mContext).notifyGeneratorUpdated(Location.GENERATOR_IDENTIFIER, updated);
+ }
- e.putFloat(Location.LAST_KNOWN_LATITUDE, (float) location.getLatitude());
- e.putFloat(Location.LAST_KNOWN_LONGITUDE, (float) location.getLongitude());
- e.putLong(Location.LAST_KNOWN_TIMESTAMP, System.currentTimeMillis());
+ public static long latestPointGenerated(Context context) {
+ long timestamp = 0;
- e.apply();
- }
+ Location me = Location.getInstance(context);
- public static void bindViewHolder(DataPointViewHolder holder, final Bundle dataPoint) {
- Log.e("PDK", "DRAWING LOCATION: " + dataPoint);
+ Cursor c = me.mDatabase.query(Location.TABLE_HISTORY, null, null, null, null, null, Location.HISTORY_OBSERVED + " DESC");
+
+ if (c.moveToNext()) {
+ timestamp = c.getLong(c.getColumnIndex(Location.HISTORY_OBSERVED));
+ }
+
+ c.close();
+
+ return timestamp;
+ }
+ public static void bindViewHolder(DataPointViewHolder holder) {
final Context context = holder.itemView.getContext();
- String identifier = dataPoint.getBundle(Generator.PDK_METADATA).getString(Generator.IDENTIFIER);
+ Location me = Location.getInstance(context);
- double timestamp = dataPoint.getBundle(Generator.PDK_METADATA).getDouble(Generator.TIMESTAMP);
+ double lastLatitude = 0.0;
+ double lastLongitude = 0.0;
+ long timestamp = 0;
- double latitude = Location.LAST_KNOWN_LATITUDE_DEFAULT;
- double longitude = Location.LAST_KNOWN_LONGITUDE_DEFAULT;
+ final List locations = new ArrayList<>();
- if (dataPoint.containsKey(Location.LATITUDE_KEY) && dataPoint.containsKey(Location.LONGITUDE_KEY)) {
- latitude = dataPoint.getDouble(Location.LATITUDE_KEY);
- longitude = dataPoint.getDouble(Location.LONGITUDE_KEY);
- } else { // Empty point - retrieve last known location...
- Log.e("PDK", "FETCHING LAST KNOWN LOCATION...");
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ String where = Location.HISTORY_OBSERVED + " > ?";
+ String[] args = { "" + (System.currentTimeMillis() - (1000 * 60 * 60 * 24)) };
- latitude = prefs.getFloat(Location.LAST_KNOWN_LATITUDE, Location.LAST_KNOWN_LATITUDE_DEFAULT);
- longitude = prefs.getFloat(Location.LAST_KNOWN_LONGITUDE, Location.LAST_KNOWN_LONGITUDE_DEFAULT);
- timestamp = ((double) prefs.getLong(Location.LAST_KNOWN_TIMESTAMP, System.currentTimeMillis())) / 1000 ;
+ Cursor c = me.mDatabase.query(Location.TABLE_HISTORY, null, where, args, null, null, Location.HISTORY_OBSERVED);
+
+ while (c.moveToNext()) {
+ lastLatitude = c.getDouble(c.getColumnIndex(Location.HISTORY_LATITUDE));
+ lastLongitude = c.getDouble(c.getColumnIndex(Location.HISTORY_LONGITUDE));
+ timestamp = c.getLong(c.getColumnIndex(Location.HISTORY_OBSERVED));
+
+ LatLng location = new LatLng(lastLatitude, lastLongitude);
+
+ locations.add(location);
}
+ c.close();
+
TextView dateLabel = (TextView) holder.itemView.findViewById(R.id.generator_data_point_date);
- dateLabel.setText(Generator.formatTimestamp(context, timestamp));
- final double finalLatitude = latitude;
- final double finalLongitude = longitude;
+ if (timestamp > 0) {
+ dateLabel.setText(Generator.formatTimestamp(context, timestamp / 1000));
+ } else {
+ dateLabel.setText(R.string.label_never_pdk);
+ }
+
+ final double finalLatitude = lastLatitude;
+ final double finalLongitude = lastLongitude;
+
+ final DisplayMetrics metrics = new DisplayMetrics();
+ ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(metrics);
if (Location.useKindleLocationServices())
{
@@ -322,24 +464,95 @@ else if (Location.useGoogleLocationServices(holder.itemView.getContext()))
final MapView mapView = (MapView) holder.itemView.findViewById(R.id.map_view);
mapView.onCreate(null);
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+
+ final boolean useHybrid = prefs.getBoolean(Location.SETTING_DISPLAY_HYBRID_MAP, Location.SETTING_DISPLAY_HYBRID_MAP_DEFAULT);
+
+ SwitchCompat hybridSwitch = (SwitchCompat) holder.itemView.findViewById(R.id.pdk_google_location_map_type_hybrid);
+ hybridSwitch.setChecked(useHybrid);
+
+ hybridSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, final boolean checked) {
+ SharedPreferences.Editor e = prefs.edit();
+ e.putBoolean(Location.SETTING_DISPLAY_HYBRID_MAP, checked);
+ e.apply();
+
+ mapView.getMapAsync(new OnMapReadyCallback() {
+ public void onMapReady(GoogleMap googleMap) {
+ if (checked) {
+ googleMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
+ } else {
+ googleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
+ }
+ }
+ });
+ }
+ });
+
+ ColorStateList buttonStates = new ColorStateList(
+ new int[][]{
+ new int[]{android.R.attr.state_checked},
+ new int[]{-android.R.attr.state_enabled},
+ new int[]{}
+ },
+ new int[]{
+ 0xfff1f1f1,
+ 0x1c000000,
+ 0xff33691E
+ }
+ );
+
+ DrawableCompat.setTintList(hybridSwitch.getThumbDrawable(), buttonStates);
+
+ IconGenerator iconGen = new IconGenerator(context);
+
+ Drawable shapeDrawable = ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_location_heatmap_marker, null);
+ iconGen.setBackground(shapeDrawable);
+
+ View view = new View(context);
+ view.setLayoutParams(new ViewGroup.LayoutParams(8, 8));
+ iconGen.setContentView(view);
+
+ final Bitmap bitmap = iconGen.makeIcon();
+
mapView.getMapAsync(new OnMapReadyCallback() {
public void onMapReady(GoogleMap googleMap) {
- googleMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
- googleMap.getUiSettings().setZoomControlsEnabled(false);
+ if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
+ ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
+ googleMap.setMyLocationEnabled(true);
+ }
+
+ if (useHybrid) {
+ googleMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
+ } else {
+ googleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
+ }
+
+ googleMap.getUiSettings().setZoomControlsEnabled(true);
googleMap.getUiSettings().setMyLocationButtonEnabled(false);
+ googleMap.getUiSettings().setMapToolbarEnabled(false);
+ googleMap.getUiSettings().setAllGesturesEnabled(false);
+
+ LatLngBounds.Builder builder = new LatLngBounds.Builder();
- googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(finalLatitude, finalLongitude), 14));
+ for (LatLng latlng : locations) {
+ builder.include(latlng);
+ }
- googleMap.addMarker(new MarkerOptions()
- .position(new LatLng(finalLatitude, finalLongitude)));
-// .icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_marker_none)));
+ if (locations.size() > 0) {
+ googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), (int) (16 * metrics.density)));
+ }
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
- googleMap.setPadding(0, 0, 0, (int) (32 * metrics.density));
+ for (LatLng latLng : locations) {
+ googleMap.addMarker(new MarkerOptions()
+ .position(latLng)
+ .icon(BitmapDescriptorFactory.fromBitmap(bitmap)));
+ }
- UiSettings settings = googleMap.getUiSettings();
- settings.setMapToolbarEnabled(false);
+ mapView.onResume();
}
});
}
@@ -348,9 +561,11 @@ public void onMapReady(GoogleMap googleMap) {
// TODO
throw new RuntimeException("Throw rocks at developer to implement generic location support.");
}
+ }
- TextView description = (TextView) holder.itemView.findViewById(R.id.generator_location_description);
- description.setText(context.getResources().getString(R.string.generator_location_value, latitude, longitude));
+ @Override
+ public List fetchPayloads() {
+ return new ArrayList();
}
public static View fetchView(ViewGroup parent)
@@ -371,7 +586,347 @@ else if (Location.useGoogleLocationServices(parent.getContext()))
}
}
+ public static String getGeneratorTitle(Context context) {
+ return context.getString(R.string.generator_location);
+ }
+
+ public static List getDisclosureActions(final Context context) {
+ List actions = new ArrayList<>();
+
+ DataDisclosureDetailActivity.Action disclosure = new DataDisclosureDetailActivity.Action();
+
+ disclosure.title = context.getString(R.string.label_data_collection_description);
+ disclosure.subtitle = context.getString(R.string.label_data_collection_description_more);
+
+ WebView disclosureView = new WebView(context);
+ disclosureView.loadUrl("file:///android_asset/html/passive_data_kit/generator_location_disclosure.html");
+
+ disclosure.view = disclosureView;
+
+ actions.add(disclosure);
+
+ DataDisclosureDetailActivity.Action accuracy = new DataDisclosureDetailActivity.Action();
+ accuracy.title = context.getString(R.string.label_data_collection_location_accuracy);
+ accuracy.subtitle = context.getString(R.string.label_data_collection_location_accuracy_more);
+
+ final Integer[] options = { Location.ACCURACY_BEST, Location.ACCURACY_RANDOMIZED, Location.ACCURACY_USER, Location.ACCURACY_DISABLED };
+
+ final ListView listView = new ListView(context);
+
+ final ArrayAdapter accuracyAdapter = new ArrayAdapter(context, R.layout.row_disclosure_location_accuracy_pdk, options) {
+ public View getView (final int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(context).inflate(R.layout.row_disclosure_location_accuracy_pdk, null);
+ }
+
+ final Integer option = options[position];
+
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ int selected = prefs.getInt(Location.ACCURACY_MODE, Location.ACCURACY_BEST);
+
+ CheckBox checked = (CheckBox) convertView.findViewById(R.id.action_checked);
+
+ checked.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
+
+ }
+ });
+
+ checked.setChecked(selected == option);
+
+ TextView title = (TextView) convertView.findViewById(R.id.action_title);
+ TextView description = (TextView) convertView.findViewById(R.id.action_description);
+
+ if (option == Location.ACCURACY_BEST) {
+ title.setText(R.string.label_data_collection_location_accuracy_best);
+ description.setText(R.string.label_data_collection_location_accuracy_best_more);
+ } else if (option == Location.ACCURACY_RANDOMIZED) {
+ title.setText(R.string.label_data_collection_location_accuracy_randomized);
+ description.setText(R.string.label_data_collection_location_accuracy_randomized_more);
+ } else if (option == Location.ACCURACY_USER) {
+ title.setText(R.string.label_data_collection_location_accuracy_user);
+ description.setText(R.string.label_data_collection_location_accuracy_user_more);
+ } else if (option == Location.ACCURACY_DISABLED) {
+ title.setText(R.string.label_data_collection_location_accuracy_disabled);
+ description.setText(R.string.label_data_collection_location_accuracy_disabled_more);
+ }
+
+ final ArrayAdapter meAdapter = this;
+
+ checked.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(final CompoundButton compoundButton, final boolean checked) {
+ Log.e("PDK", "CHECK CHANGE!");
+
+ final CompoundButton.OnCheckedChangeListener me = this;
+
+ if (option == Location.ACCURACY_BEST) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.title_location_accuracy_best);
+ builder.setMessage(R.string.message_location_accuracy_best);
+
+ builder.setPositiveButton(R.string.action_continue, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ SharedPreferences.Editor e = prefs.edit();
+ e.putInt(Location.ACCURACY_MODE, option);
+ e.apply();
+
+ meAdapter.notifyDataSetChanged();
+ }
+ });
+
+ builder.create().show();
+ } else if (option == Location.ACCURACY_RANDOMIZED) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.title_location_accuracy_randomized);
+
+ View body = LayoutInflater.from(context).inflate(R.layout.dialog_location_randomized, null);
+ builder.setView(body);
+
+ final EditText rangeField = (EditText) body.findViewById(R.id.random_range);
+
+ long existingRange = prefs.getLong(Location.ACCURACY_MODE_RANDOMIZED_RANGE, Location.ACCURACY_MODE_RANDOMIZED_RANGE_DEFAULT);
+
+ rangeField.setText("" + existingRange);
+
+ builder.setPositiveButton(R.string.action_continue, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ SharedPreferences.Editor e = prefs.edit();
+
+ long randomRange = Long.parseLong(rangeField.getText().toString());
+
+ e.putLong(Location.ACCURACY_MODE_RANDOMIZED_RANGE, randomRange);
+
+ e.putInt(Location.ACCURACY_MODE, option);
+ e.apply();
+
+ meAdapter.notifyDataSetChanged();
+ }
+ });
+
+ builder.create().show();
+ } else if (option == Location.ACCURACY_USER) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.title_location_accuracy_user);
+
+ View body = LayoutInflater.from(context).inflate(R.layout.dialog_location_user, null);
+ builder.setView(body);
+
+ final EditText locationField = (EditText) body.findViewById(R.id.user_location);
+
+ String existingLocation = prefs.getString(Location.ACCURACY_MODE_USER_LOCATION, Location.ACCURACY_MODE_USER_LOCATION_DEFAULT);
+
+ locationField.setText(existingLocation);
+
+ builder.setPositiveButton(R.string.action_continue, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ SharedPreferences.Editor e = prefs.edit();
+
+ String location = locationField.getText().toString();
+
+ e.putString(Location.ACCURACY_MODE_USER_LOCATION, location);
+
+ try {
+ List results = (new Geocoder(context)).getFromLocationName(location, 1);
+
+ if (results.size() > 0) {
+ Address match = results.get(0);
+
+ e.putFloat(Location.ACCURACY_MODE_USER_LOCATION_LATITUDE, (float) match.getLatitude());
+ e.putFloat(Location.ACCURACY_MODE_USER_LOCATION_LONGITUDE, (float) match.getLongitude());
+ } else {
+ Toast.makeText(context, R.string.toast_location_lookup_failed, Toast.LENGTH_LONG).show();
+
+ me.onCheckedChanged(compoundButton, checked);
+ }
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ }
+
+ e.putInt(Location.ACCURACY_MODE, option);
+ e.apply();
+
+ meAdapter.notifyDataSetChanged();
+ }
+ });
+
+ builder.create().show();
+ } else if (option == Location.ACCURACY_DISABLED) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.title_location_accuracy_disabled);
+ builder.setMessage(R.string.message_location_accuracy_disabled);
+
+ builder.setPositiveButton(R.string.action_continue, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ SharedPreferences.Editor e = prefs.edit();
+ e.putInt(Location.ACCURACY_MODE, option);
+ e.apply();
+
+ meAdapter.notifyDataSetChanged();
+ }
+ });
+
+ builder.create().show();
+ }
+
+ }
+ });
+
+ return convertView;
+ }
+ };
+
+ listView.setAdapter(accuracyAdapter);
+
+ accuracy.view = listView;
+
+ actions.add(accuracy);
+
+ return actions;
+ }
+
+ public static View getDisclosureDataView(final GeneratorViewHolder holder) {
+ final Context context = holder.itemView.getContext();
+
+ if (Location.useKindleLocationServices())
+ {
+ // TODO
+ throw new RuntimeException("Throw rocks at developer to implement Kindle support.");
+ }
+ else if (Location.useGoogleLocationServices(holder.itemView.getContext()))
+ {
+ final MapView mapView = new MapView(context);
+
+ FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+ mapView.setLayoutParams(params);
+
+ mapView.onCreate(null);
+
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+
+ final boolean useHybrid = prefs.getBoolean(Location.SETTING_DISPLAY_HYBRID_MAP, Location.SETTING_DISPLAY_HYBRID_MAP_DEFAULT);
+
+ IconGenerator iconGen = new IconGenerator(context);
+
+ Drawable shapeDrawable = ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_location_heatmap_marker, null);
+ iconGen.setBackground(shapeDrawable);
+
+ View view = new View(context);
+ view.setLayoutParams(new ViewGroup.LayoutParams(8, 8));
+ iconGen.setContentView(view);
+
+ final Bitmap bitmap = iconGen.makeIcon();
+
+ mapView.getMapAsync(new OnMapReadyCallback() {
+ public void onMapReady(GoogleMap googleMap) {
+ if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
+ ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
+ googleMap.setMyLocationEnabled(true);
+ }
+
+ if (useHybrid) {
+ googleMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
+ } else {
+ googleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
+ }
+
+ googleMap.getUiSettings().setZoomControlsEnabled(true);
+ googleMap.getUiSettings().setMyLocationButtonEnabled(false);
+ googleMap.getUiSettings().setMapToolbarEnabled(false);
+ googleMap.getUiSettings().setAllGesturesEnabled(false);
+
+ Location me = Location.getInstance(context);
+
+ double lastLatitude = 0.0;
+ double lastLongitude = 0.0;
+ long timestamp = 0;
+
+ final List locations = new ArrayList<>();
+
+ String where = Location.HISTORY_OBSERVED + " > ?";
+ String[] args = { "" + (System.currentTimeMillis() - (1000 * 60 * 60 * 24)) };
+
+ Cursor c = me.mDatabase.query(Location.TABLE_HISTORY, null, where, args, null, null, Location.HISTORY_OBSERVED);
+
+ while (c.moveToNext()) {
+ lastLatitude = c.getDouble(c.getColumnIndex(Location.HISTORY_LATITUDE));
+ lastLongitude = c.getDouble(c.getColumnIndex(Location.HISTORY_LONGITUDE));
+
+ LatLng location = new LatLng(lastLatitude, lastLongitude);
+
+ locations.add(location);
+ }
+
+ c.close();
+
+ LatLngBounds.Builder builder = new LatLngBounds.Builder();
+
+ for (LatLng latlng : locations) {
+ builder.include(latlng);
+ }
+
+ final DisplayMetrics metrics = new DisplayMetrics();
+ ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(metrics);
+
+ if (locations.size() > 0) {
+ googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), (int) (16 * metrics.density)));
+ }
+
+ for (LatLng latLng : locations) {
+ googleMap.addMarker(new MarkerOptions()
+ .position(latLng)
+ .icon(BitmapDescriptorFactory.fromBitmap(bitmap)));
+ }
+
+ mapView.onResume();
+ }
+ });
+
+ return mapView;
+ }
+ else
+ {
+ // TODO
+ throw new RuntimeException("Throw rocks at developer to implement generic location support.");
+ }
+ }
+
+ public static void bindDisclosureViewHolder(final GeneratorViewHolder holder) {
+ Class currentClass = new Object() { }.getClass().getEnclosingClass();
+
+ String identifier = currentClass.getCanonicalName();
+
+ TextView generatorLabel = (TextView) holder.itemView.findViewById(R.id.label_generator);
+
+ generatorLabel.setText(Location.getGeneratorTitle(holder.itemView.getContext()));
+ }
+
public android.location.Location getLastKnownLocation() {
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext);
+ int selected = prefs.getInt(Location.ACCURACY_MODE, Location.ACCURACY_BEST);
+
+ if (selected == Location.ACCURACY_USER) {
+ double latitude = prefs.getFloat(Location.ACCURACY_MODE_USER_LOCATION_LATITUDE, 0);
+ double longitude = prefs.getFloat(Location.ACCURACY_MODE_USER_LOCATION_LONGITUDE, 0);
+
+ android.location.Location location = new android.location.Location("");
+ location.setLatitude(latitude);
+ location.setLongitude(longitude);
+
+ return location;
+ } else if (selected == Location.ACCURACY_DISABLED) {
+ android.location.Location location = new android.location.Location("");
+ location.setLatitude(41.8781);
+ location.setLongitude(-87.6298);
+
+ return location;
+ }
+
if (this.mLastLocation != null) {
return this.mLastLocation;
}
@@ -380,26 +935,55 @@ public android.location.Location getLastKnownLocation() {
android.location.Location last = null;
- if (ContextCompat.checkSelfPermission(this.mContext, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
+ if (ContextCompat.checkSelfPermission(this.mContext, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(this.mContext, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
- Log.e("FC", "LOCATION PERMISSIONS GRANTED...");
-
last = locations.getLastKnownLocation(LocationManager.GPS_PROVIDER);
- Log.e("FC", "GPS: " + last);
-
if (last == null) {
last = locations.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
-
- Log.e("FC", "NETWORK: " + last);
}
}
+ if (selected == Location.ACCURACY_RANDOMIZED) {
+ // http://gis.stackexchange.com/a/68275/10230
+
+ double latitude = last.getLatitude();
+ double longitude = last.getLongitude();
+
+ double radius = prefs.getLong(Location.ACCURACY_MODE_RANDOMIZED_RANGE, Location.ACCURACY_MODE_RANDOMIZED_RANGE_DEFAULT);
+
+ double radiusInDegrees = radius / 111000;
+
+ Random r = new SecureRandom();
+
+ double u = r.nextDouble();
+ double v = r.nextDouble();
+
+ double w = radiusInDegrees * Math.sqrt(u);
+ double t = 2 * Math.PI * v;
+ double x = w * Math.cos(t);
+ double y = w * Math.sin(t);
+
+ // Adjust the x-coordinate for the shrinking of the east-west distances
+ longitude = longitude + (x / Math.cos(latitude));
+ latitude = y + latitude;
+
+ last.setLongitude(longitude);
+ last.setLatitude(latitude);
+ }
+
return last;
}
- public static void broadcastLatestDataPoint(Context context) {
- Generators.getInstance(context).transmitData(Location.GENERATOR_IDENTIFIER, new Bundle());
+ public void setUpdateInterval(long interval) {
+ this.mUpdateInterval = interval;
+
+ this.stopGenerator();
+ this.startGenerator();
+ }
+
+ public Cursor queryHistory(String[] cols, String where, String[] args, String orderBy) {
+ return this.mDatabase.query(Location.TABLE_HISTORY, cols, where, args, null, null, orderBy);
}
}
diff --git a/src/com/audacious_software/passive_data_kit/generators/device/ScreenState.java b/src/com/audacious_software/passive_data_kit/generators/device/ScreenState.java
index 4d314aa..d8a7432 100755
--- a/src/com/audacious_software/passive_data_kit/generators/device/ScreenState.java
+++ b/src/com/audacious_software/passive_data_kit/generators/device/ScreenState.java
@@ -1,13 +1,15 @@
package com.audacious_software.passive_data_kit.generators.device;
import android.content.BroadcastReceiver;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.os.Bundle;
-import android.preference.PreferenceManager;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
@@ -16,20 +18,18 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import com.audacious_software.passive_data_kit.PassiveDataKit;
import com.audacious_software.passive_data_kit.activities.generators.DataPointViewHolder;
import com.audacious_software.passive_data_kit.diagnostics.DiagnosticAction;
import com.audacious_software.passive_data_kit.generators.Generator;
import com.audacious_software.passive_data_kit.generators.Generators;
import com.audacious_software.pdk.passivedatakit.R;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
+import java.io.File;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Calendar;
-import java.util.Date;
+import java.util.List;
public class ScreenState extends Generator{
private static final String GENERATOR_IDENTIFIER = "pdk-screen-state";
@@ -37,20 +37,25 @@ public class ScreenState extends Generator{
private static final String ENABLED = "com.audacious_software.passive_data_kit.generators.device.ScreenState.ENABLED";
private static final boolean ENABLED_DEFAULT = true;
- private static final String SCREEN_STATE_KEY = "screen_state";
- private static final String STATE_DOZE = "doze";
- private static final String STATE_DOZE_SUSPEND = "doze_suspend";
- private static final String STATE_ON = "on";
- private static final String STATE_OFF = "off";
- private static final String STATE_UNKNOWN = "unknown";
- private static final String SCREEN_HISTORY_KEY = "com.audacious_software.passive_data_kit.generators.device.ScreenState.SCREEN_HISTORY_KEY";;
- private static final String SCREEN_HISTORY_TIMESTAMP = "ts";
- private static final String SCREEN_HISTORY_STATE = "state";
+ public static final String STATE_DOZE = "doze";
+ public static final String STATE_DOZE_SUSPEND = "doze_suspend";
+ public static final String STATE_ON = "on";
+ public static final String STATE_OFF = "off";
+ public static final String STATE_UNKNOWN = "unknown";
+
+ private static final String DATABASE_PATH = "pdk-screen-state.sqlite";
+ private static final int DATABASE_VERSION = 2;
+
+ public static final String HISTORY_OBSERVED = "observed";
+ public static final String HISTORY_STATE = "state";
+ public static final String TABLE_HISTORY = "history";
private static ScreenState sInstance = null;
private BroadcastReceiver mReceiver = null;
+ private SQLiteDatabase mDatabase = null;
+
public static ScreenState getInstance(Context context) {
if (ScreenState.sInstance == null) {
ScreenState.sInstance = new ScreenState(context.getApplicationContext());
@@ -68,10 +73,18 @@ public static void start(final Context context) {
}
private void startGenerator() {
+ final ScreenState me = this;
+
this.mReceiver = new BroadcastReceiver() {
@Override
- public void onReceive(Context context, Intent intent) {
- Bundle bundle = new Bundle();
+ public void onReceive(final Context context, Intent intent) {
+ long now = System.currentTimeMillis();
+
+ ContentValues values = new ContentValues();
+ values.put(ScreenState.HISTORY_OBSERVED, now);
+
+ Bundle update = new Bundle();
+ update.putLong(ScreenState.HISTORY_OBSERVED, now);
WindowManager window = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = window.getDefaultDisplay();
@@ -81,62 +94,36 @@ public void onReceive(Context context, Intent intent) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
switch (display.getState()) {
case Display.STATE_DOZE:
- bundle.putString(ScreenState.SCREEN_STATE_KEY, ScreenState.STATE_DOZE);
+ values.put(ScreenState.HISTORY_STATE, ScreenState.STATE_DOZE);
break;
case Display.STATE_DOZE_SUSPEND:
- bundle.putString(ScreenState.SCREEN_STATE_KEY, ScreenState.STATE_DOZE_SUSPEND);
+ values.put(ScreenState.HISTORY_STATE, ScreenState.STATE_DOZE_SUSPEND);
break;
case Display.STATE_ON:
- bundle.putString(ScreenState.SCREEN_STATE_KEY, ScreenState.STATE_ON);
+ values.put(ScreenState.HISTORY_STATE, ScreenState.STATE_ON);
break;
case Display.STATE_OFF:
- bundle.putString(ScreenState.SCREEN_STATE_KEY, ScreenState.STATE_OFF);
+ values.put(ScreenState.HISTORY_STATE, ScreenState.STATE_OFF);
break;
case Display.STATE_UNKNOWN:
- bundle.putString(ScreenState.SCREEN_STATE_KEY, ScreenState.STATE_UNKNOWN);
+ values.put(ScreenState.HISTORY_STATE, ScreenState.STATE_UNKNOWN);
break;
}
} else {
if (Intent.ACTION_SCREEN_OFF.equals(action)) {
- bundle.putString(ScreenState.SCREEN_STATE_KEY, ScreenState.STATE_OFF);
+ values.put(ScreenState.HISTORY_STATE, ScreenState.STATE_OFF);
} else if (Intent.ACTION_SCREEN_ON.equals(action)) {
- bundle.putString(ScreenState.SCREEN_STATE_KEY, ScreenState.STATE_ON);
+ values.put(ScreenState.HISTORY_STATE, ScreenState.STATE_ON);
} else {
- bundle.putString(ScreenState.SCREEN_STATE_KEY, ScreenState.STATE_UNKNOWN);
+ values.put(ScreenState.HISTORY_STATE, ScreenState.STATE_UNKNOWN);
}
}
- Generators.getInstance(context).transmitData(ScreenState.GENERATOR_IDENTIFIER, bundle);
-
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
-
- try {
- JSONArray history = new JSONArray(prefs.getString(ScreenState.SCREEN_HISTORY_KEY, "[]"));
-
- JSONObject latest = new JSONObject();
- latest.put(ScreenState.SCREEN_HISTORY_TIMESTAMP, System.currentTimeMillis());
-
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
- latest.put(ScreenState.SCREEN_HISTORY_STATE, display.getState());
- } else {
- if (Intent.ACTION_SCREEN_OFF.equals(action)) {
- latest.put(ScreenState.SCREEN_HISTORY_STATE, 0x01);
- } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
- latest.put(ScreenState.SCREEN_HISTORY_STATE, 0x02);
- } else {
- latest.put(ScreenState.SCREEN_HISTORY_STATE, 0x00);
- }
- }
-
- history.put(latest);
+ me.mDatabase.insert(ScreenState.TABLE_HISTORY, null, values);
- SharedPreferences.Editor e = prefs.edit();
+ update.putString(ScreenState.HISTORY_STATE, values.getAsString(ScreenState.HISTORY_STATE));
- e.putString(ScreenState.SCREEN_HISTORY_KEY, history.toString());
- e.apply();
- } catch (JSONException e) {
- e.printStackTrace();
- }
+ Generators.getInstance(context).notifyGeneratorUpdated(ScreenState.GENERATOR_IDENTIFIER, update);
}
};
@@ -147,7 +134,23 @@ public void onReceive(Context context, Intent intent) {
Generators.getInstance(this.mContext).registerCustomViewClass(ScreenState.GENERATOR_IDENTIFIER, ScreenState.class);
- this.mReceiver.onReceive(this.mContext, null);
+ File path = PassiveDataKit.getGeneratorsStorage(this.mContext);
+
+ path = new File(path, ScreenState.DATABASE_PATH);
+
+ this.mDatabase = SQLiteDatabase.openOrCreateDatabase(path, null);
+
+ int version = this.getDatabaseVersion(this.mDatabase);
+
+ switch (version) {
+ case 0:
+ this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_screen_state_create_history_table));
+ case 1:
+ this.mDatabase.execSQL("DROP TABLE history");
+ this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_screen_state_create_history_table));
+ }
+
+ this.setDatabaseVersion(this.mDatabase, ScreenState.DATABASE_VERSION);
}
public static boolean isEnabled(Context context) {
@@ -168,95 +171,111 @@ public static ArrayList diagnostics(Context context) {
return new ArrayList<>();
}
- public static void bindViewHolder(DataPointViewHolder holder, final Bundle dataPoint) {
+ public static void bindViewHolder(DataPointViewHolder holder) {
final Context context = holder.itemView.getContext();
- try {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- JSONArray history = new JSONArray(prefs.getString(ScreenState.SCREEN_HISTORY_KEY, "[]"));
-
-// Log.e("PDK", "SCREEN HISTORY: " + history.toString(2));
+ Calendar cal = Calendar.getInstance();
+ cal.set(Calendar.HOUR_OF_DAY, 0);
+ cal.set(Calendar.MINUTE, 0);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
- Calendar cal = Calendar.getInstance();
- cal.set(Calendar.HOUR_OF_DAY, 0);
- cal.set(Calendar.MINUTE, 0);
- cal.set(Calendar.SECOND, 0);
- cal.set(Calendar.MILLISECOND, 0);
+ long zeroStart = cal.getTimeInMillis();
+ cal.add(Calendar.DATE, -1);
- long zeroStart = cal.getTimeInMillis();
- cal.add(Calendar.DATE, -1);
+ LinearLayout zeroTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_zero_value);
- LinearLayout zeroTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_zero_value);
+ ScreenState.populateTimeline(context, zeroTimeline, zeroStart);
- ScreenState.populateTimeline(context, zeroTimeline, zeroStart, history);
+ long oneStart = cal.getTimeInMillis();
+ cal.add(Calendar.DATE, -1);
- long oneStart = cal.getTimeInMillis();
- cal.add(Calendar.DATE, -1);
+ LinearLayout oneTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_one_value);
- LinearLayout oneTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_one_value);
+ ScreenState.populateTimeline(context, oneTimeline, oneStart);
- ScreenState.populateTimeline(context, oneTimeline, oneStart, history);
+ long twoStart = cal.getTimeInMillis();
- long twoStart = cal.getTimeInMillis();
+ LinearLayout twoTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_two_value);
- LinearLayout twoTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_two_value);
+ ScreenState.populateTimeline(context, twoTimeline, twoStart);
- ScreenState.populateTimeline(context, twoTimeline, twoStart, history);
- } catch (JSONException e) {
- e.printStackTrace();
- }
+ ScreenState generator = ScreenState.getInstance(context);
- double timestamp = dataPoint.getBundle(Generator.PDK_METADATA).getDouble(Generator.TIMESTAMP);
+ Cursor c = generator.mDatabase.query(ScreenState.TABLE_HISTORY, null, null, null, null, null, ScreenState.HISTORY_OBSERVED + " DESC");
+ View cardContent = holder.itemView.findViewById(R.id.card_content);
+ View cardEmpty = holder.itemView.findViewById(R.id.card_empty);
TextView dateLabel = (TextView) holder.itemView.findViewById(R.id.generator_data_point_date);
- dateLabel.setText(Generator.formatTimestamp(context, timestamp));
+ if (c.moveToNext()) {
+ cardContent.setVisibility(View.VISIBLE);
+ cardEmpty.setVisibility(View.GONE);
- Calendar cal = Calendar.getInstance();
- DateFormat format = android.text.format.DateFormat.getDateFormat(context);
+ long timestamp = c.getLong(c.getColumnIndex(ScreenState.HISTORY_OBSERVED)) / 1000;
- TextView zeroDayLabel = (TextView) holder.itemView.findViewById(R.id.day_zero_label);
- zeroDayLabel.setText(format.format(cal.getTime()));
+ dateLabel.setText(Generator.formatTimestamp(context, timestamp));
- cal.add(Calendar.DATE, -1);
+ cal = Calendar.getInstance();
+ DateFormat format = android.text.format.DateFormat.getDateFormat(context);
- TextView oneDayLabel = (TextView) holder.itemView.findViewById(R.id.day_one_label);
- oneDayLabel.setText(format.format(cal.getTime()));
+ TextView zeroDayLabel = (TextView) holder.itemView.findViewById(R.id.day_zero_label);
+ zeroDayLabel.setText(format.format(cal.getTime()));
- cal.add(Calendar.DATE, -1);
+ cal.add(Calendar.DATE, -1);
+
+ TextView oneDayLabel = (TextView) holder.itemView.findViewById(R.id.day_one_label);
+ oneDayLabel.setText(format.format(cal.getTime()));
+
+ cal.add(Calendar.DATE, -1);
+
+ TextView twoDayLabel = (TextView) holder.itemView.findViewById(R.id.day_two_label);
+ twoDayLabel.setText(format.format(cal.getTime()));
+ } else {
+ cardContent.setVisibility(View.GONE);
+ cardEmpty.setVisibility(View.VISIBLE);
+
+ dateLabel.setText(R.string.label_never_pdk);
+ }
- TextView twoDayLabel = (TextView) holder.itemView.findViewById(R.id.day_two_label);
- twoDayLabel.setText(format.format(cal.getTime()));
+ c.close();
}
- private static void populateTimeline(Context context, LinearLayout timeline, long start, JSONArray history) {
+ private static void populateTimeline(Context context, LinearLayout timeline, long start) {
timeline.removeAllViews();
+ ScreenState generator = ScreenState.getInstance(context);
+
long end = start + (24 * 60 * 60 * 1000);
- long now = System.currentTimeMillis();
+ String where = ScreenState.HISTORY_OBSERVED + " >= ? AND " + ScreenState.HISTORY_OBSERVED + " < ?";
+ String[] args = { "" + start, "" + end };
- int lastState = -1;
+ Cursor c = generator.mDatabase.query(ScreenState.TABLE_HISTORY, null, where, args, null, null, ScreenState.HISTORY_OBSERVED);
- ArrayList activeStates = new ArrayList<>();
+ ArrayList activeStates = new ArrayList<>();
ArrayList activeTimestamps = new ArrayList<>();
- for (int i = 0; i < history.length(); i++) {
- try {
- JSONObject point = history.getJSONObject(i);
+ while (c.moveToNext()) {
+ long timestamp = c.getLong(c.getColumnIndex(ScreenState.HISTORY_OBSERVED));
- long timestamp = point.getLong(ScreenState.SCREEN_HISTORY_TIMESTAMP);
- int state = point.getInt(ScreenState.SCREEN_HISTORY_STATE);
+ activeTimestamps.add(timestamp);
- if (timestamp < start) {
- lastState = state;
- } else if (timestamp < end) {
- activeStates.add(state);
- activeTimestamps.add(timestamp);
- }
- } catch (JSONException e) {
- e.printStackTrace();
- }
+ String state = c.getString(c.getColumnIndex(ScreenState.HISTORY_STATE));
+ activeStates.add(state);
+ }
+
+ c.close();
+
+ String lastState = ScreenState.STATE_UNKNOWN;
+
+ String lastWhere = ScreenState.HISTORY_OBSERVED + " < ?";
+ String[] lastArgs = { "" + start };
+
+ c = generator.mDatabase.query(ScreenState.TABLE_HISTORY, null, lastWhere, lastArgs, null, null, ScreenState.HISTORY_OBSERVED + " DESC");
+
+ if (c.moveToNext()) {
+ lastState = c.getString(c.getColumnIndex(ScreenState.HISTORY_STATE));
}
if (activeStates.size() > 0) {
@@ -265,25 +284,24 @@ private static void populateTimeline(Context context, LinearLayout timeline, lon
View startView = new View(context);
-
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
- if (lastState == -1) {
+ if (ScreenState.STATE_UNKNOWN.equals(lastState)) {
- } else if (firstState == Display.STATE_ON) {
+ } else if (ScreenState.STATE_ON.equals(lastState)) {
startView.setBackgroundColor(0xff4CAF50);
- } else if (firstState == Display.STATE_OFF) {
+ } else if (ScreenState.STATE_OFF.equals(lastState)) {
startView.setBackgroundColor(0xff263238);
- } else if (firstState == Display.STATE_DOZE) {
+ } else if (ScreenState.STATE_DOZE.equals(lastState)) {
startView.setBackgroundColor(0xff1b5e20);
- } else if (firstState == Display.STATE_DOZE_SUSPEND) {
+ } else if (ScreenState.STATE_DOZE_SUSPEND.equals(lastState)) {
startView.setBackgroundColor(0xff1b5e20);
}
} else {
- if (lastState == -1) {
+ if (ScreenState.STATE_UNKNOWN.equals(lastState)) {
- } else if (firstState == 0x02) {
+ } else if (ScreenState.STATE_ON.equals(lastState)) {
startView.setBackgroundColor(0xff4CAF50);
- } else if (firstState == 0x01) {
+ } else if (ScreenState.STATE_OFF.equals(lastState)) {
startView.setBackgroundColor(0xff263238);
}
}
@@ -293,28 +311,30 @@ private static void populateTimeline(Context context, LinearLayout timeline, lon
timeline.addView(startView);
+ long now = System.currentTimeMillis();
+
if (activeStates.size() == 1) {
View v = new View(context);
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
- if (firstState == Display.STATE_ON) {
+ if (ScreenState.STATE_ON.equals(firstState)) {
v.setBackgroundColor(0xff4CAF50);
- } else if (firstState == Display.STATE_OFF) {
+ } else if (ScreenState.STATE_OFF.equals(firstState)) {
v.setBackgroundColor(0xff263238);
- } else if (firstState == Display.STATE_DOZE) {
+ } else if (ScreenState.STATE_DOZE.equals(firstState)) {
v.setBackgroundColor(0xff3f51b5);
- } else if (firstState == Display.STATE_DOZE_SUSPEND) {
+ } else if (ScreenState.STATE_DOZE_SUSPEND.equals(firstState)) {
v.setBackgroundColor(0xff3f51b5);
}
} else {
- if (firstState == 0x02) {
+ if (ScreenState.STATE_ON.equals(firstState)) {
v.setBackgroundColor(0xff4CAF50);
- } else if (firstState == 0x01) {
+ } else if (ScreenState.STATE_OFF.equals(firstState)) {
v.setBackgroundColor(0xff263238);
}
}
- if (end > now) {
+ if (end > System.currentTimeMillis()) {
params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, now - firstTimestamp);
v.setLayoutParams(params);
} else {
@@ -328,24 +348,24 @@ private static void populateTimeline(Context context, LinearLayout timeline, lon
long currentTimestamp = activeTimestamps.get(i);
long priorTimestamp = activeTimestamps.get(i - 1);
- long priorState = activeStates.get(i - 1);
+ String priorState = activeStates.get(i - 1);
View v = new View(context);
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
- if (priorState == Display.STATE_ON) {
+ if (ScreenState.STATE_ON.equals(priorState)) {
v.setBackgroundColor(0xff4CAF50);
- } else if (priorState == Display.STATE_OFF) {
+ } else if (ScreenState.STATE_OFF.equals(priorState)) {
v.setBackgroundColor(0xff263238);
- } else if (priorState == Display.STATE_DOZE) {
+ } else if (ScreenState.STATE_DOZE.equals(priorState)) {
v.setBackgroundColor(0xff3f51b5);
- } else if (priorState == Display.STATE_DOZE_SUSPEND) {
+ } else if (ScreenState.STATE_DOZE_SUSPEND.equals(priorState)) {
v.setBackgroundColor(0xff3f51b5);
}
} else {
- if (priorState == 0x02) {
+ if (ScreenState.STATE_ON.equals(priorState)) {
v.setBackgroundColor(0xff4CAF50);
- } else if (priorState == 0x01) {
+ } else if (ScreenState.STATE_OFF.equals(priorState)) {
v.setBackgroundColor(0xff263238);
}
}
@@ -357,17 +377,17 @@ private static void populateTimeline(Context context, LinearLayout timeline, lon
}
long finalTimestamp = activeTimestamps.get(activeTimestamps.size() - 1);
- long finalState = activeStates.get(activeStates.size() - 1);
+ String finalState = activeStates.get(activeStates.size() - 1);
View v = new View(context);
- if (finalState == Display.STATE_ON) {
+ if (ScreenState.STATE_ON.equals(finalState)) {
v.setBackgroundColor(0xff4CAF50);
- } else if (finalState == Display.STATE_OFF) {
+ } else if (ScreenState.STATE_OFF.equals(finalState)) {
v.setBackgroundColor(0xff263238);
- } else if (finalState == Display.STATE_DOZE) {
+ } else if (ScreenState.STATE_DOZE.equals(finalState)) {
v.setBackgroundColor(0xff3f51b5);
- } else if (finalState == Display.STATE_DOZE_SUSPEND) {
+ } else if (ScreenState.STATE_DOZE_SUSPEND.equals(finalState)) {
v.setBackgroundColor(0xff3f51b5);
}
@@ -390,6 +410,8 @@ private static void populateTimeline(Context context, LinearLayout timeline, lon
timeline.addView(v);
}
+ } else {
+
}
}
@@ -398,7 +420,28 @@ public static View fetchView(ViewGroup parent)
return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_screen_state, parent, false);
}
- public static void broadcastLatestDataPoint(Context context) {
- Generators.getInstance(context).transmitData(ScreenState.GENERATOR_IDENTIFIER, new Bundle());
+ @Override
+ public List fetchPayloads() {
+ return new ArrayList<>();
+ }
+
+ public static long latestPointGenerated(Context context) {
+ long timestamp = 0;
+
+ ScreenState me = ScreenState.getInstance(context);
+
+ Cursor c = me.mDatabase.query(ScreenState.TABLE_HISTORY, null, null, null, null, null, ScreenState.HISTORY_OBSERVED + " DESC");
+
+ if (c.moveToNext()) {
+ timestamp = c.getLong(c.getColumnIndex(ScreenState.HISTORY_OBSERVED));
+ }
+
+ c.close();
+
+ return timestamp;
+ }
+
+ public Cursor queryHistory(String[] cols, String where, String[] args, String orderBy) {
+ return this.mDatabase.query(ScreenState.TABLE_HISTORY, cols, where, args, null, null, orderBy);
}
}
diff --git a/src/com/audacious_software/passive_data_kit/generators/diagnostics/AppEvent.java b/src/com/audacious_software/passive_data_kit/generators/diagnostics/AppEvent.java
new file mode 100755
index 0000000..5eab4d1
--- /dev/null
+++ b/src/com/audacious_software/passive_data_kit/generators/diagnostics/AppEvent.java
@@ -0,0 +1,441 @@
+package com.audacious_software.passive_data_kit.generators.diagnostics;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.audacious_software.passive_data_kit.PassiveDataKit;
+import com.audacious_software.passive_data_kit.activities.generators.DataPointViewHolder;
+import com.audacious_software.passive_data_kit.diagnostics.DiagnosticAction;
+import com.audacious_software.passive_data_kit.generators.Generator;
+import com.audacious_software.passive_data_kit.generators.Generators;
+import com.audacious_software.pdk.passivedatakit.R;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class AppEvent extends Generator{
+ private static final String GENERATOR_IDENTIFIER = "pdk-app-event";
+
+ private static final String ENABLED = "com.audacious_software.passive_data_kit.generators.diagnostics.AppEvent.ENABLED";
+ private static final boolean ENABLED_DEFAULT = true;
+
+ private static final String DATABASE_PATH = "pdk-app-event.sqlite";
+ private static final int DATABASE_VERSION = 1;
+
+ public static final String HISTORY_OBSERVED = "observed";
+ public static final String HISTORY_EVENT_NAME = "event_name";
+ public static final String HISTORY_EVENT_DETAILS = "event_details";
+ public static final String TABLE_HISTORY = "history";
+
+ private static AppEvent sInstance = null;
+
+ private SQLiteDatabase mDatabase = null;
+
+ public static AppEvent getInstance(Context context) {
+ if (AppEvent.sInstance == null) {
+ AppEvent.sInstance = new AppEvent(context.getApplicationContext());
+ }
+
+ return AppEvent.sInstance;
+ }
+
+ public AppEvent(Context context) {
+ super(context);
+ }
+
+ public static void start(final Context context) {
+ AppEvent.getInstance(context).startGenerator();
+ }
+
+ private void startGenerator() {
+ final AppEvent me = this;
+
+ Generators.getInstance(this.mContext).registerCustomViewClass(AppEvent.GENERATOR_IDENTIFIER, AppEvent.class);
+
+ File path = PassiveDataKit.getGeneratorsStorage(this.mContext);
+
+ path = new File(path, AppEvent.DATABASE_PATH);
+
+ this.mDatabase = SQLiteDatabase.openOrCreateDatabase(path, null);
+
+ int version = this.getDatabaseVersion(this.mDatabase);
+
+ switch (version) {
+ case 0:
+ this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_app_events_create_history_table));
+ }
+
+ this.setDatabaseVersion(this.mDatabase, AppEvent.DATABASE_VERSION);
+ }
+
+ public static boolean isEnabled(Context context) {
+ SharedPreferences prefs = Generators.getInstance(context).getSharedPreferences(context);
+
+ return prefs.getBoolean(AppEvent.ENABLED, AppEvent.ENABLED_DEFAULT);
+ }
+
+ public static boolean isRunning(Context context) {
+ return (AppEvent.sInstance != null);
+ }
+
+ public static ArrayList diagnostics(Context context) {
+ return new ArrayList<>();
+ }
+
+ public static void bindViewHolder(DataPointViewHolder holder) {
+/* final Context context = holder.itemView.getContext();
+
+ Calendar cal = Calendar.getInstance();
+ cal.set(Calendar.HOUR_OF_DAY, 0);
+ cal.set(Calendar.MINUTE, 0);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+
+ long zeroStart = cal.getTimeInMillis();
+ cal.add(Calendar.DATE, -1);
+
+ LinearLayout zeroTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_zero_value);
+
+ AppEvents.populateTimeline(context, zeroTimeline, zeroStart);
+
+ long oneStart = cal.getTimeInMillis();
+ cal.add(Calendar.DATE, -1);
+
+ LinearLayout oneTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_one_value);
+
+ AppEvents.populateTimeline(context, oneTimeline, oneStart);
+
+ long twoStart = cal.getTimeInMillis();
+
+ LinearLayout twoTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_two_value);
+
+ AppEvents.populateTimeline(context, twoTimeline, twoStart);
+
+ AppEvents generator = AppEvents.getInstance(context);
+
+ Cursor c = generator.mDatabase.query(AppEvents.TABLE_HISTORY, null, null, null, null, null, AppEvents.HISTORY_OBSERVED + " DESC");
+
+ View cardContent = holder.itemView.findViewById(R.id.card_content);
+ View cardEmpty = holder.itemView.findViewById(R.id.card_empty);
+ TextView dateLabel = (TextView) holder.itemView.findViewById(R.id.generator_data_point_date);
+
+ if (c.moveToNext()) {
+ cardContent.setVisibility(View.VISIBLE);
+ cardEmpty.setVisibility(View.GONE);
+
+ long timestamp = c.getLong(c.getColumnIndex(AppEvents.HISTORY_OBSERVED)) / 1000;
+
+ dateLabel.setText(Generator.formatTimestamp(context, timestamp));
+
+ cal = Calendar.getInstance();
+ DateFormat format = android.text.format.DateFormat.getDateFormat(context);
+
+ TextView zeroDayLabel = (TextView) holder.itemView.findViewById(R.id.day_zero_label);
+ zeroDayLabel.setText(format.format(cal.getTime()));
+
+ cal.add(Calendar.DATE, -1);
+
+ TextView oneDayLabel = (TextView) holder.itemView.findViewById(R.id.day_one_label);
+ oneDayLabel.setText(format.format(cal.getTime()));
+
+ cal.add(Calendar.DATE, -1);
+
+ TextView twoDayLabel = (TextView) holder.itemView.findViewById(R.id.day_two_label);
+ twoDayLabel.setText(format.format(cal.getTime()));
+ } else {
+ cardContent.setVisibility(View.GONE);
+ cardEmpty.setVisibility(View.VISIBLE);
+
+ dateLabel.setText(R.string.label_never_pdk);
+ }
+
+ c.close();
+ */
+ }
+
+ /*
+ private static void populateTimeline(Context context, LinearLayout timeline, long start) {
+ timeline.removeAllViews();
+
+ AppEvents generator = AppEvents.getInstance(context);
+
+ long end = start + (24 * 60 * 60 * 1000);
+
+ String where = AppEvents.HISTORY_OBSERVED + " >= ? AND " + AppEvents.HISTORY_OBSERVED + " < ?";
+ String[] args = { "" + start, "" + end };
+
+ Cursor c = generator.mDatabase.query(AppEvents.TABLE_HISTORY, null, where, args, null, null, AppEvents.HISTORY_OBSERVED);
+
+ ArrayList activeStates = new ArrayList<>();
+ ArrayList activeTimestamps = new ArrayList<>();
+
+ while (c.moveToNext()) {
+ long timestamp = c.getLong(c.getColumnIndex(AppEvents.HISTORY_OBSERVED));
+
+ activeTimestamps.add(timestamp);
+
+ String state = c.getString(c.getColumnIndex(AppEvents.HISTORY_STATE));
+ activeStates.add(state);
+ }
+
+ c.close();
+
+ String lastState = AppEvents.STATE_UNKNOWN;
+
+ String lastWhere = AppEvents.HISTORY_OBSERVED + " < ?";
+ String[] lastArgs = { "" + start };
+
+ c = generator.mDatabase.query(AppEvents.TABLE_HISTORY, null, lastWhere, lastArgs, null, null, AppEvents.HISTORY_OBSERVED + " DESC");
+
+ if (c.moveToNext()) {
+ lastState = c.getString(c.getColumnIndex(AppEvents.HISTORY_STATE));
+ }
+
+ if (activeStates.size() > 0) {
+ long firstTimestamp = activeTimestamps.get(0);
+ long firstState = activeTimestamps.get(0);
+
+ View startView = new View(context);
+
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
+ if (AppEvents.STATE_UNKNOWN.equals(lastState)) {
+
+ } else if (AppEvents.STATE_ON.equals(lastState)) {
+ startView.setBackgroundColor(0xff4CAF50);
+ } else if (AppEvents.STATE_OFF.equals(lastState)) {
+ startView.setBackgroundColor(0xff263238);
+ } else if (AppEvents.STATE_DOZE.equals(lastState)) {
+ startView.setBackgroundColor(0xff1b5e20);
+ } else if (AppEvents.STATE_DOZE_SUSPEND.equals(lastState)) {
+ startView.setBackgroundColor(0xff1b5e20);
+ }
+ } else {
+ if (AppEvents.STATE_UNKNOWN.equals(lastState)) {
+
+ } else if (AppEvents.STATE_ON.equals(lastState)) {
+ startView.setBackgroundColor(0xff4CAF50);
+ } else if (AppEvents.STATE_OFF.equals(lastState)) {
+ startView.setBackgroundColor(0xff263238);
+ }
+ }
+
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, firstTimestamp - start);
+ startView.setLayoutParams(params);
+
+ timeline.addView(startView);
+
+ long now = System.currentTimeMillis();
+
+ if (activeStates.size() == 1) {
+ View v = new View(context);
+
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
+ if (AppEvents.STATE_ON.equals(firstState)) {
+ v.setBackgroundColor(0xff4CAF50);
+ } else if (AppEvents.STATE_OFF.equals(firstState)) {
+ v.setBackgroundColor(0xff263238);
+ } else if (AppEvents.STATE_DOZE.equals(firstState)) {
+ v.setBackgroundColor(0xff3f51b5);
+ } else if (AppEvents.STATE_DOZE_SUSPEND.equals(firstState)) {
+ v.setBackgroundColor(0xff3f51b5);
+ }
+ } else {
+ if (AppEvents.STATE_ON.equals(firstState)) {
+ v.setBackgroundColor(0xff4CAF50);
+ } else if (AppEvents.STATE_OFF.equals(firstState)) {
+ v.setBackgroundColor(0xff263238);
+ }
+ }
+
+ if (end > System.currentTimeMillis()) {
+ params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, now - firstTimestamp);
+ v.setLayoutParams(params);
+ } else {
+ params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, end - firstTimestamp);
+ v.setLayoutParams(params);
+ }
+
+ timeline.addView(v);
+ } else {
+ for (int i = 1; i < activeStates.size(); i++) {
+ long currentTimestamp = activeTimestamps.get(i);
+
+ long priorTimestamp = activeTimestamps.get(i - 1);
+ String priorState = activeStates.get(i - 1);
+
+ View v = new View(context);
+
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
+ if (AppEvents.STATE_ON.equals(priorState)) {
+ v.setBackgroundColor(0xff4CAF50);
+ } else if (AppEvents.STATE_OFF.equals(priorState)) {
+ v.setBackgroundColor(0xff263238);
+ } else if (AppEvents.STATE_DOZE.equals(priorState)) {
+ v.setBackgroundColor(0xff3f51b5);
+ } else if (AppEvents.STATE_DOZE_SUSPEND.equals(priorState)) {
+ v.setBackgroundColor(0xff3f51b5);
+ }
+ } else {
+ if (AppEvents.STATE_ON.equals(priorState)) {
+ v.setBackgroundColor(0xff4CAF50);
+ } else if (AppEvents.STATE_OFF.equals(priorState)) {
+ v.setBackgroundColor(0xff263238);
+ }
+ }
+
+ params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, currentTimestamp - priorTimestamp);
+ v.setLayoutParams(params);
+
+ timeline.addView(v);
+ }
+
+ long finalTimestamp = activeTimestamps.get(activeTimestamps.size() - 1);
+ String finalState = activeStates.get(activeStates.size() - 1);
+
+ View v = new View(context);
+
+ if (AppEvents.STATE_ON.equals(finalState)) {
+ v.setBackgroundColor(0xff4CAF50);
+ } else if (AppEvents.STATE_OFF.equals(finalState)) {
+ v.setBackgroundColor(0xff263238);
+ } else if (AppEvents.STATE_DOZE.equals(finalState)) {
+ v.setBackgroundColor(0xff3f51b5);
+ } else if (AppEvents.STATE_DOZE_SUSPEND.equals(finalState)) {
+ v.setBackgroundColor(0xff3f51b5);
+ }
+
+ if (end > now) {
+ params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, now - finalTimestamp);
+ v.setLayoutParams(params);
+ } else {
+ params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, end - finalTimestamp);
+ v.setLayoutParams(params);
+ }
+
+ timeline.addView(v);
+ }
+
+ if (end > now) {
+ View v = new View(context);
+
+ params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, end - now);
+ v.setLayoutParams(params);
+
+ timeline.addView(v);
+ }
+ } else {
+
+ }
+ }
+
+*/
+ public static View fetchView(ViewGroup parent)
+ {
+ return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_generic, parent, false);
+ }
+
+ @Override
+ public List fetchPayloads() {
+ return new ArrayList<>();
+ }
+
+ public static long latestPointGenerated(Context context) {
+ long timestamp = 0;
+
+ AppEvent me = AppEvent.getInstance(context);
+
+ Cursor c = me.mDatabase.query(AppEvent.TABLE_HISTORY, null, null, null, null, null, AppEvent.HISTORY_OBSERVED + " DESC");
+
+ if (c.moveToNext()) {
+ timestamp = c.getLong(c.getColumnIndex(AppEvent.HISTORY_OBSERVED));
+ }
+
+ c.close();
+
+ return timestamp;
+ }
+
+ public Cursor queryHistory(String[] cols, String where, String[] args, String orderBy) {
+ return this.mDatabase.query(AppEvent.TABLE_HISTORY, cols, where, args, null, null, orderBy);
+ }
+
+ public boolean logEvent(String eventName, Map eventDetails) {
+ try {
+ long now = System.currentTimeMillis();
+
+ ContentValues values = new ContentValues();
+ values.put(AppEvent.HISTORY_OBSERVED, now);
+ values.put(AppEvent.HISTORY_EVENT_NAME, eventName);
+
+ Bundle detailsBundle = new Bundle();
+ JSONObject detailsJson = new JSONObject();
+
+ for (String key : eventDetails.keySet()) {
+ Object value = eventDetails.get(key);
+
+ if (value instanceof Double) {
+ Double doubleValue = ((Double) value);
+
+ detailsBundle.putDouble(key, doubleValue);
+ detailsJson.put(key, doubleValue.doubleValue());
+ } else if (value instanceof Float) {
+ Float floatValue = ((Float) value);
+
+ detailsBundle.putDouble(key, floatValue.doubleValue());
+ detailsJson.put(key, floatValue.doubleValue());
+ } else if (value instanceof Long) {
+ Long longValue = ((Long) value);
+
+ detailsBundle.putLong(key, longValue.longValue());
+ detailsJson.put(key, longValue.longValue());
+ } else if (value instanceof Integer) {
+ Integer intValue = ((Integer) value);
+
+ detailsBundle.putLong(key, intValue.longValue());
+ detailsJson.put(key, intValue.longValue());
+ } else if (value instanceof String) {
+ detailsBundle.putString(key, value.toString());
+ detailsJson.put(key, value.toString());
+ } else {
+ detailsBundle.putString(key, "Unknown Class: " + value.getClass().getCanonicalName());
+ detailsJson.put(key, "Unknown Class: " + value.getClass().getCanonicalName());
+ }
+ }
+
+ values.put(AppEvent.HISTORY_EVENT_DETAILS, detailsJson.toString(2));
+
+ this.mDatabase.insert(AppEvent.TABLE_HISTORY, null, values);
+
+ Bundle update = new Bundle();
+ update.putLong(AppEvent.HISTORY_OBSERVED, now);
+ update.putString(AppEvent.HISTORY_EVENT_NAME, values.getAsString(AppEvent.HISTORY_EVENT_NAME));
+ update.putBundle(AppEvent.HISTORY_EVENT_DETAILS, detailsBundle);
+
+ Generators.getInstance(this.mContext).notifyGeneratorUpdated(AppEvent.GENERATOR_IDENTIFIER, update);
+
+ return true;
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+
+ return false;
+ }
+
+ public void logThrowable(Throwable t) {
+
+ }
+}
diff --git a/src/com/audacious_software/passive_data_kit/generators/services/GoogleAwareness.java b/src/com/audacious_software/passive_data_kit/generators/services/GoogleAwareness.java
index 796b1c2..0688e7d 100755
--- a/src/com/audacious_software/passive_data_kit/generators/services/GoogleAwareness.java
+++ b/src/com/audacious_software/passive_data_kit/generators/services/GoogleAwareness.java
@@ -19,15 +19,13 @@
import com.audacious_software.passive_data_kit.generators.Generators;
import com.audacious_software.pdk.passivedatakit.R;
import com.google.android.gms.awareness.Awareness;
-import com.google.android.gms.awareness.snapshot.DetectedActivityResult;
import com.google.android.gms.awareness.snapshot.HeadphoneStateResult;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
-import com.google.android.gms.location.ActivityRecognitionResult;
-import com.google.android.gms.location.DetectedActivity;
import java.util.ArrayList;
+import java.util.List;
/**
* Created by cjkarr on 6/28/2016.
@@ -105,7 +103,7 @@ private ArrayList runDiagostics() {
int permissionCheck = ContextCompat.checkSelfPermission(this.mContext, Manifest.permission.ACCESS_FINE_LOCATION);
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
- actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_location_permission), new Runnable() {
+ actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_location_permission_title), me.mContext.getString(R.string.diagnostic_missing_location_permission), new Runnable() {
@Override
public void run() {
handler.post(new Runnable() {
@@ -129,7 +127,7 @@ public void run() {
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
Log.e("PDK", "3.3");
- actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_activity_recognition_permission), new Runnable() {
+ actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_activity_recognition_permission_title), me.mContext.getString(R.string.diagnostic_missing_activity_recognition_permission), new Runnable() {
@Override
public void run() {
handler.post(new Runnable() {
@@ -219,4 +217,9 @@ public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
Log.e("PDK", "GA onConnectionFailed");
this.mGoogleApiClient = null;
}
+
+ @Override
+ public List fetchPayloads() {
+ return new ArrayList<>();
+ }
}
diff --git a/src/com/audacious_software/passive_data_kit/generators/vr/DaydreamViewController.java b/src/com/audacious_software/passive_data_kit/generators/vr/DaydreamViewController.java
index 6cbc304..531ed71 100755
--- a/src/com/audacious_software/passive_data_kit/generators/vr/DaydreamViewController.java
+++ b/src/com/audacious_software/passive_data_kit/generators/vr/DaydreamViewController.java
@@ -14,10 +14,11 @@
import com.audacious_software.passive_data_kit.generators.Generator;
import com.audacious_software.passive_data_kit.generators.Generators;
import com.audacious_software.pdk.passivedatakit.R;
-import com.google.vr.sdk.controller.Controller;
-import com.google.vr.sdk.controller.ControllerManager;
+// import com.google.vr.sdk.controller.Controller;
+/// import com.google.vr.sdk.controller.ControllerManager;
import java.util.ArrayList;
+import java.util.List;
/**
* Created by cjkarr on 11/20/2016.
@@ -32,8 +33,8 @@ public class DaydreamViewController extends Generator {
private static DaydreamViewController sInstance = null;
private BroadcastReceiver mReceiver = null;
- private ControllerManager mControllerManager = null;
- private Controller mController = null;
+// private ControllerManager mControllerManager = null;
+// private Controller mController = null;
public static DaydreamViewController getInstance(Context context) {
if (DaydreamViewController.sInstance == null) {
@@ -49,7 +50,7 @@ public DaydreamViewController(Context context) {
public static void start(final Context context) {
DaydreamViewController.getInstance(context).startGenerator();
-
+/*
DaydreamViewController.sInstance.mControllerManager = new ControllerManager(context, new ControllerManager.EventListener() {
@Override
public void onApiStatusChanged(int status) {
@@ -78,6 +79,8 @@ public void onUpdate() {
});
DaydreamViewController.sInstance.mControllerManager.start();
+
+*/
}
private void startGenerator() {
@@ -103,9 +106,9 @@ public static ArrayList diagnostics(Context context) {
}
public static void bindViewHolder(DataPointViewHolder holder, final Bundle dataPoint) {
+ /*
final Context context = holder.itemView.getContext();
- /*
try {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
JSONArray history = new JSONArray(prefs.getString(ScreenState.SCREEN_HISTORY_KEY, "[]"));
@@ -171,8 +174,12 @@ public static View fetchView(ViewGroup parent)
return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_daydream_vr_controller, parent, false);
}
- public static void broadcastLatestDataPoint(Context context) {
- Generators.getInstance(context).transmitData(DaydreamViewController.GENERATOR_IDENTIFIER, new Bundle());
+ @Override
+ public List fetchPayloads() {
+ return null;
}
+ public static void broadcastLatestDataPoint(Context context) {
+// Generators.getInstance(context).transmitData(DaydreamViewController.GENERATOR_IDENTIFIER, new Bundle());
+ }
}
diff --git a/src/com/audacious_software/passive_data_kit/generators/wearables/MicrosoftBand.java b/src/com/audacious_software/passive_data_kit/generators/wearables/MicrosoftBand.java
index 295aade..246e7d3 100755
--- a/src/com/audacious_software/passive_data_kit/generators/wearables/MicrosoftBand.java
+++ b/src/com/audacious_software/passive_data_kit/generators/wearables/MicrosoftBand.java
@@ -65,6 +65,7 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
@SuppressWarnings("unused")
public class MicrosoftBand extends Generator
@@ -543,7 +544,7 @@ private void transmitData(BandSensorManager sensors) {
this.mHeartRateVariabilityDataPoints.clear();
}
- Generators.getInstance(this.mContext).transmitData(MicrosoftBand.GENERATOR_IDENTIFIER, bundle);
+// Generators.getInstance(this.mContext).transmitData(MicrosoftBand.GENERATOR_IDENTIFIER, bundle);
}
public static ArrayList diagnostics(Context context)
@@ -560,7 +561,7 @@ private ArrayList runDiagostics() {
final MicrosoftBand me = this;
if (MicrosoftBand.sInstance.mBandClient == null) {
- actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_msft_band_client), new Runnable() {
+ actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_msft_band_client_title), me.mContext.getString(R.string.diagnostic_missing_msft_band_client), new Runnable() {
@Override
public void run() {
@@ -574,7 +575,7 @@ public void run() {
}
}));
} else if (!MicrosoftBand.sInstance.mBandClient.isConnected()) {
- actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_msft_band_client), new Runnable() {
+ actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_msft_band_client_title), me.mContext.getString(R.string.diagnostic_missing_msft_band_client), new Runnable() {
@Override
public void run() {
@@ -595,7 +596,7 @@ public void run() {
if (this.canAccessSensor(sensors, MicrosoftBand.HeartRateDataPoint.class) ||
this.canAccessSensor(sensors, MicrosoftBand.HeartRateVariabilityDataPoint.class)) {
if (sensors.getCurrentHeartRateConsent() != UserConsent.GRANTED) {
- actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_msft_band_auth), new Runnable() {
+ actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_msft_band_auth_title), me.mContext.getString(R.string.diagnostic_missing_msft_band_auth), new Runnable() {
@Override
public void run() {
@@ -1429,4 +1430,9 @@ public static void bindViewHolder(DataPointViewHolder holder, Bundle dataPoint)
uvLevel.setText(context.getString(R.string.generator_value_not_applicable));
}
}
+
+ @Override
+ public List fetchPayloads() {
+ return new ArrayList<>();
+ }
}
diff --git a/src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java b/src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java
new file mode 100755
index 0000000..8aaab44
--- /dev/null
+++ b/src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java
@@ -0,0 +1,1175 @@
+package com.audacious_software.passive_data_kit.generators.wearables;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.preference.PreferenceManager;
+import android.util.Base64;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.audacious_software.passive_data_kit.PassiveDataKit;
+import com.audacious_software.passive_data_kit.activities.generators.DataPointViewHolder;
+import com.audacious_software.passive_data_kit.diagnostics.DiagnosticAction;
+import com.audacious_software.passive_data_kit.generators.Generator;
+import com.audacious_software.passive_data_kit.generators.Generators;
+import com.audacious_software.passive_data_kit.generators.diagnostics.AppEvent;
+import com.audacious_software.pdk.passivedatakit.R;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.Charset;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.UUID;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+
+public class WithingsDevice extends Generator {
+ private static final String GENERATOR_IDENTIFIER = "pdk-withings-device";
+
+ private static final String ENABLED = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.ENABLED";
+ private static final boolean ENABLED_DEFAULT = true;
+
+ private static final String DATASTREAM = "datastream";
+ private static final String DATASTREAM_ACTIVITY_MEASURES = "activity-measures";
+
+ private static final String TABLE_ACTIVITY_MEASURE_HISTORY = "activity_measure_history";
+ private static final String ACTIVITY_MEASURE_HISTORY_DATE_START = "date_start";
+ private static final String ACTIVITY_MEASURE_HISTORY_TIMEZONE = "timezone";
+ private static final String ACTIVITY_MEASURE_STEPS = "steps";
+ private static final String ACTIVITY_MEASURE_DISTANCE = "distance";
+ private static final String ACTIVITY_MEASURE_ACTIVE_CALORIES = "active_calories";
+ private static final String ACTIVITY_MEASURE_TOTAL_CALORIES = "total_calories";
+ private static final String ACTIVITY_MEASURE_ELEVATION = "elevation";
+ private static final String ACTIVITY_MEASURE_SOFT_ACTIVITY_DURATION = "soft_activity_duration";
+ private static final String ACTIVITY_MEASURE_MODERATE_ACTIVITY_DURATION = "moderate_activity_duration";
+ private static final String ACTIVITY_MEASURE_INTENSE_ACTIVITY_DURATION = "intense_activity_duration";
+
+ private static final String TABLE_BODY_MEASURE_HISTORY = "body_measure_history";
+ private static final String BODY_MEASURE_STATUS_UNKNOWN = "unknown";
+ private static final String BODY_MEASURE_STATUS_USER_DEVICE = "user-device";
+ private static final String BODY_MEASURE_STATUS_SHARED_DEVICE = "shared-device";
+ private static final String BODY_MEASURE_STATUS_MANUAL_ENTRY = "manual-entry";
+ private static final String BODY_MEASURE_STATUS_MANUAL_ENTRY_CREATION = "manual-entry-creation";
+ private static final String BODY_MEASURE_STATUS_AUTO_DEVICE = "auto-device";
+ private static final String BODY_MEASURE_STATUS_MEASURE_CONFIRMED = "measure-confirmed";
+
+ private static final String BODY_MEASURE_CATEGORY_UNKNOWN = "unknown";
+ private static final String BODY_MEASURE_CATEGORY_REAL_MEASUREMENTS = "real-measurements";
+ private static final String BODY_MEASURE_CATEGORY_USER_OBJECTIVES = "user-objectives";
+
+ private static final String BODY_MEASURE_TYPE_UNKNOWN = "unknown";
+ private static final String BODY_MEASURE_TYPE_WEIGHT = "weight";
+ private static final String BODY_MEASURE_TYPE_HEIGHT = "height";
+ private static final String BODY_MEASURE_TYPE_FAT_FREE_MASS = "fat-free-mass";
+ private static final String BODY_MEASURE_TYPE_FAT_RATIO = "fat-ratio";
+ private static final String BODY_MEASURE_TYPE_FAT_MASS_WEIGHT = "fat-mass-weight";
+ private static final String BODY_MEASURE_TYPE_DIASTOLIC_BLOOD_PRESSURE = "diastolic-blood-pressure";
+ private static final String BODY_MEASURE_TYPE_SYSTOLIC_BLOOD_PRESSURE = "systolic-blood-pressure";
+ private static final String BODY_MEASURE_TYPE_HEART_PULSE = "heart-pulse";
+ private static final String BODY_MEASURE_TYPE_TEMPERATURE = "temperature";
+ private static final String BODY_MEASURE_TYPE_OXYGEN_SATURATION = "oxygen-saturation";
+ private static final String BODY_MEASURE_TYPE_BODY_TEMPERATURE = "body-temperature";
+ private static final String BODY_MEASURE_TYPE_SKIN_TEMPERATURE = "skin-temperature";
+ private static final String BODY_MEASURE_TYPE_MUSCLE_MASS = "muscle-mass";
+ private static final String BODY_MEASURE_TYPE_HYDRATION = "hydration";
+ private static final String BODY_MEASURE_TYPE_BONE_MASS = "bone-mass";
+ private static final String BODY_MEASURE_TYPE_PULSE_WAVE_VELOCITY = "pulse-wave-velocity";
+
+ private static final String BODY_MEASURE_HISTORY_DATE = "measure_date";
+ private static final String BODY_MEASURE_HISTORY_STATUS = "measure_status";
+ private static final String BODY_MEASURE_HISTORY_CATEGORY = "measure_category";
+ private static final String BODY_MEASURE_HISTORY_TYPE = "measure_type";
+ private static final String BODY_MEASURE_HISTORY_VALUE = "measure_value";
+
+ private static final String TABLE_INTRADAY_ACTIVITY_HISTORY = "intraday_activity_history";
+
+ private static final String TABLE_SLEEP_MEASURE_HISTORY = "sleep_measure_history";
+ private static final String SLEEP_MEASURE_MODEL_UNKNOWN = "unknown";
+ private static final String SLEEP_MEASURE_MODEL_ACTIVITY_TRACKER = "activity-tracker";
+ private static final String SLEEP_MEASURE_MODEL_AURA = "aura";
+
+ private static final String SLEEP_MEASURE_STATE_UNKNOWN = "unknown";
+ private static final String SLEEP_MEASURE_STATE_AWAKE = "awake";
+ private static final String SLEEP_MEASURE_STATE_LIGHT_SLEEP = "light-sleep";
+ private static final String SLEEP_MEASURE_STATE_DEEP_SLEEP = "deep-sleep";
+ private static final String SLEEP_MEASURE_STATE_REM_SLEEP = "rem-sleep";
+
+ private static final String SLEEP_MEASURE_START_DATE = "start_date";
+ private static final String SLEEP_MEASURE_END_DATE = "end_date";
+ private static final String SLEEP_MEASURE_STATE = "state";
+ private static final String SLEEP_MEASURE_MEASUREMENT_DEVICE = "measurement_device";
+
+
+ private static final String TABLE_SLEEP_SUMMARY_HISTORY = "sleep_summary_history";
+ private static final String SLEEP_SUMMARY_MODEL_UNKNOWN = "unknown";
+ private static final String SLEEP_SUMMARY_MODEL_ACTIVITY_TRACKER = "activity-tracker";
+ private static final String SLEEP_SUMMARY_MODEL_AURA = "aura";
+
+ private static final String SLEEP_SUMMARY_START_DATE = "start_date";
+ private static final String SLEEP_SUMMARY_END_DATE = "end_date";
+ private static final String SLEEP_SUMMARY_TIMEZONE = "timezone";
+ private static final String SLEEP_SUMMARY_MEASUREMENT_DEVICE = "measurement_device";
+ private static final String SLEEP_SUMMARY_WAKE_DURATION = "wake_duration";
+ private static final String SLEEP_SUMMARY_LIGHT_SLEEP_DURATION = "light_sleep_duration";
+ private static final String SLEEP_SUMMARY_DEEP_SLEEP_DURATION = "deep_sleep_duration";
+ private static final String SLEEP_SUMMARY_TO_SLEEP_DURATION = "to_sleep_duration";
+ private static final String SLEEP_SUMMARY_WAKE_COUNT = "wake_count";
+ private static final String SLEEP_SUMMARY_REM_SLEEP_DURATION = "rem_sleep_duration";
+ private static final String SLEEP_SUMMARY_TO_WAKE_DURATION = "to_wake_duration";
+
+ private static final String TABLE_WORKOUT_HISTORY = "workout_history";
+
+ private static final String HISTORY_OBSERVED = "observed";
+ private static final String DATABASE_PATH = "pdk-withings-device.sqlite";;
+ private static final int DATABASE_VERSION = 1;
+
+ private static final String LAST_DATA_FETCH = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.LAST_DATA_FETCH";
+
+ private static final String DATA_FETCH_INTERVAL = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.DATA_FETCH_INTERVAL";
+ private static final long DATA_FETCH_INTERVAL_DEFAULT = (60 * 60 * 1000);
+
+ private static final String ACTIVITY_MEASURES_ENABLED = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.ACTIVITY_MEASURES_ENABLED";
+ private static final boolean ACTIVITY_MEASURES_ENABLED_DEFAULT = true;
+
+ private static final String BODY_MEASURES_ENABLED = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.BODY_MEASURES_ENABLED";
+ private static final boolean BODY_MEASURES_ENABLED_DEFAULT = true;
+
+ private static final String INTRADAY_ACTIVITY_ENABLED = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.INTRADAY_ACTIVITY_ENABLED";
+ private static final boolean INTRADAY_ACTIVITY_ENABLED_DEFAULT = false;
+
+ private static final String SLEEP_MEASURES_ENABLED = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.SLEEP_MEASURES_ENABLED";
+ private static final boolean SLEEP_MEASURES_ENABLED_DEFAULT = true;
+
+ private static final String SLEEP_SUMMARY_ENABLED = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.SLEEP_SUMMARY_ENABLED";
+ private static final boolean SLEEP_SUMMARY_ENABLED_DEFAULT = true;
+
+ private static final String WORKOUTS_ENABLED = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.WORKOUTS_ENABLED";
+ private static final boolean WORKOUTS_ENABLED_DEFAULT = true;
+
+ public static final String OPTION_OAUTH_CALLBACK_URL = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.OPTION_CALLBACK_URL";
+ public static final String OPTION_OAUTH_CONSUMER_KEY = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.OPTION_OAUTH_CONSUMER_KEY";
+ public static final String OPTION_OAUTH_CONSUMER_SECRET = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.OPTION_OAUTH_CONSUMER_SECRET";
+ private static final String OPTION_OAUTH_ACCESS_TOKEN = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN";
+ private static final String OPTION_OAUTH_ACCESS_TOKEN_SECRET = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN_SECRET";
+ private static final String OPTION_OAUTH_REQUEST_SECRET = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.OPTION_OAUTH_REQUEST_SECRET";
+ private static final String OPTION_OAUTH_ACCESS_USER_ID = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.OPTION_OAUTH_ACCESS_USER_ID";
+
+ private static final String API_ACTION_ACTIVITY_URL = "https://wbsapi.withings.net/v2/measure?action=getactivity";
+ private static final String API_ACTION_BODY_MEASURES_URL = "https://wbsapi.withings.net/measure?action=getmeas";
+ private static final String API_ACTION_INTRADAY_ACTIVITY_URL = "https://wbsapi.withings.net/v2/measure?action=getintradayactivity";
+ private static final String API_ACTION_SLEEP_MEASURES_URL = "https://wbsapi.withings.net/v2/sleep?action=get";
+ private static final String API_ACTION_SLEEP_SUMMARY_URL = "https://wbsapi.withings.net/v2/sleep?action=getsummary";
+ private static final String API_ACTION_WORKOUTS_URL = "https://wbsapi.withings.net/v2/measure?action=getworkouts";
+ public static final String API_OAUTH_CALLBACK_PATH = "/oauth/withings";
+
+ private static WithingsDevice sInstance = null;
+ private Context mContext = null;
+ private SQLiteDatabase mDatabase = null;
+ private Handler mHandler = null;
+ private Map mProperties = new HashMap<>();
+
+ public static WithingsDevice getInstance(Context context) {
+ if (WithingsDevice.sInstance == null) {
+ WithingsDevice.sInstance = new WithingsDevice(context.getApplicationContext());
+ }
+
+ return WithingsDevice.sInstance;
+ }
+
+ public WithingsDevice(Context context) {
+ super(context);
+
+ this.mContext = context.getApplicationContext();
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext);
+
+ SharedPreferences.Editor e = prefs.edit();
+ e.remove(WithingsDevice.LAST_DATA_FETCH);
+ e.apply();
+ }
+
+ public static void start(final Context context) {
+ WithingsDevice.getInstance(context).startGenerator();
+ }
+
+ private void startGenerator() {
+ final WithingsDevice me = this;
+
+ if (this.mHandler != null) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ this.mHandler.getLooper().quitSafely();
+ } else {
+ this.mHandler.getLooper().quit();
+ }
+
+ this.mHandler = null;
+ }
+
+ final Runnable fetchData = new Runnable() {
+ @Override
+ public void run() {
+ Log.e("PDK", "WITHINGS FETCH DATA");
+
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(me.mContext);
+ long fetchInterval = prefs.getLong(WithingsDevice.DATA_FETCH_INTERVAL, WithingsDevice.DATA_FETCH_INTERVAL_DEFAULT);
+
+ if (me.approvalGranted()) {
+ Log.e("PDK", "WITHINGS APPROVED");
+
+ long lastFetch = prefs.getLong(WithingsDevice.LAST_DATA_FETCH, 0);
+
+ long now = System.currentTimeMillis();
+
+ if (now - lastFetch > fetchInterval) {
+ Log.e("PDK", "TIME TO FETCH");
+
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ if (prefs.getBoolean(WithingsDevice.ACTIVITY_MEASURES_ENABLED, WithingsDevice.ACTIVITY_MEASURES_ENABLED_DEFAULT)) {
+ me.fetchActivityMeasures();
+ }
+
+ if (prefs.getBoolean(WithingsDevice.BODY_MEASURES_ENABLED, WithingsDevice.BODY_MEASURES_ENABLED_DEFAULT)) {
+ me.fetchBodyMeasures();
+ }
+
+ if (prefs.getBoolean(WithingsDevice.INTRADAY_ACTIVITY_ENABLED, WithingsDevice.INTRADAY_ACTIVITY_ENABLED_DEFAULT)) {
+ me.fetchIntradayActivities();
+ }
+
+ if (prefs.getBoolean(WithingsDevice.SLEEP_MEASURES_ENABLED, WithingsDevice.SLEEP_MEASURES_ENABLED_DEFAULT)) {
+ me.fetchSleepMeasures();
+ }
+
+ if (prefs.getBoolean(WithingsDevice.SLEEP_SUMMARY_ENABLED, WithingsDevice.SLEEP_SUMMARY_ENABLED_DEFAULT)) {
+ me.fetchSleepSummary();
+ }
+
+ if (prefs.getBoolean(WithingsDevice.WORKOUTS_ENABLED, WithingsDevice.WORKOUTS_ENABLED_DEFAULT)) {
+ me.fetchWorkouts();
+ }
+ }
+ };
+
+ Thread t = new Thread(r);
+ t.start();
+
+ SharedPreferences.Editor e = prefs.edit();
+ e.putLong(WithingsDevice.LAST_DATA_FETCH, now);
+ e.apply();
+ } else {
+ Log.e("PDK", "NOT TIME TO FETCH");
+ }
+ } else {
+ Log.e("PDK", "WITHINGS NOT APPROVED");
+ }
+
+ if (me.mHandler != null) {
+ me.mHandler.postDelayed(this, fetchInterval);
+ }
+ }
+ };
+
+ File path = PassiveDataKit.getGeneratorsStorage(this.mContext);
+
+ path = new File(path, WithingsDevice.DATABASE_PATH);
+
+ this.mDatabase = SQLiteDatabase.openOrCreateDatabase(path, null);
+
+ int version = this.getDatabaseVersion(this.mDatabase);
+
+ switch (version) {
+ case 0:
+ this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_withings_create_activity_measure_history_table));
+ this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_withings_create_body_measure_history_table));
+ this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_withings_create_intraday_activity_history_table));
+ this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_withings_create_sleep_measure_history_table));
+ this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_withings_create_sleep_summary_history_table));
+ this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_withings_create_workout_history_table));
+ }
+
+ this.setDatabaseVersion(this.mDatabase, WithingsDevice.DATABASE_VERSION);
+
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ Looper.prepare();
+
+ me.mHandler = new Handler();
+
+ Looper.loop();
+ }
+ };
+
+ Thread t = new Thread(r);
+ t.start();
+
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ me.mHandler.post(fetchData);
+
+ Generators.getInstance(this.mContext).registerCustomViewClass(WithingsDevice.GENERATOR_IDENTIFIER, WithingsDevice.class);
+ }
+
+ private String getProperty(String key) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext);
+
+ if (WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN.equals(key)) {
+ return prefs.getString(WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN, null);
+ } else if (WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN_SECRET.equals(key)) {
+ return prefs.getString(WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN_SECRET, null);
+ } else if (WithingsDevice.OPTION_OAUTH_ACCESS_USER_ID.equals(key)) {
+ return prefs.getString(WithingsDevice.OPTION_OAUTH_ACCESS_USER_ID, null);
+ }
+
+ return this.mProperties.get(key);
+ }
+
+ private JSONObject queryApi(String apiUrl) {
+ final WithingsDevice me = this;
+
+ String apiKey = this.getProperty(WithingsDevice.OPTION_OAUTH_CONSUMER_KEY);
+ String apiSecret = this.getProperty(WithingsDevice.OPTION_OAUTH_CONSUMER_SECRET);
+ String token = this.getProperty(WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN);
+ String tokenSecret = this.getProperty(WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN_SECRET);
+
+ Calendar cal = Calendar.getInstance();
+ cal.set(Calendar.HOUR_OF_DAY, 0);
+ cal.set(Calendar.MINUTE, 0);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+
+ String startDate = null;
+ String endDate = null;
+
+ long startTime = 0;
+ long endTime = 0;
+
+ if (apiKey != null && apiSecret != null && token != null && tokenSecret != null) {
+ if (WithingsDevice.API_ACTION_ACTIVITY_URL.equals(apiUrl) ||
+ WithingsDevice.API_ACTION_SLEEP_SUMMARY_URL.equals(apiUrl) ||
+ WithingsDevice.API_ACTION_WORKOUTS_URL.equals(apiUrl)) {
+ SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+
+ startDate = format.format(cal.getTime());
+
+ cal.add(Calendar.DATE, 1);
+
+ endDate = format.format(cal.getTime());
+ } else if (WithingsDevice.API_ACTION_BODY_MEASURES_URL.equals(apiUrl) ||
+ WithingsDevice.API_ACTION_INTRADAY_ACTIVITY_URL.equals(apiUrl) ||
+ WithingsDevice.API_ACTION_SLEEP_MEASURES_URL.equals(apiUrl)) {
+
+ startTime = cal.getTimeInMillis() / 1000;
+
+ cal.add(Calendar.DATE, 1);
+
+ endTime = cal.getTimeInMillis() / 1000;
+ }
+ Uri apiUri = Uri.parse(apiUrl);
+
+ Uri.Builder builder = new Uri.Builder();
+ builder.scheme(apiUri.getScheme());
+ builder.authority(apiUri.getAuthority());
+ builder.path(apiUri.getPath());
+
+ try {
+ String signature = "GET&" + URLEncoder.encode(builder.build().toString(), "UTF-8");
+
+ String action = apiUri.getQueryParameter("action");
+ String nonce = UUID.randomUUID().toString();
+
+ builder.appendQueryParameter("action", action);
+
+ if (endTime != 0) {
+ builder.appendQueryParameter("enddate", "" + endTime);
+ }
+
+ if (endDate != null) {
+ builder.appendQueryParameter("enddateymd", endDate);
+ }
+
+ builder.appendQueryParameter("oauth_consumer_key", apiKey);
+ builder.appendQueryParameter("oauth_nonce", nonce);
+ builder.appendQueryParameter("oauth_signature_method", "HMAC-SHA1");
+ builder.appendQueryParameter("oauth_timestamp", "" + (System.currentTimeMillis() / 1000));
+ builder.appendQueryParameter("oauth_token", token);
+ builder.appendQueryParameter("oauth_version", "1.0");
+
+ if (startTime != 0) {
+ builder.appendQueryParameter("startdate", "" + startTime);
+ }
+
+ if (startDate != null) {
+ builder.appendQueryParameter("startdateymd", startDate);
+ }
+
+ builder.appendQueryParameter("userid", this.getProperty(WithingsDevice.OPTION_OAUTH_ACCESS_USER_ID));
+
+ Uri baseUri = builder.build();
+
+ signature += "&" + URLEncoder.encode(baseUri.getEncodedQuery(), "UTF-8");
+
+ String key = apiSecret + "&" + tokenSecret;
+
+ SecretKeySpec secret = new SecretKeySpec(key.getBytes(), "HmacSHA1");
+ Mac mac = Mac.getInstance("HmacSHA1");
+ mac.init(secret);
+
+ byte[] bytes = mac.doFinal(signature.getBytes(Charset.forName("UTF-8")));
+
+ signature = Base64.encodeToString(bytes, Base64.DEFAULT);
+
+ builder.appendQueryParameter("oauth_signature", signature.trim());
+
+ Uri uri = builder.build();
+
+ OkHttpClient client = new OkHttpClient();
+
+ Request request = new Request.Builder()
+ .url(uri.toString())
+ .build();
+
+ Response response = client.newCall(request).execute();
+
+ if (response.isSuccessful()) {
+ return new JSONObject(response.body().string());
+ }
+ } catch (NoSuchAlgorithmException e) {
+ AppEvent.getInstance(me.mContext).logThrowable(e);
+ } catch (UnsupportedEncodingException e) {
+ AppEvent.getInstance(me.mContext).logThrowable(e);
+ } catch (IOException e) {
+ AppEvent.getInstance(me.mContext).logThrowable(e);
+ } catch (InvalidKeyException e) {
+ AppEvent.getInstance(me.mContext).logThrowable(e);
+ } catch (JSONException e) {
+ AppEvent.getInstance(me.mContext).logThrowable(e);
+ }
+ }
+
+ return null;
+ }
+
+ private void fetchActivityMeasures() {
+ JSONObject response = this.queryApi(WithingsDevice.API_ACTION_ACTIVITY_URL);
+
+ if (response != null) {
+ try {
+ if (response.getInt("status") == 0) {
+ JSONObject body = response.getJSONObject("body");
+ JSONArray activities = body.getJSONArray("activities");
+
+ for (int i = 0; i < activities.length(); i++) {
+ JSONObject activity = activities.getJSONObject(i);
+
+ Calendar cal = Calendar.getInstance();
+
+ String[] tokens = activity.getString("date").split("-");
+
+ cal.set(Calendar.YEAR, Integer.parseInt(tokens[0]));
+ cal.set(Calendar.MONTH, Integer.parseInt(tokens[1]));
+ cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(tokens[2]));
+ cal.set(Calendar.HOUR_OF_DAY, 0);
+ cal.set(Calendar.MINUTE, 0);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+
+ ContentValues values = new ContentValues();
+ values.put(WithingsDevice.HISTORY_OBSERVED, System.currentTimeMillis());
+
+ values.put(WithingsDevice.ACTIVITY_MEASURE_HISTORY_DATE_START, cal.getTimeInMillis());
+ values.put(WithingsDevice.ACTIVITY_MEASURE_HISTORY_TIMEZONE, activity.getString("timezone"));
+ values.put(WithingsDevice.ACTIVITY_MEASURE_STEPS, activity.getDouble("steps"));
+ values.put(WithingsDevice.ACTIVITY_MEASURE_DISTANCE, activity.getDouble("distance"));
+ values.put(WithingsDevice.ACTIVITY_MEASURE_ACTIVE_CALORIES, activity.getDouble("calories"));
+ values.put(WithingsDevice.ACTIVITY_MEASURE_TOTAL_CALORIES, activity.getDouble("totalcalories"));
+ values.put(WithingsDevice.ACTIVITY_MEASURE_ELEVATION, activity.getDouble("elevation"));
+ values.put(WithingsDevice.ACTIVITY_MEASURE_SOFT_ACTIVITY_DURATION, activity.getDouble("soft"));
+ values.put(WithingsDevice.ACTIVITY_MEASURE_MODERATE_ACTIVITY_DURATION, activity.getDouble("moderate"));
+ values.put(WithingsDevice.ACTIVITY_MEASURE_INTENSE_ACTIVITY_DURATION, activity.getDouble("intense"));
+
+ this.mDatabase.insert(WithingsDevice.TABLE_ACTIVITY_MEASURE_HISTORY, null, values);
+
+ Bundle updated = new Bundle();
+
+ updated.putLong(WithingsDevice.HISTORY_OBSERVED, System.currentTimeMillis());
+ updated.putLong(WithingsDevice.ACTIVITY_MEASURE_HISTORY_DATE_START, cal.getTimeInMillis());
+ updated.putString(WithingsDevice.ACTIVITY_MEASURE_HISTORY_TIMEZONE, activity.getString("timezone"));
+ updated.putDouble(WithingsDevice.ACTIVITY_MEASURE_STEPS, activity.getDouble("steps"));
+ updated.putDouble(WithingsDevice.ACTIVITY_MEASURE_DISTANCE, activity.getDouble("distance"));
+ updated.putDouble(WithingsDevice.ACTIVITY_MEASURE_ACTIVE_CALORIES, activity.getDouble("calories"));
+ updated.putDouble(WithingsDevice.ACTIVITY_MEASURE_TOTAL_CALORIES, activity.getDouble("totalcalories"));
+ updated.putDouble(WithingsDevice.ACTIVITY_MEASURE_ELEVATION, activity.getDouble("elevation"));
+ updated.putDouble(WithingsDevice.ACTIVITY_MEASURE_SOFT_ACTIVITY_DURATION, activity.getDouble("soft"));
+ updated.putDouble(WithingsDevice.ACTIVITY_MEASURE_MODERATE_ACTIVITY_DURATION, activity.getDouble("moderate"));
+ updated.putDouble(WithingsDevice.ACTIVITY_MEASURE_INTENSE_ACTIVITY_DURATION, activity.getDouble("intense"));
+ updated.putString(WithingsDevice.DATASTREAM, WithingsDevice.DATASTREAM_ACTIVITY_MEASURES);
+
+ Generators.getInstance(this.mContext).notifyGeneratorUpdated(WithingsDevice.GENERATOR_IDENTIFIER, updated);
+ }
+ }
+ } catch (JSONException e) {
+ AppEvent.getInstance(this.mContext).logThrowable(e);
+ }
+ }
+ }
+
+ private void fetchBodyMeasures() {
+ JSONObject response = this.queryApi(WithingsDevice.API_ACTION_BODY_MEASURES_URL);
+
+ if (response != null) {
+ try {
+ if (response.getInt("status") == 0) {
+ JSONObject body = response.getJSONObject("body");
+ JSONArray measureGroups = body.getJSONArray("measuregrps");
+
+ for (int i = 0; i < measureGroups.length(); i++) {
+ JSONObject measureGroup = measureGroups.getJSONObject(i);
+
+ long measureDate = measureGroup.getLong("date");
+ long now = System.currentTimeMillis();
+
+ String status = WithingsDevice.BODY_MEASURE_STATUS_UNKNOWN;
+
+ switch (measureGroup.getInt("attrib")) {
+ case 0:
+ status = WithingsDevice.BODY_MEASURE_STATUS_USER_DEVICE;
+ break;
+ case 1:
+ status = WithingsDevice.BODY_MEASURE_STATUS_SHARED_DEVICE;
+ break;
+ case 2:
+ status = WithingsDevice.BODY_MEASURE_STATUS_MANUAL_ENTRY;
+ break;
+ case 4:
+ status = WithingsDevice.BODY_MEASURE_STATUS_MANUAL_ENTRY_CREATION;
+ break;
+ case 5:
+ status = WithingsDevice.BODY_MEASURE_STATUS_AUTO_DEVICE;
+ break;
+ case 7:
+ status = WithingsDevice.BODY_MEASURE_STATUS_MEASURE_CONFIRMED;
+ break;
+ }
+
+ String category = WithingsDevice.BODY_MEASURE_CATEGORY_UNKNOWN;
+
+ switch (measureGroup.getInt("category")) {
+ case 1:
+ category = WithingsDevice.BODY_MEASURE_CATEGORY_REAL_MEASUREMENTS;
+ break;
+ case 2:
+ category = WithingsDevice.BODY_MEASURE_CATEGORY_USER_OBJECTIVES;
+ break;
+ }
+
+ JSONArray measures = measureGroup.getJSONArray("measures");
+
+ for (int j = 0; j < measures.length(); j++) {
+ JSONObject measure = measures.optJSONObject(j);
+
+ ContentValues values = new ContentValues();
+ values.put(WithingsDevice.HISTORY_OBSERVED, now);
+
+ String type = WithingsDevice.BODY_MEASURE_TYPE_UNKNOWN;
+
+ switch (measure.getInt("type")) {
+ case 1:
+ type = WithingsDevice.BODY_MEASURE_TYPE_WEIGHT;
+ break;
+ case 4:
+ type = WithingsDevice.BODY_MEASURE_TYPE_HEIGHT;
+ break;
+ case 5:
+ type = WithingsDevice.BODY_MEASURE_TYPE_FAT_FREE_MASS;
+ break;
+ case 6:
+ type = WithingsDevice.BODY_MEASURE_TYPE_FAT_RATIO;
+ break;
+ case 8:
+ type = WithingsDevice.BODY_MEASURE_TYPE_FAT_MASS_WEIGHT;
+ break;
+ case 9:
+ type = WithingsDevice.BODY_MEASURE_TYPE_DIASTOLIC_BLOOD_PRESSURE;
+ break;
+ case 10:
+ type = WithingsDevice.BODY_MEASURE_TYPE_SYSTOLIC_BLOOD_PRESSURE;
+ break;
+ case 11:
+ type = WithingsDevice.BODY_MEASURE_TYPE_HEART_PULSE;
+ break;
+ case 12:
+ type = WithingsDevice.BODY_MEASURE_TYPE_TEMPERATURE;
+ break;
+ case 54:
+ type = WithingsDevice.BODY_MEASURE_TYPE_OXYGEN_SATURATION;
+ break;
+ case 71:
+ type = WithingsDevice.BODY_MEASURE_TYPE_BODY_TEMPERATURE;
+ break;
+ case 73:
+ type = WithingsDevice.BODY_MEASURE_TYPE_SKIN_TEMPERATURE;
+ break;
+ case 76:
+ type = WithingsDevice.BODY_MEASURE_TYPE_MUSCLE_MASS;
+ break;
+ case 77:
+ type = WithingsDevice.BODY_MEASURE_TYPE_HYDRATION;
+ break;
+ case 88:
+ type = WithingsDevice.BODY_MEASURE_TYPE_BONE_MASS;
+ break;
+ case 91:
+ type = WithingsDevice.BODY_MEASURE_TYPE_PULSE_WAVE_VELOCITY;
+ break;
+ }
+
+ double value = measure.getDouble("value") * Math.pow(10, measure.getDouble("unit"));
+
+ values.put(WithingsDevice.BODY_MEASURE_HISTORY_DATE, measureDate);
+ values.put(WithingsDevice.BODY_MEASURE_HISTORY_STATUS, status);
+ values.put(WithingsDevice.BODY_MEASURE_HISTORY_CATEGORY, category);
+ values.put(WithingsDevice.BODY_MEASURE_HISTORY_TYPE, type);
+ values.put(WithingsDevice.BODY_MEASURE_HISTORY_VALUE, value);
+
+ this.mDatabase.insert(WithingsDevice.TABLE_BODY_MEASURE_HISTORY, null, values);
+
+ Bundle updated = new Bundle();
+
+ updated.putLong(WithingsDevice.HISTORY_OBSERVED, System.currentTimeMillis());
+ updated.putLong(WithingsDevice.BODY_MEASURE_HISTORY_DATE, measureDate);
+ updated.putString(WithingsDevice.BODY_MEASURE_HISTORY_STATUS, status);
+ updated.putString(WithingsDevice.BODY_MEASURE_HISTORY_CATEGORY, category);
+ updated.putString(WithingsDevice.BODY_MEASURE_HISTORY_TYPE, type);
+ updated.putDouble(WithingsDevice.BODY_MEASURE_HISTORY_VALUE, value);
+
+ Generators.getInstance(this.mContext).notifyGeneratorUpdated(WithingsDevice.GENERATOR_IDENTIFIER, updated);
+ }
+ }
+ }
+ } catch (JSONException e) {
+ AppEvent.getInstance(this.mContext).logThrowable(e);
+ }
+ }
+ }
+
+ private void fetchIntradayActivities() {
+ JSONObject response = this.queryApi(WithingsDevice.API_ACTION_INTRADAY_ACTIVITY_URL);
+
+ if (response != null) {
+ // TODO
+ }
+ }
+
+ private void fetchSleepMeasures() {
+ JSONObject response = this.queryApi(WithingsDevice.API_ACTION_SLEEP_MEASURES_URL);
+
+ if (response != null) {
+ try {
+ if (response.getInt("status") == 0) {
+ JSONObject body = response.getJSONObject("body");
+
+ String model = WithingsDevice.SLEEP_MEASURE_MODEL_UNKNOWN;
+
+ switch(body.getInt("model")) {
+ case 16:
+ model = WithingsDevice.SLEEP_MEASURE_MODEL_ACTIVITY_TRACKER;
+ break;
+ case 32:
+ model = WithingsDevice.SLEEP_MEASURE_MODEL_AURA;
+ break;
+ }
+
+ JSONArray series = body.getJSONArray("series");
+
+ for (int i = 0; i < series.length(); i++) {
+ JSONObject item = series.getJSONObject(i);
+
+ long now = System.currentTimeMillis();
+
+ String state = WithingsDevice.SLEEP_MEASURE_STATE_UNKNOWN;
+
+ switch (item.getInt("state")) {
+ case 0:
+ state = WithingsDevice.SLEEP_MEASURE_STATE_AWAKE;
+ break;
+ case 1:
+ state = WithingsDevice.SLEEP_MEASURE_STATE_LIGHT_SLEEP;
+ break;
+ case 2:
+ state = WithingsDevice.SLEEP_MEASURE_STATE_DEEP_SLEEP;
+ break;
+ case 3:
+ state = WithingsDevice.SLEEP_MEASURE_STATE_REM_SLEEP;
+ break;
+ }
+
+ ContentValues values = new ContentValues();
+ values.put(WithingsDevice.HISTORY_OBSERVED, now);
+ values.put(WithingsDevice.SLEEP_MEASURE_START_DATE, item.getLong("startdate"));
+ values.put(WithingsDevice.SLEEP_MEASURE_END_DATE, item.getLong("enddate"));
+ values.put(WithingsDevice.SLEEP_MEASURE_STATE, state);
+ values.put(WithingsDevice.SLEEP_MEASURE_MEASUREMENT_DEVICE, model);
+
+ this.mDatabase.insert(WithingsDevice.TABLE_SLEEP_MEASURE_HISTORY, null, values);
+
+ Bundle updated = new Bundle();
+ updated.putLong(WithingsDevice.HISTORY_OBSERVED, System.currentTimeMillis());
+ updated.putLong(WithingsDevice.SLEEP_MEASURE_START_DATE, item.getLong("startdate"));
+ updated.putLong(WithingsDevice.SLEEP_MEASURE_END_DATE, item.getLong("enddate"));
+ updated.putString(WithingsDevice.SLEEP_MEASURE_STATE, state);
+ updated.putString(WithingsDevice.SLEEP_MEASURE_MEASUREMENT_DEVICE, model);
+
+ Generators.getInstance(this.mContext).notifyGeneratorUpdated(WithingsDevice.GENERATOR_IDENTIFIER, updated);
+ }
+ }
+ } catch (JSONException e) {
+ AppEvent.getInstance(this.mContext).logThrowable(e);
+ }
+ }
+ }
+
+ private void fetchSleepSummary() {
+ JSONObject response = this.queryApi(WithingsDevice.API_ACTION_SLEEP_SUMMARY_URL);
+
+ if (response != null) {
+ try {
+ if (response.getInt("status") == 0) {
+ JSONObject body = response.getJSONObject("body");
+
+ JSONArray series = body.getJSONArray("series");
+
+ long now = System.currentTimeMillis();
+
+ for (int i = 0; i < series.length(); i++) {
+ JSONObject item = series.getJSONObject(i);
+
+ String timezone = body.getString("timezone");
+
+ String model = WithingsDevice.SLEEP_SUMMARY_MODEL_UNKNOWN;
+
+ switch(body.getInt("model")) {
+ case 16:
+ model = WithingsDevice.SLEEP_SUMMARY_MODEL_ACTIVITY_TRACKER;
+ break;
+ case 32:
+ model = WithingsDevice.SLEEP_SUMMARY_MODEL_AURA;
+ break;
+ }
+
+ JSONObject data = item.getJSONObject("data");
+
+ ContentValues values = new ContentValues();
+ values.put(WithingsDevice.HISTORY_OBSERVED, now);
+ values.put(WithingsDevice.SLEEP_SUMMARY_START_DATE, item.getLong("startdate"));
+ values.put(WithingsDevice.SLEEP_SUMMARY_END_DATE, item.getLong("enddate"));
+ values.put(WithingsDevice.SLEEP_SUMMARY_TIMEZONE, timezone);
+ values.put(WithingsDevice.SLEEP_SUMMARY_MEASUREMENT_DEVICE, model);
+ values.put(WithingsDevice.SLEEP_SUMMARY_WAKE_DURATION, data.getDouble("wakeupduration"));
+ values.put(WithingsDevice.SLEEP_SUMMARY_LIGHT_SLEEP_DURATION, data.getDouble("lightsleepduration"));
+ values.put(WithingsDevice.SLEEP_SUMMARY_DEEP_SLEEP_DURATION, data.getDouble("deepsleepduration"));
+ values.put(WithingsDevice.SLEEP_SUMMARY_TO_SLEEP_DURATION, data.getDouble("durationtosleep"));
+ values.put(WithingsDevice.SLEEP_SUMMARY_WAKE_COUNT, data.getDouble("wakeupcount"));
+
+ if (data.has("remsleepduration")) {
+ values.put(WithingsDevice.SLEEP_SUMMARY_REM_SLEEP_DURATION, data.getDouble("remsleepduration"));
+ }
+
+ if (data.has("durationtowakeup")) {
+ values.put(WithingsDevice.SLEEP_SUMMARY_TO_WAKE_DURATION, data.getDouble("durationtowakeup"));
+ }
+
+ this.mDatabase.insert(WithingsDevice.TABLE_SLEEP_MEASURE_HISTORY, null, values);
+
+ Bundle updated = new Bundle();
+ updated.putLong(WithingsDevice.HISTORY_OBSERVED, now);
+ updated.putLong(WithingsDevice.SLEEP_SUMMARY_START_DATE, item.getLong("startdate"));
+ updated.putLong(WithingsDevice.SLEEP_SUMMARY_END_DATE, item.getLong("enddate"));
+ updated.putString(WithingsDevice.SLEEP_SUMMARY_TIMEZONE, timezone);
+ updated.putString(WithingsDevice.SLEEP_SUMMARY_MEASUREMENT_DEVICE, model);
+ updated.putDouble(WithingsDevice.SLEEP_SUMMARY_WAKE_DURATION, data.getDouble("wakeupduration"));
+ updated.putDouble(WithingsDevice.SLEEP_SUMMARY_LIGHT_SLEEP_DURATION, data.getDouble("lightsleepduration"));
+ updated.putDouble(WithingsDevice.SLEEP_SUMMARY_DEEP_SLEEP_DURATION, data.getDouble("deepsleepduration"));
+ updated.putDouble(WithingsDevice.SLEEP_SUMMARY_TO_SLEEP_DURATION, data.getDouble("durationtosleep"));
+ updated.putDouble(WithingsDevice.SLEEP_SUMMARY_WAKE_COUNT, data.getDouble("wakeupcount"));
+
+ if (data.has("remsleepduration")) {
+ updated.putDouble(WithingsDevice.SLEEP_SUMMARY_REM_SLEEP_DURATION, data.getDouble("remsleepduration"));
+ }
+
+ if (data.has("durationtowakeup")) {
+ updated.putDouble(WithingsDevice.SLEEP_SUMMARY_TO_WAKE_DURATION, data.getDouble("durationtowakeup"));
+ }
+
+ Generators.getInstance(this.mContext).notifyGeneratorUpdated(WithingsDevice.GENERATOR_IDENTIFIER, updated);
+ }
+ }
+ } catch (JSONException e) {
+ AppEvent.getInstance(this.mContext).logThrowable(e);
+ }
+ }
+ }
+
+ private void fetchWorkouts() {
+ JSONObject response = this.queryApi(WithingsDevice.API_ACTION_WORKOUTS_URL);
+
+ if (response != null) {
+
+ }
+ }
+
+ private boolean approvalGranted() {
+ String apiToken = this.getProperty(WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN);
+
+ if (apiToken != null) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public static boolean isEnabled(Context context) {
+ SharedPreferences prefs = Generators.getInstance(context).getSharedPreferences(context);
+
+ return prefs.getBoolean(WithingsDevice.ENABLED, WithingsDevice.ENABLED_DEFAULT);
+ }
+
+ public static boolean isRunning(Context context) {
+ if (WithingsDevice.sInstance == null) {
+ return false;
+ }
+
+ return WithingsDevice.sInstance.mHandler != null;
+ }
+
+ public static ArrayList diagnostics(final Context context) {
+ final WithingsDevice me = WithingsDevice.getInstance(context);
+
+ ArrayList actions = new ArrayList<>();
+
+ actions.add(new DiagnosticAction(context.getString(R.string.diagnostic_withings_auth_required_title), context.getString(R.string.diagnostic_withings_auth_required), new Runnable() {
+
+ @Override
+ public void run() {
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ String requestUrl = "https://oauth.withings.com/account/request_token";
+
+ Uri apiUri = Uri.parse(requestUrl);
+
+ Uri.Builder builder = new Uri.Builder();
+ builder.scheme(apiUri.getScheme());
+ builder.authority(apiUri.getAuthority());
+ builder.path(apiUri.getPath());
+
+ String signature = "GET&" + URLEncoder.encode(builder.build().toString(), "UTF-8");
+
+ String callbackUrl = me.getProperty(WithingsDevice.OPTION_OAUTH_CALLBACK_URL);
+ String apiKey = me.getProperty(WithingsDevice.OPTION_OAUTH_CONSUMER_KEY);
+ String apiSecret = me.getProperty(WithingsDevice.OPTION_OAUTH_CONSUMER_SECRET);
+
+ String nonce = UUID.randomUUID().toString();
+
+ builder.appendQueryParameter("oauth_callback", callbackUrl);
+ builder.appendQueryParameter("oauth_consumer_key", apiKey);
+ builder.appendQueryParameter("oauth_nonce", nonce);
+ builder.appendQueryParameter("oauth_signature_method", "HMAC-SHA1");
+ builder.appendQueryParameter("oauth_timestamp", "" + (System.currentTimeMillis() / 1000));
+ builder.appendQueryParameter("oauth_version", "1.0");
+
+ Uri baseUri = builder.build();
+
+ signature += "&" + URLEncoder.encode(baseUri.getEncodedQuery(), "UTF-8");
+
+ String key = apiSecret + "&";
+
+ SecretKeySpec secret = new SecretKeySpec(key.getBytes(), "HmacSHA1");
+ Mac mac = Mac.getInstance("HmacSHA1");
+ mac.init(secret);
+
+ byte[] bytes = mac.doFinal(signature.getBytes());
+
+ signature = Base64.encodeToString(bytes, Base64.DEFAULT).trim();
+
+ builder.appendQueryParameter("oauth_signature", signature);
+
+ Uri uri = builder.build();
+
+ OkHttpClient client = new OkHttpClient();
+
+ Request request = new Request.Builder()
+ .url(uri.toString())
+ .build();
+
+ Response response = client.newCall(request).execute();
+
+ String responseBody = response.body().string();
+
+ StringTokenizer st = new StringTokenizer(responseBody, "&");
+
+ String requestToken = null;
+ String requestSecret = null;
+
+ while (st.hasMoreTokens()) {
+ String authToken = st.nextToken();
+
+ if (authToken.startsWith("oauth_token=")) {
+ requestToken = authToken.replace("oauth_token=", "");
+ } else if (authToken.startsWith("oauth_token_secret=")) {
+ requestSecret = authToken.replace("oauth_token_secret=", "");
+ }
+ }
+
+ key = apiSecret + "&" + requestSecret;
+
+ me.setProperty(WithingsDevice.OPTION_OAUTH_REQUEST_SECRET, requestSecret);
+
+ builder = new Uri.Builder();
+ builder.scheme("https");
+ builder.authority("oauth.withings.com");
+ builder.path("/account/authorize");
+
+ signature = "GET&" + URLEncoder.encode(builder.build().toString(), "UTF-8");
+
+ nonce = UUID.randomUUID().toString();
+
+ builder.appendQueryParameter("oauth_consumer_key", apiKey);
+ builder.appendQueryParameter("oauth_nonce", nonce);
+ builder.appendQueryParameter("oauth_signature_method", "HMAC-SHA1");
+ builder.appendQueryParameter("oauth_timestamp", "" + (System.currentTimeMillis() / 1000));
+ builder.appendQueryParameter("oauth_token", requestToken);
+ builder.appendQueryParameter("oauth_version", "1.0");
+
+ baseUri = builder.build();
+
+ signature += "&" + URLEncoder.encode(baseUri.getEncodedQuery(), "UTF-8");
+
+ secret = new SecretKeySpec(key.getBytes(), "HmacSHA1");
+ mac = Mac.getInstance("HmacSHA1");
+ mac.init(secret);
+
+ bytes = mac.doFinal(signature.getBytes(Charset.forName("UTF-8")));
+
+ signature = Base64.encodeToString(bytes, Base64.DEFAULT);
+
+ builder.appendQueryParameter("oauth_signature", signature.trim());
+
+ Intent intent = new Intent(Intent.ACTION_VIEW, builder.build());
+ context.startActivity(intent);
+ } catch (NoSuchAlgorithmException e) {
+ AppEvent.getInstance(context).logThrowable(e);
+ } catch (InvalidKeyException e) {
+ AppEvent.getInstance(context).logThrowable(e);
+ } catch (UnsupportedEncodingException e) {
+ AppEvent.getInstance(context).logThrowable(e);
+ } catch (IOException e) {
+ AppEvent.getInstance(context).logThrowable(e);
+ }
+ }
+ };
+
+ Thread t = new Thread(r);
+ t.start();
+ }
+ }));
+
+ return actions;
+ }
+
+ public void finishAuthentication(final Uri responseUri) {
+ final WithingsDevice me = this;
+
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ String requestUrl = "https://oauth.withings.com/account/access_token";
+
+ Uri apiUri = Uri.parse(requestUrl);
+
+ Uri.Builder builder = new Uri.Builder();
+ builder.scheme(apiUri.getScheme());
+ builder.authority(apiUri.getAuthority());
+ builder.path(apiUri.getPath());
+
+ String signature = "GET&" + URLEncoder.encode(builder.build().toString(), "UTF-8");
+
+ String callbackUrl = me.getProperty(WithingsDevice.OPTION_OAUTH_CALLBACK_URL);
+ String apiKey = me.getProperty(WithingsDevice.OPTION_OAUTH_CONSUMER_KEY);
+ String apiSecret = me.getProperty(WithingsDevice.OPTION_OAUTH_CONSUMER_SECRET);
+
+ String nonce = UUID.randomUUID().toString();
+
+ builder.appendQueryParameter("oauth_consumer_key", apiKey);
+ builder.appendQueryParameter("oauth_nonce", nonce);
+ builder.appendQueryParameter("oauth_signature_method", "HMAC-SHA1");
+ builder.appendQueryParameter("oauth_timestamp", "" + (System.currentTimeMillis() / 1000));
+ builder.appendQueryParameter("oauth_token", responseUri.getQueryParameter("oauth_token"));
+ builder.appendQueryParameter("oauth_version", "1.0");
+
+ Uri baseUri = builder.build();
+
+ signature += "&" + URLEncoder.encode(baseUri.getEncodedQuery(), "UTF-8");
+
+ String key = apiSecret + "&" + me.getProperty(WithingsDevice.OPTION_OAUTH_REQUEST_SECRET);
+
+ SecretKeySpec secret = new SecretKeySpec(key.getBytes(), "HmacSHA1");
+ Mac mac = Mac.getInstance("HmacSHA1");
+ mac.init(secret);
+
+ byte[] bytes = mac.doFinal(signature.getBytes());
+
+ signature = Base64.encodeToString(bytes, Base64.DEFAULT).trim();
+
+ builder.appendQueryParameter("oauth_signature", signature);
+
+ Uri uri = builder.build();
+
+ OkHttpClient client = new OkHttpClient();
+
+ Request request = new Request.Builder()
+ .url(uri.toString())
+ .build();
+
+ Response response = client.newCall(request).execute();
+
+ String responseBody = response.body().string();
+
+ StringTokenizer st = new StringTokenizer(responseBody, "&");
+
+ while (st.hasMoreTokens()) {
+ String tokenString = st.nextToken();
+
+ if (tokenString.startsWith("oauth_token=")) {
+ me.setProperty(WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN, tokenString.replace("oauth_token=", ""));
+ } else if (tokenString.startsWith("oauth_token_secret=")) {
+ me.setProperty(WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN_SECRET, tokenString.replace("oauth_token_secret=", ""));
+ } else if (tokenString.startsWith("userid=")) {
+ me.setProperty(WithingsDevice.OPTION_OAUTH_ACCESS_USER_ID, tokenString.replace("userid=", ""));
+ }
+ }
+
+ me.fetchActivityMeasures();
+
+ } catch (UnsupportedEncodingException e) {
+ AppEvent.getInstance(me.mContext).logThrowable(e);
+ } catch (NoSuchAlgorithmException e) {
+ AppEvent.getInstance(me.mContext).logThrowable(e);
+ } catch (IOException e) {
+ AppEvent.getInstance(me.mContext).logThrowable(e);
+ } catch (InvalidKeyException e) {
+ AppEvent.getInstance(me.mContext).logThrowable(e);
+ }
+ }
+ };
+
+ Thread t = new Thread(r);
+ t.start();
+ }
+
+ public static void bindViewHolder(DataPointViewHolder holder) {
+/*
+ final Context context = holder.itemView.getContext();
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+
+ long lastTimestamp = 0;
+ long lastDuration = 0;
+ String callType = null;
+
+ long totalIncoming = 0;
+ long totalOutgoing = 0;
+ long totalMissed = 0;
+ long total = 0;
+
+ WithingsDevice generator = WithingsDevice.getInstance(holder.itemView.getContext());
+
+ View cardContent = holder.itemView.findViewById(R.id.card_content);
+ View cardEmpty = holder.itemView.findViewById(R.id.card_empty);
+ TextView dateLabel = (TextView) holder.itemView.findViewById(R.id.generator_data_point_date);
+*/
+ }
+
+ @Override
+ public List fetchPayloads() {
+ return new ArrayList<>();
+ }
+
+ public static View fetchView(ViewGroup parent)
+ {
+ return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_withings_device, parent, false);
+ }
+
+ public static long latestPointGenerated(Context context) {
+ long timestamp = 0;
+
+ WithingsDevice me = WithingsDevice.getInstance(context);
+
+// Cursor c = me.mDatabase.query(WithingsDevice.TABLE_HISTORY, null, null, null, null, null, WithingsDevice.HISTORY_OBSERVED + " DESC");
+//
+// if (c.moveToNext()) {
+// timestamp = c.getLong(c.getColumnIndex(WithingsDevice.HISTORY_OBSERVED));
+// }
+//
+// c.close();
+
+ return timestamp;
+ }
+
+ public void setProperty(String key, String value) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext);
+
+ if (WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN.equals(key)) {
+ SharedPreferences.Editor e = prefs.edit();
+ e.putString(WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN, value);
+ e.apply();
+ } else if (WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN_SECRET.equals(key)) {
+ SharedPreferences.Editor e = prefs.edit();
+ e.putString(WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN_SECRET, value);
+ e.apply();
+ } else if (WithingsDevice.OPTION_OAUTH_ACCESS_USER_ID.equals(key)) {
+ SharedPreferences.Editor e = prefs.edit();
+ e.putString(WithingsDevice.OPTION_OAUTH_ACCESS_USER_ID, value);
+ e.apply();
+ }
+
+ this.mProperties.put(key, value);
+ }
+}
diff --git a/src/com/audacious_software/passive_data_kit/transmitters/HttpTransmitter.java b/src/com/audacious_software/passive_data_kit/transmitters/HttpTransmitter.java
index 93f74d3..1d9a2cb 100755
--- a/src/com/audacious_software/passive_data_kit/transmitters/HttpTransmitter.java
+++ b/src/com/audacious_software/passive_data_kit/transmitters/HttpTransmitter.java
@@ -46,7 +46,7 @@
import okhttp3.Response;
import okio.Buffer;
-public class HttpTransmitter extends Transmitter implements Generators.NewDataPointListener {
+public class HttpTransmitter extends Transmitter implements Generators.GeneratorUpdatedListener {
public static final String UPLOAD_URI = "com.audacious_software.passive_data_kit.transmitters.HttpTransmitter.UPLOAD_URI";
public static final String USER_ID = "com.audacious_software.passive_data_kit.transmitters.HttpTransmitter.USER_ID";
private static final String HASH_ALGORITHM = "com.audacious_software.passive_data_kit.transmitters.HttpTransmitter.HASH_ALGORITHM";
@@ -133,16 +133,23 @@ else if (!options.containsKey(HttpTransmitter.USER_ID)) {
this.mContext = context.getApplicationContext();
- Generators.getInstance(this.mContext).addNewDataPointListener(this);
+ Generators.getInstance(this.mContext).addNewGeneratorUpdatedListener(this);
}
- private boolean shouldAttemptUpload() {
+ private boolean shouldAttemptUpload(boolean force) {
+ if (force) {
+ return true;
+ }
if (this.mWifiOnly) {
- return DeviceInformation.wifiAvailable(this.mContext);
+ if (DeviceInformation.wifiAvailable(this.mContext) == false) {
+ return false;
+ }
}
if (this.mChargingOnly) {
- return DeviceInformation.isPluggedIn(this.mContext);
+ if (DeviceInformation.isPluggedIn(this.mContext) == false) {
+ return false;
+ }
}
return true;
@@ -156,7 +163,7 @@ public void transmit(boolean force) {
this.mLastAttempt = 0;
}
- if (now - this.mLastAttempt < this.mUploadInterval || !this.shouldAttemptUpload()) {
+ if (now - this.mLastAttempt < this.mUploadInterval || !this.shouldAttemptUpload(force)) {
return;
}
@@ -314,7 +321,6 @@ private int transmitHttpPayload(String payload) {
builder = builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"payload\""), RequestBody.create(null, payload));
-
RequestBody requestBody = builder.build();
if (this.mUserAgent == null) {
@@ -418,12 +424,25 @@ public long transmittedSize() {
}
@Override
- public void onNewDataPoint(String identifier, Bundle data) {
+ public void onGeneratorUpdated(String identifier, long timestamp, Bundle data) {
if (data.keySet().size() > 1) { // Only transmit non-empty bundles...
+ timestamp = timestamp / 1000; // Convert to seconds...
+
+ Generators generators = Generators.getInstance(this.mContext);
+
+ Bundle metadata = new Bundle();
+
if (data.containsKey(Generator.PDK_METADATA)) {
- data.getBundle(Generator.PDK_METADATA).putString(Generator.SOURCE, this.mUserId);
+ metadata = data.getBundle(Generator.PDK_METADATA);
}
+ metadata.putString(Generator.IDENTIFIER, identifier);
+ metadata.putDouble(Generator.TIMESTAMP, timestamp);
+ metadata.putString(Generator.GENERATOR, generators.getGeneratorFullName(identifier));
+ metadata.putString(Generator.SOURCE, generators.getSource());
+ metadata.putString(Generator.SOURCE, this.mUserId);
+ data.putBundle(Generator.PDK_METADATA, metadata);
+
if (this.mJsonGenerator == null) {
this.mCurrentFile = new File(this.getPendingFolder(), System.currentTimeMillis() + HttpTransmitter.TEMP_EXTENSION);
@@ -591,6 +610,10 @@ else if (value instanceof Bundle) {
}
}
+ public void setUserId(String userId) {
+ this.mUserId = userId;
+ }
+
public static class IncompleteConfigurationException extends RuntimeException {
public IncompleteConfigurationException(String message) {
super(message);