diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 45147a5..5ad723f 100755 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,10 +1,14 @@ + package="com.audacious_software.pdk.passivedatakit" + xmlns:tools="http://schemas.android.com/tools"> + + + diff --git a/build.gradle b/build.gradle index 6475517..713328b 100755 --- a/build.gradle +++ b/build.gradle @@ -3,16 +3,23 @@ apply plugin: 'com.android.library' buildscript { repositories { jcenter() + flatDir { + dirs 'libs' + } } dependencies { - classpath 'com.android.tools.build:gradle:2.3.1' + classpath 'com.android.tools.build:gradle:2.3.2' } } repositories { jcenter() maven { url "https://jitpack.io" } + maven { url 'https://dl.bintray.com/rvalerio/maven' } + flatDir{ + dirs 'libs' + } } android { @@ -22,7 +29,7 @@ android { useLibrary 'org.apache.http.legacy' defaultConfig { - minSdkVersion 14 + minSdkVersion 18 targetSdkVersion 25 versionCode 1 versionName "1.0" @@ -60,6 +67,7 @@ android { 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.1' + compile 'com.rvalerio:fgchecker:1.0.1' } buildTypes { @@ -68,4 +76,4 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } -} \ No newline at end of file +} diff --git a/res/drawable-hdpi/ic_foreground_apps.png b/res/drawable-hdpi/ic_foreground_apps.png new file mode 100755 index 0000000..4879c67 Binary files /dev/null and b/res/drawable-hdpi/ic_foreground_apps.png differ diff --git a/res/drawable-hdpi/ic_page_indicator.png b/res/drawable-hdpi/ic_page_indicator.png new file mode 100755 index 0000000..0bc147b Binary files /dev/null and b/res/drawable-hdpi/ic_page_indicator.png differ diff --git a/res/drawable-hdpi/ic_pdk_accelerometer.png b/res/drawable-hdpi/ic_pdk_accelerometer.png new file mode 100755 index 0000000..0fdc766 Binary files /dev/null and b/res/drawable-hdpi/ic_pdk_accelerometer.png differ diff --git a/res/drawable-hdpi/ic_pdk_action_lock.png b/res/drawable-hdpi/ic_pdk_action_lock.png new file mode 100755 index 0000000..6409845 Binary files /dev/null and b/res/drawable-hdpi/ic_pdk_action_lock.png differ diff --git a/res/drawable-hdpi/ic_pdk_action_unlock.png b/res/drawable-hdpi/ic_pdk_action_unlock.png new file mode 100755 index 0000000..f5a7c05 Binary files /dev/null and b/res/drawable-hdpi/ic_pdk_action_unlock.png differ diff --git a/res/drawable-hdpi/ic_pdk_ambient_light.png b/res/drawable-hdpi/ic_pdk_ambient_light.png new file mode 100755 index 0000000..01cd42f Binary files /dev/null and b/res/drawable-hdpi/ic_pdk_ambient_light.png differ diff --git a/res/drawable-hdpi/ic_pdk_app_events.png b/res/drawable-hdpi/ic_pdk_app_events.png new file mode 100755 index 0000000..508e845 Binary files /dev/null and b/res/drawable-hdpi/ic_pdk_app_events.png differ diff --git a/res/drawable-hdpi/ic_pdk_device_battery.png b/res/drawable-hdpi/ic_pdk_device_battery.png new file mode 100755 index 0000000..000f85c Binary files /dev/null and b/res/drawable-hdpi/ic_pdk_device_battery.png differ diff --git a/res/drawable-hdpi/ic_microsoft_band.png b/res/drawable-hdpi/ic_pdk_microsoft_band.png similarity index 100% rename from res/drawable-hdpi/ic_microsoft_band.png rename to res/drawable-hdpi/ic_pdk_microsoft_band.png diff --git a/res/drawable-hdpi/ic_phone_calls.png b/res/drawable-hdpi/ic_pdk_phone_calls.png similarity index 100% rename from res/drawable-hdpi/ic_phone_calls.png rename to res/drawable-hdpi/ic_pdk_phone_calls.png diff --git a/res/drawable-hdpi/ic_screen_state.png b/res/drawable-hdpi/ic_pdk_screen_state.png similarity index 100% rename from res/drawable-hdpi/ic_screen_state.png rename to res/drawable-hdpi/ic_pdk_screen_state.png diff --git a/res/drawable-hdpi/ic_pdk_system_status.png b/res/drawable-hdpi/ic_pdk_system_status.png new file mode 100755 index 0000000..303b8b1 Binary files /dev/null and b/res/drawable-hdpi/ic_pdk_system_status.png differ diff --git a/res/drawable-hdpi/ic_text_messages.png b/res/drawable-hdpi/ic_pdk_text_messages.png similarity index 100% rename from res/drawable-hdpi/ic_text_messages.png rename to res/drawable-hdpi/ic_pdk_text_messages.png diff --git a/res/drawable-hdpi/ic_pdk_withings_device.png b/res/drawable-hdpi/ic_pdk_withings_device.png new file mode 100755 index 0000000..168df46 Binary files /dev/null and b/res/drawable-hdpi/ic_pdk_withings_device.png differ diff --git a/res/drawable-mdpi/ic_foreground_apps.png b/res/drawable-mdpi/ic_foreground_apps.png new file mode 100755 index 0000000..f34723a Binary files /dev/null and b/res/drawable-mdpi/ic_foreground_apps.png differ diff --git a/res/drawable-mdpi/ic_page_indicator.png b/res/drawable-mdpi/ic_page_indicator.png new file mode 100755 index 0000000..3042fde Binary files /dev/null and b/res/drawable-mdpi/ic_page_indicator.png differ diff --git a/res/drawable-mdpi/ic_pdk_accelerometer.png b/res/drawable-mdpi/ic_pdk_accelerometer.png new file mode 100755 index 0000000..5570049 Binary files /dev/null and b/res/drawable-mdpi/ic_pdk_accelerometer.png differ diff --git a/res/drawable-mdpi/ic_pdk_action_lock.png b/res/drawable-mdpi/ic_pdk_action_lock.png new file mode 100755 index 0000000..53377c1 Binary files /dev/null and b/res/drawable-mdpi/ic_pdk_action_lock.png differ diff --git a/res/drawable-mdpi/ic_pdk_action_unlock.png b/res/drawable-mdpi/ic_pdk_action_unlock.png new file mode 100755 index 0000000..542703d Binary files /dev/null and b/res/drawable-mdpi/ic_pdk_action_unlock.png differ diff --git a/res/drawable-mdpi/ic_pdk_ambient_light.png b/res/drawable-mdpi/ic_pdk_ambient_light.png new file mode 100755 index 0000000..183e053 Binary files /dev/null and b/res/drawable-mdpi/ic_pdk_ambient_light.png differ diff --git a/res/drawable-mdpi/ic_pdk_app_events.png b/res/drawable-mdpi/ic_pdk_app_events.png new file mode 100755 index 0000000..a93ed1b Binary files /dev/null and b/res/drawable-mdpi/ic_pdk_app_events.png differ diff --git a/res/drawable-mdpi/ic_pdk_device_battery.png b/res/drawable-mdpi/ic_pdk_device_battery.png new file mode 100755 index 0000000..163df18 Binary files /dev/null and b/res/drawable-mdpi/ic_pdk_device_battery.png differ diff --git a/res/drawable-mdpi/ic_microsoft_band.png b/res/drawable-mdpi/ic_pdk_microsoft_band.png similarity index 100% rename from res/drawable-mdpi/ic_microsoft_band.png rename to res/drawable-mdpi/ic_pdk_microsoft_band.png diff --git a/res/drawable-mdpi/ic_phone_calls.png b/res/drawable-mdpi/ic_pdk_phone_calls.png similarity index 100% rename from res/drawable-mdpi/ic_phone_calls.png rename to res/drawable-mdpi/ic_pdk_phone_calls.png diff --git a/res/drawable-mdpi/ic_screen_state.png b/res/drawable-mdpi/ic_pdk_screen_state.png similarity index 100% rename from res/drawable-mdpi/ic_screen_state.png rename to res/drawable-mdpi/ic_pdk_screen_state.png diff --git a/res/drawable-mdpi/ic_pdk_system_status.png b/res/drawable-mdpi/ic_pdk_system_status.png new file mode 100755 index 0000000..4deaf7c Binary files /dev/null and b/res/drawable-mdpi/ic_pdk_system_status.png differ diff --git a/res/drawable-mdpi/ic_text_messages.png b/res/drawable-mdpi/ic_pdk_text_messages.png similarity index 100% rename from res/drawable-mdpi/ic_text_messages.png rename to res/drawable-mdpi/ic_pdk_text_messages.png diff --git a/res/drawable-mdpi/ic_pdk_withings_device.png b/res/drawable-mdpi/ic_pdk_withings_device.png new file mode 100755 index 0000000..5b184d8 Binary files /dev/null and b/res/drawable-mdpi/ic_pdk_withings_device.png differ diff --git a/res/drawable-xhdpi/ic_foreground_apps.png b/res/drawable-xhdpi/ic_foreground_apps.png new file mode 100755 index 0000000..ba91b51 Binary files /dev/null and b/res/drawable-xhdpi/ic_foreground_apps.png differ diff --git a/res/drawable-xhdpi/ic_page_indicator.png b/res/drawable-xhdpi/ic_page_indicator.png new file mode 100755 index 0000000..a1900c6 Binary files /dev/null and b/res/drawable-xhdpi/ic_page_indicator.png differ diff --git a/res/drawable-xhdpi/ic_pdk_accelerometer.png b/res/drawable-xhdpi/ic_pdk_accelerometer.png new file mode 100755 index 0000000..b5cdeaf Binary files /dev/null and b/res/drawable-xhdpi/ic_pdk_accelerometer.png differ diff --git a/res/drawable-xhdpi/ic_pdk_action_lock.png b/res/drawable-xhdpi/ic_pdk_action_lock.png new file mode 100755 index 0000000..093cf20 Binary files /dev/null and b/res/drawable-xhdpi/ic_pdk_action_lock.png differ diff --git a/res/drawable-xhdpi/ic_pdk_action_unlock.png b/res/drawable-xhdpi/ic_pdk_action_unlock.png new file mode 100755 index 0000000..5d14385 Binary files /dev/null and b/res/drawable-xhdpi/ic_pdk_action_unlock.png differ diff --git a/res/drawable-xhdpi/ic_pdk_ambient_light.png b/res/drawable-xhdpi/ic_pdk_ambient_light.png new file mode 100755 index 0000000..64185f1 Binary files /dev/null and b/res/drawable-xhdpi/ic_pdk_ambient_light.png differ diff --git a/res/drawable-xhdpi/ic_pdk_app_events.png b/res/drawable-xhdpi/ic_pdk_app_events.png new file mode 100755 index 0000000..2273c76 Binary files /dev/null and b/res/drawable-xhdpi/ic_pdk_app_events.png differ diff --git a/res/drawable-xhdpi/ic_pdk_device_battery.png b/res/drawable-xhdpi/ic_pdk_device_battery.png new file mode 100755 index 0000000..6785faf Binary files /dev/null and b/res/drawable-xhdpi/ic_pdk_device_battery.png differ diff --git a/res/drawable-xhdpi/ic_microsoft_band.png b/res/drawable-xhdpi/ic_pdk_microsoft_band.png similarity index 100% rename from res/drawable-xhdpi/ic_microsoft_band.png rename to res/drawable-xhdpi/ic_pdk_microsoft_band.png diff --git a/res/drawable-xhdpi/ic_phone_calls.png b/res/drawable-xhdpi/ic_pdk_phone_calls.png similarity index 100% rename from res/drawable-xhdpi/ic_phone_calls.png rename to res/drawable-xhdpi/ic_pdk_phone_calls.png diff --git a/res/drawable-xhdpi/ic_screen_state.png b/res/drawable-xhdpi/ic_pdk_screen_state.png similarity index 100% rename from res/drawable-xhdpi/ic_screen_state.png rename to res/drawable-xhdpi/ic_pdk_screen_state.png diff --git a/res/drawable-xhdpi/ic_pdk_system_status.png b/res/drawable-xhdpi/ic_pdk_system_status.png new file mode 100755 index 0000000..db32136 Binary files /dev/null and b/res/drawable-xhdpi/ic_pdk_system_status.png differ diff --git a/res/drawable-xhdpi/ic_text_messages.png b/res/drawable-xhdpi/ic_pdk_text_messages.png similarity index 100% rename from res/drawable-xhdpi/ic_text_messages.png rename to res/drawable-xhdpi/ic_pdk_text_messages.png diff --git a/res/drawable-xhdpi/ic_pdk_withings_device.png b/res/drawable-xhdpi/ic_pdk_withings_device.png new file mode 100755 index 0000000..73aece2 Binary files /dev/null and b/res/drawable-xhdpi/ic_pdk_withings_device.png differ diff --git a/res/drawable-xxhdpi/ic_foreground_apps.png b/res/drawable-xxhdpi/ic_foreground_apps.png new file mode 100755 index 0000000..7c69935 Binary files /dev/null and b/res/drawable-xxhdpi/ic_foreground_apps.png differ diff --git a/res/drawable-xxhdpi/ic_page_indicator.png b/res/drawable-xxhdpi/ic_page_indicator.png new file mode 100755 index 0000000..af86b8c Binary files /dev/null and b/res/drawable-xxhdpi/ic_page_indicator.png differ diff --git a/res/drawable-xxhdpi/ic_pdk_accelerometer.png b/res/drawable-xxhdpi/ic_pdk_accelerometer.png new file mode 100755 index 0000000..d9970ce Binary files /dev/null and b/res/drawable-xxhdpi/ic_pdk_accelerometer.png differ diff --git a/res/drawable-xxhdpi/ic_pdk_action_lock.png b/res/drawable-xxhdpi/ic_pdk_action_lock.png new file mode 100755 index 0000000..620d614 Binary files /dev/null and b/res/drawable-xxhdpi/ic_pdk_action_lock.png differ diff --git a/res/drawable-xxhdpi/ic_pdk_action_unlock.png b/res/drawable-xxhdpi/ic_pdk_action_unlock.png new file mode 100755 index 0000000..0ffdc60 Binary files /dev/null and b/res/drawable-xxhdpi/ic_pdk_action_unlock.png differ diff --git a/res/drawable-xxhdpi/ic_pdk_ambient_light.png b/res/drawable-xxhdpi/ic_pdk_ambient_light.png new file mode 100755 index 0000000..84d9cbb Binary files /dev/null and b/res/drawable-xxhdpi/ic_pdk_ambient_light.png differ diff --git a/res/drawable-xxhdpi/ic_pdk_app_events.png b/res/drawable-xxhdpi/ic_pdk_app_events.png new file mode 100755 index 0000000..06de16c Binary files /dev/null and b/res/drawable-xxhdpi/ic_pdk_app_events.png differ diff --git a/res/drawable-xxhdpi/ic_pdk_device_battery.png b/res/drawable-xxhdpi/ic_pdk_device_battery.png new file mode 100755 index 0000000..f8a2355 Binary files /dev/null and b/res/drawable-xxhdpi/ic_pdk_device_battery.png differ diff --git a/res/drawable-xxhdpi/ic_microsoft_band.png b/res/drawable-xxhdpi/ic_pdk_microsoft_band.png similarity index 100% rename from res/drawable-xxhdpi/ic_microsoft_band.png rename to res/drawable-xxhdpi/ic_pdk_microsoft_band.png diff --git a/res/drawable-xxhdpi/ic_phone_calls.png b/res/drawable-xxhdpi/ic_pdk_phone_calls.png similarity index 100% rename from res/drawable-xxhdpi/ic_phone_calls.png rename to res/drawable-xxhdpi/ic_pdk_phone_calls.png diff --git a/res/drawable-xxhdpi/ic_screen_state.png b/res/drawable-xxhdpi/ic_pdk_screen_state.png similarity index 100% rename from res/drawable-xxhdpi/ic_screen_state.png rename to res/drawable-xxhdpi/ic_pdk_screen_state.png diff --git a/res/drawable-xxhdpi/ic_pdk_system_status.png b/res/drawable-xxhdpi/ic_pdk_system_status.png new file mode 100755 index 0000000..ce6df18 Binary files /dev/null and b/res/drawable-xxhdpi/ic_pdk_system_status.png differ diff --git a/res/drawable-xxhdpi/ic_text_messages.png b/res/drawable-xxhdpi/ic_pdk_text_messages.png similarity index 100% rename from res/drawable-xxhdpi/ic_text_messages.png rename to res/drawable-xxhdpi/ic_pdk_text_messages.png diff --git a/res/drawable-xxhdpi/ic_pdk_withings_device.png b/res/drawable-xxhdpi/ic_pdk_withings_device.png new file mode 100755 index 0000000..5dd8a9d Binary files /dev/null and b/res/drawable-xxhdpi/ic_pdk_withings_device.png differ diff --git a/res/drawable-xxxhdpi/ic_foreground_apps.png b/res/drawable-xxxhdpi/ic_foreground_apps.png new file mode 100755 index 0000000..4a34272 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_foreground_apps.png differ diff --git a/res/drawable-xxxhdpi/ic_page_indicator.png b/res/drawable-xxxhdpi/ic_page_indicator.png new file mode 100755 index 0000000..6a92866 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_page_indicator.png differ diff --git a/res/drawable-xxxhdpi/ic_pdk_accelerometer.png b/res/drawable-xxxhdpi/ic_pdk_accelerometer.png new file mode 100755 index 0000000..855cdd6 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_pdk_accelerometer.png differ diff --git a/res/drawable-xxxhdpi/ic_pdk_action_lock.png b/res/drawable-xxxhdpi/ic_pdk_action_lock.png new file mode 100755 index 0000000..ae3f973 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_pdk_action_lock.png differ diff --git a/res/drawable-xxxhdpi/ic_pdk_action_unlock.png b/res/drawable-xxxhdpi/ic_pdk_action_unlock.png new file mode 100755 index 0000000..bc597c2 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_pdk_action_unlock.png differ diff --git a/res/drawable-xxxhdpi/ic_pdk_ambient_light.png b/res/drawable-xxxhdpi/ic_pdk_ambient_light.png new file mode 100755 index 0000000..d5c3269 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_pdk_ambient_light.png differ diff --git a/res/drawable-xxxhdpi/ic_pdk_app_events.png b/res/drawable-xxxhdpi/ic_pdk_app_events.png new file mode 100755 index 0000000..9943255 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_pdk_app_events.png differ diff --git a/res/drawable-xxxhdpi/ic_pdk_device_battery.png b/res/drawable-xxxhdpi/ic_pdk_device_battery.png new file mode 100755 index 0000000..c693378 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_pdk_device_battery.png differ diff --git a/res/drawable-xxxhdpi/ic_microsoft_band.png b/res/drawable-xxxhdpi/ic_pdk_microsoft_band.png similarity index 100% rename from res/drawable-xxxhdpi/ic_microsoft_band.png rename to res/drawable-xxxhdpi/ic_pdk_microsoft_band.png diff --git a/res/drawable-xxxhdpi/ic_phone_calls.png b/res/drawable-xxxhdpi/ic_pdk_phone_calls.png similarity index 100% rename from res/drawable-xxxhdpi/ic_phone_calls.png rename to res/drawable-xxxhdpi/ic_pdk_phone_calls.png diff --git a/res/drawable-xxxhdpi/ic_screen_state.png b/res/drawable-xxxhdpi/ic_pdk_screen_state.png similarity index 100% rename from res/drawable-xxxhdpi/ic_screen_state.png rename to res/drawable-xxxhdpi/ic_pdk_screen_state.png diff --git a/res/drawable-xxxhdpi/ic_pdk_system_status.png b/res/drawable-xxxhdpi/ic_pdk_system_status.png new file mode 100755 index 0000000..595b459 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_pdk_system_status.png differ diff --git a/res/drawable-xxxhdpi/ic_text_messages.png b/res/drawable-xxxhdpi/ic_pdk_text_messages.png similarity index 100% rename from res/drawable-xxxhdpi/ic_text_messages.png rename to res/drawable-xxxhdpi/ic_pdk_text_messages.png diff --git a/res/drawable-xxxhdpi/ic_pdk_withings_device.png b/res/drawable-xxxhdpi/ic_pdk_withings_device.png new file mode 100755 index 0000000..2803e06 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_pdk_withings_device.png differ diff --git a/res/layout/card_generator_app_event.xml b/res/layout/card_generator_app_event.xml new file mode 100755 index 0000000..472985c --- /dev/null +++ b/res/layout/card_generator_app_event.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/card_generator_app_event_page.xml b/res/layout/card_generator_app_event_page.xml new file mode 100755 index 0000000..9134d02 --- /dev/null +++ b/res/layout/card_generator_app_event_page.xml @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/card_generator_daydream_vr_controller.xml b/res/layout/card_generator_daydream_vr_controller.xml new file mode 100755 index 0000000..67b1b8a --- /dev/null +++ b/res/layout/card_generator_daydream_vr_controller.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/res/layout/card_generator_device_battery.xml b/res/layout/card_generator_device_battery.xml new file mode 100755 index 0000000..ef96661 --- /dev/null +++ b/res/layout/card_generator_device_battery.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/card_generator_diagnostics_system_status.xml b/res/layout/card_generator_diagnostics_system_status.xml new file mode 100755 index 0000000..3dc84b1 --- /dev/null +++ b/res/layout/card_generator_diagnostics_system_status.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/card_generator_foreground_application.xml b/res/layout/card_generator_foreground_application.xml new file mode 100755 index 0000000..8a159dc --- /dev/null +++ b/res/layout/card_generator_foreground_application.xml @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/card_generator_microsoft_band.xml b/res/layout/card_generator_microsoft_band.xml index e360495..ef4d6b6 100755 --- a/res/layout/card_generator_microsoft_band.xml +++ b/res/layout/card_generator_microsoft_band.xml @@ -20,7 +20,7 @@ android:background="#0075DA" android:padding="8dp" android:baselineAligned="false"> - diff --git a/res/layout/card_generator_phone_calls.xml b/res/layout/card_generator_phone_calls.xml index 2a2725b..389fa6f 100755 --- a/res/layout/card_generator_phone_calls.xml +++ b/res/layout/card_generator_phone_calls.xml @@ -21,7 +21,7 @@ android:background="#FF9800" android:padding="8dp" android:baselineAligned="false"> - diff --git a/res/layout/card_generator_screen_state.xml b/res/layout/card_generator_screen_state.xml index 7af3c2d..33beadc 100755 --- a/res/layout/card_generator_screen_state.xml +++ b/res/layout/card_generator_screen_state.xml @@ -21,7 +21,7 @@ android:background="#607D8B" android:padding="8dp" android:baselineAligned="false"> - diff --git a/res/layout/card_generator_sensors_accelerometer.xml b/res/layout/card_generator_sensors_accelerometer.xml new file mode 100755 index 0000000..3f80ec3 --- /dev/null +++ b/res/layout/card_generator_sensors_accelerometer.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/card_generator_sensors_ambient_light.xml b/res/layout/card_generator_sensors_ambient_light.xml new file mode 100755 index 0000000..0176f72 --- /dev/null +++ b/res/layout/card_generator_sensors_ambient_light.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/card_generator_text_messages.xml b/res/layout/card_generator_text_messages.xml index 03c022d..e3e22be 100755 --- a/res/layout/card_generator_text_messages.xml +++ b/res/layout/card_generator_text_messages.xml @@ -21,7 +21,7 @@ android:background="#9C27B0" android:padding="8dp" android:baselineAligned="false"> - diff --git a/res/layout/card_generator_withings_activity_page.xml b/res/layout/card_generator_withings_activity_page.xml new file mode 100755 index 0000000..35251ab --- /dev/null +++ b/res/layout/card_generator_withings_activity_page.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/card_generator_withings_body_page.xml b/res/layout/card_generator_withings_body_page.xml new file mode 100755 index 0000000..2eefb66 --- /dev/null +++ b/res/layout/card_generator_withings_body_page.xml @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/card_generator_withings_device.xml b/res/layout/card_generator_withings_device.xml new file mode 100755 index 0000000..7f5993e --- /dev/null +++ b/res/layout/card_generator_withings_device.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + diff --git a/res/layout/card_generator_withings_info_page.xml b/res/layout/card_generator_withings_info_page.xml new file mode 100755 index 0000000..4ce8198 --- /dev/null +++ b/res/layout/card_generator_withings_info_page.xml @@ -0,0 +1,13 @@ + + + + diff --git a/res/layout/card_generator_withings_intraday_page.xml b/res/layout/card_generator_withings_intraday_page.xml new file mode 100755 index 0000000..ddf6b27 --- /dev/null +++ b/res/layout/card_generator_withings_intraday_page.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/card_generator_withings_sleep_page.xml b/res/layout/card_generator_withings_sleep_page.xml new file mode 100755 index 0000000..5c6a9d5 --- /dev/null +++ b/res/layout/card_generator_withings_sleep_page.xml @@ -0,0 +1,13 @@ + + + + diff --git a/res/layout/card_generator_withings_sleep_summary_page.xml b/res/layout/card_generator_withings_sleep_summary_page.xml new file mode 100755 index 0000000..c4a7d6f --- /dev/null +++ b/res/layout/card_generator_withings_sleep_summary_page.xml @@ -0,0 +1,13 @@ + + + + diff --git a/res/layout/card_generator_withings_workouts_page.xml b/res/layout/card_generator_withings_workouts_page.xml new file mode 100755 index 0000000..2be7a18 --- /dev/null +++ b/res/layout/card_generator_withings_workouts_page.xml @@ -0,0 +1,13 @@ + + + + diff --git a/res/menu/activity_data_stream.xml b/res/menu/activity_data_stream.xml new file mode 100755 index 0000000..d6a6bbf --- /dev/null +++ b/res/menu/activity_data_stream.xml @@ -0,0 +1,8 @@ + + + + diff --git a/res/values/databases.xml b/res/values/databases.xml index 672f1cd..1b84c3f 100755 --- a/res/values/databases.xml +++ b/res/values/databases.xml @@ -16,4 +16,28 @@ CREATE TABLE history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, event_name TEXT, event_details TEXT); - \ No newline at end of file + + 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); + + + CREATE TABLE history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, health TEXT, level INTERGER, plugged TEXT, present INTEGER, scale INTEGER, temperature INTEGER, voltage INTEGER, technology TEXT, status TEXT); + + + CREATE TABLE history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, application TEXT); + ALTER TABLE history ADD duration REAL; + ALTER TABLE history ADD screen_active INTEGER; + + + CREATE TABLE history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, light_level REAL, raw_timestamp BIGINT, accuracy INTEGER); + + + CREATE TABLE history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, x REAL, y REAL, z REAL, raw_timestamp BIGINT, accuracy INTEGER); + + + CREATE TABLE history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, runtime INTEGER, storage_app INTEGER, storage_other INTEGER, storage_available INTEGER, storage_total INTEGER, storage_path TEXT); + diff --git a/res/values/diagnostics.xml b/res/values/diagnostics.xml index a656358..95035f4 100755 --- a/res/values/diagnostics.xml +++ b/res/values/diagnostics.xml @@ -16,4 +16,13 @@ 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. + + App Usage Data Permission Required + This app requires access to app usage data from your device\'s settings. + + Battery Optimization Enabled + This app uses device features that do not function consistently while the app is subject to battery optimizations. Please grant a battery optimization exemption to ensure full and consistent functioning. diff --git a/res/values/generators.xml b/res/values/generators.xml index 58c12fc..85bd3b0 100755 --- a/res/values/generators.xml +++ b/res/values/generators.xml @@ -4,8 +4,15 @@ 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.communication.TextMessages - 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.device.Battery + com.audacious_software.passive_data_kit.generators.sensors.Accelerometer + com.audacious_software.passive_data_kit.generators.sensors.AmbientLight + com.audacious_software.passive_data_kit.generators.device.ForegroundApplication + com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice + com.audacious_software.passive_data_kit.generators.diagnostics.SystemStatus @@ -116,6 +123,76 @@ No phone calls have been made or received on this device. - - Application Events - \ No newline at end of file + + Withings Device + Soft Activities + Moderate Activities + Intense Activities + #757575 + #039BE5 + #43A047 + No activity data has been downloaded yet. + Info coming soon… + Steps + Distance + Elevation + Calories + + Steps + %1$d steps + + Distance + %1$.2f km + + Elevation + %1$.2f m + + - + Body Measures + + + Device Battery + No battery levels have been reported yet. + #66BB6A + #1B5E20 + + + App Events History + No app events have been logged yet. + + + Foreground Applications + No application launches have been logged yet. + Most Used (Last 24 Hours) + Recently Used + %1$s: %2$.0fm + + + Ambient Light + No ambient light levels have been recorded yet. + #FFD600 + #80FFEB3B + #ffFFEB3B + + + Accelerometer + No accelerometer readings have been recorded yet. + #3F51B5 + #80F44336 + #ffF44336 + #804CAF50 + #ff4CAF50 + #802196F3 + #ff2196F3 + + + System Status + No status information has been collected yet. + #616161 + #2E7D32 + #FAFAFA + #757575 + Continuous Runtime: %1$s + %1$dd %2$s:%3$s:%4$s.%5$s + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 73b220f..3c77eb0 100755 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -6,6 +6,8 @@ Data Stream Continue + Toggle Sort Lock + %d generator %d generators 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 3e80bb0..a0f4808 100755 --- a/src/com/audacious_software/passive_data_kit/activities/DataStreamActivity.java +++ b/src/com/audacious_software/passive_data_kit/activities/DataStreamActivity.java @@ -1,11 +1,17 @@ package com.audacious_software.passive_data_kit.activities; +import android.content.Intent; +import android.content.SharedPreferences; import android.os.Bundle; import android.os.Handler; import android.os.Looper; +import android.preference.PreferenceManager; 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.Menu; +import android.view.MenuItem; import com.audacious_software.passive_data_kit.activities.generators.DataPointsAdapter; import com.audacious_software.passive_data_kit.generators.Generators; @@ -15,6 +21,8 @@ public class DataStreamActivity extends AppCompatActivity implements Generators.GeneratorUpdatedListener { private DataPointsAdapter mAdapter = null; + private Menu mMenu = null; + private boolean mIsUpdating = false; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -22,8 +30,11 @@ protected void onCreate(Bundle savedInstanceState) { this.setTitle(R.string.activity_data_stream); this.getSupportActionBar().setSubtitle(this.getResources().getQuantityString(R.plurals.activity_data_stream_subtitle, 0, 0)); + this.getSupportActionBar().setDisplayHomeAsUpEnabled(true); + this.mAdapter = new DataPointsAdapter(); this.mAdapter.setContext(this.getApplicationContext()); + this.mAdapter.sortGenerators(); RecyclerView listView = (RecyclerView) this.findViewById(R.id.list_view); @@ -61,13 +72,77 @@ protected void onPause() { public void onGeneratorUpdated(String identifier, long timestamp, Bundle data) { final DataStreamActivity me = this; + if (me.mIsUpdating) { + return; + } + + me.mIsUpdating = true; + this.runOnUiThread(new Runnable() { @Override public void run() { + me.mAdapter.sortGenerators(); me.mAdapter.notifyDataSetChanged(); int count = me.mAdapter.getItemCount(); me.getSupportActionBar().setSubtitle(me.getResources().getQuantityString(R.plurals.activity_data_stream_subtitle, count, count)); + + me.mIsUpdating = false; } }); } + + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + this.getMenuInflater().inflate(R.menu.activity_data_stream, menu); + + this.mMenu = menu; + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + boolean sortEnabled = prefs.getBoolean(DataPointsAdapter.SORT_BY_UPDATED, DataPointsAdapter.SORT_BY_UPDATED_DEFAULT); + + MenuItem lockedItem = this.mMenu.findItem(R.id.action_pdk_toggle_sort_lock); + + if (sortEnabled) { + lockedItem.setIcon(R.drawable.ic_pdk_action_unlock); + } else { + lockedItem.setIcon(R.drawable.ic_pdk_action_lock); + } + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + + if (id == android.R.id.home) { + this.finish(); + return true; + } else if (id == R.id.action_pdk_toggle_sort_lock) { + this.toggleSortLock(); + return true; + } + + return true; + } + + private void toggleSortLock() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + + boolean sortEnabled = prefs.getBoolean(DataPointsAdapter.SORT_BY_UPDATED, DataPointsAdapter.SORT_BY_UPDATED_DEFAULT); + + MenuItem lockedItem = this.mMenu.findItem(R.id.action_pdk_toggle_sort_lock); + + if (sortEnabled) { + lockedItem.setIcon(R.drawable.ic_pdk_action_lock); + } else { + lockedItem.setIcon(R.drawable.ic_pdk_action_unlock); + } + + SharedPreferences.Editor e = prefs.edit(); + e.putBoolean(DataPointsAdapter.SORT_BY_UPDATED, (sortEnabled == false)); + e.apply(); + + this.mAdapter.sortGenerators(); + } } 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 fd1679d..5c11fd8 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,7 +1,10 @@ package com.audacious_software.passive_data_kit.activities.generators; import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -17,7 +20,11 @@ import java.util.List; public class DataPointsAdapter extends RecyclerView.Adapter { + public static final String SORT_BY_UPDATED = "com.audacious_software.passive_data_kit.activities.generators.DataPointsAdapter"; + public static final boolean SORT_BY_UPDATED_DEFAULT = true; + private Context mContext = null; + private List> mActiveGenerators = null; @Override public DataPointViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { @@ -49,13 +56,21 @@ public DataPointViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return null; } + private List> getGenerators(Context context) { + if (this.mActiveGenerators == null) { + this.mActiveGenerators = Generators.getInstance(context).activeGenerators(); + } + + this.sortGenerators(); + + return this.mActiveGenerators; + } + @Override public void onBindViewHolder(final DataPointViewHolder holder, int position) { - List> activeGenerators = Generators.getInstance(holder.itemView.getContext()).activeGenerators(); - - this.sortGenerators(this.mContext, activeGenerators); + this.getGenerators(holder.itemView.getContext()); - Class generatorClass = activeGenerators.get(position); + Class generatorClass = this.mActiveGenerators.get(position); try { Method bindViewHolder = generatorClass.getDeclaredMethod("bindViewHolder", DataPointViewHolder.class); @@ -80,58 +95,70 @@ public void onBindViewHolder(final DataPointViewHolder holder, int position) { @Override public int getItemCount() { - return Generators.getInstance(null).activeGenerators().size(); - } + this.getGenerators(this.mContext); - private void sortGenerators(final Context context, List> generators) { - Collections.sort(generators, new Comparator>() { - @Override - public int compare(Class one, Class 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; + return this.mActiveGenerators.size(); + } - try { - Method twoGenerated = two.getDeclaredMethod("latestPointGenerated", Context.class); + public void sortGenerators() { + final Context context = this.mContext; - twoUpdated = (long) twoGenerated.invoke(null, context); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } + if (this.mActiveGenerators == null) { + this.mActiveGenerators = Generators.getInstance(this.mContext).activeGenerators(); + } - if (oneUpdated < twoUpdated) { - return 1; - } else if (oneUpdated > twoUpdated) { - return -1; + final DataPointsAdapter me = this; + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + if (prefs.getBoolean(DataPointsAdapter.SORT_BY_UPDATED, DataPointsAdapter.SORT_BY_UPDATED_DEFAULT)) { + Collections.sort(me.mActiveGenerators, new Comparator>() { + @Override + public int compare(Class one, Class 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; } - - return 0; - } - }); + }); + } } public int getItemViewType (int position) { - List> activeGenerators = Generators.getInstance(this.mContext).activeGenerators(); - - this.sortGenerators(this.mContext, activeGenerators); + this.getGenerators(this.mContext); - Class generatorClass = activeGenerators.get(position); + Class generatorClass = this.mActiveGenerators.get(position); return generatorClass.hashCode(); } diff --git a/src/com/audacious_software/passive_data_kit/activities/views/ViewPagerIndicator.java b/src/com/audacious_software/passive_data_kit/activities/views/ViewPagerIndicator.java new file mode 100755 index 0000000..e1ca120 --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/activities/views/ViewPagerIndicator.java @@ -0,0 +1,92 @@ +package com.audacious_software.passive_data_kit.activities.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import com.audacious_software.pdk.passivedatakit.R; + +import java.util.ArrayList; + +/** + * Created by cjkarr on 4/27/2017. + */ + +public class ViewPagerIndicator extends LinearLayout { + private final LinearLayout mIndicatorLayout; + + public ViewPagerIndicator(Context context, AttributeSet attrs) { + super(context, attrs); + + LinearLayout.LayoutParams borderParams = new LinearLayout.LayoutParams(12, 12); + borderParams.weight = 1; + + View left = new View(context); + left.setLayoutParams(borderParams); + left.setVisibility(View.VISIBLE); + + this.addView(left); + + this.mIndicatorLayout = new LinearLayout(context); + this.mIndicatorLayout.setOrientation(LinearLayout.HORIZONTAL); + + LinearLayout.LayoutParams indicatorParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + this.mIndicatorLayout.setLayoutParams(indicatorParams); + this.mIndicatorLayout.setVisibility(View.VISIBLE); + + this.addView(this.mIndicatorLayout); + + View right = new View(context); + right.setLayoutParams(borderParams); + right.setVisibility(View.VISIBLE); + + this.addView(right); + } + + public void setPageCount(int count) { + ArrayList toRemove = new ArrayList<>(); + + for (int i = 0; i < this.mIndicatorLayout.getChildCount(); i++) { + toRemove.add(this.mIndicatorLayout.getChildAt(i)); + } + + for (View view : toRemove) { + this.mIndicatorLayout.removeView(view); + } + + DisplayMetrics metrics = this.getContext().getResources().getDisplayMetrics(); + + for (int i = 0; i < count; i++) { + ImageView indicator = new ImageView(this.getContext()); + + indicator.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + + indicator.setPadding((int) (3 * metrics.density), (int) (3 * metrics.density), (int) (3 * metrics.density), (int) (3 * metrics.density)); + + LayoutParams params = new LayoutParams((int) (12 * metrics.density), (int) (12 * metrics.density)); + indicator.setLayoutParams(params); + + indicator.setImageResource(R.drawable.ic_page_indicator); + + this.mIndicatorLayout.addView(indicator); + } + } + + public void setSelectedPage(int position) { + DisplayMetrics metrics = this.getContext().getResources().getDisplayMetrics(); + + for (int i = 0; i < this.mIndicatorLayout.getChildCount(); i++) { + View view = this.mIndicatorLayout.getChildAt(i); + + view.setPadding((int) (3 * metrics.density), (int) (3 * metrics.density), (int) (3 * metrics.density), (int) (3 * metrics.density)); + } + + View view = this.mIndicatorLayout.getChildAt(position); + + view.setPadding((int) (1 * metrics.density), (int) (1 * metrics.density), (int) (1 * metrics.density), (int) (1 * metrics.density)); + } +} 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 384e0f1..b98ad43 100755 --- a/src/com/audacious_software/passive_data_kit/generators/Generators.java +++ b/src/com/audacious_software/passive_data_kit/generators/Generators.java @@ -5,6 +5,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Bundle; +import android.os.PowerManager; import android.preference.PreferenceManager; import android.util.Log; import android.util.SparseArray; @@ -32,6 +33,7 @@ public class Generators { private HashMap> mGeneratorMap = new HashMap<>(); private SparseArray> mViewTypeMap = new SparseArray<>(); private HashSet mGeneratorUpdatedListeners = new HashSet<>(); + private HashMap mWakeLocks = new HashMap<>(); public void start() { if (!this.mStarted) @@ -257,6 +259,28 @@ public void notifyGeneratorUpdated(String identifier, Bundle bundle) { } } + public void acquireWakeLock(String tag, int lockType) { + this.releaseWakeLock(tag); + + PowerManager power = (PowerManager) this.mContext.getSystemService(Context.POWER_SERVICE); + + PowerManager.WakeLock lock = power.newWakeLock(lockType, tag); + + this.mWakeLocks.put(tag, lock); + } + + public void releaseWakeLock(String tag) { + if (this.mWakeLocks.containsKey(tag)) { + PowerManager.WakeLock lock = this.mWakeLocks.get(tag); + + if (lock.isHeld()) { + lock.release(); + } + + this.mWakeLocks.remove(tag); + } + } + private static class GeneratorsHolder { public static Generators instance = new Generators(); } @@ -281,7 +305,7 @@ public void addNewGeneratorUpdatedListener(Generators.GeneratorUpdatedListener l } public void removeGeneratorUpdatedListener(Generators.GeneratorUpdatedListener listener) { - synchronized(this.mGeneratorUpdatedListeners) { + synchronized (this.mGeneratorUpdatedListeners) { this.mGeneratorUpdatedListeners.remove(listener); } } diff --git a/src/com/audacious_software/passive_data_kit/generators/device/Battery.java b/src/com/audacious_software/passive_data_kit/generators/device/Battery.java new file mode 100755 index 0000000..48b4159 --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/generators/device/Battery.java @@ -0,0 +1,420 @@ +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.BatteryManager; +import android.os.Bundle; +import android.support.v4.content.ContextCompat; +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.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 com.github.mikephil.charting.charts.LineChart; +import com.github.mikephil.charting.components.AxisBase; +import com.github.mikephil.charting.components.XAxis; +import com.github.mikephil.charting.components.YAxis; +import com.github.mikephil.charting.data.Entry; +import com.github.mikephil.charting.data.LineData; +import com.github.mikephil.charting.data.LineDataSet; +import com.github.mikephil.charting.formatter.IAxisValueFormatter; + +import java.io.File; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * Created by cjkarr on 4/17/2017. + */ + +public class Battery extends Generator { + private static final String GENERATOR_IDENTIFIER = "pdk-device-battery"; + + private static final String ENABLED = "com.audacious_software.passive_data_kit.generators.device.Battery.ENABLED"; + private static final boolean ENABLED_DEFAULT = true; + + + private static final String DATABASE_PATH = "pdk-device-battery.sqlite"; + private static final int DATABASE_VERSION = 1; + + public static final String TABLE_HISTORY = "history"; + + public static final String HISTORY_OBSERVED = "observed"; + public static final String HISTORY_HEALTH = "health"; + public static final String HISTORY_LEVEL = "level"; + public static final String HISTORY_PLUGGED = "plugged"; + public static final String HISTORY_PRESENT = "present"; + public static final String HISTORY_SCALE = "scale"; + public static final String HISTORY_TEMPERATURE = "temperature"; + public static final String HISTORY_VOLTAGE = "voltage"; + public static final String HISTORY_TECHNOLOGY = "technology"; + public static final String HISTORY_STATUS = "status"; + + private static final String HEALTH_COLD = "cold"; + private static final String HEALTH_DEAD = "dead"; + private static final String HEALTH_GOOD = "good"; + private static final String HEALTH_OVERHEAT = "overheat"; + private static final String HEALTH_OVER_VOLTAGE = "over-voltage"; + private static final String HEALTH_UNSPECIFIED_FAILURE = "unspecified-failure"; + private static final String HEALTH_UNKNOWN = "unknown"; + + private static final String PLUGGED_AC = "ac"; + private static final String PLUGGED_USB = "usb"; + private static final String PLUGGED_WIRELESS = "wireless"; + private static final String PLUGGED_UNKNOWN = "unknown"; + + private static final String TECHNOLOGY_UNKNOWN = "unknown"; + + private static final String STATUS_CHARGING = "charging"; + private static final String STATUS_DISCHARGING = "discharging"; + private static final String STATUS_FULL = "full"; + private static final String STATUS_NOT_CHARGING = "not-charging"; + private static final String STATUS_UNKNOWN = "unknown"; + + private static Battery sInstance = null; + + private BroadcastReceiver mReceiver = null; + + private SQLiteDatabase mDatabase = null; + + private long mLastTimestamp = 0; + private long mCleanupInterval = (24 * 60 * 60 * 1000); + private long mLastCleanup = 0; + + public static Battery getInstance(Context context) { + if (Battery.sInstance == null) { + Battery.sInstance = new Battery(context.getApplicationContext()); + } + + return Battery.sInstance; + } + + public Battery(Context context) { + super(context); + } + + public static void start(final Context context) { + Battery.getInstance(context).startGenerator(); + } + + private void startGenerator() { + final Battery me = this; + + final long now = System.currentTimeMillis(); + + me.mLastTimestamp = now; + + this.mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(final Context context, Intent intent) { + ContentValues values = new ContentValues(); + values.put(Battery.HISTORY_OBSERVED, now); + + Bundle update = new Bundle(); + update.putLong(Battery.HISTORY_OBSERVED, now); + + switch (intent.getIntExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN)) { + case BatteryManager.BATTERY_HEALTH_COLD: + values.put(Battery.HISTORY_HEALTH, Battery.HEALTH_COLD); + update.putString(Battery.HISTORY_HEALTH, Battery.HEALTH_COLD); + break; + case BatteryManager.BATTERY_HEALTH_DEAD: + values.put(Battery.HISTORY_HEALTH, Battery.HEALTH_DEAD); + update.putString(Battery.HISTORY_HEALTH, Battery.HEALTH_DEAD); + break; + case BatteryManager.BATTERY_HEALTH_GOOD: + values.put(Battery.HISTORY_HEALTH, Battery.HEALTH_GOOD); + update.putString(Battery.HISTORY_HEALTH, Battery.HEALTH_GOOD); + break; + case BatteryManager.BATTERY_HEALTH_OVERHEAT: + values.put(Battery.HISTORY_HEALTH, Battery.HEALTH_OVERHEAT); + update.putString(Battery.HISTORY_HEALTH, Battery.HEALTH_OVERHEAT); + break; + case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE: + values.put(Battery.HISTORY_HEALTH, Battery.HEALTH_OVER_VOLTAGE); + update.putString(Battery.HISTORY_HEALTH, Battery.HEALTH_OVER_VOLTAGE); + break; + case BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE: + values.put(Battery.HISTORY_HEALTH, Battery.HEALTH_UNSPECIFIED_FAILURE); + update.putString(Battery.HISTORY_HEALTH, Battery.HEALTH_UNSPECIFIED_FAILURE); + break; + default: + values.put(Battery.HISTORY_HEALTH, Battery.HEALTH_UNKNOWN); + update.putString(Battery.HISTORY_HEALTH, Battery.HEALTH_UNKNOWN); + break; + } + + switch (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)) { + case BatteryManager.BATTERY_PLUGGED_AC: + values.put(Battery.HISTORY_PLUGGED, Battery.PLUGGED_AC); + update.putString(Battery.HISTORY_PLUGGED, Battery.PLUGGED_AC); + break; + case BatteryManager.BATTERY_PLUGGED_USB: + values.put(Battery.HISTORY_PLUGGED, Battery.PLUGGED_USB); + update.putString(Battery.HISTORY_PLUGGED, Battery.PLUGGED_USB); + break; + case BatteryManager.BATTERY_PLUGGED_WIRELESS: + values.put(Battery.HISTORY_PLUGGED, Battery.PLUGGED_WIRELESS); + update.putString(Battery.HISTORY_PLUGGED, Battery.PLUGGED_WIRELESS); + break; + default: + values.put(Battery.HISTORY_PLUGGED, Battery.PLUGGED_UNKNOWN); + update.putString(Battery.HISTORY_PLUGGED, Battery.PLUGGED_UNKNOWN); + break; + } + + switch (intent.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN)) { + case BatteryManager.BATTERY_STATUS_CHARGING: + values.put(Battery.HISTORY_STATUS, Battery.STATUS_CHARGING); + update.putString(Battery.HISTORY_STATUS, Battery.STATUS_CHARGING); + break; + case BatteryManager.BATTERY_STATUS_DISCHARGING: + values.put(Battery.HISTORY_STATUS, Battery.STATUS_DISCHARGING); + update.putString(Battery.HISTORY_STATUS, Battery.STATUS_DISCHARGING); + break; + case BatteryManager.BATTERY_STATUS_FULL: + values.put(Battery.HISTORY_STATUS, Battery.STATUS_FULL); + update.putString(Battery.HISTORY_STATUS, Battery.STATUS_FULL); + break; + case BatteryManager.BATTERY_STATUS_NOT_CHARGING: + values.put(Battery.HISTORY_STATUS, Battery.STATUS_NOT_CHARGING); + update.putString(Battery.HISTORY_STATUS, Battery.STATUS_NOT_CHARGING); + break; + default: + values.put(Battery.HISTORY_STATUS, Battery.STATUS_UNKNOWN); + update.putString(Battery.HISTORY_STATUS, Battery.STATUS_UNKNOWN); + break; + } + + values.put(Battery.HISTORY_PRESENT, intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, false)); + update.putBoolean(Battery.HISTORY_PRESENT, intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, false)); + + values.put(Battery.HISTORY_LEVEL, intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)); + update.putInt(Battery.HISTORY_LEVEL, intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)); + + values.put(Battery.HISTORY_SCALE, intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)); + update.putInt(Battery.HISTORY_SCALE, intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)); + + values.put(Battery.HISTORY_TEMPERATURE, intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1)); + update.putInt(Battery.HISTORY_TEMPERATURE, intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1)); + + values.put(Battery.HISTORY_VOLTAGE, intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1)); + update.putInt(Battery.HISTORY_VOLTAGE, intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1)); + + values.put(Battery.HISTORY_TECHNOLOGY, Battery.TECHNOLOGY_UNKNOWN); + update.putString(Battery.HISTORY_TECHNOLOGY, Battery.TECHNOLOGY_UNKNOWN); + + me.mDatabase.insert(Battery.TABLE_HISTORY, null, values); + + Generators.getInstance(context).notifyGeneratorUpdated(Battery.GENERATOR_IDENTIFIER, update); + + if (now - me.mLastCleanup > me.mCleanupInterval) { + me.mLastCleanup = now; + + long start = now - (24 * 60 * 60 * 1000); + + String where = Battery.HISTORY_OBSERVED + " < ?"; + String[] args = { "" + start }; + + me.mDatabase.delete(Battery.TABLE_HISTORY, where, args); + } + } + }; + + IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); + + this.mContext.registerReceiver(this.mReceiver, filter); + + Generators.getInstance(this.mContext).registerCustomViewClass(Battery.GENERATOR_IDENTIFIER, Battery.class); + + File path = PassiveDataKit.getGeneratorsStorage(this.mContext); + + path = new File(path, Battery.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_device_battery_create_history_table)); + } + + this.setDatabaseVersion(this.mDatabase, Battery.DATABASE_VERSION); + } + + public static boolean isEnabled(Context context) { + SharedPreferences prefs = Generators.getInstance(context).getSharedPreferences(context); + + return prefs.getBoolean(Battery.ENABLED, Battery.ENABLED_DEFAULT); + } + + public static boolean isRunning(Context context) { + if (Battery.sInstance == null) { + return false; + } + + return Battery.sInstance.mReceiver != null; + } + + public static ArrayList diagnostics(Context context) { + return new ArrayList<>(); + } + + public static void bindViewHolder(DataPointViewHolder holder) { + final Context context = holder.itemView.getContext(); + + Battery generator = Battery.getInstance(context); + + long now = System.currentTimeMillis(); + long start = now - (24 * 60 * 60 * 1000); + + String where = Battery.HISTORY_OBSERVED + " >= ?"; + String[] args = { "" + start }; + + Cursor c = generator.mDatabase.query(Battery.TABLE_HISTORY, null, where, args, null, null, Battery.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(Battery.HISTORY_OBSERVED)) / 1000; + + dateLabel.setText(Generator.formatTimestamp(context, timestamp)); + + c.moveToPrevious(); + + final LineChart chart = (LineChart) holder.itemView.findViewById(R.id.battery_level_chart); + chart.setViewPortOffsets(0,0,0,0); + chart.setHighlightPerDragEnabled(false); + chart.setHighlightPerTapEnabled(false); + chart.setBackgroundColor(ContextCompat.getColor(context, android.R.color.black)); + chart.setPinchZoom(false); + + final DateFormat timeFormat = android.text.format.DateFormat.getTimeFormat(context); + + final XAxis xAxis = chart.getXAxis(); + xAxis.setPosition(XAxis.XAxisPosition.BOTTOM_INSIDE); + xAxis.setTextSize(10f); + xAxis.setDrawAxisLine(true); + xAxis.setDrawGridLines(true); + xAxis.setCenterAxisLabels(true); + xAxis.setDrawLabels(true); + xAxis.setTextColor(ContextCompat.getColor(context, android.R.color.white)); + xAxis.setGranularityEnabled(true); + xAxis.setGranularity(1); + xAxis.setAxisMinimum(start); + xAxis.setAxisMaximum(now); + xAxis.setValueFormatter(new IAxisValueFormatter() { + @Override + public String getFormattedValue(float value, AxisBase axis) { + Date date = new Date((long) value); + + return timeFormat.format(date); + } + }); + + YAxis leftAxis = chart.getAxisLeft(); + leftAxis.setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART); + leftAxis.setDrawGridLines(true); + leftAxis.setDrawAxisLine(true); + leftAxis.setGranularityEnabled(true); + leftAxis.setAxisMaximum(110); + leftAxis.setAxisMinimum(-10); + leftAxis.setTextColor(ContextCompat.getColor(context, android.R.color.white)); + + YAxis rightAxis = chart.getAxisRight(); + rightAxis.setEnabled(false); + + chart.getLegend().setEnabled(false); + chart.getDescription().setEnabled(false); + + ArrayList values = new ArrayList<>(); + + long lastLevel = -1; + + int observedIndex = c.getColumnIndex(Battery.HISTORY_OBSERVED); + int levelIndex = c.getColumnIndex(Battery.HISTORY_LEVEL); + + while (c.moveToNext()) { + long when = c.getLong(observedIndex); + long level = c.getLong(levelIndex); + + if (level != lastLevel) { + values.add(0, new Entry(when, level)); + lastLevel = level; + } + } + + LineDataSet set = new LineDataSet(values, "Battery"); + set.setAxisDependency(YAxis.AxisDependency.LEFT); + set.setLineWidth(2.0f); + set.setDrawCircles(false); + set.setFillAlpha(192); + set.setDrawFilled(false); + set.setDrawValues(true); + set.setColor(ContextCompat.getColor(context, R.color.generator_battery_plot)); + set.setDrawCircleHole(false); + set.setDrawValues(false); + set.setMode(LineDataSet.Mode.LINEAR); + + chart.setVisibleYRange(0, 120, YAxis.AxisDependency.LEFT); + chart.setData(new LineData(set)); + } else { + cardContent.setVisibility(View.GONE); + cardEmpty.setVisibility(View.VISIBLE); + + dateLabel.setText(R.string.label_never_pdk); + } + + c.close(); + } + + public static View fetchView(ViewGroup parent) + { + return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_device_battery, parent, false); + } + + @Override + public List fetchPayloads() { + return new ArrayList<>(); + } + + public static long latestPointGenerated(Context context) { + Battery me = Battery.getInstance(context); + + if (me.mLastTimestamp == 0) { + Cursor c = me.mDatabase.query(Battery.TABLE_HISTORY, null, null, null, null, null, Battery.HISTORY_OBSERVED + " DESC"); + + if (c.moveToNext()) { + me.mLastTimestamp = c.getLong(c.getColumnIndex(Battery.HISTORY_OBSERVED)); + } + + c.close(); + } + + return me.mLastTimestamp; + } + + public Cursor queryHistory(String[] cols, String where, String[] args, String orderBy) { + return this.mDatabase.query(Battery.TABLE_HISTORY, cols, where, args, null, null, orderBy); + } +} diff --git a/src/com/audacious_software/passive_data_kit/generators/device/ForegroundApplication.java b/src/com/audacious_software/passive_data_kit/generators/device/ForegroundApplication.java new file mode 100755 index 0000000..85c58de --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/generators/device/ForegroundApplication.java @@ -0,0 +1,409 @@ +package com.audacious_software.passive_data_kit.generators.device; + +import android.app.AppOpsManager; +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.drawable.Drawable; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; +import android.util.Log; +import android.view.Display; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.ImageView; +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 com.rvalerio.fgchecker.AppChecker; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; + +/** + * Created by cjkarr on 5/10/2017. + */ + +public class ForegroundApplication extends Generator{ + + private static final String GENERATOR_IDENTIFIER = "pdk-foreground-application"; + + private static final String ENABLED = "com.audacious_software.passive_data_kit.generators.device.ForegroundApplication.ENABLED"; + private static final boolean ENABLED_DEFAULT = true; + + private static int DATABASE_VERSION = 3; + + private static final String TABLE_HISTORY = "history"; + private static final String HISTORY_OBSERVED = "observed"; + private static final String HISTORY_APPLICATION = "application"; + private static final String HISTORY_DURATION = "duration"; + private static final String HISTORY_SCREEN_ACTIVE = "screen_active"; + + private static ForegroundApplication sInstance = null; + + private Context mContext = null; + + private static final String DATABASE_PATH = "pdk-foreground-application.sqlite"; + + private SQLiteDatabase mDatabase = null; + private long mSampleInterval = 15000; + private AppChecker mAppChecker = null; + private long mLastTimestamp = 0; + + public static ForegroundApplication getInstance(Context context) { + if (ForegroundApplication.sInstance == null) { + ForegroundApplication.sInstance = new ForegroundApplication(context.getApplicationContext()); + } + + return ForegroundApplication.sInstance; + } + + public ForegroundApplication(Context context) { + super(context); + + this.mContext = context.getApplicationContext(); + } + + public static void start(final Context context) { + ForegroundApplication.getInstance(context).startGenerator(); + } + + private void startGenerator() { + final ForegroundApplication me = this; + + if (this.mAppChecker != null) { + this.mAppChecker.stop(); + + this.mAppChecker = null; + } + + this.mAppChecker = new AppChecker(); + this.mAppChecker.other(new AppChecker.Listener() { + @Override + public void onForeground(String process) { + long now = System.currentTimeMillis(); + + WindowManager window = (WindowManager) me.mContext.getSystemService(Context.WINDOW_SERVICE); + Display display = window.getDefaultDisplay(); + + boolean screenActive = true; + + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { + if (display.getState() != Display.STATE_ON) { + screenActive = false; + } + } + + Log.e("PDK", "PROCESS: " + process + " -- " + screenActive); + + ContentValues values = new ContentValues(); + values.put(ForegroundApplication.HISTORY_OBSERVED, now); + values.put(ForegroundApplication.HISTORY_APPLICATION, process); + values.put(ForegroundApplication.HISTORY_DURATION, me.mSampleInterval); + values.put(ForegroundApplication.HISTORY_SCREEN_ACTIVE, screenActive); + + me.mDatabase.insert(ForegroundApplication.TABLE_HISTORY, null, values); + + Bundle update = new Bundle(); + update.putLong(ForegroundApplication.HISTORY_OBSERVED, now); + update.putString(ForegroundApplication.HISTORY_APPLICATION, process); + update.putLong(ForegroundApplication.HISTORY_DURATION, me.mSampleInterval); + + Generators.getInstance(me.mContext).notifyGeneratorUpdated(ForegroundApplication.GENERATOR_IDENTIFIER, update); + } + }); + + this.mAppChecker.timeout((int) this.mSampleInterval); + this.mAppChecker.start(this.mContext); + + File path = PassiveDataKit.getGeneratorsStorage(this.mContext); + + path = new File(path, ForegroundApplication.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_foreground_applications_create_history_table)); + case 1: + this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_foreground_applications_history_table_add_duration)); + case 2: + this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_foreground_applications_history_table_add_screen_active)); + } + + this.setDatabaseVersion(this.mDatabase, ForegroundApplication.DATABASE_VERSION); + + Generators.getInstance(this.mContext).registerCustomViewClass(ForegroundApplication.GENERATOR_IDENTIFIER, ForegroundApplication.class); + } + + public static boolean isEnabled(Context context) { + SharedPreferences prefs = Generators.getInstance(context).getSharedPreferences(context); + + return prefs.getBoolean(ForegroundApplication.ENABLED, ForegroundApplication.ENABLED_DEFAULT); + } + + public static boolean isRunning(Context context) { + if (ForegroundApplication.sInstance == null) { + return false; + } + + return ForegroundApplication.sInstance.mAppChecker != null; + } + + public static ArrayList diagnostics(final Context context) { + ArrayList actions = new ArrayList<>(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + int mode = appOps.checkOpNoThrow("android:get_usage_stats", android.os.Process.myUid(), context.getPackageName()); + + if (mode != AppOpsManager.MODE_ALLOWED) { + actions.add(new DiagnosticAction(context.getString(R.string.diagnostic_usage_stats_permission_required_title), context.getString(R.string.diagnostic_usage_stats_permission_required), new Runnable() { + @Override + public void run() { + Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + })); + } + } + + return actions; + } + + public static void bindViewHolder(DataPointViewHolder holder) { + final Context context = holder.itemView.getContext(); + + long lastTimestamp = 0; + + ForegroundApplication generator = ForegroundApplication.getInstance(holder.itemView.getContext()); + + Cursor c = generator.mDatabase.query(ForegroundApplication.TABLE_HISTORY, null, null, null, null, null, ForegroundApplication.HISTORY_OBSERVED + " DESC"); + + while (c.moveToNext()) { + if (lastTimestamp == 0) { + lastTimestamp = c.getLong(c.getColumnIndex(ForegroundApplication.HISTORY_OBSERVED)); + } + } + + 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); + + long now = System.currentTimeMillis(); + long yesterday = now - (24 * 60 * 60 * 1000); + + HashMap appDurations = new HashMap<>(); + HashMap appWhens = new HashMap<>(); + + ArrayList latest = new ArrayList<>(); + + String where = ForegroundApplication.HISTORY_OBSERVED + " > ? AND " + ForegroundApplication.HISTORY_SCREEN_ACTIVE + " = ?"; + String[] args = { "" + yesterday, "1" }; + + c = generator.mDatabase.query(ForegroundApplication.TABLE_HISTORY, null, where, args, null, null, ForegroundApplication.HISTORY_OBSERVED); + + while (c.moveToNext()) { + String application = c.getString(c.getColumnIndex(ForegroundApplication.HISTORY_APPLICATION)); + + if (latest.contains(application)) { + latest.remove(application); + } + + latest.add(0, application); + + if (appDurations.containsKey(application) == false) { + appDurations.put(application, 0.0); + } + + double appDuration = appDurations.get(application); + double duration = c.getDouble(c.getColumnIndex(ForegroundApplication.HISTORY_DURATION)); + + long lastObserved = c.getLong(c.getColumnIndex(ForegroundApplication.HISTORY_OBSERVED)); + + appDuration += duration; + + appDurations.put(application, appDuration); + appWhens.put(application, lastObserved); + } + + c.close(); + + double largestUsage = 0.0; + + ArrayList> largest = new ArrayList<>(); + + for (String key : appDurations.keySet()) { + HashMap app = new HashMap<>(); + + double duration = appDurations.get(key); + + app.put(key, duration); + + if (duration > largestUsage) { + largestUsage = duration; + } + + largest.add(app); + } + + Collections.sort(largest, new Comparator>() { + @Override + public int compare(HashMap mapOne, HashMap mapTwo) { + String keyOne = mapOne.keySet().iterator().next(); + String keyTwo = mapTwo.keySet().iterator().next(); + + Double valueOne = mapOne.get(keyOne); + Double valueTwo = mapTwo.get(keyTwo); + + return valueTwo.compareTo(valueOne); + } + }); + + int[] appRowIds = { R.id.application_one, R.id.application_two, R.id.application_three, R.id.application_four }; + int[] whenAppRowIds = { R.id.application_recent_one, R.id.application_recent_two, R.id.application_recent_three, R.id.application_recent_four }; + + while (largest.size() > appRowIds.length) { + largest.remove(largest.size() - 1); + } + + while (latest.size() > whenAppRowIds.length) { + latest.remove(latest.size() - 1); + } + + PackageManager packageManager = context.getPackageManager(); + + if (largest.size() > 0) { + for (int appRowId : appRowIds) { + View row = cardContent.findViewById(appRowId); + + row.setVisibility(View.GONE); + } + + for (int i = 0; i < appRowIds.length && i < largest.size(); i++) { + HashMap appDef = largest.get(i); + int appRowId = appRowIds[i]; + + View row = cardContent.findViewById(appRowId); + row.setVisibility(View.VISIBLE); + + TextView appName = (TextView) row.findViewById(R.id.app_name); + ImageView appIcon = (ImageView) row.findViewById(R.id.application_icon); + + View usedDuration = row.findViewById(R.id.app_used_duration); + View remainderDuration = row.findViewById(R.id.app_remaining_duration); + + for (String key : appDef.keySet()) { + double duration = appDef.get(key); + + double minutes = duration / (1000 * 60); + + try { + String name = packageManager.getApplicationLabel(packageManager.getApplicationInfo(key, PackageManager.GET_META_DATA)).toString(); + + appName.setText(context.getString(R.string.generator_foreground_application_app_name_duration, name, minutes)); + Drawable icon = packageManager.getApplicationIcon(key); + appIcon.setImageDrawable(icon); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + + appName.setText(context.getString(R.string.generator_foreground_application_app_name_duration, key, minutes)); + appIcon.setImageDrawable(null); + } + + double remainder = largestUsage - duration; + + LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) usedDuration.getLayoutParams(); + params.weight = (float) duration; + usedDuration.setLayoutParams(params); + + params = (LinearLayout.LayoutParams) remainderDuration.getLayoutParams(); + params.weight = (float) remainder; + remainderDuration.setLayoutParams(params); + } + } + + for (int i = 0; i < whenAppRowIds.length && i < latest.size(); i++) { + String appPackage = latest.get(i); + int appRowId = whenAppRowIds[i]; + + View row = cardContent.findViewById(appRowId); + row.setVisibility(View.VISIBLE); + + TextView appName = (TextView) row.findViewById(R.id.app_name); + ImageView appIcon = (ImageView) row.findViewById(R.id.application_icon); + + try { + String name = packageManager.getApplicationLabel(packageManager.getApplicationInfo(appPackage, PackageManager.GET_META_DATA)).toString(); + + appName.setText(name); + Drawable icon = packageManager.getApplicationIcon(appPackage); + appIcon.setImageDrawable(icon); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + + TextView appWhen = (TextView) row.findViewById(R.id.app_last_used); + appWhen.setText(Generator.formatTimestamp(context, appWhens.get(appPackage) / 1000)); + } + + cardContent.setVisibility(View.VISIBLE); + cardEmpty.setVisibility(View.GONE); + + 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_foreground_application, parent, false); + } + + public static long latestPointGenerated(Context context) { + ForegroundApplication me = ForegroundApplication.getInstance(context); + + if (me.mLastTimestamp == 0) { + Cursor c = me.mDatabase.query(ForegroundApplication.TABLE_HISTORY, null, null, null, null, null, ForegroundApplication.HISTORY_OBSERVED + " DESC"); + + if (c.moveToNext()) { + me.mLastTimestamp = c.getLong(c.getColumnIndex(ForegroundApplication.HISTORY_OBSERVED)); + } + + c.close(); + } + + return me.mLastTimestamp; + } +} 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 index 329b5c3..7d41388 100755 --- a/src/com/audacious_software/passive_data_kit/generators/diagnostics/AppEvent.java +++ b/src/com/audacious_software/passive_data_kit/generators/diagnostics/AppEvent.java @@ -6,10 +6,13 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.webkit.WebView; +import android.widget.LinearLayout; import android.widget.TextView; import com.audacious_software.passive_data_kit.PassiveDataKit; @@ -26,7 +29,6 @@ import java.io.File; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -44,10 +46,15 @@ public class AppEvent extends Generator{ public static final String HISTORY_EVENT_DETAILS = "event_details"; public static final String TABLE_HISTORY = "history"; + private static final int CARD_PAGE_SIZE = 8; + private static final int CARD_MAX_PAGES = 8; + private static AppEvent sInstance = null; private SQLiteDatabase mDatabase = null; + private int mPage = 0; + public static AppEvent getInstance(Context context) { if (AppEvent.sInstance == null) { AppEvent.sInstance = new AppEvent(context.getApplicationContext()); @@ -112,255 +119,165 @@ public static void bindDisclosureViewHolder(final GeneratorViewHolder holder) { } 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); + final Context context = holder.itemView.getContext(); - AppEvents.populateTimeline(context, twoTimeline, twoStart); + final AppEvent generator = AppEvent.getInstance(context); - AppEvents generator = AppEvents.getInstance(context); + final ArrayList events = new ArrayList<>(); - Cursor c = generator.mDatabase.query(AppEvents.TABLE_HISTORY, null, null, null, null, null, AppEvents.HISTORY_OBSERVED + " DESC"); + Cursor c = generator.mDatabase.query(AppEvent.TABLE_HISTORY, null, null, null, null, null, AppEvent.HISTORY_OBSERVED + " DESC"); View cardContent = holder.itemView.findViewById(R.id.card_content); + View cardSizer = holder.itemView.findViewById(R.id.card_sizer); View cardEmpty = holder.itemView.findViewById(R.id.card_empty); TextView dateLabel = (TextView) holder.itemView.findViewById(R.id.generator_data_point_date); + int eventCount = c.getCount(); + if (c.moveToNext()) { cardContent.setVisibility(View.VISIBLE); cardEmpty.setVisibility(View.GONE); + cardSizer.setVisibility(View.INVISIBLE); - long timestamp = c.getLong(c.getColumnIndex(AppEvents.HISTORY_OBSERVED)) / 1000; + long timestamp = c.getLong(c.getColumnIndex(AppEvent.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); + cardSizer.setVisibility(View.INVISIBLE); 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<>(); + c.moveToPrevious(); while (c.moveToNext()) { - long timestamp = c.getLong(c.getColumnIndex(AppEvents.HISTORY_OBSERVED)); + String event = c.getString(c.getColumnIndex(AppEvent.HISTORY_EVENT_NAME)); + long timestamp = c.getLong(c.getColumnIndex(AppEvent.HISTORY_OBSERVED)); - activeTimestamps.add(timestamp); + Object[] values = { event, timestamp }; - String state = c.getString(c.getColumnIndex(AppEvents.HISTORY_STATE)); - activeStates.add(state); + events.add(values); } 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 (eventCount > AppEvent.CARD_PAGE_SIZE * AppEvent.CARD_MAX_PAGES) { + eventCount = AppEvent.CARD_PAGE_SIZE * AppEvent.CARD_MAX_PAGES; } - if (activeStates.size() > 0) { - long firstTimestamp = activeTimestamps.get(0); - long firstState = activeTimestamps.get(0); + final int pages = (int) Math.ceil(((double) eventCount) / AppEvent.CARD_PAGE_SIZE); - View startView = new View(context); + final AppEvent appEvent = AppEvent.getInstance(holder.itemView.getContext()); - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { - if (AppEvents.STATE_UNKNOWN.equals(lastState)) { + ViewPager pager = (ViewPager) holder.itemView.findViewById(R.id.content_pager); - } 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); - } + PagerAdapter adapter = new PagerAdapter() { + @Override + public int getCount() { + return pages; } - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, firstTimestamp - start); - startView.setLayoutParams(params); + @Override + public boolean isViewFromObject(View view, Object content) { + return view.getTag().equals(content); + } - timeline.addView(startView); + public void destroyItem(ViewGroup container, int position, Object content) { + int toRemove = -1; - long now = System.currentTimeMillis(); + for (int i = 0; i < container.getChildCount(); i++) { + View child = container.getChildAt(i); - 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 (this.isViewFromObject(child, content)) + toRemove = i; } - 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); - } + if (toRemove >= 0) + container.removeViewAt(toRemove); + } - 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); - } + public Object instantiateItem(ViewGroup container, int position) { + LinearLayout list = (LinearLayout) LayoutInflater.from(container.getContext()).inflate(R.layout.card_generator_app_event_page, container, false); + + int listPosition = AppEvent.CARD_PAGE_SIZE * position; + + for (int i = 0; i < AppEvent.CARD_PAGE_SIZE && listPosition + i < events.size(); i++) { + Object[] values = events.get(listPosition + i); + + String event = (String) values[0]; + long timestamp = (long) values[1]; + + LinearLayout row = null; + + switch (i) { + case 0: + row = (LinearLayout) list.findViewById(R.id.app_event_row_0); + break; + case 1: + row = (LinearLayout) list.findViewById(R.id.app_event_row_1); + break; + case 2: + row = (LinearLayout) list.findViewById(R.id.app_event_row_2); + break; + case 3: + row = (LinearLayout) list.findViewById(R.id.app_event_row_3); + break; + case 4: + row = (LinearLayout) list.findViewById(R.id.app_event_row_4); + break; + case 5: + row = (LinearLayout) list.findViewById(R.id.app_event_row_5); + break; + case 6: + row = (LinearLayout) list.findViewById(R.id.app_event_row_6); + break; + default: + row = (LinearLayout) list.findViewById(R.id.app_event_row_7); + break; } - params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, currentTimestamp - priorTimestamp); - v.setLayoutParams(params); + TextView eventName = (TextView) row.findViewById(R.id.app_event_row_event_name); + TextView eventWhen = (TextView) row.findViewById(R.id.app_event_row_event_when); - timeline.addView(v); + eventName.setText(event); + eventWhen.setText(Generator.formatTimestamp(context, timestamp / 1000)); } - long finalTimestamp = activeTimestamps.get(activeTimestamps.size() - 1); - String finalState = activeStates.get(activeStates.size() - 1); + list.setTag("" + position); - View v = new View(context); + container.addView(list); - 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); - } + return "" + list.getTag(); + } + }; - 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); - } + pager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - timeline.addView(v); } - if (end > now) { - View v = new View(context); + @Override + public void onPageSelected(int position) { + appEvent.mPage = position; + } - params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, end - now); - v.setLayoutParams(params); + @Override + public void onPageScrollStateChanged(int state) { - timeline.addView(v); } - } else { + }); - } + pager.setAdapter(adapter); + + pager.setCurrentItem(appEvent.mPage); } -*/ public static View fetchView(ViewGroup parent) { - return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_generic, parent, false); + return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_app_event, parent, false); } @Override @@ -453,6 +370,10 @@ public boolean logEvent(String eventName, Map eventDet return false; } + public void logThrowable(Throwable t) { + + } + public static List getDisclosureActions(final Context context) { List actions = new ArrayList<>(); diff --git a/src/com/audacious_software/passive_data_kit/generators/diagnostics/SystemStatus.java b/src/com/audacious_software/passive_data_kit/generators/diagnostics/SystemStatus.java new file mode 100755 index 0000000..d1a6298 --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/generators/diagnostics/SystemStatus.java @@ -0,0 +1,465 @@ +package com.audacious_software.passive_data_kit.generators.diagnostics; + +import android.app.AlarmManager; +import android.app.PendingIntent; +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.os.StatFs; +import android.support.v4.content.ContextCompat; +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.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 com.github.mikephil.charting.charts.LineChart; +import com.github.mikephil.charting.components.AxisBase; +import com.github.mikephil.charting.components.XAxis; +import com.github.mikephil.charting.components.YAxis; +import com.github.mikephil.charting.data.Entry; +import com.github.mikephil.charting.data.LineData; +import com.github.mikephil.charting.data.LineDataSet; +import com.github.mikephil.charting.formatter.IAxisValueFormatter; + +import java.io.File; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +/** + * Created by cjkarr on 4/17/2017. + */ + +public class SystemStatus extends Generator { + private static final String GENERATOR_IDENTIFIER = "pdk-system-status"; + + private static final String ENABLED = "com.audacious_software.passive_data_kit.generators.diagnostics.SystemStatus.ENABLED"; + private static final boolean ENABLED_DEFAULT = true; + + private static final String ACTION_HEARTBEAT = "com.audacious_software.passive_data_kit.generators.diagnostics.SystemStatus.ACTION_HEARTBEAT"; + + private static final String DATABASE_PATH = "pdk-system-status.sqlite"; + private static final int DATABASE_VERSION = 1; + + public static final String TABLE_HISTORY = "history"; + + public static final String HISTORY_OBSERVED = "observed"; + public static final String HISTORY_RUNTIME = "runtime"; + public static final String HISTORY_STORAGE_USED_APP = "storage_app"; + public static final String HISTORY_STORAGE_USED_OTHER = "storage_other"; + public static final String HISTORY_STORAGE_AVAILABLE = "storage_available"; + public static final String HISTORY_STORAGE_TOTAL = "storage_total"; + public static final String HISTORY_STORAGE_PATH = "storage_path"; + private static final double GIGABYTE = (1024 * 1024 * 1024); + + private static SystemStatus sInstance = null; + + private BroadcastReceiver mReceiver = null; + + private SQLiteDatabase mDatabase = null; + + private long mLastTimestamp = 0; + private long mRefreshInterval = (5 * 60 * 1000); + + public static SystemStatus getInstance(Context context) { + if (SystemStatus.sInstance == null) { + SystemStatus.sInstance = new SystemStatus(context.getApplicationContext()); + } + + return SystemStatus.sInstance; + } + + public SystemStatus(Context context) { + super(context); + } + + public static void start(final Context context) { + SystemStatus.getInstance(context).startGenerator(); + } + + private void startGenerator() { + final SystemStatus me = this; + + final long runtimeStart = System.currentTimeMillis(); + + Generators.getInstance(this.mContext).registerCustomViewClass(SystemStatus.GENERATOR_IDENTIFIER, SystemStatus.class); + + File path = new File(PassiveDataKit.getGeneratorsStorage(this.mContext), SystemStatus.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_diagnostics_system_status_create_history_table)); + } + + this.setDatabaseVersion(this.mDatabase, SystemStatus.DATABASE_VERSION); + + this.mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + File path = PassiveDataKit.getGeneratorsStorage(context); + + context = context.getApplicationContext(); + + long now = System.currentTimeMillis(); + + me.mLastTimestamp = now; + + PassiveDataKit.getInstance(context).start(); + + StatFs fsInfo = new StatFs(path.getAbsolutePath()); + + String storagePath = path.getAbsolutePath(); + + long bytesTotal = fsInfo.getTotalBytes(); + long bytesAvailable = fsInfo.getBlockSizeLong() * fsInfo.getAvailableBlocksLong(); + + long bytesAppUsed = SystemStatus.getFileSize(context.getFilesDir()); + bytesAppUsed += SystemStatus.getFileSize(context.getExternalFilesDir(null)); + bytesAppUsed += SystemStatus.getFileSize(context.getCacheDir()); + bytesAppUsed += SystemStatus.getFileSize(context.getExternalCacheDir()); + + long bytesOtherUsed = bytesTotal - bytesAvailable - bytesAppUsed; + + ContentValues values = new ContentValues(); + values.put(SystemStatus.HISTORY_OBSERVED, now); + values.put(SystemStatus.HISTORY_RUNTIME, now - runtimeStart); + values.put(SystemStatus.HISTORY_STORAGE_PATH, storagePath); + values.put(SystemStatus.HISTORY_STORAGE_TOTAL, bytesTotal); + values.put(SystemStatus.HISTORY_STORAGE_AVAILABLE, bytesAvailable); + values.put(SystemStatus.HISTORY_STORAGE_USED_APP, bytesAppUsed); + values.put(SystemStatus.HISTORY_STORAGE_USED_OTHER, bytesOtherUsed); + + Bundle update = new Bundle(); + update.putLong(SystemStatus.HISTORY_OBSERVED, now); + update.putLong(SystemStatus.HISTORY_RUNTIME, now - runtimeStart); + update.putString(SystemStatus.HISTORY_STORAGE_PATH, storagePath); + update.putLong(SystemStatus.HISTORY_STORAGE_TOTAL, bytesTotal); + update.putLong(SystemStatus.HISTORY_STORAGE_AVAILABLE, bytesAvailable); + update.putLong(SystemStatus.HISTORY_STORAGE_USED_APP, bytesAppUsed); + update.putLong(SystemStatus.HISTORY_STORAGE_USED_OTHER, bytesOtherUsed); + + me.mDatabase.insert(SystemStatus.TABLE_HISTORY, null, values); + + Generators.getInstance(context).notifyGeneratorUpdated(SystemStatus.GENERATOR_IDENTIFIER, update); + + AlarmManager alarms = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + PendingIntent pi = PendingIntent.getBroadcast(context, 0, new Intent(SystemStatus.ACTION_HEARTBEAT), PendingIntent.FLAG_UPDATE_CURRENT); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + alarms.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, now + me.mRefreshInterval, pi); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + alarms.setExact(AlarmManager.RTC_WAKEUP, now + me.mRefreshInterval, pi); + } else { + alarms.set(AlarmManager.RTC_WAKEUP, now + me.mRefreshInterval, pi); + } + } + }; + + this.mReceiver.onReceive(this.mContext, null); + + IntentFilter filter = new IntentFilter(SystemStatus.ACTION_HEARTBEAT); + this.mContext.registerReceiver(this.mReceiver, filter); + } + + public static boolean isEnabled(Context context) { + SharedPreferences prefs = Generators.getInstance(context).getSharedPreferences(context); + + return prefs.getBoolean(SystemStatus.ENABLED, SystemStatus.ENABLED_DEFAULT); + } + + public static boolean isRunning(Context context) { + if (SystemStatus.sInstance == null) { + return false; + } + + return SystemStatus.sInstance.mReceiver != null; + } + + public static ArrayList diagnostics(Context context) { + return new ArrayList<>(); + } + + public static void bindViewHolder(DataPointViewHolder holder) { + final Context context = holder.itemView.getContext(); + + SystemStatus generator = SystemStatus.getInstance(context); + + long now = System.currentTimeMillis(); + long start = now - (24 * 60 * 60 * 1000); + + String where = SystemStatus.HISTORY_OBSERVED + " >= ?"; + String[] args = { "" + start }; + + Cursor c = generator.mDatabase.query(SystemStatus.TABLE_HISTORY, null, where, args, null, null, SystemStatus.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(SystemStatus.HISTORY_OBSERVED)) / 1000; + + dateLabel.setText(Generator.formatTimestamp(context, timestamp)); + + c.moveToPrevious(); + + final LineChart chart = (LineChart) holder.itemView.findViewById(R.id.system_status_chart); + chart.setViewPortOffsets(0,0,0,0); + chart.setHighlightPerDragEnabled(false); + chart.setHighlightPerTapEnabled(false); + chart.setBackgroundColor(ContextCompat.getColor(context, android.R.color.black)); + chart.setPinchZoom(false); + + final DateFormat timeFormat = android.text.format.DateFormat.getTimeFormat(context); + + final XAxis xAxis = chart.getXAxis(); + xAxis.setPosition(XAxis.XAxisPosition.BOTTOM_INSIDE); + xAxis.setTextSize(10f); + xAxis.setDrawAxisLine(true); + xAxis.setDrawGridLines(true); + xAxis.setCenterAxisLabels(true); + xAxis.setDrawLabels(true); + xAxis.setTextColor(ContextCompat.getColor(context, android.R.color.white)); + xAxis.setGranularityEnabled(true); + xAxis.setGranularity(1); + xAxis.setAxisMinimum(start); + xAxis.setAxisMaximum(now); + xAxis.setValueFormatter(new IAxisValueFormatter() { + @Override + public String getFormattedValue(float value, AxisBase axis) { + Date date = new Date((long) value); + + return timeFormat.format(date); + } + }); + + YAxis leftAxis = chart.getAxisLeft(); + leftAxis.setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART); + leftAxis.setDrawGridLines(true); + leftAxis.setDrawAxisLine(true); + leftAxis.setGranularityEnabled(true); + leftAxis.setTextColor(ContextCompat.getColor(context, android.R.color.white)); + leftAxis.setValueFormatter(new IAxisValueFormatter() { + @Override + public String getFormattedValue(float value, AxisBase axis) { + return "" + value + " GB"; + } + }); + + YAxis rightAxis = chart.getAxisRight(); + rightAxis.setEnabled(false); + + chart.getLegend().setEnabled(false); + chart.getDescription().setEnabled(false); + + int observedIndex = c.getColumnIndex(SystemStatus.HISTORY_OBSERVED); + int availableIndex = c.getColumnIndex(SystemStatus.HISTORY_STORAGE_AVAILABLE); + int appUsedIndex = c.getColumnIndex(SystemStatus.HISTORY_STORAGE_USED_APP); + int othersUsedIndex = c.getColumnIndex(SystemStatus.HISTORY_STORAGE_USED_OTHER); + + ArrayList availableValues = new ArrayList<>(); + ArrayList appValues = new ArrayList<>(); + ArrayList otherValues = new ArrayList<>(); + + long runtime = -1; + + while (c.moveToNext()) { + long when = c.getLong(observedIndex); + + double available = (double) c.getLong(availableIndex); + double app = (double) c.getLong(appUsedIndex); + double other = (double) c.getLong(othersUsedIndex); + + availableValues.add(0, new Entry(when, (float) (available / SystemStatus.GIGABYTE))); + appValues.add(0, new Entry(when, (float) (app / SystemStatus.GIGABYTE))); + otherValues.add(0, new Entry(when, (float) (other / SystemStatus.GIGABYTE))); + + if (runtime == -1) { + runtime = c.getLong(c.getColumnIndex(SystemStatus.HISTORY_RUNTIME)); + } + } + + LineData sets = new LineData(); + + LineDataSet set = new LineDataSet(availableValues, "available"); + set.setAxisDependency(YAxis.AxisDependency.LEFT); + set.setLineWidth(1.0f); + set.setDrawCircles(true); + set.setFillAlpha(192); + set.setDrawFilled(false); + set.setDrawValues(true); + set.setCircleColor(ContextCompat.getColor(context, R.color.generator_system_status_free)); + set.setCircleRadius(1.5f); + set.setCircleHoleRadius(0.0f); + set.setDrawCircleHole(false); + set.setDrawValues(false); + set.setColor(ContextCompat.getColor(context, R.color.generator_system_status_free)); + set.setMode(LineDataSet.Mode.LINEAR); + + sets.addDataSet(set); + + set = new LineDataSet(appValues, "app"); + set.setAxisDependency(YAxis.AxisDependency.LEFT); + set.setLineWidth(1.0f); + set.setDrawCircles(true); + set.setCircleColor(ContextCompat.getColor(context, R.color.generator_system_status_app)); + set.setCircleRadius(1.5f); + set.setCircleHoleRadius(0.0f); + set.setFillAlpha(192); + set.setDrawFilled(false); + set.setDrawValues(true); + set.setColor(ContextCompat.getColor(context, R.color.generator_system_status_app)); + set.setDrawCircleHole(false); + set.setDrawValues(false); + set.setMode(LineDataSet.Mode.LINEAR); + + sets.addDataSet(set); + + chart.setData(sets); + + TextView runtimeLabel = (TextView) holder.itemView.findViewById(R.id.system_status_runtime); + runtimeLabel.setText(context.getString(R.string.generator_system_status_runtime, SystemStatus.formatRuntime(context, runtime))); + } else { + cardContent.setVisibility(View.GONE); + cardEmpty.setVisibility(View.VISIBLE); + + dateLabel.setText(R.string.label_never_pdk); + } + + c.close(); + } + + private static String formatRuntime(Context context, long runtime) { + long days = runtime / (24 * 60 * 60 * 1000); + runtime -= (24 * 60 * 60 * 1000) * days; + + long hours = runtime / (60 * 60 * 1000); + runtime -= (60 * 60 * 1000) * hours; + + long minutes = runtime / (60 * 1000); + runtime -= (60 * 1000) * minutes; + + long seconds = runtime / (1000); + runtime -= (1000) * seconds; + + String hourString = "" + hours; + + if (hourString.length() == 1) { + hourString = "0" + hourString; + } + + String minuteString = "" + minutes; + + if (minuteString.length() == 1) { + minuteString = "0" + minuteString; + } + + String secondString = "" + seconds; + + if (secondString.length() == 1) { + secondString = "0" + secondString; + } + + String msString = "" + runtime; + + while (msString.length() < 3) { + msString = "0" + msString; + + } + + return context.getString(R.string.generator_system_status_runtime_formatted, days, hourString, minuteString, secondString, msString); + } + + public static long getFileSize(final File file) + { + if (file == null||!file.exists()) { + return 0; + } + + if (!file.isDirectory()) { + return file.length(); + } + + final List dirs = new LinkedList<>(); + + dirs.add(file); + + long result=0; + + while(!dirs.isEmpty()) { + final File dir = dirs.remove(0); + + if (!dir.exists()) { + continue; + } + + final File[] listFiles = dir.listFiles(); + + if (listFiles==null||listFiles.length==0) { + continue; + } + + for (final File child : listFiles) { + result += child.length(); + + if (child.isDirectory()) { + dirs.add(child); + } + } + } + + return result; + } + + public static View fetchView(ViewGroup parent) + { + return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_diagnostics_system_status, parent, false); + } + + @Override + public List fetchPayloads() { + return new ArrayList<>(); + } + + public static long latestPointGenerated(Context context) { + SystemStatus me = SystemStatus.getInstance(context); + + if (me.mLastTimestamp == 0) { + Cursor c = me.mDatabase.query(SystemStatus.TABLE_HISTORY, null, null, null, null, null, SystemStatus.HISTORY_OBSERVED + " DESC"); + + if (c.moveToNext()) { + me.mLastTimestamp = c.getLong(c.getColumnIndex(SystemStatus.HISTORY_OBSERVED)); + } + + c.close(); + } + + return me.mLastTimestamp; + } + + public Cursor queryHistory(String[] cols, String where, String[] args, String orderBy) { + return this.mDatabase.query(SystemStatus.TABLE_HISTORY, cols, where, args, null, null, orderBy); + } +} diff --git a/src/com/audacious_software/passive_data_kit/generators/sensors/Accelerometer.java b/src/com/audacious_software/passive_data_kit/generators/sensors/Accelerometer.java new file mode 100755 index 0000000..7f4f8a2 --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/generators/sensors/Accelerometer.java @@ -0,0 +1,745 @@ +package com.audacious_software.passive_data_kit.generators.sensors; + +import android.app.Activity; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.PowerManager; +import android.preference.PreferenceManager; +import android.provider.Settings; +import android.support.v4.content.ContextCompat; +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.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 com.github.mikephil.charting.charts.LineChart; +import com.github.mikephil.charting.components.AxisBase; +import com.github.mikephil.charting.components.XAxis; +import com.github.mikephil.charting.components.YAxis; +import com.github.mikephil.charting.data.Entry; +import com.github.mikephil.charting.data.LineData; +import com.github.mikephil.charting.data.LineDataSet; +import com.github.mikephil.charting.formatter.IAxisValueFormatter; + +import java.io.File; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * Created by cjkarr on 4/17/2017. + */ + +public class Accelerometer extends SensorGenerator implements SensorEventListener { + private static final String GENERATOR_IDENTIFIER = "pdk-sensor-accelerometer"; + + private static final String ENABLED = "com.audacious_software.passive_data_kit.generators.device.Accelerometer.ENABLED"; + private static final boolean ENABLED_DEFAULT = true; + + private static final String IGNORE_POWER_MANAGEMENT = "com.audacious_software.passive_data_kit.generators.sensors.Accelerometer.IGNORE_POWER_MANAGEMENT"; + private static final boolean IGNORE_POWER_MANAGEMENT_DEFAULT = true; + + private static final String REFRESH_INTERVAL = "com.audacious_software.passive_data_kit.generators.sensors.Accelerometer.REFRESH_INTERVAL"; + private static final long REFRESH_INTERVAL_DEFAULT = 0; + + private static final String REFRESH_DURATION = "com.audacious_software.passive_data_kit.generators.sensors.Accelerometer.REFRESH_DURATION"; + private static final long REFRESH_DURATION_DEFAULT = (5 * 60 * 1000); + + private static final String DATABASE_PATH = "pdk-sensor-accelerometer.sqlite"; + private static final int DATABASE_VERSION = 1; + + public static final String TABLE_HISTORY = "history"; + + public static final String HISTORY_OBSERVED = "observed"; + private static final String HISTORY_RAW_TIMESTAMP = "raw_timestamp"; + private static final String HISTORY_ACCURACY = "accuracy"; + public static final String HISTORY_X = "x"; + public static final String HISTORY_Y = "y"; + public static final String HISTORY_Z = "z"; + + private static Accelerometer sInstance = null; + private static Handler sHandler = null; + + private static boolean sIsDrawing = false; + private static long sLastDrawStart = 0; + + private SQLiteDatabase mDatabase = null; + + private Sensor mSensor = null; + + private static int NUM_BUFFERS = 3; + private static int BUFFER_SIZE = 1024; + + private long mLastCleanup = 0; + private long mCleanupInterval = 15 * 60 * 1000; + + private int mActiveBuffersIndex = 0; + private int mCurrentBufferIndex = 0; + + private float[][] mXValueBuffers = null; + private float[][] mYValueBuffers = null; + private float[][] mZValueBuffers = null; + private int[][] mAccuracyBuffers = null; + private long[][] mRawTimestampBuffers = null; + private long[][] mTimestampBuffers = null; + + long mBaseTimestamp = 0; + + private long mLatestTimestamp = 0; + private Thread mIntervalThread = null; + + public static Accelerometer getInstance(Context context) { + if (Accelerometer.sInstance == null) { + Accelerometer.sInstance = new Accelerometer(context.getApplicationContext()); + } + + return Accelerometer.sInstance; + } + + public Accelerometer(Context context) { + super(context); + } + + public static void start(final Context context) { + Accelerometer.getInstance(context).startGenerator(); + } + + public void setIgnorePowerManagement(boolean ignore) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + SharedPreferences.Editor e = prefs.edit(); + + e.putBoolean(Accelerometer.IGNORE_POWER_MANAGEMENT, ignore); + e.apply(); + + this.stopGenerator(); + this.startGenerator(); + } + + public void setRefreshInterval(long interval) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + SharedPreferences.Editor e = prefs.edit(); + + e.putLong(Accelerometer.REFRESH_INTERVAL, interval); + e.apply(); + + this.stopGenerator(); + this.startGenerator(); + } + + public void setRefreshDuration(long duration) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + SharedPreferences.Editor e = prefs.edit(); + + e.putLong(Accelerometer.REFRESH_DURATION, duration); + e.apply(); + + this.stopGenerator(); + this.startGenerator(); + } + + private void startGenerator() { + final SensorManager sensors = (SensorManager) this.mContext.getSystemService(Context.SENSOR_SERVICE); + + final Accelerometer me = this; + + Generators.getInstance(this.mContext).registerCustomViewClass(Accelerometer.GENERATOR_IDENTIFIER, Accelerometer.class); + + File path = PassiveDataKit.getGeneratorsStorage(this.mContext); + + path = new File(path, Accelerometer.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_accelerometer_create_history_table)); + } + + this.setDatabaseVersion(this.mDatabase, Accelerometer.DATABASE_VERSION); + + if (Accelerometer.isEnabled(this.mContext)) { + this.mSensor = sensors.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + + Runnable r = new Runnable() + { + public void run() + { + Looper.prepare(); + + me.mXValueBuffers = new float[Accelerometer.NUM_BUFFERS][Accelerometer.BUFFER_SIZE]; + me.mYValueBuffers = new float[Accelerometer.NUM_BUFFERS][Accelerometer.BUFFER_SIZE]; + me.mZValueBuffers = new float[Accelerometer.NUM_BUFFERS][Accelerometer.BUFFER_SIZE]; + me.mAccuracyBuffers = new int[Accelerometer.NUM_BUFFERS][Accelerometer.BUFFER_SIZE]; + me.mRawTimestampBuffers = new long[Accelerometer.NUM_BUFFERS][Accelerometer.BUFFER_SIZE]; + me.mTimestampBuffers = new long[Accelerometer.NUM_BUFFERS][Accelerometer.BUFFER_SIZE]; + + me.mActiveBuffersIndex = 0; + me.mCurrentBufferIndex = 0; + + Accelerometer.sHandler = new Handler(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) + sensors.registerListener(me, me.mSensor, SensorManager.SENSOR_DELAY_NORMAL, 0, Accelerometer.sHandler); + else + sensors.registerListener(me, me.mSensor, SensorManager.SENSOR_DELAY_NORMAL, Accelerometer.sHandler); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(me.mContext); + + final long refreshInterval = prefs.getLong(Accelerometer.REFRESH_INTERVAL, Accelerometer.REFRESH_INTERVAL_DEFAULT); + + if (refreshInterval > 0) { + final long refreshDuration = prefs.getLong(Accelerometer.REFRESH_DURATION, Accelerometer.REFRESH_DURATION_DEFAULT); + + if (me.mIntervalThread != null) { + me.mIntervalThread.interrupt(); + me.mIntervalThread = null; + } + + Runnable managerRunnable = new Runnable() { + @Override + public void run() { + try { + while (true) { + Thread.sleep(refreshDuration); + + sensors.unregisterListener(me, me.mSensor); + + Thread.sleep(refreshInterval - refreshDuration); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) + sensors.registerListener(me, me.mSensor, SensorManager.SENSOR_DELAY_NORMAL, 0, Accelerometer.sHandler); + else + sensors.registerListener(me, me.mSensor, SensorManager.SENSOR_DELAY_NORMAL, Accelerometer.sHandler); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }; + + me.mIntervalThread = new Thread(managerRunnable, "accelerometer-interval"); + me.mIntervalThread.start(); + } + + Looper.loop(); + } + }; + + Thread t = new Thread(r, "accelerometer"); + t.start(); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(me.mContext); + + if (prefs.getBoolean(Accelerometer.IGNORE_POWER_MANAGEMENT, Accelerometer.IGNORE_POWER_MANAGEMENT_DEFAULT)) { + Generators.getInstance(this.mContext).acquireWakeLock(Accelerometer.IDENTIFIER, PowerManager.PARTIAL_WAKE_LOCK); + } else { + Generators.getInstance(this.mContext).releaseWakeLock(Accelerometer.IDENTIFIER); + } + } else { + this.stopGenerator(); + } + } + + private void stopGenerator() { + final SensorManager sensors = (SensorManager) this.mContext.getSystemService(Context.SENSOR_SERVICE); + + if (this.mSensor != null) { + if (this.mIntervalThread != null) { + this.mIntervalThread.interrupt(); + this.mIntervalThread = null; + } + + sensors.unregisterListener(this, this.mSensor); + + if (Accelerometer.sHandler != null) { + Looper loop = Accelerometer.sHandler.getLooper(); + loop.quit(); + + Accelerometer.sHandler = null; + } + + this.mXValueBuffers = null; + this.mYValueBuffers = null; + this.mZValueBuffers = null; + this.mAccuracyBuffers = null; + this.mRawTimestampBuffers = null; + this.mTimestampBuffers = null; + + this.mActiveBuffersIndex = 0; + this.mCurrentBufferIndex = 0; + + this.mSensor = null; + } + + Generators.getInstance(this.mContext).releaseWakeLock(Accelerometer.IDENTIFIER); + } + + public static boolean isEnabled(Context context) { + SharedPreferences prefs = Generators.getInstance(context).getSharedPreferences(context); + + return prefs.getBoolean(Accelerometer.ENABLED, Accelerometer.ENABLED_DEFAULT); + } + + public static boolean isRunning(Context context) { + if (Accelerometer.sInstance == null) { + return false; + } + + return Accelerometer.sInstance.mSensor != null; + } + + public static ArrayList diagnostics(final Context context) { + ArrayList actions = new ArrayList<>(); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + if (prefs.getBoolean(Accelerometer.IGNORE_POWER_MANAGEMENT, Accelerometer.IGNORE_POWER_MANAGEMENT_DEFAULT)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PowerManager power = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + + if (power.isIgnoringBatteryOptimizations(context.getPackageName()) == false) { + actions.add(new DiagnosticAction(context.getString(R.string.diagnostic_battery_optimization_exempt_title), context.getString(R.string.diagnostic_battery_optimization_exempt), new Runnable() { + + @Override + public void run() { + Intent intent = new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + })); + } + } + } + + return actions; + } + + public static void bindViewHolder(final DataPointViewHolder holder) { + if (Accelerometer.sIsDrawing) { + return; + } + + final long drawStart = System.currentTimeMillis(); + + if (drawStart - Accelerometer.sLastDrawStart < (30 * 1000)) { + return; + } + + Accelerometer.sLastDrawStart = drawStart; + + Accelerometer.sIsDrawing = true; + + final Context context = holder.itemView.getContext(); + final View itemView = holder.itemView; + + final Accelerometer generator = Accelerometer.getInstance(context); + + final long now = System.currentTimeMillis() / (1000 * 60 * 5); + final long start = now - (24 * 12); // * 60); + + View cardContent = itemView.findViewById(R.id.card_content); + View cardEmpty = itemView.findViewById(R.id.card_empty); + TextView dateLabel = (TextView) itemView.findViewById(R.id.generator_data_point_date); + + if (context instanceof Activity) { + cardContent.setVisibility(View.VISIBLE); + cardEmpty.setVisibility(View.GONE); + + dateLabel.setText(Generator.formatTimestamp(context, Accelerometer.latestPointGenerated(generator.mContext) / 1000)); + + Runnable r = new Runnable() { + @Override + public void run() { + final ArrayList xLowValues = new ArrayList<>(); + final ArrayList xHighValues = new ArrayList<>(); + + final ArrayList yLowValues = new ArrayList<>(); + final ArrayList yHighValues = new ArrayList<>(); + + final ArrayList zLowValues = new ArrayList<>(); + final ArrayList zHighValues = new ArrayList<>(); + + final String where = Accelerometer.HISTORY_OBSERVED + " >= ? AND _id % 1024 = 0"; + final String[] args = { "" + (System.currentTimeMillis() - (24 * 60 * 60 * 1000)) }; + + Cursor c = generator.mDatabase.query(Accelerometer.TABLE_HISTORY, null, where, args, null, null, Accelerometer.HISTORY_OBSERVED + " DESC"); + + long lastTimestamp = -1; + + float maxValue = 0; + float minValue = 0; + + float lowX = -1; + float highX = -1; + + float lowY = -1; + float highY = -1; + + float lowZ = -1; + float highZ = -1; + + int whenIndex = c.getColumnIndex(Accelerometer.HISTORY_OBSERVED); + int xIndex = c.getColumnIndex(Accelerometer.HISTORY_X); + int yIndex = c.getColumnIndex(Accelerometer.HISTORY_Y); + int zIndex = c.getColumnIndex(Accelerometer.HISTORY_Z); + + while (c.moveToNext()) { + long when = c.getLong(whenIndex); + + when = when / (1000 * 1000); + when = when / (1000 * 6 * 50); + + float x = c.getFloat(xIndex); + float y = c.getFloat(yIndex); + float z = c.getFloat(zIndex); + + if (lastTimestamp != when) { + if (lastTimestamp != -1) { + xLowValues.add(0, new Entry(lastTimestamp, lowX)); + xHighValues.add(0, new Entry(lastTimestamp, highX)); + + yLowValues.add(0, new Entry(lastTimestamp, lowY)); + yHighValues.add(0, new Entry(lastTimestamp, highY)); + + zLowValues.add(0, new Entry(lastTimestamp, lowZ)); + zHighValues.add(0, new Entry(lastTimestamp, highZ)); + } + + lastTimestamp = when; + + lowX = x; + highX = x; + + lowY = y; + highY = y; + + lowZ = z; + highZ = z; + } else { + if (x < lowX) { + lowX = x; + } + + if (x > highX) { + highX = x; + } + + if (y < lowY) { + lowY = y; + } + + if (y > highY) { + highY = y; + } + + if (z < lowZ) { + lowZ = z; + } + + if (z > highZ) { + highZ = z; + } + } + + if (x > maxValue) { + maxValue = x; + } + + if (x < minValue) { + minValue = x; + } + + if (y > maxValue) { + maxValue = y; + } + + if (y < minValue) { + minValue = y; + } + + if (z > maxValue) { + maxValue = z; + } + + if (z < minValue) { + minValue = z; + } + } + + if (lastTimestamp != -1) { + xLowValues.add(0, new Entry(lastTimestamp, lowX)); + xHighValues.add(0, new Entry(lastTimestamp, highX)); + + yLowValues.add(0, new Entry(lastTimestamp, lowY)); + yHighValues.add(0, new Entry(lastTimestamp, highY)); + + zLowValues.add(0, new Entry(lastTimestamp, lowZ)); + zHighValues.add(0, new Entry(lastTimestamp, highZ)); + } + + c.close(); + + Activity activity = (Activity) context; + + final float finalMaxValue = maxValue; + final float finalMinValue = minValue; + + final List> data = new ArrayList<>(); + data.add(xLowValues); + data.add(xHighValues); + data.add(yLowValues); + data.add(yHighValues); + data.add(zLowValues); + data.add(zHighValues); + + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + int[] colors = { + R.color.generator_accelerometer_x_low, + R.color.generator_accelerometer_x_high, + R.color.generator_accelerometer_y_low, + R.color.generator_accelerometer_y_high, + R.color.generator_accelerometer_z_low, + R.color.generator_accelerometer_z_high + }; + + LineData chartData = new LineData(); + + for (int i = 0; i < colors.length; i++) { + int color = colors[i]; + + ArrayList entries = data.get(i); + + LineDataSet set = new LineDataSet(entries, ""); + set.setAxisDependency(YAxis.AxisDependency.LEFT); + set.setLineWidth(1.0f); + set.setDrawCircles(false); + set.setFillAlpha(192); + set.setDrawFilled(false); + set.setDrawValues(true); + set.setColor(ContextCompat.getColor(context, color)); + set.setDrawCircleHole(false); + set.setDrawValues(false); + set.setMode(LineDataSet.Mode.LINEAR); + + chartData.addDataSet(set); + } + + final LineChart chart = (LineChart) itemView.findViewById(R.id.accelerometer_chart); + + if (chart != null) { + chart.setViewPortOffsets(0, 0, 0, 0); + chart.setHighlightPerDragEnabled(false); + chart.setHighlightPerTapEnabled(false); + chart.setBackgroundColor(ContextCompat.getColor(context, android.R.color.black)); + chart.setPinchZoom(false); + + final DateFormat timeFormat = android.text.format.DateFormat.getTimeFormat(context); + + final XAxis xAxis = chart.getXAxis(); + xAxis.setPosition(XAxis.XAxisPosition.BOTTOM_INSIDE); + xAxis.setTextSize(10f); + xAxis.setDrawAxisLine(true); + xAxis.setDrawGridLines(true); + xAxis.setCenterAxisLabels(true); + xAxis.setDrawLabels(true); + xAxis.setTextColor(ContextCompat.getColor(context, android.R.color.white)); + xAxis.setGranularityEnabled(true); + xAxis.setGranularity(1); + xAxis.setAxisMinimum(start); + xAxis.setAxisMaximum(now); + xAxis.setValueFormatter(new IAxisValueFormatter() { + @Override + public String getFormattedValue(float value, AxisBase axis) { + Date date = new Date((long) value * 5 * 60 * 1000); + + return timeFormat.format(date); + } + }); + + YAxis leftAxis = chart.getAxisLeft(); + leftAxis.setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART); + leftAxis.setDrawGridLines(true); + leftAxis.setDrawAxisLine(true); + leftAxis.setGranularityEnabled(true); + leftAxis.setTextColor(ContextCompat.getColor(context, android.R.color.white)); + + YAxis rightAxis = chart.getAxisRight(); + rightAxis.setEnabled(false); + + chart.getLegend().setEnabled(false); + chart.getDescription().setEnabled(false); + + chart.setVisibleYRange((float) Math.floor(finalMinValue) - 1, (float) Math.ceil(finalMaxValue) + 1, YAxis.AxisDependency.LEFT); + chart.setData(chartData); + } + + Accelerometer.sIsDrawing = false; + } + }); + } + }; + + Thread t = new Thread(r, "render-accelerometer-graph"); + t.start(); + } else { + cardContent.setVisibility(View.GONE); + cardEmpty.setVisibility(View.VISIBLE); + + dateLabel.setText(R.string.label_never_pdk); + } + } + + public static View fetchView(ViewGroup parent) { + return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_sensors_accelerometer, parent, false); + } + + @Override + public List fetchPayloads() { + return new ArrayList<>(); + } + + public static long latestPointGenerated(Context context) { + Accelerometer me = Accelerometer.getInstance(context); + + if (me.mLatestTimestamp == 0) { + Cursor c = me.mDatabase.query(Accelerometer.TABLE_HISTORY, null, null, null, null, null, Accelerometer.HISTORY_OBSERVED + " DESC", "1"); + + if (c.moveToNext()) { + me.mLatestTimestamp = c.getLong(c.getColumnIndex(Accelerometer.HISTORY_OBSERVED)) / (1000 * 1000); + } + + c.close(); + } + + return me.mLatestTimestamp; + } + + public Cursor queryHistory(String[] cols, String where, String[] args, String orderBy) { + return this.mDatabase.query(Accelerometer.TABLE_HISTORY, cols, where, args, null, null, orderBy); + } + + @Override + public void onSensorChanged(SensorEvent sensorEvent) { + long rawTimestamp = sensorEvent.timestamp; + + if (this.mBaseTimestamp == 0) { + this.mBaseTimestamp = (System.currentTimeMillis() * (1000 * 1000)) - rawTimestamp; + } + + int accuracy = sensorEvent.accuracy; + long normalizedTimestamp = this.mBaseTimestamp + rawTimestamp; + + if (this.mCurrentBufferIndex >= Accelerometer.BUFFER_SIZE) { + this.saveBuffer(this.mActiveBuffersIndex, this.mCurrentBufferIndex); + + this.mCurrentBufferIndex = 0; + this.mActiveBuffersIndex += 1; + + if (this.mActiveBuffersIndex >= Accelerometer.NUM_BUFFERS) { + this.mActiveBuffersIndex = 0; + } + } + + this.mXValueBuffers[this.mActiveBuffersIndex][this.mCurrentBufferIndex] = sensorEvent.values[0]; + this.mYValueBuffers[this.mActiveBuffersIndex][this.mCurrentBufferIndex] = sensorEvent.values[1]; + this.mZValueBuffers[this.mActiveBuffersIndex][this.mCurrentBufferIndex] = sensorEvent.values[2]; + this.mAccuracyBuffers[this.mActiveBuffersIndex][this.mCurrentBufferIndex] = accuracy; + this.mRawTimestampBuffers[this.mActiveBuffersIndex][this.mCurrentBufferIndex] = rawTimestamp; + this.mTimestampBuffers[this.mActiveBuffersIndex][this.mCurrentBufferIndex] = normalizedTimestamp; + + this.mCurrentBufferIndex += 1; + } + + private void saveBuffer(final int bufferIndex, final int bufferSize) { + final Accelerometer me = this; + + me.mLatestTimestamp = System.currentTimeMillis(); + + Runnable r = new Runnable() { + @Override + public void run() { + long now = System.currentTimeMillis(); + + try { + me.mDatabase.beginTransaction(); + + for (int i = 0; i < bufferSize; i++) { + ContentValues values = new ContentValues(); + + values.put(Accelerometer.HISTORY_X, me.mXValueBuffers[bufferIndex][i]); + values.put(Accelerometer.HISTORY_Y, me.mYValueBuffers[bufferIndex][i]); + values.put(Accelerometer.HISTORY_Z, me.mZValueBuffers[bufferIndex][i]); + values.put(Accelerometer.HISTORY_OBSERVED, me.mTimestampBuffers[bufferIndex][i]); + values.put(Accelerometer.HISTORY_RAW_TIMESTAMP, me.mRawTimestampBuffers[bufferIndex][i]); + values.put(Accelerometer.HISTORY_ACCURACY, me.mAccuracyBuffers[bufferIndex][i]); + + me.mDatabase.insert(Accelerometer.TABLE_HISTORY, null, values); + } + + me.mDatabase.setTransactionSuccessful(); + } finally { + me.mDatabase.endTransaction(); + } + + Bundle update = new Bundle(); + update.putLong(Accelerometer.HISTORY_OBSERVED, now); + + Bundle sensorReadings = new Bundle(); + + sensorReadings.putFloatArray(Accelerometer.HISTORY_X, me.mXValueBuffers[bufferIndex]); + sensorReadings.putFloatArray(Accelerometer.HISTORY_Y, me.mYValueBuffers[bufferIndex]); + sensorReadings.putFloatArray(Accelerometer.HISTORY_Z, me.mZValueBuffers[bufferIndex]); + sensorReadings.putLongArray(Accelerometer.HISTORY_RAW_TIMESTAMP, me.mRawTimestampBuffers[bufferIndex]); + sensorReadings.putLongArray(Accelerometer.HISTORY_OBSERVED, me.mTimestampBuffers[bufferIndex]); + sensorReadings.putIntArray(Accelerometer.HISTORY_ACCURACY, me.mAccuracyBuffers[bufferIndex]); + + update.putBundle(SensorGenerator.SENSOR_DATA, sensorReadings); + SensorGenerator.addSensorMetadata(update, me.mSensor); + + Generators.getInstance(me.mContext).notifyGeneratorUpdated(Accelerometer.GENERATOR_IDENTIFIER, update); + + if (now - me.mLastCleanup > me.mCleanupInterval) { + me.mLastCleanup = now; + + long start = (now - (24 * 60 * 60 * 1000)) * 1000 * 1000; + + String where = Accelerometer.HISTORY_OBSERVED + " < ?"; + String[] args = { "" + start }; + + me.mDatabase.delete(Accelerometer.TABLE_HISTORY, where, args); + } + } + }; + + Thread t = new Thread(r, "accelerometer-save-buffer"); + t.start(); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int newAccuracy) { + // Do nothing... + } +} diff --git a/src/com/audacious_software/passive_data_kit/generators/sensors/AmbientLight.java b/src/com/audacious_software/passive_data_kit/generators/sensors/AmbientLight.java new file mode 100755 index 0000000..5018de4 --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/generators/sensors/AmbientLight.java @@ -0,0 +1,585 @@ +package com.audacious_software.passive_data_kit.generators.sensors; + +import android.app.Activity; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.PowerManager; +import android.preference.PreferenceManager; +import android.provider.Settings; +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.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 com.github.mikephil.charting.charts.LineChart; +import com.github.mikephil.charting.components.AxisBase; +import com.github.mikephil.charting.components.XAxis; +import com.github.mikephil.charting.components.YAxis; +import com.github.mikephil.charting.data.Entry; +import com.github.mikephil.charting.data.LineData; +import com.github.mikephil.charting.data.LineDataSet; +import com.github.mikephil.charting.formatter.IAxisValueFormatter; + +import java.io.File; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * Created by cjkarr on 4/17/2017. + */ + +public class AmbientLight extends SensorGenerator implements SensorEventListener { + private static final String GENERATOR_IDENTIFIER = "pdk-sensor-light"; + + private static final String ENABLED = "com.audacious_software.passive_data_kit.generators.device.AmbientLight.ENABLED"; + private static final boolean ENABLED_DEFAULT = true; + + private static final String IGNORE_POWER_MANAGEMENT = "com.audacious_software.passive_data_kit.generators.sensors.AmbientLight.IGNORE_POWER_MANAGEMENT"; + private static final boolean IGNORE_POWER_MANAGEMENT_DEFAULT = true; + + private static final String DATABASE_PATH = "pdk-sensor-ambient-light.sqlite"; + private static final int DATABASE_VERSION = 1; + + public static final String TABLE_HISTORY = "history"; + + public static final String HISTORY_OBSERVED = "observed"; + public static final String HISTORY_LEVEL = "light_level"; + private static final String HISTORY_RAW_TIMESTAMP = "raw_timestamp"; + private static final String HISTORY_ACCURACY = "accuracy"; + + private static AmbientLight sInstance = null; + private static Handler sHandler = null; + private static boolean sIsDrawing = false; + private static long sLastDrawStart = 0; + + private SQLiteDatabase mDatabase = null; + + private Sensor mSensor = null; + + private static int NUM_BUFFERS = 3; + private static int BUFFER_SIZE = 32; + + private long mLastCleanup = 0; + private long mCleanupInterval = 15 * 60 * 1000; + + private int mActiveBuffersIndex = 0; + private int mCurrentBufferIndex = 0; + + private float[][] mValueBuffers = null; + private int[][] mAccuracyBuffers = null; + private long[][] mRawTimestampBuffers = null; + private long[][] mTimestampBuffers = null; + + long mBaseTimestamp = 0; + private long mLatestTimestamp = 0; + + public static AmbientLight getInstance(Context context) { + if (AmbientLight.sInstance == null) { + AmbientLight.sInstance = new AmbientLight(context.getApplicationContext()); + } + + return AmbientLight.sInstance; + } + + public AmbientLight(Context context) { + super(context); + } + + public void setIgnorePowerManagement(boolean ignore) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + SharedPreferences.Editor e = prefs.edit(); + + e.putBoolean(AmbientLight.IGNORE_POWER_MANAGEMENT, ignore); + e.apply(); + + this.stopGenerator(); + this.startGenerator(); + } + + public static void start(final Context context) { + AmbientLight.getInstance(context).startGenerator(); + } + + private void startGenerator() { + final SensorManager sensors = (SensorManager) this.mContext.getSystemService(Context.SENSOR_SERVICE); + + final AmbientLight me = this; + + Generators.getInstance(this.mContext).registerCustomViewClass(AmbientLight.GENERATOR_IDENTIFIER, AmbientLight.class); + + File path = PassiveDataKit.getGeneratorsStorage(this.mContext); + + path = new File(path, AmbientLight.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_ambient_light_create_history_table)); + } + + this.setDatabaseVersion(this.mDatabase, AmbientLight.DATABASE_VERSION); + + if (AmbientLight.isEnabled(this.mContext)) { + this.mSensor = sensors.getDefaultSensor(Sensor.TYPE_LIGHT); + + Runnable r = new Runnable() + { + public void run() + { + Looper.prepare(); + + me.mValueBuffers = new float[AmbientLight.NUM_BUFFERS][AmbientLight.BUFFER_SIZE]; + me.mAccuracyBuffers = new int[AmbientLight.NUM_BUFFERS][AmbientLight.BUFFER_SIZE]; + me.mRawTimestampBuffers = new long[AmbientLight.NUM_BUFFERS][AmbientLight.BUFFER_SIZE]; + me.mTimestampBuffers = new long[AmbientLight.NUM_BUFFERS][AmbientLight.BUFFER_SIZE]; + + me.mActiveBuffersIndex = 0; + me.mCurrentBufferIndex = 0; + + AmbientLight.sHandler = new Handler(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) + sensors.registerListener(me, me.mSensor, SensorManager.SENSOR_DELAY_FASTEST, 0, AmbientLight.sHandler); + else + sensors.registerListener(me, me.mSensor, SensorManager.SENSOR_DELAY_FASTEST, AmbientLight.sHandler); + + Looper.loop(); + } + }; + + Thread t = new Thread(r, "ambient-light"); + t.start(); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + + if (prefs.getBoolean(AmbientLight.IGNORE_POWER_MANAGEMENT, AmbientLight.IGNORE_POWER_MANAGEMENT_DEFAULT)) { + Generators.getInstance(this.mContext).acquireWakeLock(Accelerometer.IDENTIFIER, PowerManager.PARTIAL_WAKE_LOCK); + } else { + Generators.getInstance(this.mContext).releaseWakeLock(Accelerometer.IDENTIFIER); + } + } else { + this.stopGenerator(); + } + } + + private void stopGenerator() { + final SensorManager sensors = (SensorManager) this.mContext.getSystemService(Context.SENSOR_SERVICE); + + if (this.mSensor != null) { + sensors.unregisterListener(this, this.mSensor); + + if (AmbientLight.sHandler != null) { + Looper loop = AmbientLight.sHandler.getLooper(); + loop.quit(); + + AmbientLight.sHandler = null; + } + + this.mValueBuffers = null; + this.mAccuracyBuffers = null; + this.mRawTimestampBuffers = null; + this.mTimestampBuffers = null; + + this.mActiveBuffersIndex = 0; + this.mCurrentBufferIndex = 0; + + this.mSensor = null; + } + + Generators.getInstance(this.mContext).releaseWakeLock(AmbientLight.IDENTIFIER); + } + + public static boolean isEnabled(Context context) { + SharedPreferences prefs = Generators.getInstance(context).getSharedPreferences(context); + + return prefs.getBoolean(AmbientLight.ENABLED, AmbientLight.ENABLED_DEFAULT); + } + + public static boolean isRunning(Context context) { + if (AmbientLight.sInstance == null) { + return false; + } + + return AmbientLight.sInstance.mSensor != null; + } + + public static ArrayList diagnostics(final Context context) { + ArrayList actions = new ArrayList<>(); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + if (prefs.getBoolean(AmbientLight.IGNORE_POWER_MANAGEMENT, AmbientLight.IGNORE_POWER_MANAGEMENT_DEFAULT)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PowerManager power = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + + if (power.isIgnoringBatteryOptimizations(context.getPackageName()) == false) { + actions.add(new DiagnosticAction(context.getString(R.string.diagnostic_battery_optimization_exempt_title), context.getString(R.string.diagnostic_battery_optimization_exempt), new Runnable() { + + @Override + public void run() { + Intent intent = new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + })); + } + } + } + + return actions; + } + + public static void bindViewHolder(final DataPointViewHolder holder) { + if (AmbientLight.sIsDrawing) { + return; + } + + final long drawStart = System.currentTimeMillis(); + + if (drawStart - AmbientLight.sLastDrawStart < (30 * 1000)) { + return; + } + + AmbientLight.sLastDrawStart = drawStart; + + AmbientLight.sIsDrawing = true; + + final Context context = holder.itemView.getContext(); + + final AmbientLight generator = AmbientLight.getInstance(context); + + final long now = System.currentTimeMillis() / (1000 * 60 * 5); + final long start = now - (24 * 12); // * 60); + + 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 (context instanceof Activity) { + cardContent.setVisibility(View.VISIBLE); + cardEmpty.setVisibility(View.GONE); + + dateLabel.setText(Generator.formatTimestamp(context, AmbientLight.latestPointGenerated(context) / 1000)); + + Runnable r = new Runnable() { + @Override + public void run() { + final ArrayList lowValues = new ArrayList<>(); + final ArrayList highValues = new ArrayList<>(); + + long lastTimestamp = -1; + + float maxValue = 0; + float minValue = 0; + + float lowLevel = -1; + float highLevel = -1; + + final String where = Accelerometer.HISTORY_OBSERVED + " >= ? AND _id"; + final String[] args = { "" + (System.currentTimeMillis() - (24 * 60 * 60 * 1000)) }; + + Cursor c = generator.mDatabase.query(AmbientLight.TABLE_HISTORY, null, where, args, null, null, AmbientLight.HISTORY_OBSERVED + " DESC"); + + while (c.moveToNext()) { + long when = c.getLong(c.getColumnIndex(AmbientLight.HISTORY_OBSERVED)); + + when = when / (1000 * 1000); + when = when / (1000 * 6 * 50); + + float level = c.getFloat(c.getColumnIndex(AmbientLight.HISTORY_LEVEL)); + + if (level < 0.001) { + level = 0.001f; + } + + level = (float) Math.log10(level); + + if (lastTimestamp != when) { + if (lastTimestamp != -1) { + lowValues.add(0, new Entry(lastTimestamp, lowLevel)); + highValues.add(0, new Entry(lastTimestamp, highLevel)); + } + + lastTimestamp = when; + lowLevel = level; + highLevel = level; + } else { + if (level < lowLevel) { + lowLevel = level; + } + + if (level > highLevel) { + highLevel = level; + } + } + + if (level > maxValue) { + maxValue = level; + } + + if (level < minValue) { + minValue = level; + } + } + + if (lastTimestamp != -1) { + lowValues.add(0, new Entry(lastTimestamp, lowLevel)); + highValues.add(0, new Entry(lastTimestamp, highLevel)); + } + + final float finalMaxValue = maxValue; + final float finalMinValue = minValue; + + Activity activity = (Activity) context; + + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + final LineChart chart = (LineChart) holder.itemView.findViewById(R.id.light_chart); + chart.setViewPortOffsets(0,0,0,0); + chart.setHighlightPerDragEnabled(false); + chart.setHighlightPerTapEnabled(false); + chart.setBackgroundColor(ContextCompat.getColor(context, android.R.color.black)); + chart.setPinchZoom(false); + + final DateFormat timeFormat = android.text.format.DateFormat.getTimeFormat(context); + + final XAxis xAxis = chart.getXAxis(); + xAxis.setPosition(XAxis.XAxisPosition.BOTTOM_INSIDE); + xAxis.setTextSize(10f); + xAxis.setDrawAxisLine(true); + xAxis.setDrawGridLines(true); + xAxis.setCenterAxisLabels(true); + xAxis.setDrawLabels(true); + xAxis.setTextColor(ContextCompat.getColor(context, android.R.color.white)); + xAxis.setGranularityEnabled(true); + xAxis.setGranularity(1); + xAxis.setAxisMinimum(start); + xAxis.setAxisMaximum(now); + xAxis.setValueFormatter(new IAxisValueFormatter() { + @Override + public String getFormattedValue(float value, AxisBase axis) { + Date date = new Date((long) value * 5 * 60 * 1000); + + return timeFormat.format(date); + } + }); + + YAxis leftAxis = chart.getAxisLeft(); + leftAxis.setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART); + leftAxis.setDrawGridLines(true); + leftAxis.setDrawAxisLine(true); + leftAxis.setGranularityEnabled(true); + leftAxis.setTextColor(ContextCompat.getColor(context, android.R.color.white)); + leftAxis.setValueFormatter(new IAxisValueFormatter() { + @Override + public String getFormattedValue(float value, AxisBase axis) { + return "" + Math.pow(10, value); + } + }); + + YAxis rightAxis = chart.getAxisRight(); + rightAxis.setEnabled(false); + + chart.getLegend().setEnabled(false); + chart.getDescription().setEnabled(false); + + LineDataSet set = new LineDataSet(lowValues, "Low Light Levels"); + set.setAxisDependency(YAxis.AxisDependency.LEFT); + set.setLineWidth(1.0f); + set.setDrawCircles(false); + set.setFillAlpha(192); + set.setDrawFilled(false); + set.setDrawValues(true); + set.setColor(ContextCompat.getColor(context, R.color.generator_ambient_light_low)); + set.setDrawCircleHole(false); + set.setDrawValues(false); + set.setMode(LineDataSet.Mode.LINEAR); + + LineData chartData = new LineData(set); + + set = new LineDataSet(highValues, "High Light Levels"); + set.setAxisDependency(YAxis.AxisDependency.LEFT); + set.setLineWidth(1.0f); + set.setDrawCircles(false); + set.setFillAlpha(192); + set.setDrawFilled(false); + set.setDrawValues(true); + set.setColor(ContextCompat.getColor(context, R.color.generator_ambient_light_high)); + set.setDrawCircleHole(false); + set.setDrawValues(false); + set.setMode(LineDataSet.Mode.LINEAR); + + chartData.addDataSet(set); + + chart.setVisibleYRange((float) Math.floor(finalMinValue) - 1, (float) Math.ceil(finalMaxValue) + 1, YAxis.AxisDependency.LEFT); + chart.setData(chartData); + + AmbientLight.sIsDrawing = false; + } + }); + } + }; + + Thread t = new Thread(r, "render-ambient-light-graph"); + t.start(); + } else { + cardContent.setVisibility(View.GONE); + cardEmpty.setVisibility(View.VISIBLE); + + dateLabel.setText(R.string.label_never_pdk); + } + } + + public static View fetchView(ViewGroup parent) + { + return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_sensors_ambient_light, parent, false); + } + + @Override + public List fetchPayloads() { + return new ArrayList<>(); + } + + public static long latestPointGenerated(Context context) { + AmbientLight me = AmbientLight.getInstance(context); + + if (me.mLatestTimestamp == 0) { + Cursor c = me.mDatabase.query(AmbientLight.TABLE_HISTORY, null, null, null, null, null, Accelerometer.HISTORY_OBSERVED + " DESC", "1"); + + if (c.moveToNext()) { + me.mLatestTimestamp = c.getLong(c.getColumnIndex(AmbientLight.HISTORY_OBSERVED) / (1000 * 1000)); + } + + c.close(); + } + + return me.mLatestTimestamp; + } + + public Cursor queryHistory(String[] cols, String where, String[] args, String orderBy) { + return this.mDatabase.query(AmbientLight.TABLE_HISTORY, cols, where, args, null, null, orderBy); + } + + @Override + public void onSensorChanged(SensorEvent sensorEvent) { + long rawTimestamp = sensorEvent.timestamp; + + if (this.mBaseTimestamp == 0) { + this.mBaseTimestamp = (System.currentTimeMillis() * (1000 * 1000)) - rawTimestamp; + } + + int accuracy = sensorEvent.accuracy; + long normalizedTimestamp = this.mBaseTimestamp + rawTimestamp; + float value = sensorEvent.values[0]; + + if (this.mCurrentBufferIndex >= AmbientLight.BUFFER_SIZE) { + this.saveBuffer(this.mActiveBuffersIndex, this.mCurrentBufferIndex); + + this.mCurrentBufferIndex = 0; + this.mActiveBuffersIndex += 1; + + if (this.mActiveBuffersIndex >= AmbientLight.NUM_BUFFERS) { + this.mActiveBuffersIndex = 0; + } + } + + this.mValueBuffers[this.mActiveBuffersIndex][this.mCurrentBufferIndex] = value; + this.mAccuracyBuffers[this.mActiveBuffersIndex][this.mCurrentBufferIndex] = accuracy; + this.mRawTimestampBuffers[this.mActiveBuffersIndex][this.mCurrentBufferIndex] = rawTimestamp; + this.mTimestampBuffers[this.mActiveBuffersIndex][this.mCurrentBufferIndex] = normalizedTimestamp; + + this.mCurrentBufferIndex += 1; + } + + private void saveBuffer(final int bufferIndex, final int bufferSize) { + final AmbientLight me = this; + + me.mLatestTimestamp = System.currentTimeMillis(); + + Runnable r = new Runnable() { + @Override + public void run() { + long now = System.currentTimeMillis(); + + try { + me.mDatabase.beginTransaction(); + + for (int i = 0; i < bufferSize; i++) { + ContentValues values = new ContentValues(); + + values.put(AmbientLight.HISTORY_LEVEL, me.mValueBuffers[bufferIndex][i]); + values.put(AmbientLight.HISTORY_OBSERVED, me.mTimestampBuffers[bufferIndex][i]); + values.put(AmbientLight.HISTORY_RAW_TIMESTAMP, me.mRawTimestampBuffers[bufferIndex][i]); + values.put(AmbientLight.HISTORY_ACCURACY, me.mAccuracyBuffers[bufferIndex][i]); + + me.mDatabase.insert(AmbientLight.TABLE_HISTORY, null, values); + } + + me.mDatabase.setTransactionSuccessful(); + } finally { + me.mDatabase.endTransaction(); + } + + Bundle update = new Bundle(); + update.putLong(AmbientLight.HISTORY_OBSERVED, now); + + Bundle sensorReadings = new Bundle(); + + sensorReadings.putFloatArray(AmbientLight.HISTORY_LEVEL, me.mValueBuffers[bufferIndex]); + sensorReadings.putLongArray(AmbientLight.HISTORY_RAW_TIMESTAMP, me.mRawTimestampBuffers[bufferIndex]); + sensorReadings.putLongArray(AmbientLight.HISTORY_OBSERVED, me.mTimestampBuffers[bufferIndex]); + sensorReadings.putIntArray(AmbientLight.HISTORY_ACCURACY, me.mAccuracyBuffers[bufferIndex]); + + update.putBundle(SensorGenerator.SENSOR_DATA, sensorReadings); + SensorGenerator.addSensorMetadata(update, me.mSensor); + + Generators.getInstance(me.mContext).notifyGeneratorUpdated(AmbientLight.GENERATOR_IDENTIFIER, update); + + if (now - me.mLastCleanup > me.mCleanupInterval) { + me.mLastCleanup = now; + + long start = (now - (24 * 60 * 60 * 1000)) * 1000 * 1000; + + String where = AmbientLight.HISTORY_OBSERVED + " < ?"; + String[] args = { "" + start }; + + me.mDatabase.delete(AmbientLight.TABLE_HISTORY, where, args); + } + } + }; + + Thread t = new Thread(r, "ambient-light-save-buffer"); + t.start(); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int newAccuracy) { + // Do nothing... + } +} diff --git a/src/com/audacious_software/passive_data_kit/generators/sensors/SensorGenerator.java b/src/com/audacious_software/passive_data_kit/generators/sensors/SensorGenerator.java new file mode 100755 index 0000000..947858b --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/generators/sensors/SensorGenerator.java @@ -0,0 +1,74 @@ +package com.audacious_software.passive_data_kit.generators.sensors; + +import android.content.Context; +import android.hardware.Sensor; +import android.os.Build; +import android.os.Bundle; + +import com.audacious_software.passive_data_kit.generators.Generator; + +/** + * Created by cjkarr on 5/11/2017. + */ + +public abstract class SensorGenerator extends Generator { + public static final String SENSOR_DATA = "sensor_data"; + + private static final String SENSOR_REPORTING_MODE = "reporting_mode"; + private static final String SENSOR_NAME = "name"; + private static final String SENSOR_VENDOR = "vendor"; + private static final String SENSOR_MAX_RANGE = "max_range"; + private static final String SENSOR_POWER_USAGE = "power_usage"; + private static final String SENSOR_RESOLUTION = "resolution"; + private static final String SENSOR_MIN_DELAY = "min_delay"; + private static final String SENSOR_VERSION = "version"; + private static final String SENSOR_TYPE = "type"; + private static final String SENSOR_MAX_DELAY = "max_delay"; + private static final String SENSOR_IS_WAKEUP = "is_wakeup"; + + private static final String SENSOR_REPORTING_MODE_CONTINUOUS = "continuous"; + private static final String SENSOR_REPORTING_MODE_ON_CHANGE = "on_change"; + private static final String SENSOR_REPORTING_MODE_ONE_SHOT = "one_shot"; + private static final String SENSOR_REPORTING_MODE_SPECIAL_TRIGGER = "special_trigger"; + + public SensorGenerator(Context context) { + super(context); + } + + public static void addSensorMetadata(Bundle update, Sensor sensor) { + update.putString(SensorGenerator.SENSOR_NAME, sensor.getName()); + update.putString(SensorGenerator.SENSOR_VENDOR, sensor.getVendor()); + + update.putFloat(SensorGenerator.SENSOR_MAX_RANGE, sensor.getMaximumRange()); + update.putFloat(SensorGenerator.SENSOR_POWER_USAGE, sensor.getPower()); + update.putFloat(SensorGenerator.SENSOR_RESOLUTION, sensor.getResolution()); + + update.putInt(SensorGenerator.SENSOR_MIN_DELAY, sensor.getMinDelay()); + update.putInt(SensorGenerator.SENSOR_VERSION, sensor.getVersion()); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { + update.putString(SensorGenerator.SENSOR_TYPE, sensor.getStringType()); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + update.putInt(SensorGenerator.SENSOR_MAX_DELAY, sensor.getMaxDelay()); + + switch(sensor.getReportingMode()) { + case Sensor.REPORTING_MODE_CONTINUOUS: + update.putString(SensorGenerator.SENSOR_REPORTING_MODE, SensorGenerator.SENSOR_REPORTING_MODE_CONTINUOUS); + break; + case Sensor.REPORTING_MODE_ON_CHANGE: + update.putString(SensorGenerator.SENSOR_REPORTING_MODE, SensorGenerator.SENSOR_REPORTING_MODE_ON_CHANGE); + break; + case Sensor.REPORTING_MODE_ONE_SHOT: + update.putString(SensorGenerator.SENSOR_REPORTING_MODE, SensorGenerator.SENSOR_REPORTING_MODE_ONE_SHOT); + break; + case Sensor.REPORTING_MODE_SPECIAL_TRIGGER: + update.putString(SensorGenerator.SENSOR_REPORTING_MODE, SensorGenerator.SENSOR_REPORTING_MODE_SPECIAL_TRIGGER); + break; + } + + update.putBoolean(SensorGenerator.SENSOR_IS_WAKEUP, sensor.isWakeUpSensor()); + } + } + } +} 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 new file mode 100755 index 0000000..531ed71 --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/generators/vr/DaydreamViewController.java @@ -0,0 +1,185 @@ +package com.audacious_software.passive_data_kit.generators.vr; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +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 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. + */ + +public class DaydreamViewController extends Generator { + private static final String GENERATOR_IDENTIFIER = "pdk-daydream-vr-controller"; + + private static final String ENABLED = "com.audacious_software.passive_data_kit.generators.vr.DaydreamViewController.ENABLED"; + private static final boolean ENABLED_DEFAULT = true; + + private static DaydreamViewController sInstance = null; + + private BroadcastReceiver mReceiver = null; +// private ControllerManager mControllerManager = null; +// private Controller mController = null; + + public static DaydreamViewController getInstance(Context context) { + if (DaydreamViewController.sInstance == null) { + DaydreamViewController.sInstance = new DaydreamViewController(context.getApplicationContext()); + } + + return DaydreamViewController.sInstance; + } + + public DaydreamViewController(Context context) { + super(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) { + Log.e("PDK", "DVC STATUS CHANGE: " + status); + } + + @Override + public void onRecentered() { + Log.e("PDK", "DVC RECENTERED"); + } + }); + + DaydreamViewController.sInstance.mController = DaydreamViewController.sInstance.mControllerManager.getController(); + DaydreamViewController.sInstance.mController.setEventListener(new Controller.EventListener() { + public void onConnectionStateChanged (int state) { + super.onConnectionStateChanged(state); + + Log.e("PDK", "DVC STATE CHANGE: " + state); + } + + public void onUpdate() { + super.onUpdate(); + + Log.e("PDK", "DVC UPDATE"); + } + }); + + DaydreamViewController.sInstance.mControllerManager.start(); + +*/ + } + + private void startGenerator() { + Generators.getInstance(this.mContext).registerCustomViewClass(DaydreamViewController.GENERATOR_IDENTIFIER, DaydreamViewController.class); + } + + public static boolean isEnabled(Context context) { + SharedPreferences prefs = Generators.getInstance(context).getSharedPreferences(context); + + return prefs.getBoolean(DaydreamViewController.ENABLED, DaydreamViewController.ENABLED_DEFAULT); + } + + public static boolean isRunning(Context context) { + if (DaydreamViewController.sInstance == null) { + return false; + } + + return DaydreamViewController.sInstance.mReceiver != null; + } + + public static ArrayList diagnostics(Context context) { + return new ArrayList<>(); + } + + 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, "[]")); + +// 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); + + long zeroStart = cal.getTimeInMillis(); + cal.add(Calendar.DATE, -1); + + LinearLayout zeroTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_zero_value); + + ScreenState.populateTimeline(context, zeroTimeline, zeroStart, history); + + long oneStart = cal.getTimeInMillis(); + cal.add(Calendar.DATE, -1); + + LinearLayout oneTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_one_value); + + ScreenState.populateTimeline(context, oneTimeline, oneStart, history); + + long twoStart = cal.getTimeInMillis(); + + LinearLayout twoTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_two_value); + + ScreenState.populateTimeline(context, twoTimeline, twoStart, history); + } catch (JSONException e) { + e.printStackTrace(); + } + + double timestamp = dataPoint.getBundle(Generator.PDK_METADATA).getDouble(Generator.TIMESTAMP); + + TextView dateLabel = (TextView) holder.itemView.findViewById(R.id.generator_data_point_date); + + dateLabel.setText(Generator.formatTimestamp(context, timestamp)); + + Calendar 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())); + + */ + } + + public static View fetchView(ViewGroup parent) + { + return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_daydream_vr_controller, parent, false); + } + + @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/WithingsDevice.java b/src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java new file mode 100755 index 0000000..7cdcff4 --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java @@ -0,0 +1,1815 @@ +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.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.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.util.Base64; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +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.passive_data_kit.generators.diagnostics.AppEvent; +import com.audacious_software.pdk.passivedatakit.R; +import com.github.mikephil.charting.charts.LineChart; +import com.github.mikephil.charting.charts.PieChart; +import com.github.mikephil.charting.components.YAxis; +import com.github.mikephil.charting.data.Entry; +import com.github.mikephil.charting.data.LineData; +import com.github.mikephil.charting.data.LineDataSet; +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.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.Iterator; +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 DATASTREAM_INTRADAY_ACTIVITY = "intraday-activity"; + private static final String DATASTREAM_BODY = "body"; + private static final String DATASTREAM_SLEEP_MEASURES = "sleep-measures"; + private static final String DATASTREAM_SLEEP_SUMMARY = "sleep-summary"; + + 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 INTRADAY_ACTIVITY_START = "activity_start"; + private static final String INTRADAY_ACTIVITY_DURATION = "activity_duration"; + private static final String INTRADAY_ACTIVITY_CALORIES = "calories"; + private static final String INTRADAY_ACTIVITY_DISTANCE = "distance"; + private static final String INTRADAY_ACTIVITY_ELEVATION_CLIMBED = "elevation_climbed"; + private static final String INTRADAY_ACTIVITY_STEPS = "steps"; + private static final String INTRADAY_ACTIVITY_SWIM_STROKES = "swim_strokes"; + private static final String INTRADAY_ACTIVITY_POOL_LAPS = "pool_laps"; + + 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 = (15 * 60 * 1000); // (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; + + private static final String SERVER_FETCH_ENABLED = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.SERVER_FETCH_ENABLED"; + private static final boolean SERVER_FETCH_ENABLED_DEFAULT = false; + + 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 final String OAUTH_CONSUMER_KEY = "oauth_consumer_key"; + private static final String OAUTH_USER_TOKEN = "oauth_user_token"; + private static final String OAUTH_USER_SECRET = "oauth_user_secret"; + + private static WithingsDevice sInstance = null; + private Context mContext = null; + private SQLiteDatabase mDatabase = null; + private Handler mHandler = null; + private Map mProperties = new HashMap<>(); + + private int mPage = 0; + + 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() { + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(me.mContext); + long fetchInterval = prefs.getLong(WithingsDevice.DATA_FETCH_INTERVAL, WithingsDevice.DATA_FETCH_INTERVAL_DEFAULT); + + if (me.approvalGranted()) { + long lastFetch = prefs.getLong(WithingsDevice.LAST_DATA_FETCH, 0); + + long now = System.currentTimeMillis(); + + if (now - lastFetch > fetchInterval) { + 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(); + } + } + + 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); + + this.annotateGeneratorReading(updated); + + 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); + updated.putString(WithingsDevice.DATASTREAM, WithingsDevice.DATASTREAM_BODY); + + this.annotateGeneratorReading(updated); + + 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) { + try { + long now = System.currentTimeMillis(); + + if (response.getInt("status") == 0) { + JSONObject body = response.getJSONObject("body"); + + JSONObject series = body.getJSONObject("series"); + + Iterator keys = series.keys(); + + while (keys.hasNext()) { + String key = keys.next(); + + long timestamp = Long.parseLong(key); + + String where = WithingsDevice.INTRADAY_ACTIVITY_START + " = ?"; + String[] args = { "" + timestamp }; + + Cursor c = this.mDatabase.query(WithingsDevice.TABLE_INTRADAY_ACTIVITY_HISTORY, null, where, args, null, null, WithingsDevice.HISTORY_OBSERVED + " DESC"); + + if (c.moveToNext() == false) { + JSONObject item = series.getJSONObject(key); + + ContentValues values = new ContentValues(); + values.put(WithingsDevice.HISTORY_OBSERVED, now); + values.put(WithingsDevice.INTRADAY_ACTIVITY_START, timestamp); + values.put(WithingsDevice.INTRADAY_ACTIVITY_DURATION, item.getLong("duration")); + + Bundle updated = new Bundle(); + updated.putLong(WithingsDevice.HISTORY_OBSERVED, System.currentTimeMillis()); + updated.putLong(WithingsDevice.INTRADAY_ACTIVITY_START, timestamp); + updated.putLong(WithingsDevice.INTRADAY_ACTIVITY_DURATION, item.getLong("duration")); + + if (item.has("steps")) { + values.put(WithingsDevice.INTRADAY_ACTIVITY_STEPS, item.getLong("steps")); + updated.putLong(WithingsDevice.INTRADAY_ACTIVITY_STEPS, item.getLong("steps")); + } + + if (item.has("elevation")) { + values.put(WithingsDevice.INTRADAY_ACTIVITY_ELEVATION_CLIMBED, item.getLong("elevation")); + updated.putLong(WithingsDevice.INTRADAY_ACTIVITY_ELEVATION_CLIMBED, item.getLong("elevation")); + } + + if (item.has("distance")) { + values.put(WithingsDevice.INTRADAY_ACTIVITY_DISTANCE, item.getLong("distance")); + updated.putLong(WithingsDevice.INTRADAY_ACTIVITY_DISTANCE, item.getLong("distance")); + } + + if (item.has("calories")) { + values.put(WithingsDevice.INTRADAY_ACTIVITY_CALORIES, item.getLong("calories")); + updated.putLong(WithingsDevice.INTRADAY_ACTIVITY_CALORIES, item.getLong("calories")); + } + + if (item.has("stroke")) { + values.put(WithingsDevice.INTRADAY_ACTIVITY_SWIM_STROKES, item.getLong("stroke")); + updated.putLong(WithingsDevice.INTRADAY_ACTIVITY_SWIM_STROKES, item.getLong("stroke")); + } + + if (item.has("pool_lap")) { + values.put(WithingsDevice.INTRADAY_ACTIVITY_POOL_LAPS, item.getLong("pool_lap")); + updated.putLong(WithingsDevice.INTRADAY_ACTIVITY_POOL_LAPS, item.getLong("pool_lap")); + } + + this.mDatabase.insert(WithingsDevice.TABLE_INTRADAY_ACTIVITY_HISTORY, null, values); + + this.annotateGeneratorReading(updated); + + updated.putString(WithingsDevice.DATASTREAM, WithingsDevice.DATASTREAM_INTRADAY_ACTIVITY); + + Generators.getInstance(this.mContext).notifyGeneratorUpdated(WithingsDevice.GENERATOR_IDENTIFIER, updated); + } + + c.close(); + } + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + + 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); + + this.annotateGeneratorReading(updated); + + updated.putString(WithingsDevice.DATASTREAM, WithingsDevice.DATASTREAM_SLEEP_MEASURES); + + 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")); + } + + this.annotateGeneratorReading(updated); + + updated.putString(WithingsDevice.DATASTREAM, WithingsDevice.DATASTREAM_SLEEP_SUMMARY); + + Generators.getInstance(this.mContext).notifyGeneratorUpdated(WithingsDevice.GENERATOR_IDENTIFIER, updated); + } + } + } catch (JSONException e) { + AppEvent.getInstance(this.mContext).logThrowable(e); + } + } + } + + private void annotateGeneratorReading(Bundle reading) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + + if (prefs.getBoolean(WithingsDevice.SERVER_FETCH_ENABLED, WithingsDevice.SERVER_FETCH_ENABLED_DEFAULT)) { + String apiKey = this.getProperty(WithingsDevice.OPTION_OAUTH_CONSUMER_KEY); + String token = this.getProperty(WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN); + String tokenSecret = this.getProperty(WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN_SECRET); + + reading.putString(WithingsDevice.OAUTH_CONSUMER_KEY, apiKey); + reading.putString(WithingsDevice.OAUTH_USER_TOKEN, token); + reading.putString(WithingsDevice.OAUTH_USER_SECRET, tokenSecret); + } + } + + 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<>(); + + if (me.approvalGranted() == false) { + 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()); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + 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(final DataPointViewHolder holder) { + final WithingsDevice withings = WithingsDevice.getInstance(holder.itemView.getContext()); + + ViewPager pager = (ViewPager) holder.itemView.findViewById(R.id.content_pager); + + PagerAdapter adapter = new PagerAdapter() { + @Override + public int getCount() { + return 2; + } + + @Override + public boolean isViewFromObject(View view, Object content) { + return view.getTag().equals(content); + } + + public void destroyItem(ViewGroup container, int position, Object content) { + int toRemove = -1; + + for (int i = 0; i < container.getChildCount(); i++) { + View child = container.getChildAt(i); + + if (this.isViewFromObject(child, content)) + toRemove = i; + } + + if (toRemove >= 0) + container.removeViewAt(toRemove); + } + + public Object instantiateItem(ViewGroup container, int position) { + switch (position) { + case 0: + return WithingsDevice.bindActivityPage(container, holder, position); + case 1: + return WithingsDevice.bindIntradayPage(container, holder, position); + case 2: + return WithingsDevice.bindBodyPage(container, holder, position); + case 3: + return WithingsDevice.bindSleepPage(container, holder, position); + case 4: + return WithingsDevice.bindSleepSummaryPage(container, holder, position); + case 5: + return WithingsDevice.bindWorkoutsPage(container, holder, position); + default: + return WithingsDevice.bindInformationPage(container, holder, position); + } + } + }; + + pager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + withings.mPage = position; + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + }); + + pager.setAdapter(adapter); + + pager.setCurrentItem(withings.mPage); + } + + private static String bindInformationPage(ViewGroup container, DataPointViewHolder holder, int position) { + final Context context = container.getContext(); + + LinearLayout card = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.card_generator_withings_info_page, null); + card.setTag("" + position); + + container.addView(card); + + return "" + card.getTag(); + } + + private static String bindActivityPage(ViewGroup container, DataPointViewHolder holder, int position) { + final Context context = container.getContext(); + + LinearLayout card = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.card_generator_withings_activity_page, null); + card.setTag("" + position); + + long lastTimestamp = 0; + + double steps = 0; + double distance = 0; + double elevation = 0; + + double softActivity = 0; + double moderateActivity = 0; + double intenseActivity = 0; + + WithingsDevice generator = WithingsDevice.getInstance(card.getContext()); + + Cursor c = generator.mDatabase.query(WithingsDevice.TABLE_ACTIVITY_MEASURE_HISTORY, null, null, null, null, null, WithingsDevice.HISTORY_OBSERVED + " DESC"); + + if (c.moveToNext()) { + if (lastTimestamp == 0) { + lastTimestamp = c.getLong(c.getColumnIndex(WithingsDevice.HISTORY_OBSERVED)); + } + + steps = c.getDouble(c.getColumnIndex(WithingsDevice.ACTIVITY_MEASURE_STEPS)); + distance = c.getDouble(c.getColumnIndex(WithingsDevice.ACTIVITY_MEASURE_DISTANCE)); + elevation = c.getDouble(c.getColumnIndex(WithingsDevice.ACTIVITY_MEASURE_ELEVATION)); + + softActivity = c.getDouble(c.getColumnIndex(WithingsDevice.ACTIVITY_MEASURE_SOFT_ACTIVITY_DURATION)); + moderateActivity = c.getDouble(c.getColumnIndex(WithingsDevice.ACTIVITY_MEASURE_MODERATE_ACTIVITY_DURATION)); + intenseActivity = c.getDouble(c.getColumnIndex(WithingsDevice.ACTIVITY_MEASURE_INTENSE_ACTIVITY_DURATION)); + } + + c.close(); + + View cardContent = card.findViewById(R.id.content_activity); + View cardEmpty = card.findViewById(R.id.card_empty); + TextView dateLabel = (TextView) holder.itemView.findViewById(R.id.generator_data_point_date); + + if (lastTimestamp > 0) { + cardContent.setVisibility(View.VISIBLE); + cardEmpty.setVisibility(View.GONE); + + dateLabel.setText(Generator.formatTimestamp(context, lastTimestamp)); + + PieChart pieChart = (PieChart) card.findViewById(R.id.chart_phone_calls); + pieChart.getLegend().setEnabled(false); + + pieChart.setEntryLabelColor(android.R.color.transparent); + pieChart.getDescription().setEnabled(false); + pieChart.setDrawHoleEnabled(false); + + List entries = new ArrayList<>(); + + if (softActivity > 0) { + entries.add(new PieEntry((long) softActivity, context.getString(R.string.generator_withings_soft_activities_label))); + } + + if (moderateActivity > 0) { + entries.add(new PieEntry((long) moderateActivity, context.getString(R.string.generator_withings_moderate_activities_label))); + } + + if (intenseActivity > 0) { + entries.add(new PieEntry((long) intenseActivity, context.getString(R.string.generator_withings_intense_activities_label))); + } + + PieDataSet set = new PieDataSet(entries, " "); + + int[] colors = { + R.color.generator_withings_soft_activities, + R.color.generator_withings_moderate_activities, + R.color.generator_withings_intense_activities + }; + + 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(); + + dateLabel.setText(Generator.formatTimestamp(context, lastTimestamp / 1000)); + + TextView stepsValue = (TextView) card.findViewById(R.id.field_steps); + stepsValue.setText(context.getString(R.string.generator_withings_steps_value, (int) steps)); + + TextView distanceValue = (TextView) card.findViewById(R.id.field_distance); + distanceValue.setText(context.getString(R.string.generator_withings_distance_value, (distance / 1000))); + + TextView elevationValue = (TextView) card.findViewById(R.id.field_elevation); + elevationValue.setText(context.getString(R.string.generator_withings_elevation_value, elevation)); + } else { + cardContent.setVisibility(View.GONE); + cardEmpty.setVisibility(View.VISIBLE); + + dateLabel.setText(R.string.label_never_pdk); + } + + container.addView(card); + + return "" + card.getTag(); + } + + @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_ACTIVITY_MEASURE_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); + } + + private static String bindIntradayPage(ViewGroup container, DataPointViewHolder holder, int position) { + final Context context = container.getContext(); + + long now = System.currentTimeMillis(); + + WithingsDevice withings = WithingsDevice.getInstance(context); + + LinearLayout card = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.card_generator_withings_intraday_page, null); + card.setTag("" + position); + + LineChart stepsChart = (LineChart) card.findViewById(R.id.chart_steps); + LineChart distanceChart = (LineChart) card.findViewById(R.id.chart_distance); + LineChart elevationChart = (LineChart) card.findViewById(R.id.chart_elevation); + LineChart caloriesChart = (LineChart) card.findViewById(R.id.chart_calories); + + ArrayList steps = new ArrayList<>(); + ArrayList distance = new ArrayList<>(); + ArrayList elevation = new ArrayList<>(); + ArrayList calories = new ArrayList<>(); + + float stepSum = 0; + float distanceSum = 0; + float elevationSum = 0; + float caloriesSum = 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 start = cal.getTimeInMillis() / 1000; + + cal.add(Calendar.DATE, 1); + + long end = cal.getTimeInMillis() / 1000; + + String where = WithingsDevice.INTRADAY_ACTIVITY_START + " > ?"; + String[] args = { "" + start }; + + Cursor c = withings.mDatabase.query(WithingsDevice.TABLE_INTRADAY_ACTIVITY_HISTORY, null, where, args, null, null, WithingsDevice.INTRADAY_ACTIVITY_START); + + steps.add(new Entry(0, 0)); + distance.add(new Entry(0, 0)); + elevation.add(new Entry(0, 0)); + calories.add(new Entry(0, 0)); + + while (c.moveToNext()) { + long when = c.getLong(c.getColumnIndex(WithingsDevice.INTRADAY_ACTIVITY_START)); + + if (c.isNull(c.getColumnIndex(WithingsDevice.INTRADAY_ACTIVITY_STEPS)) == false) { + float value = c.getFloat(c.getColumnIndex(WithingsDevice.INTRADAY_ACTIVITY_STEPS)); + + stepSum += value; + + steps.add(new Entry(when - start, stepSum)); + } + + if (c.isNull(c.getColumnIndex(WithingsDevice.INTRADAY_ACTIVITY_DISTANCE)) == false) { + float value = c.getFloat(c.getColumnIndex(WithingsDevice.INTRADAY_ACTIVITY_DISTANCE)); + + distanceSum += value; + + distance.add(new Entry(when - start, distanceSum)); + } + + if (c.isNull(c.getColumnIndex(WithingsDevice.INTRADAY_ACTIVITY_ELEVATION_CLIMBED)) == false) { + float value = c.getFloat(c.getColumnIndex(WithingsDevice.INTRADAY_ACTIVITY_ELEVATION_CLIMBED)); + + elevationSum += value; + + elevation.add(new Entry(when - start, elevationSum)); + } + + if (c.isNull(c.getColumnIndex(WithingsDevice.INTRADAY_ACTIVITY_CALORIES)) == false) { + float value = c.getFloat(c.getColumnIndex(WithingsDevice.INTRADAY_ACTIVITY_CALORIES)); + + caloriesSum += value; + + calories.add(new Entry(when - start, caloriesSum)); + } + } + + steps.add(new Entry((now / 1000) - start, stepSum)); + distance.add(new Entry((now / 1000) - start, distanceSum)); + elevation.add(new Entry((now / 1000) - start, elevationSum)); + calories.add(new Entry((now / 1000) - start, caloriesSum)); + + WithingsDevice.populateIntradayChart(context, stepsChart, steps, 0, end - start); + WithingsDevice.populateIntradayChart(context, distanceChart, distance, 0, end - start); + WithingsDevice.populateIntradayChart(context, elevationChart, elevation, 0, end - start); + WithingsDevice.populateIntradayChart(context, caloriesChart, calories, 0, end - start); + + c.close(); + + container.addView(card); + + return "" + card.getTag(); + } + + private static void populateIntradayChart(Context context, LineChart chart, ArrayList values, long start, long end) { + chart.getLegend().setEnabled(false); + chart.getAxisRight().setEnabled(false); + chart.getDescription().setEnabled(false); + + LineData data = new LineData(); + + LineDataSet set = new LineDataSet(values, ""); + set.setAxisDependency(YAxis.AxisDependency.LEFT); + set.setLineWidth(1.0f); + set.setDrawCircles(false); + set.setFillAlpha(128); + set.setDrawFilled(true); + set.setDrawValues(false); + set.setColor(ContextCompat.getColor(context, R.color.generator_battery_plot)); + set.setFillColor(ContextCompat.getColor(context, R.color.generator_battery_plot)); + + data.addDataSet(set); + + float minimum = (float) (0 - (values.get(values.size() - 1).getY() * 0.05)); + float maximum = (float) (values.get(values.size() - 1).getY() * 1.25); + + if (minimum == 0) { + minimum = -0.05f; + maximum = 0.95f; + } + + chart.getAxisLeft().setAxisMinimum(minimum); + chart.getAxisLeft().setAxisMaximum(maximum); + chart.getAxisLeft().setDrawGridLines(false); + chart.getAxisLeft().setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART); + chart.getAxisLeft().setDrawAxisLine(false); + chart.getAxisLeft().setDrawLabels(false); + chart.getAxisLeft().setTextColor(ContextCompat.getColor(context, android.R.color.white)); + + chart.getXAxis().setAxisMinimum(start); + chart.getXAxis().setAxisMaximum(end); + chart.getXAxis().setDrawGridLines(false); + chart.getXAxis().setDrawLabels(false); + chart.getXAxis().setDrawAxisLine(false); + + chart.setViewPortOffsets(0,0,8,0); + chart.setHighlightPerDragEnabled(false); + chart.setHighlightPerTapEnabled(false); + chart.setBackgroundColor(ContextCompat.getColor(context, android.R.color.black)); + chart.setPinchZoom(false); + + ArrayList lastValue = new ArrayList<>(); + lastValue.add(values.get(values.size() - 1)); + + LineDataSet lastItem = new LineDataSet(lastValue, ""); + lastItem.setAxisDependency(YAxis.AxisDependency.LEFT); + lastItem.setLineWidth(1.0f); + lastItem.setCircleRadius(3.0f); + lastItem.setCircleHoleRadius(2.0f); + lastItem.setDrawCircles(true); + lastItem.setValueTextSize(10f); + lastItem.setDrawValues(true); + lastItem.setCircleColor(ContextCompat.getColor(context, R.color.generator_battery_plot)); + lastItem.setCircleColorHole(ContextCompat.getColor(context, android.R.color.black)); + lastItem.setValueTextColor(ContextCompat.getColor(context, android.R.color.white)); + + data.addDataSet(lastItem); + + chart.setData(data); + } + + private static String bindBodyPage(ViewGroup container, DataPointViewHolder holder, int position) { + final Context context = container.getContext(); + + WithingsDevice withings = WithingsDevice.getInstance(holder.itemView.getContext()); + + LinearLayout card = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.card_generator_withings_body_page, null); + card.setTag("" + position); + + int[] labels = { + R.id.label_body_one, + R.id.label_body_two, + R.id.label_body_three, + R.id.label_body_four, + R.id.label_body_five, + R.id.label_body_six, + R.id.label_body_seven, + R.id.label_body_eight + }; + + int[] values = { + R.id.value_body_one, + R.id.value_body_two, + R.id.value_body_three, + R.id.value_body_four, + R.id.value_body_five, + R.id.value_body_six, + R.id.value_body_seven, + R.id.value_body_eight + }; + + HashMap bodyValues = new HashMap<>(); + ArrayList keys = new ArrayList<>(); + + Cursor c = withings.mDatabase.query(WithingsDevice.TABLE_BODY_MEASURE_HISTORY, null, null, null, null, null, WithingsDevice.BODY_MEASURE_HISTORY_DATE + " DESC"); + + Log.e("PDK", "MEASURE COUNT: " + c.getCount()); + + while (c.moveToNext() && bodyValues.size() < labels.length) { + String label = c.getString(c.getColumnIndex(WithingsDevice.BODY_MEASURE_HISTORY_TYPE)); + + if (bodyValues.containsKey(label) == false) { + double value = c.getDouble(c.getColumnIndex(WithingsDevice.BODY_MEASURE_HISTORY_VALUE)); + + bodyValues.put(label, value); + + keys.add(label); + } + } + + for (int i = 0; i < keys.size() && i < labels.length; i++) { + String label = keys.get(i); + + TextView labelView = (TextView) card.findViewById(labels[i]); + labelView.setText(label.substring(0, 1).toUpperCase() + label.substring(1) + ":"); + + Double value = bodyValues.get(label); + + TextView valueView = (TextView) card.findViewById(values[i]); + valueView.setText(value.toString()); + } + + container.addView(card); + + return "" + card.getTag(); + } + + private static String bindSleepPage(ViewGroup container, DataPointViewHolder holder, int position) { + final Context context = container.getContext(); + + LinearLayout card = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.card_generator_withings_sleep_page, null); + card.setTag("" + position); + + container.addView(card); + + return "" + card.getTag(); + } + + private static String bindSleepSummaryPage(ViewGroup container, DataPointViewHolder holder, int position) { + final Context context = container.getContext(); + + LinearLayout card = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.card_generator_withings_sleep_summary_page, null); + card.setTag("" + position); + + container.addView(card); + + return "" + card.getTag(); + } + + private static String bindWorkoutsPage(ViewGroup container, DataPointViewHolder holder, int position) { + final Context context = container.getContext(); + + LinearLayout card = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.card_generator_withings_workouts_page, null); + card.setTag("" + position); + + container.addView(card); + + return "" + card.getTag(); + } + + public void enableActivityMeasures(boolean enable) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + SharedPreferences.Editor e = prefs.edit(); + + e.putBoolean(WithingsDevice.ACTIVITY_MEASURES_ENABLED, enable); + + e.commit(); + } + + public void enableBodyMeasures(boolean enable) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + SharedPreferences.Editor e = prefs.edit(); + + e.putBoolean(WithingsDevice.BODY_MEASURES_ENABLED, enable); + + e.commit(); + } + + public void enableIntradayActivity(boolean enable) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + SharedPreferences.Editor e = prefs.edit(); + + e.putBoolean(WithingsDevice.INTRADAY_ACTIVITY_ENABLED, enable); + + e.commit(); + } + + public void enableSleepMeasures(boolean enable) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + SharedPreferences.Editor e = prefs.edit(); + + e.putBoolean(WithingsDevice.SLEEP_MEASURES_ENABLED, enable); + + e.commit(); + } + + public void enableSleepSummary(boolean enable) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + SharedPreferences.Editor e = prefs.edit(); + + e.putBoolean(WithingsDevice.SLEEP_SUMMARY_ENABLED, enable); + + e.commit(); + } + + public void enableWorkouts(boolean enable) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + SharedPreferences.Editor e = prefs.edit(); + + e.putBoolean(WithingsDevice.WORKOUTS_ENABLED, enable); + + e.commit(); + } + + public void enableServerFetch(boolean enable) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + SharedPreferences.Editor e = prefs.edit(); + + e.putBoolean(WithingsDevice.SERVER_FETCH_ENABLED, enable); + + e.commit(); + } +} 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 1d9a2cb..939f96a 100755 --- a/src/com/audacious_software/passive_data_kit/transmitters/HttpTransmitter.java +++ b/src/com/audacious_software/passive_data_kit/transmitters/HttpTransmitter.java @@ -44,7 +44,6 @@ import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; -import okio.Buffer; public class HttpTransmitter extends Transmitter implements Generators.GeneratorUpdatedListener { public static final String UPLOAD_URI = "com.audacious_software.passive_data_kit.transmitters.HttpTransmitter.UPLOAD_URI"; @@ -345,6 +344,8 @@ private int transmitHttpPayload(String payload) { } return HttpTransmitter.RESULT_SUCCESS; + } else { + } } catch (Exception e) { e.printStackTrace();