From 4b56ef7082299c7cbd22ce99639e4c22a31f1f07 Mon Sep 17 00:00:00 2001 From: "Chris J. Karr" Date: Fri, 17 Mar 2017 20:58:37 -0500 Subject: [PATCH 01/10] Initial Daydream support --- build.gradle | 13 +- gvr-android-sdk | 1 + .../card_generator_daydream_vr_controller.xml | 20 ++ res/values/generators.xml | 1 + .../generators/vr/DaydreamViewController.java | 178 ++++++++++++++++++ 5 files changed, 212 insertions(+), 1 deletion(-) create mode 160000 gvr-android-sdk create mode 100755 res/layout/card_generator_daydream_vr_controller.xml create mode 100755 src/com/audacious_software/passive_data_kit/generators/vr/DaydreamViewController.java diff --git a/build.gradle b/build.gradle index d66c442..22ab1c0 100755 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,9 @@ apply plugin: 'com.android.library' buildscript { repositories { jcenter() + flatDir { + dirs 'libs' + } } dependencies { @@ -13,6 +16,9 @@ buildscript { repositories { jcenter() maven { url "https://jitpack.io" } + flatDir{ + dirs 'libs' + } } android { @@ -22,7 +28,7 @@ android { useLibrary 'org.apache.http.legacy' defaultConfig { - minSdkVersion 14 + minSdkVersion 19 targetSdkVersion 25 versionCode 1 versionName "1.0" @@ -59,6 +65,11 @@ 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.0' + compile 'com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-7' + + compile project(':libraries-base') + compile project(':libraries-common') + compile project(':libraries-controller') } buildTypes { diff --git a/gvr-android-sdk b/gvr-android-sdk new file mode 160000 index 0000000..3360cbd --- /dev/null +++ b/gvr-android-sdk @@ -0,0 +1 @@ +Subproject commit 3360cbd21d8ba07c305fa379b905ff8c7632f6cc 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/values/generators.xml b/res/values/generators.xml index c4d60a2..166f4b6 100755 --- a/res/values/generators.xml +++ b/res/values/generators.xml @@ -5,6 +5,7 @@ com.audacious_software.passive_data_kit.generators.communication.PhoneCalls com.audacious_software.passive_data_kit.generators.wearables.MicrosoftBand com.audacious_software.passive_data_kit.generators.services.GoogleAwareness + com.audacious_software.passive_data_kit.generators.vr.DaydreamViewController 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..6cbc304 --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/generators/vr/DaydreamViewController.java @@ -0,0 +1,178 @@ +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; + +/** + * 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); + } + + public static void broadcastLatestDataPoint(Context context) { + Generators.getInstance(context).transmitData(DaydreamViewController.GENERATOR_IDENTIFIER, new Bundle()); + } + +} From 1b792823349dbcab5378921a737a82d297f9af28 Mon Sep 17 00:00:00 2001 From: "Chris J. Karr" Date: Sun, 19 Mar 2017 11:10:24 -0500 Subject: [PATCH 02/10] Initial Withings integration --- .travis.yml | 8 +- AndroidManifest.xml | 9 +- .../generator_location_disclosure.html | 26 + build.gradle | 31 +- gvr-android-sdk | 1 - .../ic_button_disclosure_setting.png | Bin 0 -> 1968 bytes res/drawable-hdpi/ic_pdk_diagnostic.png | Bin 0 -> 1397 bytes res/drawable-hdpi/ic_text_messages.png | Bin 0 -> 1363 bytes res/drawable-hdpi/ic_withings_device.png | Bin 0 -> 1521 bytes .../ic_button_disclosure_setting.png | Bin 0 -> 1650 bytes res/drawable-mdpi/ic_pdk_diagnostic.png | Bin 0 -> 1212 bytes res/drawable-mdpi/ic_text_messages.png | Bin 0 -> 1244 bytes res/drawable-mdpi/ic_withings_device.png | Bin 0 -> 1300 bytes .../ic_button_disclosure_setting.png | Bin 0 -> 2330 bytes res/drawable-xhdpi/ic_pdk_diagnostic.png | Bin 0 -> 1504 bytes res/drawable-xhdpi/ic_text_messages.png | Bin 0 -> 1426 bytes res/drawable-xhdpi/ic_withings_device.png | Bin 0 -> 1728 bytes .../ic_button_disclosure_setting.png | Bin 0 -> 3127 bytes res/drawable-xxhdpi/ic_pdk_diagnostic.png | Bin 0 -> 2018 bytes res/drawable-xxhdpi/ic_text_messages.png | Bin 0 -> 1634 bytes res/drawable-xxhdpi/ic_withings_device.png | Bin 0 -> 2220 bytes .../ic_button_disclosure_setting.png | Bin 0 -> 3577 bytes res/drawable-xxxhdpi/ic_pdk_diagnostic.png | Bin 0 -> 2068 bytes res/drawable-xxxhdpi/ic_text_messages.png | Bin 0 -> 1588 bytes res/drawable-xxxhdpi/ic_withings_device.png | Bin 0 -> 2588 bytes res/drawable/ic_location_heatmap_marker.xml | 6 + res/layout/card_diagnostic_action.xml | 37 +- res/layout/card_generator_location_google.xml | 30 +- res/layout/card_generator_phone_calls.xml | 9 +- res/layout/card_generator_screen_state.xml | 9 +- res/layout/card_generator_text_messages.xml | 134 ++ res/layout/card_generator_withings_device.xml | 49 + res/layout/dialog_location_randomized.xml | 18 + res/layout/dialog_location_user.xml | 18 + .../layout_data_disclosure_detail_pdk.xml | 19 + res/layout/layout_data_disclosure_pdk.xml | 31 + res/layout/layout_diagnostics_pdk.xml | 19 +- res/layout/row_disclosure_action_pdk.xml | 19 + .../row_disclosure_location_accuracy_pdk.xml | 26 + .../row_generator_disclosure_generic.xml | 25 + res/menu/diagnostic_menu.xml | 6 + res/values/databases.xml | 26 + res/values/diagnostics.xml | 20 +- res/values/generators.xml | 62 +- res/values/strings.xml | 18 +- .../passive_data_kit/Logger.java | 11 +- .../passive_data_kit/PassiveDataKit.java | 15 + .../passive_data_kit/PhoneUtililties.java | 30 + .../activities/DataDisclosureActivity.java | 44 + .../DataDisclosureDetailActivity.java | 123 ++ .../activities/DataStreamActivity.java | 39 +- .../activities/DiagnosticsActivity.java | 92 +- .../activities/OAuthResponseActivity.java | 32 + .../activities/PdkActivity.java | 1 - .../generators/DataPointsAdapter.java | 117 +- .../generators/GeneratorViewHolder.java | 10 + .../generators/GeneratorsAdapter.java | 129 ++ .../diagnostics/DiagnosticAction.java | 8 +- .../generators/Generator.java | 107 +- .../generators/Generators.java | 99 +- .../generators/communication/PhoneCalls.java | 495 ++++--- .../communication/TextMessages.java | 473 +++++++ .../generators/device/Location.java | 728 +++++++++- .../generators/device/ScreenState.java | 323 +++-- .../generators/diagnostics/AppEvent.java | 441 +++++++ .../generators/services/GoogleAwareness.java | 13 +- .../generators/vr/DaydreamViewController.java | 23 +- .../generators/wearables/MicrosoftBand.java | 14 +- .../generators/wearables/WithingsDevice.java | 1175 +++++++++++++++++ .../transmitters/HttpTransmitter.java | 41 +- 70 files changed, 4593 insertions(+), 646 deletions(-) create mode 100755 assets/html/passive_data_kit/generator_location_disclosure.html delete mode 160000 gvr-android-sdk create mode 100755 res/drawable-hdpi/ic_button_disclosure_setting.png create mode 100755 res/drawable-hdpi/ic_pdk_diagnostic.png create mode 100755 res/drawable-hdpi/ic_text_messages.png create mode 100755 res/drawable-hdpi/ic_withings_device.png create mode 100755 res/drawable-mdpi/ic_button_disclosure_setting.png create mode 100755 res/drawable-mdpi/ic_pdk_diagnostic.png create mode 100755 res/drawable-mdpi/ic_text_messages.png create mode 100755 res/drawable-mdpi/ic_withings_device.png create mode 100755 res/drawable-xhdpi/ic_button_disclosure_setting.png create mode 100755 res/drawable-xhdpi/ic_pdk_diagnostic.png create mode 100755 res/drawable-xhdpi/ic_text_messages.png create mode 100755 res/drawable-xhdpi/ic_withings_device.png create mode 100755 res/drawable-xxhdpi/ic_button_disclosure_setting.png create mode 100755 res/drawable-xxhdpi/ic_pdk_diagnostic.png create mode 100755 res/drawable-xxhdpi/ic_text_messages.png create mode 100755 res/drawable-xxhdpi/ic_withings_device.png create mode 100755 res/drawable-xxxhdpi/ic_button_disclosure_setting.png create mode 100755 res/drawable-xxxhdpi/ic_pdk_diagnostic.png create mode 100755 res/drawable-xxxhdpi/ic_text_messages.png create mode 100755 res/drawable-xxxhdpi/ic_withings_device.png create mode 100755 res/drawable/ic_location_heatmap_marker.xml create mode 100755 res/layout/card_generator_text_messages.xml create mode 100755 res/layout/card_generator_withings_device.xml create mode 100755 res/layout/dialog_location_randomized.xml create mode 100755 res/layout/dialog_location_user.xml create mode 100755 res/layout/layout_data_disclosure_detail_pdk.xml create mode 100755 res/layout/layout_data_disclosure_pdk.xml create mode 100755 res/layout/row_disclosure_action_pdk.xml create mode 100755 res/layout/row_disclosure_location_accuracy_pdk.xml create mode 100755 res/layout/row_generator_disclosure_generic.xml create mode 100755 res/menu/diagnostic_menu.xml create mode 100755 res/values/databases.xml create mode 100755 src/com/audacious_software/passive_data_kit/PhoneUtililties.java create mode 100755 src/com/audacious_software/passive_data_kit/activities/DataDisclosureActivity.java create mode 100755 src/com/audacious_software/passive_data_kit/activities/DataDisclosureDetailActivity.java create mode 100755 src/com/audacious_software/passive_data_kit/activities/OAuthResponseActivity.java create mode 100755 src/com/audacious_software/passive_data_kit/activities/generators/GeneratorViewHolder.java create mode 100755 src/com/audacious_software/passive_data_kit/activities/generators/GeneratorsAdapter.java create mode 100755 src/com/audacious_software/passive_data_kit/generators/communication/TextMessages.java create mode 100755 src/com/audacious_software/passive_data_kit/generators/diagnostics/AppEvent.java create mode 100755 src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java diff --git a/.travis.yml b/.travis.yml index c65a9c3..6d11e4c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,11 +3,11 @@ language: android jdk: oraclejdk8 before_install: - - wget http://services.gradle.org/distributions/gradle-2.14.1-bin.zip - - unzip gradle-2.14.1-bin.zip - - export GRADLE_HOME=$PWD/gradle-2.14.1 + - wget http://services.gradle.org/distributions/gradle-3.3-bin.zip + - unzip gradle-3.3-bin.zip + - export GRADLE_HOME=$PWD/gradle-3.3 - export PATH=$GRADLE_HOME/bin:$PATH - - ( sleep 5 && while [ 1 ]; do sleep 1; echo y; done ) | android update sdk -a --no-ui --filter tool,platform-tool,build-tools-25.0.0 + - ( sleep 5 && while [ 1 ]; do sleep 1; echo y; done ) | android update sdk -a --no-ui --filter tool,platform-tool,build-tools-25.0.2 script: - gradle assembleDebug diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 3b7277a..45147a5 100755 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,13 +1,18 @@ - + - + + + + diff --git a/assets/html/passive_data_kit/generator_location_disclosure.html b/assets/html/passive_data_kit/generator_location_disclosure.html new file mode 100755 index 0000000..583b4d7 --- /dev/null +++ b/assets/html/passive_data_kit/generator_location_disclosure.html @@ -0,0 +1,26 @@ + + + + + + + +

+ TODO: Write disclosure for use of location data... +

+ +

+ Place completed file in assets/html/passive_data_kit/generator_location_disclosure.html in your project to override this placeholder. +

+ + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 22ab1c0..7a80bca 100755 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.2.2' + classpath 'com.android.tools.build:gradle:2.3.0' } } @@ -23,7 +23,7 @@ repositories { android { compileSdkVersion 25 - buildToolsVersion "25" + buildToolsVersion "25.0.2" useLibrary 'org.apache.http.legacy' @@ -51,25 +51,22 @@ android { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) // testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:25.0.1' - compile 'com.android.support:recyclerview-v7:25.0.1' - compile 'com.android.support:cardview-v7:25.0.1' - compile 'com.google.android.gms:play-services-location:9.8.0' - compile 'com.google.android.gms:play-services-maps:9.8.0' - compile 'com.google.android.gms:play-services-nearby:9.8.0' - compile 'com.google.android.gms:play-services-places:9.8.0' - compile 'com.google.android.gms:play-services-awareness:9.8.0' + compile 'com.android.support:appcompat-v7:25.3.0' + compile 'com.android.support:recyclerview-v7:25.3.0' + compile 'com.android.support:cardview-v7:25.3.0' + compile 'com.google.android.gms:play-services-location:10.2.0' + compile 'com.google.android.gms:play-services-maps:10.2.0' + compile 'com.google.android.gms:play-services-nearby:10.2.0' + compile 'com.google.android.gms:play-services-places:10.2.0' + compile 'com.google.android.gms:play-services-awareness:10.2.0' + compile 'com.android.support:customtabs:23.3.0' + compile 'com.google.maps.android:android-maps-utils:0.4' compile 'com.squareup.okhttp3:okhttp:3.2.0' compile 'commons-io:commons-io:2.4' compile 'commons-codec:commons-codec:1.10' compile 'org.apache.commons:commons-lang3:3.4' compile 'com.fasterxml.jackson.core:jackson-core:2.7.3' - compile 'com.github.philjay:mpandroidchart:v3.0.0' - compile 'com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-7' - - compile project(':libraries-base') - compile project(':libraries-common') - compile project(':libraries-controller') + compile 'com.github.philjay:mpandroidchart:v3.0.1' } buildTypes { @@ -78,4 +75,4 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } -} \ No newline at end of file +} diff --git a/gvr-android-sdk b/gvr-android-sdk deleted file mode 160000 index 3360cbd..0000000 --- a/gvr-android-sdk +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3360cbd21d8ba07c305fa379b905ff8c7632f6cc diff --git a/res/drawable-hdpi/ic_button_disclosure_setting.png b/res/drawable-hdpi/ic_button_disclosure_setting.png new file mode 100755 index 0000000000000000000000000000000000000000..9628f97acd37043be76d45a8eb3ddd6f6d6c3d4e GIT binary patch literal 1968 zcmaJ?dsI?)9LE4P6*om!X~%Bl@rcD!MPQO57nBkXp%v9;B3$97aOrYU)KyN-9=2Ax zxn6UR>|NGYL20_IHm#12v@(|K zy$njDPyrGIg@AY_kD=ql@__^alM^rCbK_7GjA`u*vvtNO0|;4l zC~U!C#0+>CwK<3l6VdQUU#4KPjM18{ugZiMjAhqaSR5wXlhUZ6Lh=8mCexU;6;ng6 zz?6w+b{nu#F!qg7NH5e%^!5ep!Zc&e5FL@Ts9 z*z8f#U!W8Uq1eMH8W`eSW%Q!cf?^gQBf&FM{KX~jS1>Y3K|v$i zyT5m~EigQpHb|22hp#pu53h7tuRr?bN@4Zm_7#u5F1gk{DZu*+a3JkXGA)f%4g^J( zffc^N;@~k!)w=p8P2}#{+RW|Sx5rEuw>~1U+iKe19l9u2y9ejYnWHW$DvDt++Lwr3 zF4vyM#zt|)*+FaZ(;#+pjbfr^AQA@NRMF}5n1qC$rx9R|IwQkIa`{Z*xhdo-{%uf& zq6a(P$h|PRD)29!+eZ9qNk4GK-+8EOzyIy4i^;v^k$0txIjV?gZ)(k&nRBNOETMSI zDmIV<%NEI$!=HkyETuj--m~~j^)bNbh+igqrP(TCebR|fkDEI~J40NAH3PguCoDr} zF!9@(j;6`2`Cd!w|L7`Tn;ot>y{@{cGPQuHF+7;DzPV}Hwq9d$z;l_bs+aXmNo8f( zXX5ESv$CXqtGs8B_mQ>+Xi^kD!wTw!@ce>?TH?CnI@QnI9%kp6NbwzeZddu8L?XpY zw{d#n+$-nX1irOwd8bLWb@?@A#3dH`-DBcPD;nnu;oGK_rd!%%mltMlb@b)shm?w$k%rPqJ1&a5v*}5~uA8KV z-39#=KGL?R}Uxi&E<>BH56OI}OoJR+(jlRqL za~C@|?W!#Nta+sVKCo+A9bv&DXY2e9-U;u^6~{uWv3cL~>VeH!$nqP8P*HNFLpt>Q z@53sZG_WVBKb;CT_X)B-@1?lk5-j7?6%5ii=fzp>eT@@t)jv}o=wsj6y9wFkXWB7u z{tD`Q1Jl<2)ESsFd$1tv!sT;{U}x$F1y7zB`{w#*xNDw;Mt$6}lgFHWR&w{Nmc*R- zoJ)7V?Ok_RQF63OGI#CB^#<61O-zc)@?q%qZ{~kO{&U0ApSM(Rp8RkiYM;YMDd3-U z?TmTociN<)-{+omos8~TvwPc-;q1NkX63yf51ufxBWlU_;vbxQt0gB?NJ%NVhTZH} zU;6dgCWm`Zui?PZpU0Q#Nzr+FB=`ZHZ&Rag-59w+ZrItf@{n!Nh-ZoO-1KC;6KU6MNg8Ngl G%l-l9RRj(I literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/ic_pdk_diagnostic.png b/res/drawable-hdpi/ic_pdk_diagnostic.png new file mode 100755 index 0000000000000000000000000000000000000000..e80247a9482c0d1707daba4b4c829418dd5ae2c0 GIT binary patch literal 1397 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@%o>>?5hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|8hm3bwJ6}oxF$}kgLQj3#|G7CyF^YauyCMG83 zmzLNn0bL65LT&-v*t}wBFaZNhzap_f-%!s04CfFugAM$)&lec_lEtDG0rmIQ4=OL~a4lW|!2W%(B!Jx1#)91+d4hGI6`b z45xWey(zfeVvbXF!Z)WcGV{#N}ZM^HkEnKZ_dMjh~gZs=EI9{pQG|bIkFt^pNsdK%9$imHA0w+G; zz7cUT!$)UHP>IcCp2AIsj1Hf<`$nyJvi`+4acSplKNnxrN!FSC?1UAgUxV-i77Io{ zhQv3`kp*mV9&QhqIy5%!UlG*n{Ir4norB|_f4tWYBuB|;Z2n(3QH;IfYrgBzr!N|L zFZk_Zw$7=zGU=T|#oP1~u^k(K{oUcSih0kk?+W&hZ0nTPMi(nSdlg-Na2oUCYj$_z zHiUG>E|U*8JGRZRl79=E&j!w}rpb)w_dD2eGPQ-q@2l(%(R;!ZzJQ-kdEVFLq7&At zt-;zxKCC&?eh0cW7bP0l+XkK DN+0+^ literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/ic_text_messages.png b/res/drawable-hdpi/ic_text_messages.png new file mode 100755 index 0000000000000000000000000000000000000000..ef5d391720158ab483952d80eb3716bc78560ea4 GIT binary patch literal 1363 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBBuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFl%InM3hAM`dB6B=jtVb)aX^@765fKFxc2v6eK2Rr0UTq__OB&@Hb09I0xZL0)vRD^GUf^&XRs)DJWnQpS7v4w)UrJkXw zrG=4+j)IYap_#scrM{twu7RPIfu)s!p#l^r0c|TvNwW%aaf8|g1^l#~=$>Fbx5 zm+O@q>*W`v>l<2HTIw4Z=^Gj80#)c1SLT%@R_NvxE5l51Ni9w;$}A|!%+FH*nV6WA zUs__T1av9H3%LbwWAlok!2}F2{ffi_eM3D1ke6TzeSPsO&CP|YE-nd5MYtEM!Nnn! z1*!T$sm1xFMajU3OH&3}Rbb^@l$uzQUlfv`p92fUfQdzUH`+b07r4*z=(Yi43VS5;M;nE(9+AX4eH0@yc=SH=ZyTpP219 zVP3qF^$Q*Wc}a)kf7l-FJQ2vI^=a{fmq+Dh{fJm-V)lVMi({#(jmCA`hHt0+7OB5F z?jXZ)WYUES_OFwzHb`ud@A$mX*3av-VSwxpkynm$cD}KkQrC3Y(9z*fq5_v&m`)$( zvZcu$nRPM?N>5A(DwSziwwC|Pi98>cU$>UO5H&ht@j^i0H}l8e2QN+CXCa`f;?Tgz Y&|$avvSHx8^`K(T)78&qol`;+0G3eStpET3 literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/ic_withings_device.png b/res/drawable-hdpi/ic_withings_device.png new file mode 100755 index 0000000000000000000000000000000000000000..168df46f7bdafae139e1eb67cafdbf46d7dbd6d0 GIT binary patch literal 1521 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBBuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFl%InM3hAM`dB6B=jtVb)aX^@765fKFxc2v6eK2Rr0UTq__OB&@Hb09I0xZL0)vRD^GUf^&XRs)DJWnQpS7v4w)UrJkXw zrG=4+j)IYap_#scrM{twu7RPIfu)s!p#l^r0c|TvNwW%aaf8|g1^l#~=$>Fbx5 zm+O@q>*W`v>l<2HTIw4Z=^Gj80#)c1SLT%@R_NvxE5l51Ni9w;$}A|!%+FH*nV6WA zUs__T1av9H3%LbwWAlok!2}F2{ffi_eM3D1ke6TzeSPsO&CP|YE-nd5MYtEM!Nnn! z1*!T$sm1xFMajU3OH&3}Rbb^@l$uzQUlfv`p92fUfQ6x)>U|np-+rxVgDF z8#|ginpzmRxx)0i*>!(m7D z_fPNkmcM`Ze3Q;=2jkAug(rY;Mcv0HfgkK9nQm$}`GKGA?ze1_dF`$Rv5V^O8BNZ; za8lon$A&B2DIqjz15Xue_Pgk3LhHr83Vsl%VUo{S*!e{3MbMHo4+FVvfn0CdU-olO zNKl*Ca9`o5uElD@idk~6_n(Ql*XA65w}jpB;FE@gH|@tB_y;^Ymgac!h~$P4-KC+U9P;dzg^2*D|Q;%cVzuK<5}Q%y(fe(Rpqh# zK`HaHkd2ICR$HXHK1}?eSCshQuU~h4HHVJ$ooBVXro5EPuoGB5J&SS4LG#XcxgQ)7 zUmIJv>%X0)lp=m9BHmNUS+2mg_l0v=CS&=_?oZ+o?B@?i21{3em2?TJ?OB$7`e#6i hOp#2M+E4uh3_J5vSf)PyECnirJzf1=);T3K0RY+yFU$Y{ literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_button_disclosure_setting.png b/res/drawable-mdpi/ic_button_disclosure_setting.png new file mode 100755 index 0000000000000000000000000000000000000000..a252c984fe4042ede1b548642abd8ea2b6ef8ed7 GIT binary patch literal 1650 zcmaJ?c~BE~6kb9Vxom5hXi?}It;QqSBqWhU0zx)Hu%Q^zf>e$*yGVrWZa1q5;8YG# zBUoCXii(vgicn5dK~V&!SjCDk)~Sf-pw5UhX1s7Zq_xrw0``y6o!R{z^SBhDDU#Yi$}00;`Pkcd7FqrnZ>Mgy+kJU!da0S%~v z6D!dOG^7$sHLT90u$as!ojx;7FGD#Y!C;U@&JY+e8UZcFblfDjC^%!ha%S(mhB)9@ z2%Vjl&2R41cEDMe4#`ZC=xFRMFOD|638H-m?sd*1v0rn2#!A- zCL4vO$hEL~JQs6Pa8hZSltYl&Z04H-_yn~P63S#UNFahlA|4aLGiBg3V&UPY1rrP~ zX3|pzk~R=H=ww7T5SwWQhf(^b1S2^~i<`#V#54@DAS5K@3!EiQ1Zp(@A8IsCMw@6Y z_EztI5}R}xBnD|Q6S0}nGlNT6;7mozl@x~11f?U0^ocG;rxG+_N+n28sdRR21<2JP zdIRpfTr!5zXyg&NiAHce76B_b3=iL6K;<%tC_tc=h?THdA{45mfdNt#3=1VIL}GQI zSPf5LVM4#zh~e}E7JZAAOo??G!bmciVT>|t!B90t7{Rf^<%X%Xh^FKl$D&hf378TK zF=8O+c>gu-i7UoK&h4amnZu<0F`V%{#hANjWl0(Uum&UGP@SdUzCU4C;o3QudhGF6 z<8PUTO^&5YZ1tscHK;Od;cV^t8M8M<_QN9XddKboGC%ChswG*WwGC?))omT%S{uDR zx+@&dw!KW(5JM0Ax@ES5!#5i4e7}GAiR18a`B27PTT|pQ&JLTzir|Uavj?YHToOA* zdC4#@iO(+J?EBTd=5AA*J&9eF?yKYPQ@!^N$G_-i5}*k#>3a3|g6S?_bVON4gZqxp zjpO>j@@ZaPJX9=I-6PyS=*YS=oZx@IYgW?S zvigzNa|AK>Kh0*}T$jou$Mg17xGtY&4y{TVP&FPh>xHK~dSs~z&(Ch9lG_(IZFgK= zW{5AX9`xz+o7I|kQDJU!{VCTJc0NK9+z@k;8=kPe*Q@c8HX(lI@BM7DOnC{V+0r~! zQ1vi)<*&!?y_<%;dVfghmL)8vk}jM527rCp^;Z**9wAI_+%!7xtBXTz#a*!l(b&Ox zeL!XhaeL>8dx2%K!(NLl&%fUCO;K2y;EqLHNjrrHfyn8Mek#3ezN}~*MmMks_S0*UhG@9{sJcbC}_~u zi^c$0*_i`9;knDoBhLm_9&c(4)*KC&`jQ9yLv)q zp1(bivvc0)+S>fA$`d73>L-;2%5#T*L)!Liu~&Jzl-k~X{?xTRwDruD1I_K7#$gr- zoU5t1-d2Ct?eewkn}foU&huG=OGmwY9yC*uMOmHe4D&4yU*xOFb}Hun)1giCxW5dz zJGdzBeX5|Y#Uli(Z16tYcf^y|+F~W^Uij1&k0j-O)SBbinphfn>`}|YQ?H5&7ai_S zIXZCl6!pAVLiO8KE!(IrkqEzkNgi literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_pdk_diagnostic.png b/res/drawable-mdpi/ic_pdk_diagnostic.png new file mode 100755 index 0000000000000000000000000000000000000000..539cf8afd7e58849ba1e79786869ffd2442626f0 GIT binary patch literal 1212 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 PDyKYM{an^LB{Ts5i;~sw literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_button_disclosure_setting.png b/res/drawable-xhdpi/ic_button_disclosure_setting.png new file mode 100755 index 0000000000000000000000000000000000000000..c006523765298112123dc083b8b542b22dcda1ce GIT binary patch literal 2330 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 Q0E=7(Pgg&ebxsLQ01~Jk9RL6T literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_text_messages.png b/res/drawable-xhdpi/ic_text_messages.png new file mode 100755 index 0000000000000000000000000000000000000000..a0a8cebff4faeed6cfcda799581a69d2fba071bb GIT binary patch literal 1426 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@%o>>?5hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|8hm3bwJ6}oxF$}kgLQj3#|G7CyF^YauyCMG83 zmzLNn0bL65LT&-v*t}wBFaZNhzap_f-%!s0qE{2A#W=@WdmQJo_ z&c+tz&K9oD#xT7u`N^fZsd*(Zy(tL2PB`^~5=3qR&}Ns^qRg_?6t|-MTm`VltunE> z#n{cw+0@(=r+HAlDcIZs(QAfNuRhQ*`k?4Vif)(?Fb#p2@Wcz`z>|M!9x%-p0TXv? zcen%t0~3R%i(^Q|tv9o5y_p>a+VUrBs71MRWGr^&TD@TFFK&^I+q{>y?mu+Q&GqOp zucPx5RyOsvZdK@*n&fb#Luh}|^W6sO)vs0aZ1iqkl&;{osk5`Pyu9q&`pw^DXY=0RpCp2|8NJTJ7008Y z*SC8XOvyA1-nOZ=XLa6zcvH5xnMdEx)_c%0@w2FvA}7ZUws0SoH4V|R2Y6?&ri)qY z@iR<2P;^7*NwmHM`(cq7=7f*!3irz&?UfNM$x@tOD0fEbp!PC@t^>J6Pd78R9Q=Qz z$y4o|>CaqY9Sd2TS^b;DUhViC$P)4JPd;nJ;~H(Imfmx-xjwwkx%q6*N(RgA>zgZ& uAL@2U{IJbynwd=4y(F%8>u=X^888@b-*=@^|NAjeDe39z=d#Wzp$P!!LIQvQ literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_withings_device.png b/res/drawable-xhdpi/ic_withings_device.png new file mode 100755 index 0000000000000000000000000000000000000000..73aece2cc0db28272e810ca5244e16fdf6bd17b2 GIT binary patch literal 1728 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 literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_pdk_diagnostic.png b/res/drawable-xxhdpi/ic_pdk_diagnostic.png new file mode 100755 index 0000000000000000000000000000000000000000..62b411dc02edcd53bbc3b8f18fee93e27217fa77 GIT binary patch literal 2018 zcmaJ?3s4hx8vlpaa47VU`UdI^FV1FaV*7!*?2mdkC#fd2uPHpc?cv@OBIL& zk;=<94j}OWxQ3}Rvhi&3Ke#efO_Mr(XnHk9paFGaalQd(&=4b>{>Ob&-bXGGDXq9O^8NL`r*m+B)m zI_fL~jOb)q6^5%&4di5$=Ap$nk3wYnYYA%X6|F}1vQ0$8=z1weXVMtXl4c#n;{Wfe zR=@Jr;S%JxeE*bKmr;fxbP1wEi?uRhaQRecD2yx6B2paHW}s-%Y!_3ND30osCUx2u{qx+B)5fII?Eq%C~@q;h@eR+Yr-ehhw}k zP$aq{pUK^JD}5}K)1vp7xth|+TfOr6*21y@DT^zNB575DT; zj49kk-#UW5wrJ_hvsoS{(0G9n72K=KW$Rwb;6v~ zsOeqDO;3wtPgM-5R?!{E`Q+UzK52D3vP13etnahC%vSiPkg4Z6APft)?H^pf_S2$f z-Z{1<=7~M0y8ZR@Wj=j^12-*zF7xu*z2=+gGKmjpSp}0pQT2mWHvh>>5plq8?E59w z;Mam9^TAq5_2Gi)j~Wl`@R7i!)dOy_a@f;XRXAvZ!x7J*`L>8J~8pQ zbzKn%DWtOoQsu_5H^IUhJNxmc%Yu`0DAhk0vzzROhr^M6T@T_Xwe0yLXCDT3JaB!- zdwDV!i1^*r+XDMq&IZNb4RMW|Z~8g49JvsDAooz^!4L08%B_<@!veH>{Lw+h-)(zK z+D06UI{Qwgd!Ih@G|2Lq=l+==s5ii&c)TCjYL9fMJKD#mwvCcEZ2Iouf{=lg*B`!p zNB{Zhu^kTkeDjx#UU^_gwEw2S{Ob0Ar^kJ5Ap*+{#}76S*U^ literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_text_messages.png b/res/drawable-xxhdpi/ic_text_messages.png new file mode 100755 index 0000000000000000000000000000000000000000..31ceb298ecdf4b0a0d7026f783c30b3b040e8db7 GIT binary patch literal 1634 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m^Cs(B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxOgGuk*h0bFQqR!T z(!$6@N5ROz&`jUJQs2--*TB%qz|zXVPyq^*fVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5 zFD$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;NpiyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr7I$DAddqG<*}2 zGxI=#nqXbNzE+-j#U+V($*G<$wn{*A^fEJ3tPG6Z+?-9#jh!qlTnr6e&72$^EuCD= zoQ*Baoh@9QjbVCS@{>z*Q}aq-dQ%X3O>pW3C5YStpv^9+MVV!(DQ-pixe8#9TV>*Q ziy=<)pn6kqyTu5nUVWfr^g+>!6x}c(U>X83;fWW>fhYgeJYbqH0w(Uds|z$47?>`2 zx;TbZ+~Q& z)KQ}C&<1yL(SU$;5!WAm{rf=U24@yu+<&18F0})zci402)Luv{knDLiKepfefz}ST zuxQR(Over`XqPNyo)fLDxsHF=la(@}=?70P5i1d}VX~F`e)@6($DZzN$HVhZ2W}L9 zvw7(aKE5}b;-1q`iV0pC5A8O)>1mUOZRxT-V%e~3P8oD$}fd>#6Pg+NQ%2XOt8&t7EK=3-!K81s%z7ebX!y zUXg0#{_nHp27WEC00-5xaz2joFQ56>Z04LLxNd44cib(OyR60YxM#6!Hgk+WaI|ID zk*^UGGL#b^)fryxI@hp%-pRnsQYM@HS8Ij0waT+SKge)HrQUuPXYc*nD|bxSG7Hjs zy=JHK#k|8m>)SSq{C;*->}*|USeB{Y7d4A3$2xVIxPR$|ruqi0Sp8jGsP@m?os+_x z|8FT=GuPE~{XxynYr)mVk-op}w$9#hZ)MSvC(qWs>U{iQ;ro8;oJ`YeFN;nIUGZ4m zH$B*`%W`5^>Q;sHORIPs{g#Ga)O;2bC3>@Ij?Zc(mai|kcE8&+$EN97Nz~o)+AjH& sKa;kP{CnEt!c095LGy85}Sb4q9e0P9C@Y5)KL literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_withings_device.png b/res/drawable-xxhdpi/ic_withings_device.png new file mode 100755 index 0000000000000000000000000000000000000000..5dd8a9da672479b654a50430748c025989451aa5 GIT binary patch literal 2220 zcmaJ@X;f3!77k&MK?Gz_5HJm5iINEdA%SROLLf*mLU;^PAS4$OA%n?90y6VJz(<85 zDnm;IE5%|J3s!U?iJ-+IGs@sl6czfkh^PpPwuuGXACGs}y7!#3*Z#geoNw)Q3s?a@ z#)dYA5D3JW;Y$ltufCdBUr+rF{{2TE^a#ASfk(%}t5t5_s_K+hAUDqMASmfE<`ym>`l7U0jfA52P$v1ajm^ zk<4M4fdeRFBZ|o+GDI?vBjN%K8VRoEK?wvrB8E=Kc~J4L7*`4ni}l76 zC?0e=jp|ClQ`|f-bnj&>P0URc0wQo3%ljLPTM?@fgixZ6Oar8X{Q!?H6$@b>3nvOz z&IP+7-x8L$axN|_V$tek(3o( zu;pXVD#Q4}Ar^VNeqj732=^V;1bQ}LnJ~pCjvxc5^Lg8uT zZ4#l?lN}>5tr8?aU+e8ug%(@0=IrNn8}!PGC<+6G>7%{3w`%>UDti;Ac;RIezW#RG z>g$>D#uSBZpRE@JtuyviyO8YJwf}=oDLFVzpBCTlk6GKK%ar@HbcoaO&rz4?Pj8|k ziya#v390YQ4URy=vxCPOxR$a^j^6Kw^M46PbS8Vs17QJHBc~qRZHumn-RN06>rS+U zdMkJ7^}N^K#(w6o;jR%yVLX(+DH?S_kBQW7PS$(PPYYKak{7=ocM2Qh_rDpKScDth z)T)*^Qy$0>^VU^EQ<+!aS{Is>``$gQEXkB68>$oqkFV`Hc{z(~kQZSY+A&B6rY5sj z9c@o-+_8Ov%+wEhc0Qvp5pqX0zG>B!=c-AYeE|ouIN#W_o~)5uUDdzD%%G(n$k?7+ z*hOBKD{X-WTnIFsf;yQ{6t#`e{;AI4_10XAUxJN17OHm~d2gFwX*KCM^4q%a>gd{A z9E$QjB<}Rtl@((6*xlf8P51cejKXgPIMdxu)cTw#WYx*4?$55B2cip#J@BfEKcf8o z&n42ixz)87OA~cNjFU>rDkzArx8lR6BYMf{36~q~oY@JF^!RlT(f?4hbM4Ne##e%o6yl)eSZDw;N!;en6>*pj3*8Zn&nSv z6}FV@ohergwkji|Qn*^3`iR;--G)_X(`xhFD{d;@-bf!cO#QY*S<^FX(dDiyOFlDo zWbmBNZCm7 z@tnaWKYBWen%~^`DOI(Q`Sp~dZ~F7irUfr^u3ClnnDEDX z*{u5YT@g_24TWS^TsDTd7sO3*{i+a_yd<1-^n}6CHrR3h0r+H^Zj5u5j(x8>-D zv*ne3M^4)XM?v2US#^2lyD7KLKuf<=<8zCr;+*?9J){2(t&e%@wr9p6dQC><&KB}J zm7tsWv~}!zF=g&5d6729##SRyrX(d4-ILg?b3uFYROsB5=-Kyq9dFL(b6O`-d+VGP zwrTlQz466`B5+#QY>)cvwDW6{O$9vJzoo5gWHj`Nm6R`RX zv0;l}6iw^hKl3gjo^wZOap-MS&PGT3h7*wwpFO*EKWbXQdXpJ);OCha@0^Q&?tf)< zb`E;+wB5t4c*~f`lT%;TAnwhl+zOlQd&%zI zx{-Fz?^7qAk9bQjRSZ|PmmagVV&}fye>dacKWC6;Bt2Q0O2*yy^)Zm^!8QHH^u|`r P{{zE2fcC?V= literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_button_disclosure_setting.png b/res/drawable-xxxhdpi/ic_button_disclosure_setting.png new file mode 100755 index 0000000000000000000000000000000000000000..b25919efb545a64c2ae4e18934ef85780c348c28 GIT binary patch literal 3577 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 B?{xqG literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_pdk_diagnostic.png b/res/drawable-xxxhdpi/ic_pdk_diagnostic.png new file mode 100755 index 0000000000000000000000000000000000000000..ed16022e0facabfbd4eba73f338232cd9465e72b GIT binary patch literal 2068 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 E1NQ_regFUf literal 0 HcmV?d00001 diff --git a/res/drawable/ic_location_heatmap_marker.xml b/res/drawable/ic_location_heatmap_marker.xml new file mode 100755 index 0000000..1df13e4 --- /dev/null +++ b/res/drawable/ic_location_heatmap_marker.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/res/layout/card_diagnostic_action.xml b/res/layout/card_diagnostic_action.xml index a3274e1..acfd651 100755 --- a/res/layout/card_diagnostic_action.xml +++ b/res/layout/card_diagnostic_action.xml @@ -1,20 +1,41 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content"> - + + android:layout_height="wrap_content"> + + + + \ No newline at end of file diff --git a/res/layout/card_generator_location_google.xml b/res/layout/card_generator_location_google.xml index 716536d..979c932 100755 --- a/res/layout/card_generator_location_google.xml +++ b/res/layout/card_generator_location_google.xml @@ -2,6 +2,7 @@ @@ -42,24 +43,29 @@ android:textColor="@android:color/white" android:layout_marginRight="8dp"/> - + android:layout_height="200dp"> - + - + android:layout_gravity="bottom|right" + android:layout_marginRight="6dp" + android:layout_marginBottom="104dp" /> + \ No newline at end of file diff --git a/res/layout/card_generator_phone_calls.xml b/res/layout/card_generator_phone_calls.xml index f66de04..2a2725b 100755 --- a/res/layout/card_generator_phone_calls.xml +++ b/res/layout/card_generator_phone_calls.xml @@ -41,7 +41,8 @@ android:textColor="@android:color/white" android:layout_marginRight="8dp"/> - @@ -150,6 +151,12 @@ android:layout_marginBottom="8dp"/> + \ No newline at end of file diff --git a/res/layout/card_generator_screen_state.xml b/res/layout/card_generator_screen_state.xml index f09f805..7af3c2d 100755 --- a/res/layout/card_generator_screen_state.xml +++ b/res/layout/card_generator_screen_state.xml @@ -41,7 +41,8 @@ android:textColor="@android:color/white" android:layout_marginRight="8dp"/> - @@ -138,6 +139,12 @@ + \ No newline at end of file diff --git a/res/layout/card_generator_text_messages.xml b/res/layout/card_generator_text_messages.xml new file mode 100755 index 0000000..03c022d --- /dev/null +++ b/res/layout/card_generator_text_messages.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/card_generator_withings_device.xml b/res/layout/card_generator_withings_device.xml new file mode 100755 index 0000000..5eb8c89 --- /dev/null +++ b/res/layout/card_generator_withings_device.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/dialog_location_randomized.xml b/res/layout/dialog_location_randomized.xml new file mode 100755 index 0000000..58965d7 --- /dev/null +++ b/res/layout/dialog_location_randomized.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/res/layout/dialog_location_user.xml b/res/layout/dialog_location_user.xml new file mode 100755 index 0000000..17bbf31 --- /dev/null +++ b/res/layout/dialog_location_user.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/res/layout/layout_data_disclosure_detail_pdk.xml b/res/layout/layout_data_disclosure_detail_pdk.xml new file mode 100755 index 0000000..9cd2e4e --- /dev/null +++ b/res/layout/layout_data_disclosure_detail_pdk.xml @@ -0,0 +1,19 @@ + + + + + + + diff --git a/res/layout/layout_data_disclosure_pdk.xml b/res/layout/layout_data_disclosure_pdk.xml new file mode 100755 index 0000000..cc763d0 --- /dev/null +++ b/res/layout/layout_data_disclosure_pdk.xml @@ -0,0 +1,31 @@ + + + + + + + + + + diff --git a/res/layout/layout_diagnostics_pdk.xml b/res/layout/layout_diagnostics_pdk.xml index e71a783..379c418 100755 --- a/res/layout/layout_diagnostics_pdk.xml +++ b/res/layout/layout_diagnostics_pdk.xml @@ -1,7 +1,16 @@ - + android:layout_height="match_parent"> + + + diff --git a/res/layout/row_disclosure_action_pdk.xml b/res/layout/row_disclosure_action_pdk.xml new file mode 100755 index 0000000..a5250e3 --- /dev/null +++ b/res/layout/row_disclosure_action_pdk.xml @@ -0,0 +1,19 @@ + + + + diff --git a/res/layout/row_disclosure_location_accuracy_pdk.xml b/res/layout/row_disclosure_location_accuracy_pdk.xml new file mode 100755 index 0000000..78cd4cc --- /dev/null +++ b/res/layout/row_disclosure_location_accuracy_pdk.xml @@ -0,0 +1,26 @@ + + + + + + + diff --git a/res/layout/row_generator_disclosure_generic.xml b/res/layout/row_generator_disclosure_generic.xml new file mode 100755 index 0000000..200f44e --- /dev/null +++ b/res/layout/row_generator_disclosure_generic.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/res/menu/diagnostic_menu.xml b/res/menu/diagnostic_menu.xml new file mode 100755 index 0000000..65f394a --- /dev/null +++ b/res/menu/diagnostic_menu.xml @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/res/values/databases.xml b/res/values/databases.xml new file mode 100755 index 0000000..02d98e8 --- /dev/null +++ b/res/values/databases.xml @@ -0,0 +1,26 @@ + + CREATE TABLE metadata(key TEXT, value TEXT, last_updated INTEGER); + + + CREATE TABLE history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, latitude REAL, longitude REAL, altitude REAL, bearing REAL, speed REAL, provider TEXT, location_timestamp INTEGER, accuracy REAL); + + + CREATE TABLE history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, state TEXT); + + + CREATE TABLE history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, duration INTEGER, call_type TEXT, number TEXT, post_dial_digits TEXT, via_number TEXT, is_new INTEGER, pulled_externally INTEGER, country_iso TEXT, data_usage INTEGER, geocoded_location TEXT, is_video INTEGER, presentation TEXT, is_read INTEGER); + + + CREATE TABLE history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, direction TEXT, length INTEGER, body TEXT, number_name TEXT, number TEXT); + + + CREATE TABLE history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, event_name TEXT, event_details TEXT); + + + CREATE TABLE activity_measure_history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, date_start INTEGER, timezone TEXT, steps REAL, distance REAL, active_calories REAL, total_calories REAL, elevation REAL, soft_activity_duration REAL, moderate_activity_duration REAL, intense_activity_duration REAL); + CREATE TABLE body_measure_history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, measure_date INTEGER, measure_status TEXT, measure_category TEXT, measure_type TEXT, measure_value REAL); + CREATE TABLE intraday_activity_history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, activity_start REAL, activity_duration REAL, calories REAL, distance REAL, elevation_climbed REAL, steps REAL, swim_strokes REAL, pool_laps REAL); + CREATE TABLE sleep_measure_history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, start_date REAL, end_date REAL, state TEXT, measurement_device TEXT); + CREATE TABLE sleep_summary_history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, start_date REAL, end_date REAL, timezone TEXT, measurement_device TEXT, wake_duration REAL, light_sleep_duration REAL, deep_sleep_duration REAL, rem_sleep_duration REAL, wake_count INTEGER, to_sleep_duration REAL, to_wake_duration REAL); + CREATE TABLE workout_history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, start_date REAL, end_date REAL, measurement_device TEXT, workout_category TEXT, caolories REAL, effective_duration REAL, raw_data TEXT); + diff --git a/res/values/diagnostics.xml b/res/values/diagnostics.xml index c0adb6a..026fce6 100755 --- a/res/values/diagnostics.xml +++ b/res/values/diagnostics.xml @@ -1,7 +1,23 @@ - Unable to connect to Microsoft Band. + Microsoft Band App Not Installed + Unable to connect to Microsoft Band. Please install the app and try again. + + Microsoft Band Permission Required Please grant the app sensor access on the Microsoft Band. - Please grant the app permission to access location services on this device. + + Activity Recognition Permission Required Please grant the app permission to recognize your activity using data from this device. + + Call Log Permission Required Please grant the app permission to access the phone call logs on this device. + + Text Messaging Permission Required + Please all the app permission to access your text messages to gather and report your messaging activity statistics. + + Location Permission Required + Please grant the app permission to access location services on this device. + + Access to Withing Account Required + Permission is required to fetch data from the Withings server. Please grant this application the requested permissions. + diff --git a/res/values/generators.xml b/res/values/generators.xml index 166f4b6..934f041 100755 --- a/res/values/generators.xml +++ b/res/values/generators.xml @@ -3,9 +3,11 @@ com.audacious_software.passive_data_kit.generators.device.Location com.audacious_software.passive_data_kit.generators.device.ScreenState com.audacious_software.passive_data_kit.generators.communication.PhoneCalls - com.audacious_software.passive_data_kit.generators.wearables.MicrosoftBand - com.audacious_software.passive_data_kit.generators.services.GoogleAwareness - com.audacious_software.passive_data_kit.generators.vr.DaydreamViewController + com.audacious_software.passive_data_kit.generators.communication.TextMessages + + + + com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice @@ -43,9 +45,59 @@ Device Location Coordinates: %1$.4f, %2$.4f + Location Accuracy + Tap here to update the accuracy of your data. + + Best Accuracy + Best Available Data From Location Hardware + + Locally Randomized + Location Combined With Random Noise + + User Provided + Static Location Provided By User + + Disabled + App Does Not Use Location Data + + Best Accuracy + This app will use the location hardware on this device to obtain the most accurate location readings. + + Location Disabled + This app will not use your location, but use an placeholder instead. + + Locally Randomized + Please enter the random distance (kilometers) to use to obfuscate your exact location: + + User Provided + Please enter a postal code or city and province to use as your location: + Unable to find your location. Please enter your location another way and try again. Screen State + On + Off + Doze + Unknown + Legend: + No screen state changes have been observed yet. + + + SMS Text Messages + Incoming + Outgoing + Other + + #3F51B5 + #4CAF50 + #9E9E9E + + Latest Text Message + Length + Direction + %1$d chars. + + No text messages have been sent or received on this device. Phone Calls @@ -64,4 +116,8 @@ Direction %1$.2f min. + No phone calls have been made or received on this device. + + + Withings Device \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 61e918e..73b220f 100755 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4,6 +4,7 @@ 0.0.1 Unknown Version Data Stream + Continue %d generator @@ -14,10 +15,17 @@ Today - On - Off - Doze - Unknown - Legend: + Never + Diagnostics + Diagnostics (%d) + Diagnostics + + The app is set up correctly.\n\nNo further actions are needed. + + Passive Data Disclosure + Select a disclosure item for more information… + + Data Collection Description + Tap here to learn how this app uses your data. diff --git a/src/com/audacious_software/passive_data_kit/Logger.java b/src/com/audacious_software/passive_data_kit/Logger.java index b315574..9ea620e 100755 --- a/src/com/audacious_software/passive_data_kit/Logger.java +++ b/src/com/audacious_software/passive_data_kit/Logger.java @@ -2,7 +2,10 @@ import android.content.Context; +import com.audacious_software.passive_data_kit.generators.diagnostics.AppEvent; + import java.util.HashMap; +import java.util.Map; /** * Created by cjkarr on 4/3/2016. @@ -10,8 +13,12 @@ public class Logger { private Context mContext = null; - public void log(String event, HashMap details) { - // TODO + public void log(String event, Map details) { + if (details == null) { + details = new HashMap<>(); + } + + AppEvent.getInstance(this.mContext).logEvent(event, details); } private static class LoggerHolder { diff --git a/src/com/audacious_software/passive_data_kit/PassiveDataKit.java b/src/com/audacious_software/passive_data_kit/PassiveDataKit.java index 8a8efc1..285f97e 100755 --- a/src/com/audacious_software/passive_data_kit/PassiveDataKit.java +++ b/src/com/audacious_software/passive_data_kit/PassiveDataKit.java @@ -5,9 +5,13 @@ import com.audacious_software.passive_data_kit.diagnostics.DiagnosticAction; import com.audacious_software.passive_data_kit.generators.Generators; +import java.io.File; import java.util.ArrayList; public class PassiveDataKit { + private static final String STORAGE_PATH = "passive-data-kit"; + private static final String GENERATORS_PATH = "generators"; + private Context mContext = null; private boolean mStarted = false; @@ -30,6 +34,17 @@ public static ArrayList diagnostics(Context context) return actions; } + public static File getGeneratorsStorage(Context context) { + File path = new File(context.getFilesDir(), PassiveDataKit.STORAGE_PATH); + path = new File(path, PassiveDataKit.GENERATORS_PATH); + + if (path.exists() == false) { + path.mkdirs(); + } + + return path; + } + private static class PassiveDataKitHolder { public static PassiveDataKit instance = new PassiveDataKit(); } diff --git a/src/com/audacious_software/passive_data_kit/PhoneUtililties.java b/src/com/audacious_software/passive_data_kit/PhoneUtililties.java new file mode 100755 index 0000000..0213eb1 --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/PhoneUtililties.java @@ -0,0 +1,30 @@ +package com.audacious_software.passive_data_kit; + +import android.content.Context; + +/** + * Created by cjkarr on 12/13/2016. + */ + +public class PhoneUtililties { + public static String normalizedPhoneNumber(String phoneNumber) + { + if (phoneNumber == null) { + return null; + } + + phoneNumber = phoneNumber.replaceAll("[^\\d.]", ""); + + while (phoneNumber.length() > 10) { + phoneNumber = phoneNumber.substring(1); + } + + while (phoneNumber.length() < 10) { + phoneNumber += "0"; + } + + + return phoneNumber; + } +} + diff --git a/src/com/audacious_software/passive_data_kit/activities/DataDisclosureActivity.java b/src/com/audacious_software/passive_data_kit/activities/DataDisclosureActivity.java new file mode 100755 index 0000000..70d41f3 --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/activities/DataDisclosureActivity.java @@ -0,0 +1,44 @@ +package com.audacious_software.passive_data_kit.activities; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.MenuItem; +import android.widget.FrameLayout; + +import com.audacious_software.passive_data_kit.activities.generators.GeneratorsAdapter; +import com.audacious_software.pdk.passivedatakit.R; + +public class DataDisclosureActivity extends AppCompatActivity { + private GeneratorsAdapter mAdapter = null; + + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + this.setContentView(R.layout.layout_data_disclosure_pdk); + this.getSupportActionBar().setTitle(R.string.title_data_disclosure); + this.getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + FrameLayout dataView = (FrameLayout) this.findViewById(R.id.data_view); + + this.mAdapter = new GeneratorsAdapter(); + this.mAdapter.setContext(this.getApplicationContext()); + this.mAdapter.setDataView(dataView); + + RecyclerView listView = (RecyclerView) this.findViewById(R.id.list_view); + + listView.setLayoutManager(new LinearLayoutManager(this)); + + listView.setAdapter(this.mAdapter); + } + + public boolean onOptionsItemSelected(MenuItem item) + { + if (item.getItemId() == android.R.id.home) + { + this.finish(); + } + + return true; + } +} diff --git a/src/com/audacious_software/passive_data_kit/activities/DataDisclosureDetailActivity.java b/src/com/audacious_software/passive_data_kit/activities/DataDisclosureDetailActivity.java new file mode 100755 index 0000000..aaf79bb --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/activities/DataDisclosureDetailActivity.java @@ -0,0 +1,123 @@ +package com.audacious_software.passive_data_kit.activities; + +import android.content.Context; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.FrameLayout; +import android.widget.ListView; +import android.widget.TextView; + +import com.audacious_software.passive_data_kit.Logger; +import com.audacious_software.passive_data_kit.generators.Generator; +import com.audacious_software.pdk.passivedatakit.R; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; + +public class DataDisclosureDetailActivity extends AppCompatActivity { + public static class Action { + public String title; + public String subtitle; + + public View view; + } + + public static final String GENERATOR_CLASS_NAME = "com.audacious_software.passive_data_kit.activities.DataDisclosureDetailActivity.GENERATOR_CLASS_NAME"; + + private Class mGeneratorClass = null; + + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final DataDisclosureDetailActivity me = this; + + this.setContentView(R.layout.layout_data_disclosure_detail_pdk); + this.getSupportActionBar().setSubtitle(R.string.title_data_disclosure); + this.getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + try { + this.mGeneratorClass = (Class) Class.forName(this.getIntent().getStringExtra(DataDisclosureDetailActivity.GENERATOR_CLASS_NAME)); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + + if (this.mGeneratorClass != null) { + try { + Method getGeneratorTitle = this.mGeneratorClass.getDeclaredMethod("getGeneratorTitle", Context.class); + + String title = (String) getGeneratorTitle.invoke(null, this); + this.getSupportActionBar().setTitle(title); + + Method getDisclosureActions = this.mGeneratorClass.getDeclaredMethod("getDisclosureActions", Context.class); + + final List actions = (List) getDisclosureActions.invoke(null, this); + + ListView actionsList = (ListView) this.findViewById(R.id.disclosure_actions); + ArrayAdapter adapter = new ArrayAdapter(this, R.layout.row_disclosure_action_pdk, actions) { + public View getView (int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = LayoutInflater.from(me).inflate(R.layout.row_disclosure_action_pdk, null); + } + + Action action = actions.get(position); + + TextView title = (TextView) convertView.findViewById(R.id.action_title); + title.setText(action.title); + + TextView description = (TextView) convertView.findViewById(R.id.action_description); + description.setText(action.subtitle); + + return convertView; + } + }; + + actionsList.setAdapter(adapter); + + actionsList.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView adapterView, View view, int position, long l) { + Log.e("PDK", "TAPPED: " + position); + + Action action = actions.get(position); + + FrameLayout dataView = (FrameLayout) me.findViewById(R.id.data_view); + dataView.removeAllViews(); + + if (action.view != null) { + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + + action.view.setLayoutParams(params); + + dataView.addView(action.view); + } + } + }); + + actionsList.performItemClick(null, 0, 0); + } catch (NoSuchMethodException e1) { + Logger.getInstance(this).logThrowable(e1); + } catch (InvocationTargetException e1) { + Logger.getInstance(this).logThrowable(e1); + } catch (IllegalAccessException e1) { + Logger.getInstance(this).logThrowable(e1); + } + } + } + + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + this.finish(); + } + + return true; + } + +} diff --git a/src/com/audacious_software/passive_data_kit/activities/DataStreamActivity.java b/src/com/audacious_software/passive_data_kit/activities/DataStreamActivity.java index 184173a..3e80bb0 100755 --- a/src/com/audacious_software/passive_data_kit/activities/DataStreamActivity.java +++ b/src/com/audacious_software/passive_data_kit/activities/DataStreamActivity.java @@ -6,21 +6,14 @@ import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; -import com.audacious_software.passive_data_kit.PassiveDataKit; import com.audacious_software.passive_data_kit.activities.generators.DataPointsAdapter; -import com.audacious_software.passive_data_kit.diagnostics.DiagnosticAction; import com.audacious_software.passive_data_kit.generators.Generators; import com.audacious_software.pdk.passivedatakit.R; import java.util.ArrayList; -public class DataStreamActivity extends AppCompatActivity implements Generators.NewDataPointListener { +public class DataStreamActivity extends AppCompatActivity implements Generators.GeneratorUpdatedListener { private DataPointsAdapter mAdapter = null; protected void onCreate(Bundle savedInstanceState) { @@ -30,6 +23,7 @@ protected void onCreate(Bundle savedInstanceState) { this.getSupportActionBar().setSubtitle(this.getResources().getQuantityString(R.plurals.activity_data_stream_subtitle, 0, 0)); this.mAdapter = new DataPointsAdapter(); + this.mAdapter.setContext(this.getApplicationContext()); RecyclerView listView = (RecyclerView) this.findViewById(R.id.list_view); @@ -41,30 +35,37 @@ protected void onCreate(Bundle savedInstanceState) { protected void onResume() { super.onResume(); - Generators.getInstance(this).addNewDataPointListener(this); + Generators.getInstance(this).addNewGeneratorUpdatedListener(this); - Generators.getInstance(this).broadcastLatestDataPoints(); + final int count = this.mAdapter.getItemCount(); + + Handler mainHandler = new Handler(Looper.getMainLooper()); + + final DataStreamActivity me = this; + + mainHandler.post(new Runnable() { + @Override + public void run() { + me.getSupportActionBar().setSubtitle(me.getResources().getQuantityString(R.plurals.activity_data_stream_subtitle, count, count)); + } + }); } protected void onPause() { super.onPause(); - Generators.getInstance(this).removeNewDataPointListener(this); + Generators.getInstance(this).removeGeneratorUpdatedListener(this); } @Override - public void onNewDataPoint(String identifier, Bundle data) { - this.mAdapter.updateDataPoint(identifier, data); - - final int count = this.mAdapter.getItemCount(); - - Handler mainHandler = new Handler(Looper.getMainLooper()); - + public void onGeneratorUpdated(String identifier, long timestamp, Bundle data) { final DataStreamActivity me = this; - mainHandler.post(new Runnable() { + this.runOnUiThread(new Runnable() { @Override public void run() { + me.mAdapter.notifyDataSetChanged(); + int count = me.mAdapter.getItemCount(); me.getSupportActionBar().setSubtitle(me.getResources().getQuantityString(R.plurals.activity_data_stream_subtitle, count, count)); } }); diff --git a/src/com/audacious_software/passive_data_kit/activities/DiagnosticsActivity.java b/src/com/audacious_software/passive_data_kit/activities/DiagnosticsActivity.java index ce8a3d5..851103d 100755 --- a/src/com/audacious_software/passive_data_kit/activities/DiagnosticsActivity.java +++ b/src/com/audacious_software/passive_data_kit/activities/DiagnosticsActivity.java @@ -1,11 +1,14 @@ package com.audacious_software.passive_data_kit.activities; +import android.app.Activity; +import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; -import android.util.Log; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; @@ -17,6 +20,38 @@ import java.util.ArrayList; public class DiagnosticsActivity extends AppCompatActivity { + public static void setUpDiagnositicsItem(Activity activity, Menu menu, boolean showAction) { + final ArrayList actions = PassiveDataKit.diagnostics(activity); + + MenuItem item = menu.add(Menu.NONE, R.id.action_diagnostics, 0, activity.getString(R.string.action_diagnostics)); + + if (actions.size() > 0 && showAction) { + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + + item.setIcon(R.drawable.ic_pdk_diagnostic); + item.setTitle("" + actions.size()); + } else { + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + + if (actions.size() > 0) { + item.setTitle(activity.getString(R.string.action_diagnostics_incomplete, actions.size())); + } + } + } + + public static boolean diagnosticItemSelected(Activity activity, MenuItem item) { + int id = item.getItemId(); + + if (id == R.id.action_diagnostics) { + Intent diagnosticsIntent = new Intent(activity, DiagnosticsActivity.class); + activity.startActivity(diagnosticsIntent); + + return true; + } + + return false; + } + private class DiagnosticViewHolder extends RecyclerView.ViewHolder { private View mView = null; @@ -44,7 +79,10 @@ public void bindDiagnosticAction(DiagnosticAction action) { this.mAction = action; - TextView message = (TextView) this.mView.findViewById(R.id.message_action); + TextView title = (TextView) this.mView.findViewById(R.id.action_title); + title.setText(action.getTitle()); + + TextView message = (TextView) this.mView.findViewById(R.id.action_message); message.setText(action.getMessage()); } } @@ -52,34 +90,46 @@ public void bindDiagnosticAction(DiagnosticAction action) protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.layout_diagnostics_pdk); + this.getSupportActionBar().setTitle(R.string.title_pdk_diagnostics); + } - final ArrayList actions = PassiveDataKit.diagnostics(this); + protected void onResume() { + super.onResume(); - Log.e("PDK", "ACTIONS COUNT: " + actions.size()); + final ArrayList actions = PassiveDataKit.diagnostics(this); RecyclerView listView = (RecyclerView) this.findViewById(R.id.list_view); + TextView emptyMessage = (TextView) this.findViewById(R.id.message_no_diagnostics); - listView.setLayoutManager(new LinearLayoutManager(this)); + if (actions.size() > 0) { + listView.setVisibility(View.VISIBLE); + emptyMessage.setVisibility(View.GONE); - listView.setAdapter(new RecyclerView.Adapter() { - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_diagnostic_action, parent, false); + listView.setLayoutManager(new LinearLayoutManager(this)); - return new DiagnosticViewHolder(v); - } + listView.setAdapter(new RecyclerView.Adapter() { + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_diagnostic_action, parent, false); - @Override - public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { - DiagnosticViewHolder diagHolder = (DiagnosticViewHolder) holder; + return new DiagnosticViewHolder(v); + } - diagHolder.bindDiagnosticAction(actions.get(position)); - } + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + DiagnosticViewHolder diagHolder = (DiagnosticViewHolder) holder; - @Override - public int getItemCount() { - return actions.size(); - } - }); + diagHolder.bindDiagnosticAction(actions.get(position)); + } + + @Override + public int getItemCount() { + return actions.size(); + } + }); + } else { + listView.setVisibility(View.GONE); + emptyMessage.setVisibility(View.VISIBLE); + } } } diff --git a/src/com/audacious_software/passive_data_kit/activities/OAuthResponseActivity.java b/src/com/audacious_software/passive_data_kit/activities/OAuthResponseActivity.java new file mode 100755 index 0000000..3edcfa3 --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/activities/OAuthResponseActivity.java @@ -0,0 +1,32 @@ +package com.audacious_software.passive_data_kit.activities; + +import android.net.Uri; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; + +import com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice; +import com.audacious_software.pdk.passivedatakit.R; + +/** + * Created by cjkarr on 3/18/2017. + */ + +public class OAuthResponseActivity extends AppCompatActivity { + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + protected void onResume() { + super.onResume(); + + Uri u = this.getIntent().getData(); + + Log.e("PDK", "CALLBACK FROM OAUTH: " + u); + Log.e("PDK", "PATH: " + u.getPath()); + + if (u.getPath().startsWith(WithingsDevice.API_OAUTH_CALLBACK_PATH)) { + WithingsDevice.getInstance(this).finishAuthentication(u); + } + } +} diff --git a/src/com/audacious_software/passive_data_kit/activities/PdkActivity.java b/src/com/audacious_software/passive_data_kit/activities/PdkActivity.java index d8ef541..b0a4a1e 100755 --- a/src/com/audacious_software/passive_data_kit/activities/PdkActivity.java +++ b/src/com/audacious_software/passive_data_kit/activities/PdkActivity.java @@ -1,6 +1,5 @@ package com.audacious_software.passive_data_kit.activities; -import android.os.Bundle; import android.support.v7.app.AppCompatActivity; public abstract class PdkActivity extends AppCompatActivity diff --git a/src/com/audacious_software/passive_data_kit/activities/generators/DataPointsAdapter.java b/src/com/audacious_software/passive_data_kit/activities/generators/DataPointsAdapter.java index b711460..fd1679d 100755 --- a/src/com/audacious_software/passive_data_kit/activities/generators/DataPointsAdapter.java +++ b/src/com/audacious_software/passive_data_kit/activities/generators/DataPointsAdapter.java @@ -1,9 +1,6 @@ package com.audacious_software.passive_data_kit.activities.generators; import android.content.Context; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; import android.support.v7.widget.RecyclerView; import android.view.View; import android.view.ViewGroup; @@ -14,15 +11,17 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; public class DataPointsAdapter extends RecyclerView.Adapter { - private ArrayList mDataPoints = new ArrayList<>(); + private Context mContext = null; @Override public DataPointViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - Class generatorClass = Generators.getInstance(null).fetchCustomViewClass(viewType); + Class generatorClass = Generators.getInstance(this.mContext).fetchCustomViewClass(viewType); try { Method fetchView = generatorClass.getDeclaredMethod("fetchView", ViewGroup.class); @@ -51,20 +50,24 @@ public DataPointViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { } @Override - public void onBindViewHolder(DataPointViewHolder holder, int position) { - Bundle dataPoint = this.mDataPoints.get(position); - Class generatorClass = Generators.getInstance(null).fetchCustomViewClass(dataPoint.getBundle(Generator.PDK_METADATA).getString(Generator.IDENTIFIER)); + public void onBindViewHolder(final DataPointViewHolder holder, int position) { + List> activeGenerators = Generators.getInstance(holder.itemView.getContext()).activeGenerators(); + + this.sortGenerators(this.mContext, activeGenerators); + + Class generatorClass = activeGenerators.get(position); try { - Method bindViewHolder = generatorClass.getDeclaredMethod("bindViewHolder", DataPointViewHolder.class, Bundle.class); - bindViewHolder.invoke(null, holder, dataPoint); + Method bindViewHolder = generatorClass.getDeclaredMethod("bindViewHolder", DataPointViewHolder.class); + bindViewHolder.invoke(null, holder); } catch (Exception e) { e.printStackTrace(); try { generatorClass = Generator.class; - Method bindViewHolder = generatorClass.getDeclaredMethod("bindViewHolder", DataPointViewHolder.class, Bundle.class); - bindViewHolder.invoke(null, holder, dataPoint); + Method bindViewHolder = generatorClass.getDeclaredMethod("bindViewHolder", DataPointViewHolder.class); + + bindViewHolder.invoke(null, holder); } catch (NoSuchMethodException e1) { Logger.getInstance(holder.itemView.getContext()).logThrowable(e1); } catch (InvocationTargetException e1) { @@ -77,51 +80,63 @@ public void onBindViewHolder(DataPointViewHolder holder, int position) { @Override public int getItemCount() { - return this.mDataPoints.size(); + return Generators.getInstance(null).activeGenerators().size(); } - public int getItemViewType (int position) { - Bundle dataPoint = this.mDataPoints.get(position); - Class generatorClass = Generators.getInstance(null).fetchCustomViewClass(dataPoint.getBundle(Generator.PDK_METADATA).getString(Generator.IDENTIFIER)); - - return generatorClass.hashCode(); - } - - public void updateDataPoint(String identifier, Bundle data) { - ArrayList toDelete = new ArrayList<>(); - - Handler mainHandler = new Handler(Looper.getMainLooper()); - final DataPointsAdapter me = this; - - for (Bundle bundle : this.mDataPoints) { - if (bundle.getBundle(Generator.PDK_METADATA).getString(Generator.IDENTIFIER).equals(identifier)) { - toDelete.add(bundle); + private void sortGenerators(final Context context, List> generators) { + Collections.sort(generators, new Comparator>() { + @Override + public int compare(Class 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; } - } - - Collections.reverse(toDelete); + }); + } - for (Bundle delete : toDelete) { - final int position = this.mDataPoints.indexOf(delete); + public int getItemViewType (int position) { + List> activeGenerators = Generators.getInstance(this.mContext).activeGenerators(); - this.mDataPoints.remove(position); + this.sortGenerators(this.mContext, activeGenerators); -// mainHandler.post(new Runnable() { -// @Override -// public void run() { -// me.notifyItemRemoved(position); -// } -// }); - } + Class generatorClass = activeGenerators.get(position); - this.mDataPoints.add(0, data); + return generatorClass.hashCode(); + } - mainHandler.post(new Runnable() { - @Override - public void run() { -// me.notifyItemInserted(0); - me.notifyDataSetChanged(); - } - }); + public void setContext(Context context) { + this.mContext = context; } } diff --git a/src/com/audacious_software/passive_data_kit/activities/generators/GeneratorViewHolder.java b/src/com/audacious_software/passive_data_kit/activities/generators/GeneratorViewHolder.java new file mode 100755 index 0000000..5726eb2 --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/activities/generators/GeneratorViewHolder.java @@ -0,0 +1,10 @@ +package com.audacious_software.passive_data_kit.activities.generators; + +import android.support.v7.widget.RecyclerView; +import android.view.View; + +public class GeneratorViewHolder extends RecyclerView.ViewHolder { + public GeneratorViewHolder(View itemView) { + super(itemView); + } +} diff --git a/src/com/audacious_software/passive_data_kit/activities/generators/GeneratorsAdapter.java b/src/com/audacious_software/passive_data_kit/activities/generators/GeneratorsAdapter.java new file mode 100755 index 0000000..adf3618 --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/activities/generators/GeneratorsAdapter.java @@ -0,0 +1,129 @@ +package com.audacious_software.passive_data_kit.activities.generators; + +import android.content.Context; +import android.content.Intent; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.Toast; + +import com.audacious_software.passive_data_kit.Logger; +import com.audacious_software.passive_data_kit.activities.DataDisclosureDetailActivity; +import com.audacious_software.passive_data_kit.generators.Generator; +import com.audacious_software.passive_data_kit.generators.Generators; +import com.audacious_software.pdk.passivedatakit.R; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class GeneratorsAdapter extends RecyclerView.Adapter { + private Context mContext = null; + private FrameLayout mDataView = null; + + @Override + public GeneratorViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = Generator.fetchDisclosureView(parent); + + return new GeneratorViewHolder(view); + } + + @Override + public void onBindViewHolder(final GeneratorViewHolder holder, int position) { + final GeneratorsAdapter me = this; + + List> activeGenerators = Generators.getInstance(holder.itemView.getContext()).activeGenerators(); + + this.sortGenerators(this.mContext, activeGenerators); + + Class generatorClass = activeGenerators.get(position); + + Log.e("PDK", "GENERATOR CLASS: " + generatorClass); + + try { + Method bindViewHolder = generatorClass.getDeclaredMethod("bindDisclosureViewHolder", GeneratorViewHolder.class); + bindViewHolder.invoke(null, holder); + } catch (Exception e) { +// e.printStackTrace(); + try { + generatorClass = Generator.class; + + Method bindViewHolder = generatorClass.getDeclaredMethod("bindDisclosureViewHolder", GeneratorViewHolder.class); + + bindViewHolder.invoke(null, holder); + } catch (NoSuchMethodException e1) { + Logger.getInstance(holder.itemView.getContext()).logThrowable(e1); + } catch (InvocationTargetException e1) { + Logger.getInstance(holder.itemView.getContext()).logThrowable(e1); + } catch (IllegalAccessException e1) { + Logger.getInstance(holder.itemView.getContext()).logThrowable(e1); + } + } + + final Class finalClass = generatorClass; + + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + me.mDataView.removeAllViews(); + + try { + Method bindViewHolder = finalClass.getDeclaredMethod("getDisclosureDataView", GeneratorViewHolder.class); + + View dataView = (View) bindViewHolder.invoke(null, holder); + me.mDataView.addView(dataView); + } catch (NoSuchMethodException e1) { + Logger.getInstance(holder.itemView.getContext()).logThrowable(e1); + } catch (InvocationTargetException e1) { + Logger.getInstance(holder.itemView.getContext()).logThrowable(e1); + } catch (IllegalAccessException e1) { + Logger.getInstance(holder.itemView.getContext()).logThrowable(e1); + } + } + }); + + ImageView settingsButton = (ImageView) holder.itemView.findViewById(R.id.button_disclosure_item); + + settingsButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(holder.itemView.getContext(), DataDisclosureDetailActivity.class); + intent.putExtra(DataDisclosureDetailActivity.GENERATOR_CLASS_NAME, finalClass.getCanonicalName()); + + holder.itemView.getContext().startActivity(intent); + } + }); + + } + + @Override + public int getItemCount() { + return Generators.getInstance(null).activeGenerators().size(); + } + + private void sortGenerators(final Context context, List> generators) { + Collections.sort(generators, new Comparator>() { + @Override + public int compare(Class one, Class two) { + return one.getName().compareTo(two.getName()); + } + }); + } + + public int getItemViewType (int position) { + return 0; + } + + public void setContext(Context context) { + this.mContext = context; + } + + public void setDataView(FrameLayout dataView) { + this.mDataView = dataView; + } +} diff --git a/src/com/audacious_software/passive_data_kit/diagnostics/DiagnosticAction.java b/src/com/audacious_software/passive_data_kit/diagnostics/DiagnosticAction.java index c3869ff..d466198 100755 --- a/src/com/audacious_software/passive_data_kit/diagnostics/DiagnosticAction.java +++ b/src/com/audacious_software/passive_data_kit/diagnostics/DiagnosticAction.java @@ -8,8 +8,10 @@ public class DiagnosticAction { private String mMessage = null; private Runnable mAction = null; + private String mTitle = null; - public DiagnosticAction(String message, Runnable action) { + public DiagnosticAction(String title, String message, Runnable action) { + this.mTitle = title; this.mMessage = message; this.mAction = action; } @@ -25,4 +27,8 @@ public void run() { public String getMessage() { return this.mMessage; } + + public String getTitle() { + return this.mTitle; + } } diff --git a/src/com/audacious_software/passive_data_kit/generators/Generator.java b/src/com/audacious_software/passive_data_kit/generators/Generator.java index dedb648..66563c0 100755 --- a/src/com/audacious_software/passive_data_kit/generators/Generator.java +++ b/src/com/audacious_software/passive_data_kit/generators/Generator.java @@ -1,6 +1,10 @@ package com.audacious_software.passive_data_kit.generators; +import android.content.ContentValues; import android.content.Context; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -8,11 +12,13 @@ import android.widget.TextView; import com.audacious_software.passive_data_kit.activities.generators.DataPointViewHolder; +import com.audacious_software.passive_data_kit.activities.generators.GeneratorViewHolder; import com.audacious_software.pdk.passivedatakit.R; import java.text.DateFormat; import java.util.Calendar; import java.util.Date; +import java.util.List; @SuppressWarnings("unused") public abstract class Generator @@ -25,6 +31,15 @@ public abstract class Generator public static final String MEDIA_ATTACHMENT_KEY = "attachment"; public static final String MEDIA_CONTENT_TYPE_KEY = "attachment-type"; public static final String MEDIA_ATTACHMENT_GUID_KEY = "attachment-guid"; + public static final String LATITUDE = "latitude"; + public static final String LONGITUDE = "longitude"; + + private static final String TABLE_SQLITE_MASTER = "sqlite_master"; + + private static final String TABLE_METADATA = "metadata"; + private static String TABLE_METADATA_LAST_UPDATED = "last_updated"; + private static String TABLE_METADATA_KEY = "key"; + private static String TABLE_METADATA_VALUE = "value"; protected Context mContext = null; @@ -41,10 +56,6 @@ public static void stop(Context context) { // Do nothing - override in subclasses. } - public static void broadcastLatestDataPoint(Context context) { - // Do nothing - override in subclasses. - } - public static boolean isEnabled(Context context) { return false; @@ -55,12 +66,32 @@ public static boolean isRunning(Context context) return false; } + public static long latestPointGenerated(Context context) { + return 0; + } + public static View fetchView(ViewGroup parent) { return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_generic, parent, false); } - public static void bindViewHolder(DataPointViewHolder holder, Bundle dataPoint) { - String identifier = dataPoint.getBundle(Generator.PDK_METADATA).getString(Generator.IDENTIFIER); + public static void bindViewHolder(DataPointViewHolder holder) { + Class currentClass = new Object() { }.getClass().getEnclosingClass(); + + String identifier = currentClass.getCanonicalName(); + + TextView generatorLabel = (TextView) holder.itemView.findViewById(R.id.label_generator); + + generatorLabel.setText(identifier); + } + + public static View fetchDisclosureView(ViewGroup parent) { + return LayoutInflater.from(parent.getContext()).inflate(R.layout.row_generator_disclosure_generic, parent, false); + } + + public static void bindDisclosureViewHolder(GeneratorViewHolder holder) { + Class currentClass = new Object() { }.getClass().getEnclosingClass(); + + String identifier = currentClass.getCanonicalName(); TextView generatorLabel = (TextView) holder.itemView.findViewById(R.id.label_generator); @@ -87,4 +118,68 @@ public static String formatTimestamp(Context context, double timestamp) { return context.getString(R.string.format_full_timestamp_pdk, date, time); } + + public abstract List fetchPayloads(); + + public Cursor queryHistory(String[] cols, String where, String[] args, String orderBy) { + Cursor c = new MatrixCursor(cols); + + return c; + } + + protected int getDatabaseVersion(SQLiteDatabase db) { + String where = "type = ? AND name = ?"; + String[] args = { "table", Generator.TABLE_METADATA }; + + Cursor c = db.query(Generator.TABLE_SQLITE_MASTER, null, where, args, null, null, null); + + if (c.getCount() > 0) { + // Do nothing - table exists... + } else { + db.execSQL(this.mContext.getString(R.string.pdk_generator_create_version_table)); + } + + c.close(); + + String versionWhere = Generator.TABLE_METADATA_KEY + " = ?"; + String[] versionArgs = { "version" }; + + c = db.query(Generator.TABLE_METADATA, null, versionWhere, versionArgs, null, null, Generator.TABLE_METADATA_LAST_UPDATED + " DESC"); + + int version = 0; + + if (c.moveToNext()) { + version = Integer.parseInt(c.getString(c.getColumnIndex(Generator.TABLE_METADATA_VALUE))); + } + + c.close(); + + return version; + } + + protected void setDatabaseVersion(SQLiteDatabase db, int newVersion) { + boolean keyExists = false; + + String versionWhere = Generator.TABLE_METADATA_KEY + " = ?"; + String[] versionArgs = { "version" }; + + Cursor c = db.query(Generator.TABLE_METADATA, null, versionWhere, versionArgs, null, null, Generator.TABLE_METADATA_LAST_UPDATED + " DESC"); + + if (c.getCount() > 0) { + keyExists = true; + } + + c.close(); + + ContentValues values = new ContentValues(); + values.put(Generator.TABLE_METADATA_KEY, "version"); + values.put(Generator.TABLE_METADATA_VALUE, "" + newVersion); + values.put(Generator.TABLE_METADATA_LAST_UPDATED, System.currentTimeMillis()); + + if (keyExists) { + db.update(Generator.TABLE_METADATA, values, versionWhere, versionArgs); + } else { + db.insert(Generator.TABLE_METADATA, null, values); + } + } } diff --git a/src/com/audacious_software/passive_data_kit/generators/Generators.java b/src/com/audacious_software/passive_data_kit/generators/Generators.java index fb9e412..14a4d26 100755 --- a/src/com/audacious_software/passive_data_kit/generators/Generators.java +++ b/src/com/audacious_software/passive_data_kit/generators/Generators.java @@ -11,6 +11,7 @@ import com.audacious_software.passive_data_kit.Logger; import com.audacious_software.passive_data_kit.diagnostics.DiagnosticAction; +import com.audacious_software.passive_data_kit.generators.diagnostics.AppEvent; import com.audacious_software.pdk.passivedatakit.R; import java.lang.reflect.InvocationTargetException; @@ -19,22 +20,26 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.List; public class Generators { private Context mContext = null; private boolean mStarted = false; + private ArrayList mGenerators = new ArrayList<>(); private HashSet mActiveGenerators = new HashSet<>(); private SharedPreferences mSharedPreferences = null; - private HashSet mNewDataPointListeners = new HashSet<>(); private HashMap> mGeneratorMap = new HashMap<>(); private SparseArray> mViewTypeMap = new SparseArray<>(); + private HashSet mGeneratorUpdatedListeners = new HashSet<>(); public void start() { if (!this.mStarted) { this.mGenerators.clear(); + this.mGenerators.add(AppEvent.class.getCanonicalName()); + for (String className : this.mContext.getResources().getStringArray(R.array.pdk_available_generators)) { this.mGenerators.add(className); @@ -148,27 +153,11 @@ public ArrayList diagnostics() { return actions; } - public void transmitData(String identifier, Bundle data) { - double now = (double) System.currentTimeMillis(); - now = now / 1000; // Convert to seconds... - - Bundle metadata = new Bundle(); - metadata.putString(Generator.IDENTIFIER, identifier); - metadata.putDouble(Generator.TIMESTAMP, now); - metadata.putString(Generator.GENERATOR, this.getGeneratorFullName(identifier)); - metadata.putString(Generator.SOURCE, this.getSource()); - data.putBundle(Generator.PDK_METADATA, metadata); - - for (Generators.NewDataPointListener listener : this.mNewDataPointListeners) { - listener.onNewDataPoint(identifier, data); - } - } - - private String getSource() { + public String getSource() { return "unknown-user-please-set-me"; } - private String getGeneratorFullName(String identifier) { + public String getGeneratorFullName(String identifier) { String pdkName = this.mContext.getString(R.string.pdk_name); String pdkVersion = this.mContext.getString(R.string.pdk_version); String appName = this.mContext.getString(this.mContext.getApplicationInfo().labelRes); @@ -186,10 +175,6 @@ private String getGeneratorFullName(String identifier) { return identifier + ": " + appName + "/" + version + " " + pdkName + "/" + pdkVersion; } - public void removeNewDataPointListener(Generators.NewDataPointListener listener) { - this.mNewDataPointListeners.remove(listener); - } - public void registerCustomViewClass(String identifier, Class generatorClass) { this.mGeneratorMap.put(identifier, generatorClass); this.mViewTypeMap.put(generatorClass.hashCode(), generatorClass); @@ -215,29 +200,59 @@ public Class fetchCustomViewClass(int viewType) { return generatorClass; } - public void broadcastLatestDataPoints() { - for (String className : this.mGenerators) - { - try { - Class generatorClass = (Class) Class.forName(className); - - Log.e("PDK", "CLASS " + generatorClass); + public Generator getGenerator(String className) { + Log.e("BB", "GENERATOR FIND START"); + for (String name : this.mActiveGenerators) { + Log.e("BB", "GENERATOR NAME: " + name); + } + Log.e("BB", "GENERATOR FIND END"); - if (generatorClass != null) { - Method broadcast = generatorClass.getDeclaredMethod("broadcastLatestDataPoint", Context.class); + if (this.mActiveGenerators.contains(className)) { + try { + Class probeClass = (Class) Class.forName(className); - broadcast.invoke(null, this.mContext); - } + Method getInstance = probeClass.getDeclaredMethod("getInstance", Context.class); + return (Generator) getInstance.invoke(null, this.mContext); + } catch (ClassNotFoundException e) { + e.printStackTrace(); } catch (NoSuchMethodException e) { - - } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } + + return null; + } + + public List> activeGenerators() { + ArrayList> active = new ArrayList<>(); + + for (String className : this.mActiveGenerators) { + try { + active.add((Class) Class.forName(className)); } catch (ClassNotFoundException e) { e.printStackTrace(); } } + + return active; + } + + public void notifyGeneratorUpdated(String identifier, long timestamp, Bundle bundle) { + for (GeneratorUpdatedListener listener : this.mGeneratorUpdatedListeners) { + listener.onGeneratorUpdated(identifier, timestamp, bundle); + } + } + + public void notifyGeneratorUpdated(String identifier, Bundle bundle) { + long timestamp = System.currentTimeMillis(); + + for (GeneratorUpdatedListener listener : this.mGeneratorUpdatedListeners) { + listener.onGeneratorUpdated(identifier, timestamp, bundle); + } } private static class GeneratorsHolder { @@ -257,11 +272,15 @@ private void setContext(Context context) { this.mContext = context.getApplicationContext(); } - public void addNewDataPointListener(Generators.NewDataPointListener listener) { - this.mNewDataPointListeners.add(listener); + public void addNewGeneratorUpdatedListener(Generators.GeneratorUpdatedListener listener) { + this.mGeneratorUpdatedListeners.add(listener); + } + + public void removeGeneratorUpdatedListener(Generators.GeneratorUpdatedListener listener) { + this.mGeneratorUpdatedListeners.remove(listener); } - public interface NewDataPointListener { - void onNewDataPoint(String identifier, Bundle data); + public interface GeneratorUpdatedListener { + void onGeneratorUpdated(String identifier, long timestamp, Bundle data); } } diff --git a/src/com/audacious_software/passive_data_kit/generators/communication/PhoneCalls.java b/src/com/audacious_software/passive_data_kit/generators/communication/PhoneCalls.java index 4294ab8..cea19b6 100755 --- a/src/com/audacious_software/passive_data_kit/generators/communication/PhoneCalls.java +++ b/src/com/audacious_software/passive_data_kit/generators/communication/PhoneCalls.java @@ -1,11 +1,13 @@ package com.audacious_software.passive_data_kit.generators.communication; import android.Manifest; +import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; import android.graphics.Typeface; import android.os.Build; import android.os.Bundle; @@ -20,6 +22,7 @@ import android.view.ViewGroup; import android.widget.TextView; +import com.audacious_software.passive_data_kit.PassiveDataKit; import com.audacious_software.passive_data_kit.activities.generators.DataPointViewHolder; import com.audacious_software.passive_data_kit.activities.generators.RequestPermissionActivity; import com.audacious_software.passive_data_kit.diagnostics.DiagnosticAction; @@ -27,19 +30,17 @@ import com.audacious_software.passive_data_kit.generators.Generators; import com.audacious_software.pdk.passivedatakit.R; import com.github.mikephil.charting.charts.PieChart; -import com.github.mikephil.charting.components.Description; -import com.github.mikephil.charting.components.Legend; import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.data.PieData; import com.github.mikephil.charting.data.PieDataSet; import com.github.mikephil.charting.data.PieEntry; import com.github.mikephil.charting.formatter.IValueFormatter; -import com.github.mikephil.charting.utils.ColorTemplate; import com.github.mikephil.charting.utils.ViewPortHandler; import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.DigestUtils; +import java.io.File; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -50,21 +51,6 @@ public class PhoneCalls extends Generator { private static final String ENABLED = "com.audacious_software.passive_data_kit.generators.communication.PhoneCalls.ENABLED"; private static final boolean ENABLED_DEFAULT = true; - private static final String SAMPLE_INTERVAL = "com.audacious_software.passive_data_kit.generators.communication.PhoneCalls.SAMPLE_INTERVAL"; - private static final long SAMPLE_INTERVAL_DEFAULT = 30000; // 300000; - - private static final String LAST_INCOMING_COUNT = "com.audacious_software.passive_data_kit.generators.communication.PhoneCalls.LAST_INCOMING_COUNT"; - private static final String LAST_OUTGOING_COUNT = "com.audacious_software.passive_data_kit.generators.communication.PhoneCalls.LAST_OUTGOING_COUNT"; - private static final String LAST_MISSED_COUNT = "com.audacious_software.passive_data_kit.generators.communication.PhoneCalls.LAST_MISSED_COUNT"; - private static final String LAST_TOTAL_COUNT = "com.audacious_software.passive_data_kit.generators.communication.PhoneCalls.LAST_TOTAL_COUNT"; - - private static final String LAST_CALL_TIMESTAMP = "com.audacious_software.passive_data_kit.generators.communication.PhoneCalls.LAST_CALL_TIMESTAMP"; - private static final String LAST_CALL_DURATION = "com.audacious_software.passive_data_kit.generators.communication.PhoneCalls.LAST_CALL_DURATION"; - private static final String LAST_CALL_TYPE = "com.audacious_software.passive_data_kit.generators.communication.PhoneCalls.LAST_CALL_TYPE"; - - private static final String LAST_SAMPLE = "com.audacious_software.passive_data_kit.generators.communication.PhoneCalls.LAST_SAMPLE"; - private static final long LAST_SAMPLE_DEFAULT = 0; - private static final String CALL_DATE_KEY = "call_timestamp"; private static final String CALL_DURATION_KEY = "duration"; private static final String CALL_IS_NEW_KEY = "is_new"; @@ -94,9 +80,33 @@ public class PhoneCalls extends Generator { private static final String CALL_PRESENTATION_PAYPHONE = "payphone"; private static final String CALL_PRESENTATION_UNKNOWN = "unknown"; + private static int DATABASE_VERSION = 2; + + private static final String TABLE_HISTORY = "history"; + private static final String HISTORY_OBSERVED = "observed"; + private static final String HISTORY_DURATION = "duration"; + private static final String HISTORY_NUMBER = "number"; + private static final String HISTORY_IS_NEW = "is_new"; + private static final String HISTORY_PULLED_EXTERNALLY = "pulled_externally"; + private static final String HISTORY_POST_DIAL_DIGITS = "post_dial_digits"; + private static final String HISTORY_COUNTRY_ISO = "country_iso"; + private static final String HISTORY_DATA_USAGE = "data_usage"; + private static final String HISTORY_GEOCODED_LOCATION = "geocoded_location"; + private static final String HISTORY_VIDEO = "is_video"; + private static final String HISTORY_VIA_NUMBER = "via_number"; + private static final String HISTORY_PRESENTATION = "presentation"; + private static final String HISTORY_IS_READ = "is_read"; + private static final String HISTORY_CALL_TYPE = "call_type"; + private static PhoneCalls sInstance = null; private Handler mHandler = null; + private Context mContext = null; + + private static final String DATABASE_PATH = "pdk-phone-calls.sqlite"; + + private SQLiteDatabase mDatabase = null; + private long mSampleInterval = 60000; public static PhoneCalls getInstance(Context context) { if (PhoneCalls.sInstance == null) { @@ -108,6 +118,8 @@ public static PhoneCalls getInstance(Context context) { public PhoneCalls(Context context) { super(context); + + this.mContext = context.getApplicationContext(); } public static void start(final Context context) { @@ -115,12 +127,14 @@ public static void start(final Context context) { } private void startGenerator() { - Log.e("PDK", "START PHONE CALL GENERATOR"); - final PhoneCalls me = this; if (this.mHandler != null) { - this.mHandler.getLooper().quitSafely(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + this.mHandler.getLooper().quitSafely(); + } else { + this.mHandler.getLooper().quit(); + } this.mHandler = null; } @@ -128,9 +142,7 @@ private void startGenerator() { final Runnable checkLogs = new Runnable() { @Override public void run() { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(me.mContext); - SharedPreferences.Editor e = prefs.edit(); - + Log.e("PDK", "CHECK PHONE LOGS"); boolean approved = false; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { @@ -143,156 +155,172 @@ public void run() { Log.e("PDK", "TODO: Fetch Call Logs..."); - long now = System.currentTimeMillis(); - if (approved) { - long totalIncoming = prefs.getLong(PhoneCalls.LAST_INCOMING_COUNT, 0); - long totalOutgoing = prefs.getLong(PhoneCalls.LAST_OUTGOING_COUNT, 0); - long totalMissed = prefs.getLong(PhoneCalls.LAST_MISSED_COUNT, 0); - long total = prefs.getLong(PhoneCalls.LAST_TOTAL_COUNT, 0); + long lastObserved = 0; - long lastSample = prefs.getLong(PhoneCalls.LAST_SAMPLE, PhoneCalls.LAST_SAMPLE_DEFAULT); + Cursor lastCursor = me.mDatabase.query(PhoneCalls.TABLE_HISTORY, null, null, null, null, null, PhoneCalls.HISTORY_OBSERVED + " DESC"); - String where = CallLog.Calls.DATE + " > ?"; - String[] args = {"" + lastSample}; + if (lastCursor.moveToNext()) { + lastObserved = lastCursor.getLong(lastCursor.getColumnIndex(PhoneCalls.HISTORY_OBSERVED)); + } - long latestCallTimestamp = -1; - String latestCallType = null; - long latestCallDuration = -1; + lastCursor.close(); - Cursor c = me.mContext.getContentResolver().query(CallLog.Calls.CONTENT_URI, null, where, args, CallLog.Calls.DATE); + String where = CallLog.Calls.DATE + " > ?"; + String[] args = {"" + lastObserved}; - while (c.moveToNext()) { - Bundle bundle = new Bundle(); + Cursor c = me.mContext.getContentResolver().query(CallLog.Calls.CONTENT_URI, null, where, args, CallLog.Calls.DATE); - bundle.putLong(PhoneCalls.CALL_DATE_KEY, c.getLong(c.getColumnIndex(CallLog.Calls.DATE))); - bundle.putLong(PhoneCalls.CALL_DURATION_KEY, c.getLong(c.getColumnIndex(CallLog.Calls.DURATION))); - bundle.putString(PhoneCalls.CALL_NUMBER_KEY, c.getString(c.getColumnIndex(CallLog.Calls.NUMBER))); - bundle.putBoolean(PhoneCalls.CALL_IS_NEW_KEY, (c.getInt(c.getColumnIndex(CallLog.Calls.NEW)) != 0)); + if (c != null) { + while (c.moveToNext()) { + ContentValues values = new ContentValues(); + values.put(PhoneCalls.HISTORY_OBSERVED, c.getLong(c.getColumnIndex(CallLog.Calls.DATE))); + values.put(PhoneCalls.HISTORY_DURATION, c.getLong(c.getColumnIndex(CallLog.Calls.DURATION))); + values.put(PhoneCalls.HISTORY_NUMBER, c.getLong(c.getColumnIndex(CallLog.Calls.DURATION))); + values.put(PhoneCalls.HISTORY_IS_NEW, (c.getInt(c.getColumnIndex(CallLog.Calls.NEW)) != 0)); - int features = 0; + Bundle bundle = new Bundle(); + bundle.putLong(PhoneCalls.CALL_DATE_KEY, c.getLong(c.getColumnIndex(CallLog.Calls.DATE))); + bundle.putLong(PhoneCalls.CALL_DURATION_KEY, c.getLong(c.getColumnIndex(CallLog.Calls.DURATION))); + bundle.putString(PhoneCalls.CALL_NUMBER_KEY, c.getString(c.getColumnIndex(CallLog.Calls.NUMBER))); + bundle.putBoolean(PhoneCalls.CALL_IS_NEW_KEY, (c.getInt(c.getColumnIndex(CallLog.Calls.NEW)) != 0)); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - features = c.getInt(c.getColumnIndex(CallLog.Calls.FEATURES)); - } + int features = 0; - int typeInt = c.getInt(c.getColumnIndex(CallLog.Calls.TYPE)); - String type = PhoneCalls.CALL_TYPE_UNKNOWN; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + features = c.getInt(c.getColumnIndex(CallLog.Calls.FEATURES)); + } - switch(Build.VERSION.SDK_INT) { - case 25: - if (typeInt == CallLog.Calls.ANSWERED_EXTERNALLY_TYPE) { - type = PhoneCalls.CALL_TYPE_ANSWERED_EXTERNALLY; - } + int typeInt = c.getInt(c.getColumnIndex(CallLog.Calls.TYPE)); + String type = PhoneCalls.CALL_TYPE_UNKNOWN; + + switch (Build.VERSION.SDK_INT) { + case 25: + if (typeInt == CallLog.Calls.ANSWERED_EXTERNALLY_TYPE) { + type = PhoneCalls.CALL_TYPE_ANSWERED_EXTERNALLY; + } + + bundle.putBoolean(PhoneCalls.CALL_PULLED_EXTERNALLY_KEY, ((features & CallLog.Calls.FEATURES_PULLED_EXTERNALLY) == CallLog.Calls.FEATURES_PULLED_EXTERNALLY)); + + values.put(PhoneCalls.HISTORY_PULLED_EXTERNALLY, ((features & CallLog.Calls.FEATURES_PULLED_EXTERNALLY) == CallLog.Calls.FEATURES_PULLED_EXTERNALLY)); + case 24: + if (typeInt == CallLog.Calls.REJECTED_TYPE) { + type = PhoneCalls.CALL_TYPE_REJECTED; + } else if (typeInt == CallLog.Calls.BLOCKED_TYPE) { + type = PhoneCalls.CALL_TYPE_BLOCKED; + } + + bundle.putString(PhoneCalls.CALL_POST_DIAL_DIGITS_KEY, c.getString(c.getColumnIndex(CallLog.Calls.POST_DIAL_DIGITS))); + bundle.putString(PhoneCalls.CALL_VIA_NUMBER_KEY, c.getString(c.getColumnIndex(CallLog.Calls.VIA_NUMBER))); + + values.put(PhoneCalls.HISTORY_POST_DIAL_DIGITS, c.getString(c.getColumnIndex(CallLog.Calls.POST_DIAL_DIGITS))); + values.put(PhoneCalls.HISTORY_VIA_NUMBER, c.getString(c.getColumnIndex(CallLog.Calls.VIA_NUMBER))); + case 21: + if (typeInt == CallLog.Calls.VOICEMAIL_TYPE) { + type = PhoneCalls.CALL_TYPE_VOICEMAIL; + } + + bundle.putString(PhoneCalls.CALL_COUNTRY_ISO_KEY, c.getString(c.getColumnIndex(CallLog.Calls.COUNTRY_ISO))); + bundle.putLong(PhoneCalls.CALL_DATA_USAGE_KEY, c.getLong(c.getColumnIndex(CallLog.Calls.DATA_USAGE))); + bundle.putString(PhoneCalls.CALL_GEOCODED_LOCATION_KEY, c.getString(c.getColumnIndex(CallLog.Calls.GEOCODED_LOCATION))); + bundle.putBoolean(PhoneCalls.CALL_VIDEO_KEY, ((features & CallLog.Calls.FEATURES_VIDEO) == CallLog.Calls.FEATURES_VIDEO)); + + values.put(PhoneCalls.HISTORY_COUNTRY_ISO, c.getString(c.getColumnIndex(CallLog.Calls.COUNTRY_ISO))); + values.put(PhoneCalls.HISTORY_DATA_USAGE, c.getLong(c.getColumnIndex(CallLog.Calls.DATA_USAGE))); + values.put(PhoneCalls.HISTORY_GEOCODED_LOCATION, c.getString(c.getColumnIndex(CallLog.Calls.GEOCODED_LOCATION))); + values.put(PhoneCalls.HISTORY_VIDEO, ((features & CallLog.Calls.FEATURES_VIDEO) == CallLog.Calls.FEATURES_VIDEO)); - bundle.putBoolean(PhoneCalls.CALL_PULLED_EXTERNALLY_KEY, ((features & CallLog.Calls.FEATURES_PULLED_EXTERNALLY) == CallLog.Calls.FEATURES_PULLED_EXTERNALLY)); - case 24: - if (typeInt == CallLog.Calls.REJECTED_TYPE) { - type = PhoneCalls.CALL_TYPE_REJECTED; - } else if (typeInt == CallLog.Calls.BLOCKED_TYPE) { - type = PhoneCalls.CALL_TYPE_BLOCKED; - } - - bundle.putString(PhoneCalls.CALL_POST_DIAL_DIGITS_KEY, c.getString(c.getColumnIndex(CallLog.Calls.POST_DIAL_DIGITS))); - bundle.putString(PhoneCalls.CALL_VIA_NUMBER_KEY, c.getString(c.getColumnIndex(CallLog.Calls.VIA_NUMBER))); - case 21: - if (typeInt == CallLog.Calls.VOICEMAIL_TYPE) { - type = PhoneCalls.CALL_TYPE_VOICEMAIL; - } +// bundle.putString(PhoneCalls.CALL_TRANSCRIPTION_KEY, c.getString(c.getColumnIndex(CallLog.Calls.TRANSCRIPTION))); + case 19: + switch (c.getInt(c.getColumnIndex(CallLog.Calls.NUMBER_PRESENTATION))) { + case CallLog.Calls.PRESENTATION_ALLOWED: + bundle.putString(PhoneCalls.CALL_PRESENTATION_KEY, PhoneCalls.CALL_PRESENTATION_ALLOWED); + values.put(PhoneCalls.HISTORY_PRESENTATION, PhoneCalls.CALL_PRESENTATION_ALLOWED); + break; + case CallLog.Calls.PRESENTATION_RESTRICTED: + bundle.putString(PhoneCalls.CALL_PRESENTATION_KEY, PhoneCalls.CALL_PRESENTATION_RESTRICTED); + values.put(PhoneCalls.HISTORY_PRESENTATION, PhoneCalls.CALL_PRESENTATION_RESTRICTED); + break; + case CallLog.Calls.PRESENTATION_PAYPHONE: + bundle.putString(PhoneCalls.CALL_PRESENTATION_KEY, PhoneCalls.CALL_PRESENTATION_PAYPHONE); + values.put(PhoneCalls.HISTORY_PRESENTATION, PhoneCalls.CALL_PRESENTATION_PAYPHONE); + break; + case CallLog.Calls.PRESENTATION_UNKNOWN: + bundle.putString(PhoneCalls.CALL_PRESENTATION_KEY, PhoneCalls.CALL_PRESENTATION_UNKNOWN); + values.put(PhoneCalls.HISTORY_PRESENTATION, PhoneCalls.CALL_PRESENTATION_UNKNOWN); + break; + } + case 14: + bundle.putBoolean(PhoneCalls.CALL_IS_READ_KEY, (c.getInt(c.getColumnIndex(CallLog.Calls.IS_READ)) != 0)); + values.put(PhoneCalls.HISTORY_IS_READ, (c.getInt(c.getColumnIndex(CallLog.Calls.IS_READ)) != 0)); + case 1: + if (typeInt == CallLog.Calls.INCOMING_TYPE) { + type = PhoneCalls.CALL_TYPE_INCOMING; + } else if (typeInt == CallLog.Calls.OUTGOING_TYPE) { + type = PhoneCalls.CALL_TYPE_OUTGOING; + } else if (typeInt == CallLog.Calls.MISSED_TYPE) { + type = PhoneCalls.CALL_TYPE_MISSED; + } + } - bundle.putString(PhoneCalls.CALL_COUNTRY_ISO_KEY, c.getString(c.getColumnIndex(CallLog.Calls.COUNTRY_ISO))); - bundle.putLong(PhoneCalls.CALL_DATA_USAGE_KEY, c.getLong(c.getColumnIndex(CallLog.Calls.DATA_USAGE))); - bundle.putString(PhoneCalls.CALL_GEOCODED_LOCATION_KEY, c.getString(c.getColumnIndex(CallLog.Calls.GEOCODED_LOCATION))); + bundle.putString(PhoneCalls.CALL_TYPE_KEY, type); + values.put(PhoneCalls.HISTORY_CALL_TYPE, type); - bundle.putBoolean(PhoneCalls.CALL_VIDEO_KEY, ((features & CallLog.Calls.FEATURES_VIDEO) == CallLog.Calls.FEATURES_VIDEO)); + String[] sensitiveFields = { + PhoneCalls.CALL_NUMBER_KEY, + PhoneCalls.CALL_POST_DIAL_DIGITS_KEY, + PhoneCalls.CALL_VIA_NUMBER_KEY, + }; -// bundle.putString(PhoneCalls.CALL_TRANSCRIPTION_KEY, c.getString(c.getColumnIndex(CallLog.Calls.TRANSCRIPTION))); - case 19: - switch (c.getInt(c.getColumnIndex(CallLog.Calls.NUMBER_PRESENTATION))) { - case CallLog.Calls.PRESENTATION_ALLOWED: - bundle.putString(PhoneCalls.CALL_PRESENTATION_KEY, PhoneCalls.CALL_PRESENTATION_ALLOWED); - break; - case CallLog.Calls.PRESENTATION_RESTRICTED: - bundle.putString(PhoneCalls.CALL_PRESENTATION_KEY, PhoneCalls.CALL_PRESENTATION_RESTRICTED); - break; - case CallLog.Calls.PRESENTATION_PAYPHONE: - bundle.putString(PhoneCalls.CALL_PRESENTATION_KEY, PhoneCalls.CALL_PRESENTATION_PAYPHONE); - break; - case CallLog.Calls.PRESENTATION_UNKNOWN: - bundle.putString(PhoneCalls.CALL_PRESENTATION_KEY, PhoneCalls.CALL_PRESENTATION_UNKNOWN); - break; + for (String field : sensitiveFields) { + if (bundle.containsKey(field)) { + bundle.putString(field, new String(Hex.encodeHex(DigestUtils.sha256(bundle.getString(field))))); } - case 14: - bundle.putBoolean(PhoneCalls.CALL_IS_READ_KEY, (c.getInt(c.getColumnIndex(CallLog.Calls.IS_READ)) != 0)); - case 1: - if (typeInt == CallLog.Calls.INCOMING_TYPE) { - type = PhoneCalls.CALL_TYPE_INCOMING; - totalIncoming += 1; - } else if (typeInt == CallLog.Calls.OUTGOING_TYPE) { - type = PhoneCalls.CALL_TYPE_OUTGOING; - totalOutgoing += 1; - } else if (typeInt == CallLog.Calls.MISSED_TYPE) { - type = PhoneCalls.CALL_TYPE_MISSED; - totalMissed += 1; - } - } - - bundle.putString(PhoneCalls.CALL_TYPE_KEY, type); + } - if (bundle.getLong(PhoneCalls.CALL_DATE_KEY, 0) > latestCallTimestamp) { - latestCallTimestamp = bundle.getLong(PhoneCalls.CALL_DATE_KEY, 0); - latestCallDuration = bundle.getLong(PhoneCalls.CALL_DURATION_KEY, 0); - latestCallType = type; - } + String[] valueSensitiveFields = { + PhoneCalls.HISTORY_NUMBER, + PhoneCalls.HISTORY_POST_DIAL_DIGITS, + PhoneCalls.HISTORY_VIA_NUMBER, + }; - Log.e("PDK", "------"); - for (int i = 0; i < c.getColumnCount(); i++) { - Log.e("PDK", "CALL LOG: " + c.getColumnName(i) + " --> " + c.getString(i)); - } + for (String field : valueSensitiveFields) { + if (values.containsKey(field)) { + values.put(field, new String(Hex.encodeHex(DigestUtils.sha256(values.getAsString(field))))); + } + } - String[] sensitiveFields = { - PhoneCalls.CALL_NUMBER_KEY, - PhoneCalls.CALL_POST_DIAL_DIGITS_KEY, - PhoneCalls.CALL_VIA_NUMBER_KEY - }; + me.mDatabase.insert(PhoneCalls.TABLE_HISTORY, null, values); - for (String field : sensitiveFields) { - if (bundle.containsKey(field)) { - bundle.putString(field, new String(Hex.encodeHex(DigestUtils.sha256(bundle.getString(field))))); - } + Generators.getInstance(me.mContext).notifyGeneratorUpdated(PhoneCalls.GENERATOR_IDENTIFIER, bundle); } - Generators.getInstance(me.mContext).transmitData(PhoneCalls.GENERATOR_IDENTIFIER, bundle); + c.close(); } + } - Log.e("PDK", "------"); + if (me.mHandler != null) { + me.mHandler.postDelayed(this, me.mSampleInterval); + } + } + }; - total += c.getCount(); + File path = PassiveDataKit.getGeneratorsStorage(this.mContext); - c.close(); + path = new File(path, PhoneCalls.DATABASE_PATH); - if (latestCallType != null) { - e.putLong(PhoneCalls.LAST_CALL_TIMESTAMP, latestCallTimestamp); - e.putLong(PhoneCalls.LAST_CALL_DURATION, latestCallDuration); - e.putString(PhoneCalls.LAST_CALL_TYPE, latestCallType); - } + this.mDatabase = SQLiteDatabase.openOrCreateDatabase(path, null); - e.putLong(PhoneCalls.LAST_INCOMING_COUNT, totalIncoming); - e.putLong(PhoneCalls.LAST_OUTGOING_COUNT, totalOutgoing); - e.putLong(PhoneCalls.LAST_MISSED_COUNT, totalMissed); - e.putLong(PhoneCalls.LAST_TOTAL_COUNT, total); + int version = this.getDatabaseVersion(this.mDatabase); - e.putLong(PhoneCalls.LAST_SAMPLE, now); - e.apply(); - } - - long sampleInterval = prefs.getLong(PhoneCalls.SAMPLE_INTERVAL, PhoneCalls.SAMPLE_INTERVAL_DEFAULT); + switch (version) { + case 0: + this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_phone_calls_create_history_table)); + case 1: + this.mDatabase.delete(PhoneCalls.TABLE_HISTORY, null, null); + } - if (me.mHandler != null) { - me.mHandler.postDelayed(this, sampleInterval); - } - } - }; + this.setDatabaseVersion(this.mDatabase, PhoneCalls.DATABASE_VERSION); Runnable r = new Runnable() { @Override @@ -340,7 +368,7 @@ public static ArrayList diagnostics(final Context context) { if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CALL_LOG) != PackageManager.PERMISSION_GRANTED){ final Handler handler = new Handler(Looper.getMainLooper()); - actions.add(new DiagnosticAction(context.getString(R.string.diagnostic_call_log_permission_required), new Runnable() { + actions.add(new DiagnosticAction(context.getString(R.string.diagnostic_call_log_permission_required_title), context.getString(R.string.diagnostic_call_log_permission_required), new Runnable() { @Override public void run() { @@ -363,92 +391,136 @@ public void run() { return actions; } - public static void bindViewHolder(DataPointViewHolder holder, final Bundle dataPoint) { + public static void bindViewHolder(DataPointViewHolder holder) { final Context context = holder.itemView.getContext(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - double timestamp = dataPoint.getBundle(Generator.PDK_METADATA).getDouble(Generator.TIMESTAMP); + long lastTimestamp = 0; + long lastDuration = 0; + String callType = null; - TextView dateLabel = (TextView) holder.itemView.findViewById(R.id.generator_data_point_date); + long totalIncoming = 0; + long totalOutgoing = 0; + long totalMissed = 0; + long total = 0; - dateLabel.setText(Generator.formatTimestamp(context, timestamp)); + PhoneCalls generator = PhoneCalls.getInstance(holder.itemView.getContext()); - PieChart pieChart = (PieChart) holder.itemView.findViewById(R.id.chart_phone_calls); - pieChart.getLegend().setEnabled(false); + Cursor c = generator.mDatabase.query(PhoneCalls.TABLE_HISTORY, null, null, null, null, null, PhoneCalls.HISTORY_OBSERVED + " DESC"); - pieChart.setEntryLabelColor(android.R.color.transparent); - pieChart.getDescription().setEnabled(false); - pieChart.setDrawHoleEnabled(false); + while (c.moveToNext()) { + if (lastTimestamp == 0) { + lastTimestamp = c.getLong(c.getColumnIndex(PhoneCalls.HISTORY_OBSERVED)); + lastDuration = c.getLong(c.getColumnIndex(PhoneCalls.HISTORY_DURATION)); + } - long totalIncoming = prefs.getLong(PhoneCalls.LAST_INCOMING_COUNT, 0); - long totalOutgoing = prefs.getLong(PhoneCalls.LAST_OUTGOING_COUNT, 0); - long totalMissed = prefs.getLong(PhoneCalls.LAST_MISSED_COUNT, 0); - long total = prefs.getLong(PhoneCalls.LAST_TOTAL_COUNT, 0); + total += 1; - List entries = new ArrayList<>(); + String type = c.getString(c.getColumnIndex(PhoneCalls.HISTORY_CALL_TYPE)); - if (totalIncoming > 0) { - entries.add(new PieEntry(totalIncoming, context.getString(R.string.generator_phone_calls_incoming_label))); - } + if (PhoneCalls.CALL_TYPE_INCOMING.equals(type)) { + totalIncoming += 1; + } else if (PhoneCalls.CALL_TYPE_OUTGOING.equals(type)) { + totalOutgoing += 1; + } else if (PhoneCalls.CALL_TYPE_MISSED.equals(type)) { + totalOutgoing += 1; + } - if (totalOutgoing > 0) { - entries.add(new PieEntry(totalOutgoing, context.getString(R.string.generator_phone_calls_outgoing_label))); + if (callType == null) { + callType = type; + } } - if (totalMissed > 0) { - entries.add(new PieEntry(totalMissed, context.getString(R.string.generator_phone_calls_missed_label))); - } + c.close(); - long other = total - (totalIncoming + totalOutgoing + totalMissed); + View cardContent = holder.itemView.findViewById(R.id.card_content); + View cardEmpty = holder.itemView.findViewById(R.id.card_empty); + TextView dateLabel = (TextView) holder.itemView.findViewById(R.id.generator_data_point_date); - if (other > 0) { - entries.add(new PieEntry(other, context.getString(R.string.generator_phone_calls_other_label))); - } + if (total > 0) { + cardContent.setVisibility(View.VISIBLE); + cardEmpty.setVisibility(View.GONE); - PieDataSet set = new PieDataSet(entries, " "); + dateLabel.setText(Generator.formatTimestamp(context, lastTimestamp)); - int[] colors = { - R.color.generator_phone_call_incoming, - R.color.generator_phone_call_outgoing, - R.color.generator_phone_call_missed, - R.color.generator_phone_call_other - }; + PieChart pieChart = (PieChart) holder.itemView.findViewById(R.id.chart_phone_calls); + pieChart.getLegend().setEnabled(false); - set.setColors(colors, context); + pieChart.setEntryLabelColor(android.R.color.transparent); + pieChart.getDescription().setEnabled(false); + pieChart.setDrawHoleEnabled(false); - PieData data = new PieData(set); - data.setValueTextSize(14); - data.setValueTypeface(Typeface.DEFAULT_BOLD); - data.setValueTextColor(0xffffffff); + List entries = new ArrayList<>(); - data.setValueFormatter(new IValueFormatter() { - @Override - public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) { - return "" + ((Float) value).intValue(); + if (totalIncoming > 0) { + entries.add(new PieEntry(totalIncoming, context.getString(R.string.generator_phone_calls_incoming_label))); + } + + if (totalOutgoing > 0) { + entries.add(new PieEntry(totalOutgoing, context.getString(R.string.generator_phone_calls_outgoing_label))); + } + + if (totalMissed > 0) { + entries.add(new PieEntry(totalMissed, context.getString(R.string.generator_phone_calls_missed_label))); } - }); - pieChart.setData(data); - pieChart.invalidate(); + long other = total - (totalIncoming + totalOutgoing + totalMissed); + + if (other > 0) { + entries.add(new PieEntry(other, context.getString(R.string.generator_phone_calls_other_label))); + } + + PieDataSet set = new PieDataSet(entries, " "); + + int[] colors = { + R.color.generator_phone_call_incoming, + R.color.generator_phone_call_outgoing, + R.color.generator_phone_call_missed, + R.color.generator_phone_call_other + }; + + set.setColors(colors, context); + + PieData data = new PieData(set); + data.setValueTextSize(14); + data.setValueTypeface(Typeface.DEFAULT_BOLD); + data.setValueTextColor(0xffffffff); + + data.setValueFormatter(new IValueFormatter() { + @Override + public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) { + return "" + ((Float) value).intValue(); + } + }); + + pieChart.setData(data); + pieChart.invalidate(); - long latest = prefs.getLong(PhoneCalls.LAST_CALL_TIMESTAMP, 0); - long duration = prefs.getLong(PhoneCalls.LAST_CALL_DURATION, 0); - String direction = prefs.getString(PhoneCalls.LAST_CALL_TYPE, ""); + TextView latestField = (TextView) holder.itemView.findViewById(R.id.field_latest_call); + TextView durationField = (TextView) holder.itemView.findViewById(R.id.field_duration); + TextView directionField = (TextView) holder.itemView.findViewById(R.id.field_direction); - TextView latestField = (TextView) holder.itemView.findViewById(R.id.field_latest_call); - TextView durationField = (TextView) holder.itemView.findViewById(R.id.field_duration); - TextView directionField = (TextView) holder.itemView.findViewById(R.id.field_direction); + Date lateDate = new Date(lastTimestamp); + String day = android.text.format.DateFormat.getMediumDateFormat(context).format(lateDate); + String time = android.text.format.DateFormat.getTimeFormat(context).format(lateDate); - Date lateDate = new Date(latest); - String day = android.text.format.DateFormat.getMediumDateFormat(context).format(lateDate); - String time = android.text.format.DateFormat.getTimeFormat(context).format(lateDate); + latestField.setText(context.getString(R.string.format_full_timestamp_pdk, day, time)); + durationField.setText(context.getString(R.string.generator_phone_calls_duration_format, ((float) lastDuration) / 60)); + directionField.setText(callType); - latestField.setText(context.getString(R.string.format_full_timestamp_pdk, day, time)); - durationField.setText(context.getString(R.string.generator_phone_calls_duration_format, ((float) duration) / 60)); - directionField.setText(direction); + dateLabel.setText(Generator.formatTimestamp(context, lastTimestamp / 1000)); + } else { + cardContent.setVisibility(View.GONE); + cardEmpty.setVisibility(View.VISIBLE); - dateLabel.setText(Generator.formatTimestamp(context, latest / 1000)); + dateLabel.setText(R.string.label_never_pdk); + } + } + + @Override + public List fetchPayloads() { + return new ArrayList<>(); } public static View fetchView(ViewGroup parent) @@ -456,8 +528,19 @@ public static View fetchView(ViewGroup parent) return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_phone_calls, parent, false); } - public static void broadcastLatestDataPoint(Context context) { - Generators.getInstance(context).transmitData(PhoneCalls.GENERATOR_IDENTIFIER, new Bundle()); - } + public static long latestPointGenerated(Context context) { + long timestamp = 0; + + PhoneCalls me = PhoneCalls.getInstance(context); + Cursor c = me.mDatabase.query(PhoneCalls.TABLE_HISTORY, null, null, null, null, null, PhoneCalls.HISTORY_OBSERVED + " DESC"); + + if (c.moveToNext()) { + timestamp = c.getLong(c.getColumnIndex(PhoneCalls.HISTORY_OBSERVED)); + } + + c.close(); + + return timestamp; + } } diff --git a/src/com/audacious_software/passive_data_kit/generators/communication/TextMessages.java b/src/com/audacious_software/passive_data_kit/generators/communication/TextMessages.java new file mode 100755 index 0000000..f82e641 --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/generators/communication/TextMessages.java @@ -0,0 +1,473 @@ +package com.audacious_software.passive_data_kit.generators.communication; + +import android.Manifest; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.graphics.Typeface; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.preference.PreferenceManager; +import android.support.v4.content.ContextCompat; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.audacious_software.passive_data_kit.PassiveDataKit; +import com.audacious_software.passive_data_kit.PhoneUtililties; +import com.audacious_software.passive_data_kit.activities.generators.DataPointViewHolder; +import com.audacious_software.passive_data_kit.activities.generators.RequestPermissionActivity; +import com.audacious_software.passive_data_kit.diagnostics.DiagnosticAction; +import com.audacious_software.passive_data_kit.generators.Generator; +import com.audacious_software.passive_data_kit.generators.Generators; +import com.audacious_software.pdk.passivedatakit.R; +import com.github.mikephil.charting.charts.PieChart; +import com.github.mikephil.charting.data.Entry; +import com.github.mikephil.charting.data.PieData; +import com.github.mikephil.charting.data.PieDataSet; +import com.github.mikephil.charting.data.PieEntry; +import com.github.mikephil.charting.formatter.IValueFormatter; +import com.github.mikephil.charting.utils.ViewPortHandler; + +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.codec.digest.DigestUtils; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +public class TextMessages extends Generator { + private static final String GENERATOR_IDENTIFIER = "pdk-text-messages"; + + private static final String ENABLED = "com.audacious_software.passive_data_kit.generators.communication.TextMessages.ENABLED"; + private static final boolean ENABLED_DEFAULT = true; + + private static final Uri SMS_INBOX_URI = Uri.parse("content://sms/inbox"); + private static final Uri SMS_SENT_URI = Uri.parse("content://sms/sent"); + + private static final String SMS_DATE = "date"; + private static final String SMS_BODY = "body"; + private static final String SMS_NUMBER_NAME = "person"; + private static final String SMS_NUMBER = "address"; + private static final String SMS_LENGTH = "length"; + private static final String SMS_DIRECTION = "direction"; + + private static int DATABASE_VERSION = 1; + + private static final String TABLE_HISTORY = "history"; + private static final String HISTORY_OBSERVED = "observed"; + private static final String HISTORY_DIRECTION = "direction"; + private static final String HISTORY_LENGTH = "length"; + private static final String HISTORY_BODY = "body"; + private static final String HISTORY_NUMBER_NAME = "number_name"; + private static final String HISTORY_NUMBER = "number"; + private static final String HISTORY_DIRECTION_INCOMING = "incoming"; + private static final String HISTORY_DIRECTION_OUTGOING = "outgoing"; + + private static TextMessages sInstance = null; + + private Handler mHandler = null; + private Context mContext = null; + + private static final String DATABASE_PATH = "pdk-text-messages.sqlite"; + + private SQLiteDatabase mDatabase = null; + private long mSampleInterval = 60000; + + public static TextMessages getInstance(Context context) { + if (TextMessages.sInstance == null) { + TextMessages.sInstance = new TextMessages(context.getApplicationContext()); + } + + return TextMessages.sInstance; + } + + public TextMessages(Context context) { + super(context); + + this.mContext = context.getApplicationContext(); + } + + public static void start(final Context context) { + TextMessages.getInstance(context).startGenerator(); + } + + private void startGenerator() { + final TextMessages me = this; + + if (this.mHandler != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + this.mHandler.getLooper().quitSafely(); + } else { + this.mHandler.getLooper().quit(); + } + + this.mHandler = null; + } + + final Runnable checkLogs = new Runnable() { + @Override + public void run() { + + Log.e("PDK", "CHECK TEXT LOGS"); + + boolean approved = false; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (ContextCompat.checkSelfPermission(me.mContext, Manifest.permission.READ_SMS) == PackageManager.PERMISSION_GRANTED){ + approved = true; + } + } else { + approved = true; + } + + if (approved) { + long lastObserved = 0; + + Cursor lastCursor = me.mDatabase.query(TextMessages.TABLE_HISTORY, null, null, null, null, null, TextMessages.HISTORY_OBSERVED + " DESC"); + + if (lastCursor.moveToNext()) { + lastObserved = lastCursor.getLong(lastCursor.getColumnIndex(TextMessages.HISTORY_OBSERVED)); + } + + lastCursor.close(); + + ArrayList toTransmit = new ArrayList<>(); + + String where = TextMessages.SMS_DATE + " > ?"; + String[] args = {"" + lastObserved}; + + Cursor c = me.mContext.getContentResolver().query(TextMessages.SMS_INBOX_URI, null, where, args, TextMessages.SMS_DATE); + + while (c.moveToNext()) { + ContentValues values = new ContentValues(); + values.put(TextMessages.HISTORY_OBSERVED, c.getLong(c.getColumnIndex(TextMessages.SMS_DATE))); + + String body = c.getString(c.getColumnIndex(TextMessages.SMS_BODY)); + + values.put(TextMessages.HISTORY_LENGTH, body.length()); + values.put(TextMessages.HISTORY_BODY, body); + + String name = c.getString(c.getColumnIndex(TextMessages.SMS_NUMBER_NAME)); + String number = c.getString(c.getColumnIndex(TextMessages.SMS_NUMBER)); + + if (name == null) { + name = number; + } + + values.put(TextMessages.HISTORY_NUMBER_NAME, name); + values.put(TextMessages.HISTORY_NUMBER, number); + + values.put(TextMessages.HISTORY_DIRECTION, TextMessages.HISTORY_DIRECTION_INCOMING); + + toTransmit.add(values); + } + + c.close(); + + c = me.mContext.getContentResolver().query(TextMessages.SMS_SENT_URI, null, where, args, TextMessages.SMS_DATE); + + while (c.moveToNext()) { + ContentValues values = new ContentValues(); + values.put(TextMessages.HISTORY_OBSERVED, c.getLong(c.getColumnIndex(TextMessages.SMS_DATE))); + + String body = c.getString(c.getColumnIndex(TextMessages.SMS_BODY)); + + values.put(TextMessages.HISTORY_LENGTH, body.length()); + values.put(TextMessages.HISTORY_BODY, body); + + String name = c.getString(c.getColumnIndex(TextMessages.SMS_NUMBER_NAME)); + String number = c.getString(c.getColumnIndex(TextMessages.SMS_NUMBER)); + + if (name == null) { + name = number; + } + + values.put(TextMessages.HISTORY_NUMBER_NAME, name); + values.put(TextMessages.HISTORY_NUMBER, number); + + values.put(TextMessages.HISTORY_DIRECTION, TextMessages.HISTORY_DIRECTION_OUTGOING); + + toTransmit.add(values); + } + + c.close(); + + Collections.sort(toTransmit, new Comparator() { + @Override + public int compare(ContentValues one, ContentValues two) { + Long oneTime = one.getAsLong(TextMessages.HISTORY_OBSERVED); + Long twoTime = two.getAsLong(TextMessages.HISTORY_OBSERVED); + + return oneTime.compareTo(twoTime); + } + }); + + for (ContentValues values : toTransmit) { + String[] sensitiveFields = { + TextMessages.HISTORY_NUMBER_NAME, + TextMessages.HISTORY_NUMBER, + TextMessages.HISTORY_BODY, + }; + + for (String field : sensitiveFields) { + if (values.containsKey(field)) { + String value = values.getAsString(field); + + if (TextMessages.HISTORY_NUMBER.equals(TextMessages.HISTORY_NUMBER)) { + value = PhoneUtililties.normalizedPhoneNumber(value); + } + + values.put(field, new String(Hex.encodeHex(DigestUtils.sha256(value)))); + } + } + + Bundle bundle = new Bundle(); + bundle.putLong(TextMessages.SMS_DATE, values.getAsLong(TextMessages.HISTORY_OBSERVED)); + bundle.putInt(TextMessages.SMS_LENGTH, values.getAsInteger(TextMessages.HISTORY_LENGTH)); + bundle.putString(TextMessages.SMS_NUMBER_NAME, values.getAsString(TextMessages.HISTORY_NUMBER_NAME)); + bundle.putString(TextMessages.SMS_NUMBER, values.getAsString(TextMessages.HISTORY_NUMBER)); + bundle.putString(TextMessages.SMS_DIRECTION, values.getAsString(TextMessages.HISTORY_DIRECTION)); + bundle.putString(TextMessages.SMS_BODY, values.getAsString(TextMessages.HISTORY_BODY)); + + me.mDatabase.insert(TextMessages.TABLE_HISTORY, null, values); + + Generators.getInstance(me.mContext).notifyGeneratorUpdated(TextMessages.GENERATOR_IDENTIFIER, bundle); + } + } + + if (me.mHandler != null) { + me.mHandler.postDelayed(this, me.mSampleInterval); + } + } + }; + + File path = PassiveDataKit.getGeneratorsStorage(this.mContext); + + path = new File(path, TextMessages.DATABASE_PATH); + + this.mDatabase = SQLiteDatabase.openOrCreateDatabase(path, null); + + int version = this.getDatabaseVersion(this.mDatabase); + + switch (version) { + case 0: + this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_text_messages_create_history_table)); + } + + this.setDatabaseVersion(this.mDatabase, TextMessages.DATABASE_VERSION); + + Runnable r = new Runnable() { + @Override + public void run() { + Looper.prepare(); + + me.mHandler = new Handler(); + + Looper.loop(); + } + }; + + Thread t = new Thread(r); + t.start(); + + try { + Thread.sleep(50); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + me.mHandler.post(checkLogs); + + Generators.getInstance(this.mContext).registerCustomViewClass(TextMessages.GENERATOR_IDENTIFIER, TextMessages.class); + } + + public static boolean isEnabled(Context context) { + SharedPreferences prefs = Generators.getInstance(context).getSharedPreferences(context); + + return prefs.getBoolean(TextMessages.ENABLED, TextMessages.ENABLED_DEFAULT); + } + + public static boolean isRunning(Context context) { + if (TextMessages.sInstance == null) { + return false; + } + + return TextMessages.sInstance.mHandler != null; + } + + public static ArrayList diagnostics(final Context context) { + ArrayList actions = new ArrayList<>(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_SMS) != PackageManager.PERMISSION_GRANTED){ + final Handler handler = new Handler(Looper.getMainLooper()); + + actions.add(new DiagnosticAction(context.getString(R.string.diagnostic_sms_log_permission_required_title), context.getString(R.string.diagnostic_sms_log_permission_required), new Runnable() { + + @Override + public void run() { + handler.post(new Runnable() { + + @Override + public void run() { + Intent intent = new Intent(context, RequestPermissionActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(RequestPermissionActivity.PERMISSION, Manifest.permission.READ_SMS); + + context.startActivity(intent); + } + }); + } + })); + } + } + + return actions; + } + + public static void bindViewHolder(DataPointViewHolder holder) { + final Context context = holder.itemView.getContext(); + + long lastTimestamp = 0; + int lastLength = 0; + + long totalIncoming = 0; + long totalOutgoing = 0; + long total = 0; + + TextMessages generator = TextMessages.getInstance(holder.itemView.getContext()); + String lastDirection = null; + + Cursor c = generator.mDatabase.query(TextMessages.TABLE_HISTORY, null, null, null, null, null, TextMessages.HISTORY_OBSERVED + " DESC"); + + while (c.moveToNext()) { + if (lastTimestamp == 0) { + lastTimestamp = c.getLong(c.getColumnIndex(TextMessages.HISTORY_OBSERVED)); + lastDirection = c.getString(c.getColumnIndex(TextMessages.HISTORY_DIRECTION)); + lastLength = c.getInt(c.getColumnIndex(TextMessages.HISTORY_LENGTH)); + } + + total += 1; + + String direction = c.getString(c.getColumnIndex(TextMessages.HISTORY_DIRECTION)); + + if (TextMessages.HISTORY_DIRECTION_INCOMING.equals(direction)) { + totalIncoming += 1; + } else if (TextMessages.HISTORY_DIRECTION_OUTGOING.equals(direction)) { + totalOutgoing += 1; + } + } + + c.close(); + + View cardContent = holder.itemView.findViewById(R.id.card_content); + View cardEmpty = holder.itemView.findViewById(R.id.card_empty); + TextView dateLabel = (TextView) holder.itemView.findViewById(R.id.generator_data_point_date); + + if (total > 0) { + cardContent.setVisibility(View.VISIBLE); + cardEmpty.setVisibility(View.GONE); + + dateLabel.setText(Generator.formatTimestamp(context, lastTimestamp)); + + PieChart pieChart = (PieChart) holder.itemView.findViewById(R.id.chart_text_messages); + pieChart.getLegend().setEnabled(false); + + pieChart.setEntryLabelColor(android.R.color.transparent); + pieChart.getDescription().setEnabled(false); + pieChart.setDrawHoleEnabled(false); + + List entries = new ArrayList<>(); + + if (totalIncoming > 0) { + entries.add(new PieEntry(totalIncoming, context.getString(R.string.generator_text_messages_incoming_label))); + } + + if (totalOutgoing > 0) { + entries.add(new PieEntry(totalOutgoing, context.getString(R.string.generator_text_messages_outgoing_label))); + } + + PieDataSet set = new PieDataSet(entries, " "); + + int[] colors = { + R.color.generator_text_messages_incoming, + R.color.generator_text_messages_outgoing + }; + + set.setColors(colors, context); + + PieData data = new PieData(set); + data.setValueTextSize(14); + data.setValueTypeface(Typeface.DEFAULT_BOLD); + data.setValueTextColor(0xffffffff); + + data.setValueFormatter(new IValueFormatter() { + @Override + public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) { + return "" + ((Float) value).intValue(); + } + }); + + pieChart.setData(data); + pieChart.invalidate(); + + TextView latestField = (TextView) holder.itemView.findViewById(R.id.field_latest_text_message); + TextView lengthField = (TextView) holder.itemView.findViewById(R.id.field_length); + TextView directionField = (TextView) holder.itemView.findViewById(R.id.field_direction); + + Date lateDate = new Date(lastTimestamp); + String day = android.text.format.DateFormat.getMediumDateFormat(context).format(lateDate); + String time = android.text.format.DateFormat.getTimeFormat(context).format(lateDate); + + latestField.setText(context.getString(R.string.format_full_timestamp_pdk, day, time)); + lengthField.setText(context.getString(R.string.generator_text_messages_length_format, lastLength)); + directionField.setText(lastDirection); + + dateLabel.setText(Generator.formatTimestamp(context, lastTimestamp / 1000)); + } else { + cardContent.setVisibility(View.GONE); + cardEmpty.setVisibility(View.VISIBLE); + + dateLabel.setText(R.string.label_never_pdk); + } + } + + @Override + public List fetchPayloads() { + return new ArrayList<>(); + } + + public static View fetchView(ViewGroup parent) + { + return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_text_messages, parent, false); + } + + public static long latestPointGenerated(Context context) { + long timestamp = 0; + + TextMessages me = TextMessages.getInstance(context); + + Cursor c = me.mDatabase.query(TextMessages.TABLE_HISTORY, null, null, null, null, null, TextMessages.HISTORY_OBSERVED + " DESC"); + + if (c.moveToNext()) { + timestamp = c.getLong(c.getColumnIndex(TextMessages.HISTORY_OBSERVED)); + } + + c.close(); + + return timestamp; + } +} diff --git a/src/com/audacious_software/passive_data_kit/generators/device/Location.java b/src/com/audacious_software/passive_data_kit/generators/device/Location.java index 814c5ad..058f4ec 100755 --- a/src/com/audacious_software/passive_data_kit/generators/device/Location.java +++ b/src/com/audacious_software/passive_data_kit/generators/device/Location.java @@ -1,26 +1,51 @@ package com.audacious_software.passive_data_kit.generators.device; import android.Manifest; +import android.app.Activity; +import android.content.ContentValues; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.content.res.ColorStateList; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.database.sqlite.SQLiteDatabase; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.location.Address; +import android.location.Geocoder; import android.location.LocationManager; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.preference.PreferenceManager; -import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; +import android.support.v4.content.res.ResourcesCompat; +import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.SwitchCompat; import android.util.DisplayMetrics; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.webkit.WebView; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.ListView; import android.widget.TextView; +import android.widget.Toast; import com.audacious_software.passive_data_kit.DeviceInformation; +import com.audacious_software.passive_data_kit.PassiveDataKit; +import com.audacious_software.passive_data_kit.activities.DataDisclosureDetailActivity; import com.audacious_software.passive_data_kit.activities.generators.DataPointViewHolder; +import com.audacious_software.passive_data_kit.activities.generators.GeneratorViewHolder; import com.audacious_software.passive_data_kit.activities.generators.RequestPermissionActivity; import com.audacious_software.passive_data_kit.diagnostics.DiagnosticAction; import com.audacious_software.passive_data_kit.generators.Generator; @@ -35,16 +60,23 @@ import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.MapView; import com.google.android.gms.maps.OnMapReadyCallback; -import com.google.android.gms.maps.UiSettings; import com.google.android.gms.maps.model.BitmapDescriptorFactory; import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.LatLngBounds; import com.google.android.gms.maps.model.MarkerOptions; +import com.google.maps.android.ui.IconGenerator; +import java.io.File; +import java.io.IOException; +import java.security.SecureRandom; import java.util.ArrayList; +import java.util.List; +import java.util.Random; @SuppressWarnings("unused") public class Location extends Generator implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener { private static final String GENERATOR_IDENTIFIER = "pdk-location"; + private static final String DATABASE_PATH = "pdk-location.sqlite"; private static final String ENABLED = "com.audacious_software.passive_data_kit.generators.device.Location.ENABLED"; private static final boolean ENABLED_DEFAULT = true; @@ -61,18 +93,43 @@ public class Location extends Generator implements GoogleApiClient.ConnectionCal private static final String BEARING_KEY = "bearing"; private static final String SPEED_KEY = "speed"; private static final String EXTRAS_KEY = "extras"; + private static final String SETTING_DISPLAY_HYBRID_MAP = "com.audacious_software.passive_data_kit.generators.device.Location.SETTING_DISPLAY_HYBRID_MAP"; + private static final boolean SETTING_DISPLAY_HYBRID_MAP_DEFAULT = true; - private static final String LAST_KNOWN_LATITUDE = "com.audacious_software.passive_data_kit.generators.device.Location.LAST_KNOWN_LATITUDE"; - private static final float LAST_KNOWN_LATITUDE_DEFAULT = 0; + private static String ACCURACY_MODE = "com.audacious_software.passive_data_kit.generators.device.Location.ACCURACY_MODE"; - private static final String LAST_KNOWN_LONGITUDE = "com.audacious_software.passive_data_kit.generators.device.Location.LAST_KNOWN_LONGITUDE"; - private static final float LAST_KNOWN_LONGITUDE_DEFAULT = 0; + private static int ACCURACY_BEST = 0; + private static int ACCURACY_RANDOMIZED = 1; + private static int ACCURACY_USER = 2; + private static int ACCURACY_DISABLED = 3; - private static final String LAST_KNOWN_TIMESTAMP = "com.audacious_software.passive_data_kit.generators.device.Location.LAST_KNOWN_TIMESTAMP";; + private static final String ACCURACY_MODE_RANDOMIZED_RANGE = "com.audacious_software.passive_data_kit.generators.device.Location.ACCURACY_MODE_RANDOMIZED_RANGE"; + private static final long ACCURACY_MODE_RANDOMIZED_RANGE_DEFAULT = 100; + + private static final String ACCURACY_MODE_USER_LOCATION = "com.audacious_software.passive_data_kit.generators.device.Location.ACCURACY_MODE_USER_LOCATION"; + private static final String ACCURACY_MODE_USER_LOCATION_DEFAULT = "Chicago, Illinois"; + + private static final String ACCURACY_MODE_USER_LOCATION_LATITUDE = "com.audacious_software.passive_data_kit.generators.device.Location.ACCURACY_MODE_USER_LOCATION_LATITUDE"; + private static final String ACCURACY_MODE_USER_LOCATION_LONGITUDE = "com.audacious_software.passive_data_kit.generators.device.Location.ACCURACY_MODE_USER_LOCATION_LONGITUDE"; private static Location sInstance = null; private GoogleApiClient mGoogleApiClient = null; private android.location.Location mLastLocation = null; + private long mUpdateInterval = 60000; + + private SQLiteDatabase mDatabase = null; + private static int DATABASE_VERSION = 1; + + private static final String TABLE_HISTORY = "history"; + public static final String HISTORY_OBSERVED = "observed"; + public static final String HISTORY_LATITUDE = "latitude"; + public static final String HISTORY_LONGITUDE = "longitude"; + public static final String HISTORY_ALTITUDE = "altitude"; + public static final String HISTORY_BEARING = "bearing"; + public static final String HISTORY_SPEED = "speed"; + public static final String HISTORY_PROVIDER = "provider"; + public static final String HISTORY_LOCATION_TIMESTAMP = "location_timestamp"; + public static final String HISTORY_ACCURACY = "accuracy"; public static Location getInstance(Context context) { if (Location.sInstance == null) { @@ -114,7 +171,6 @@ else if (Location.useGoogleLocationServices(me.mContext)) me.mGoogleApiClient = builder.build(); me.mGoogleApiClient.connect(); } - } else { @@ -128,6 +184,31 @@ else if (Location.useGoogleLocationServices(me.mContext)) t.start(); Generators.getInstance(this.mContext).registerCustomViewClass(Location.GENERATOR_IDENTIFIER, Location.class); + + File path = PassiveDataKit.getGeneratorsStorage(this.mContext); + + path = new File(path, Location.DATABASE_PATH); + + this.mDatabase = SQLiteDatabase.openOrCreateDatabase(path, null); + + int version = this.getDatabaseVersion(this.mDatabase); + + switch (version) { + case 0: + this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_location_create_history_table)); + } + + this.setDatabaseVersion(this.mDatabase, Location.DATABASE_VERSION); + } + + private void stopGenerator() { + if (this.mGoogleApiClient != null) { + this.mGoogleApiClient.disconnect(); + this.mGoogleApiClient = null; + } + + this.mDatabase.close(); + this.mDatabase = null; } public static boolean useGoogleLocationServices(Context context) { @@ -183,7 +264,7 @@ private ArrayList runDiagostics() { int permissionCheck = ContextCompat.checkSelfPermission(this.mContext, Manifest.permission.ACCESS_FINE_LOCATION); if (permissionCheck != PackageManager.PERMISSION_GRANTED) { - actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_location_permission), new Runnable() { + actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_location_permission_title), me.mContext.getString(R.string.diagnostic_missing_location_permission), new Runnable() { @Override public void run() { @@ -206,13 +287,13 @@ public void run() { return actions; } - @Override public void onConnected(Bundle bundle) { final LocationRequest request = new LocationRequest(); request.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY); - request.setInterval(60000); + request.setFastestInterval(this.mUpdateInterval); + request.setInterval(this.mUpdateInterval); if (this.mGoogleApiClient != null && this.mGoogleApiClient.isConnected()) { if (ContextCompat.checkSelfPermission(this.mContext, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { @@ -239,78 +320,139 @@ public void onLocationChanged(android.location.Location location) { long now = System.currentTimeMillis(); - Bundle bundle = new Bundle(); + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + int selected = prefs.getInt(Location.ACCURACY_MODE, Location.ACCURACY_BEST); - bundle.putDouble(Location.LATITUDE_KEY, location.getLatitude()); - bundle.putDouble(Location.LONGITUDE_KEY, location.getLongitude()); - bundle.putDouble(Location.FIX_TIMESTAMP_KEY, ((double) location.getTime()) / 1000); - bundle.putString(Location.PROVIDER_KEY, location.getProvider()); + if (selected == Location.ACCURACY_RANDOMIZED) { + // http://gis.stackexchange.com/a/68275/10230 - this.mLastLocation = location; + double latitude = location.getLatitude(); + double longitude = location.getLongitude(); - if (location.hasAccuracy()) { - bundle.putFloat(Location.ACCURACY_KEY, location.getAccuracy()); + double radius = prefs.getLong(Location.ACCURACY_MODE_RANDOMIZED_RANGE, Location.ACCURACY_MODE_RANDOMIZED_RANGE_DEFAULT); + + double radiusInDegrees = radius / 111000; + + Random r = new SecureRandom(); + + double u = r.nextDouble(); + double v = r.nextDouble(); + + double w = radiusInDegrees * Math.sqrt(u); + double t = 2 * Math.PI * v; + double x = w * Math.cos(t); + double y = w * Math.sin(t); + + // Adjust the x-coordinate for the shrinking of the east-west distances + longitude = longitude + (x / Math.cos(latitude)); + latitude = y + latitude; + + location.setLongitude(longitude); + location.setLatitude(latitude); } + ContentValues values = new ContentValues(); + values.put(Location.HISTORY_OBSERVED, System.currentTimeMillis()); + values.put(Location.HISTORY_LATITUDE, location.getLatitude()); + values.put(Location.HISTORY_LONGITUDE, location.getLongitude()); + values.put(Location.HISTORY_PROVIDER, location.getProvider()); + values.put(Location.HISTORY_LOCATION_TIMESTAMP, location.getTime()); + + Bundle updated = new Bundle(); + updated.putLong(Location.HISTORY_OBSERVED, System.currentTimeMillis()); + updated.putDouble(Location.HISTORY_LATITUDE, location.getLatitude()); + updated.putDouble(Location.HISTORY_LONGITUDE, location.getLongitude()); + updated.putString(Location.HISTORY_PROVIDER, location.getProvider()); + updated.putLong(Location.HISTORY_LOCATION_TIMESTAMP, location.getTime()); + + Bundle metadata = new Bundle(); + metadata.putDouble(Generator.LATITUDE, location.getLatitude()); + metadata.putDouble(Generator.LONGITUDE, location.getLongitude()); + + updated.putBundle(Generator.PDK_METADATA, metadata); + if (location.hasAltitude()) { - bundle.putDouble(Location.ALTITUDE_KEY, location.getAltitude()); + values.put(Location.HISTORY_ALTITUDE, location.getAltitude()); + updated.putDouble(Location.HISTORY_ALTITUDE, location.getAltitude()); } if (location.hasBearing()) { - bundle.putFloat(Location.BEARING_KEY, location.getBearing()); + values.put(Location.HISTORY_BEARING, location.getBearing()); + updated.putDouble(Location.HISTORY_BEARING, location.getBearing()); } if (location.hasSpeed()) { - bundle.putFloat(Location.SPEED_KEY, location.getSpeed()); + values.put(Location.HISTORY_SPEED, location.getBearing()); + updated.putDouble(Location.HISTORY_SPEED, location.getBearing()); } - Bundle extras = location.getExtras(); - - if (extras != null) { - bundle.putBundle(Location.EXTRAS_KEY, extras); + if (location.hasAccuracy()) { + values.put(Location.HISTORY_ACCURACY, location.getAccuracy()); + updated.putDouble(Location.HISTORY_ACCURACY, location.getAccuracy()); } - Generators.getInstance(this.mContext).transmitData(Location.GENERATOR_IDENTIFIER, bundle); + this.mDatabase.insert(Location.TABLE_HISTORY, null, values); - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); - SharedPreferences.Editor e = prefs.edit(); + Generators.getInstance(this.mContext).notifyGeneratorUpdated(Location.GENERATOR_IDENTIFIER, updated); + } - e.putFloat(Location.LAST_KNOWN_LATITUDE, (float) location.getLatitude()); - e.putFloat(Location.LAST_KNOWN_LONGITUDE, (float) location.getLongitude()); - e.putLong(Location.LAST_KNOWN_TIMESTAMP, System.currentTimeMillis()); + public static long latestPointGenerated(Context context) { + long timestamp = 0; - e.apply(); - } + Location me = Location.getInstance(context); - public static void bindViewHolder(DataPointViewHolder holder, final Bundle dataPoint) { - Log.e("PDK", "DRAWING LOCATION: " + dataPoint); + Cursor c = me.mDatabase.query(Location.TABLE_HISTORY, null, null, null, null, null, Location.HISTORY_OBSERVED + " DESC"); + + if (c.moveToNext()) { + timestamp = c.getLong(c.getColumnIndex(Location.HISTORY_OBSERVED)); + } + + c.close(); + + return timestamp; + } + public static void bindViewHolder(DataPointViewHolder holder) { final Context context = holder.itemView.getContext(); - String identifier = dataPoint.getBundle(Generator.PDK_METADATA).getString(Generator.IDENTIFIER); + Location me = Location.getInstance(context); - double timestamp = dataPoint.getBundle(Generator.PDK_METADATA).getDouble(Generator.TIMESTAMP); + double lastLatitude = 0.0; + double lastLongitude = 0.0; + long timestamp = 0; - double latitude = Location.LAST_KNOWN_LATITUDE_DEFAULT; - double longitude = Location.LAST_KNOWN_LONGITUDE_DEFAULT; + final List locations = new ArrayList<>(); - if (dataPoint.containsKey(Location.LATITUDE_KEY) && dataPoint.containsKey(Location.LONGITUDE_KEY)) { - latitude = dataPoint.getDouble(Location.LATITUDE_KEY); - longitude = dataPoint.getDouble(Location.LONGITUDE_KEY); - } else { // Empty point - retrieve last known location... - Log.e("PDK", "FETCHING LAST KNOWN LOCATION..."); - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + String where = Location.HISTORY_OBSERVED + " > ?"; + String[] args = { "" + (System.currentTimeMillis() - (1000 * 60 * 60 * 24)) }; - latitude = prefs.getFloat(Location.LAST_KNOWN_LATITUDE, Location.LAST_KNOWN_LATITUDE_DEFAULT); - longitude = prefs.getFloat(Location.LAST_KNOWN_LONGITUDE, Location.LAST_KNOWN_LONGITUDE_DEFAULT); - timestamp = ((double) prefs.getLong(Location.LAST_KNOWN_TIMESTAMP, System.currentTimeMillis())) / 1000 ; + Cursor c = me.mDatabase.query(Location.TABLE_HISTORY, null, where, args, null, null, Location.HISTORY_OBSERVED); + + while (c.moveToNext()) { + lastLatitude = c.getDouble(c.getColumnIndex(Location.HISTORY_LATITUDE)); + lastLongitude = c.getDouble(c.getColumnIndex(Location.HISTORY_LONGITUDE)); + timestamp = c.getLong(c.getColumnIndex(Location.HISTORY_OBSERVED)); + + LatLng location = new LatLng(lastLatitude, lastLongitude); + + locations.add(location); } + c.close(); + TextView dateLabel = (TextView) holder.itemView.findViewById(R.id.generator_data_point_date); - dateLabel.setText(Generator.formatTimestamp(context, timestamp)); - final double finalLatitude = latitude; - final double finalLongitude = longitude; + if (timestamp > 0) { + dateLabel.setText(Generator.formatTimestamp(context, timestamp / 1000)); + } else { + dateLabel.setText(R.string.label_never_pdk); + } + + final double finalLatitude = lastLatitude; + final double finalLongitude = lastLongitude; + + final DisplayMetrics metrics = new DisplayMetrics(); + ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(metrics); if (Location.useKindleLocationServices()) { @@ -322,24 +464,95 @@ else if (Location.useGoogleLocationServices(holder.itemView.getContext())) final MapView mapView = (MapView) holder.itemView.findViewById(R.id.map_view); mapView.onCreate(null); + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + final boolean useHybrid = prefs.getBoolean(Location.SETTING_DISPLAY_HYBRID_MAP, Location.SETTING_DISPLAY_HYBRID_MAP_DEFAULT); + + SwitchCompat hybridSwitch = (SwitchCompat) holder.itemView.findViewById(R.id.pdk_google_location_map_type_hybrid); + hybridSwitch.setChecked(useHybrid); + + hybridSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, final boolean checked) { + SharedPreferences.Editor e = prefs.edit(); + e.putBoolean(Location.SETTING_DISPLAY_HYBRID_MAP, checked); + e.apply(); + + mapView.getMapAsync(new OnMapReadyCallback() { + public void onMapReady(GoogleMap googleMap) { + if (checked) { + googleMap.setMapType(GoogleMap.MAP_TYPE_HYBRID); + } else { + googleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL); + } + } + }); + } + }); + + ColorStateList buttonStates = new ColorStateList( + new int[][]{ + new int[]{android.R.attr.state_checked}, + new int[]{-android.R.attr.state_enabled}, + new int[]{} + }, + new int[]{ + 0xfff1f1f1, + 0x1c000000, + 0xff33691E + } + ); + + DrawableCompat.setTintList(hybridSwitch.getThumbDrawable(), buttonStates); + + IconGenerator iconGen = new IconGenerator(context); + + Drawable shapeDrawable = ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_location_heatmap_marker, null); + iconGen.setBackground(shapeDrawable); + + View view = new View(context); + view.setLayoutParams(new ViewGroup.LayoutParams(8, 8)); + iconGen.setContentView(view); + + final Bitmap bitmap = iconGen.makeIcon(); + mapView.getMapAsync(new OnMapReadyCallback() { public void onMapReady(GoogleMap googleMap) { - googleMap.setMapType(GoogleMap.MAP_TYPE_HYBRID); - googleMap.getUiSettings().setZoomControlsEnabled(false); + if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED || + ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { + googleMap.setMyLocationEnabled(true); + } + + if (useHybrid) { + googleMap.setMapType(GoogleMap.MAP_TYPE_HYBRID); + } else { + googleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL); + } + + googleMap.getUiSettings().setZoomControlsEnabled(true); googleMap.getUiSettings().setMyLocationButtonEnabled(false); + googleMap.getUiSettings().setMapToolbarEnabled(false); + googleMap.getUiSettings().setAllGesturesEnabled(false); + + LatLngBounds.Builder builder = new LatLngBounds.Builder(); - googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(finalLatitude, finalLongitude), 14)); + for (LatLng latlng : locations) { + builder.include(latlng); + } - googleMap.addMarker(new MarkerOptions() - .position(new LatLng(finalLatitude, finalLongitude))); -// .icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_marker_none))); + if (locations.size() > 0) { + googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), (int) (16 * metrics.density))); + } DisplayMetrics metrics = context.getResources().getDisplayMetrics(); - googleMap.setPadding(0, 0, 0, (int) (32 * metrics.density)); + for (LatLng latLng : locations) { + googleMap.addMarker(new MarkerOptions() + .position(latLng) + .icon(BitmapDescriptorFactory.fromBitmap(bitmap))); + } - UiSettings settings = googleMap.getUiSettings(); - settings.setMapToolbarEnabled(false); + mapView.onResume(); } }); } @@ -348,9 +561,11 @@ public void onMapReady(GoogleMap googleMap) { // TODO throw new RuntimeException("Throw rocks at developer to implement generic location support."); } + } - TextView description = (TextView) holder.itemView.findViewById(R.id.generator_location_description); - description.setText(context.getResources().getString(R.string.generator_location_value, latitude, longitude)); + @Override + public List fetchPayloads() { + return new ArrayList(); } public static View fetchView(ViewGroup parent) @@ -371,7 +586,347 @@ else if (Location.useGoogleLocationServices(parent.getContext())) } } + public static String getGeneratorTitle(Context context) { + return context.getString(R.string.generator_location); + } + + public static List getDisclosureActions(final Context context) { + List actions = new ArrayList<>(); + + DataDisclosureDetailActivity.Action disclosure = new DataDisclosureDetailActivity.Action(); + + disclosure.title = context.getString(R.string.label_data_collection_description); + disclosure.subtitle = context.getString(R.string.label_data_collection_description_more); + + WebView disclosureView = new WebView(context); + disclosureView.loadUrl("file:///android_asset/html/passive_data_kit/generator_location_disclosure.html"); + + disclosure.view = disclosureView; + + actions.add(disclosure); + + DataDisclosureDetailActivity.Action accuracy = new DataDisclosureDetailActivity.Action(); + accuracy.title = context.getString(R.string.label_data_collection_location_accuracy); + accuracy.subtitle = context.getString(R.string.label_data_collection_location_accuracy_more); + + final Integer[] options = { Location.ACCURACY_BEST, Location.ACCURACY_RANDOMIZED, Location.ACCURACY_USER, Location.ACCURACY_DISABLED }; + + final ListView listView = new ListView(context); + + final ArrayAdapter accuracyAdapter = new ArrayAdapter(context, R.layout.row_disclosure_location_accuracy_pdk, options) { + public View getView (final int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = LayoutInflater.from(context).inflate(R.layout.row_disclosure_location_accuracy_pdk, null); + } + + final Integer option = options[position]; + + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + int selected = prefs.getInt(Location.ACCURACY_MODE, Location.ACCURACY_BEST); + + CheckBox checked = (CheckBox) convertView.findViewById(R.id.action_checked); + + checked.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { + + } + }); + + checked.setChecked(selected == option); + + TextView title = (TextView) convertView.findViewById(R.id.action_title); + TextView description = (TextView) convertView.findViewById(R.id.action_description); + + if (option == Location.ACCURACY_BEST) { + title.setText(R.string.label_data_collection_location_accuracy_best); + description.setText(R.string.label_data_collection_location_accuracy_best_more); + } else if (option == Location.ACCURACY_RANDOMIZED) { + title.setText(R.string.label_data_collection_location_accuracy_randomized); + description.setText(R.string.label_data_collection_location_accuracy_randomized_more); + } else if (option == Location.ACCURACY_USER) { + title.setText(R.string.label_data_collection_location_accuracy_user); + description.setText(R.string.label_data_collection_location_accuracy_user_more); + } else if (option == Location.ACCURACY_DISABLED) { + title.setText(R.string.label_data_collection_location_accuracy_disabled); + description.setText(R.string.label_data_collection_location_accuracy_disabled_more); + } + + final ArrayAdapter meAdapter = this; + + checked.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(final CompoundButton compoundButton, final boolean checked) { + Log.e("PDK", "CHECK CHANGE!"); + + final CompoundButton.OnCheckedChangeListener me = this; + + if (option == Location.ACCURACY_BEST) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.title_location_accuracy_best); + builder.setMessage(R.string.message_location_accuracy_best); + + builder.setPositiveButton(R.string.action_continue, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + SharedPreferences.Editor e = prefs.edit(); + e.putInt(Location.ACCURACY_MODE, option); + e.apply(); + + meAdapter.notifyDataSetChanged(); + } + }); + + builder.create().show(); + } else if (option == Location.ACCURACY_RANDOMIZED) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.title_location_accuracy_randomized); + + View body = LayoutInflater.from(context).inflate(R.layout.dialog_location_randomized, null); + builder.setView(body); + + final EditText rangeField = (EditText) body.findViewById(R.id.random_range); + + long existingRange = prefs.getLong(Location.ACCURACY_MODE_RANDOMIZED_RANGE, Location.ACCURACY_MODE_RANDOMIZED_RANGE_DEFAULT); + + rangeField.setText("" + existingRange); + + builder.setPositiveButton(R.string.action_continue, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + SharedPreferences.Editor e = prefs.edit(); + + long randomRange = Long.parseLong(rangeField.getText().toString()); + + e.putLong(Location.ACCURACY_MODE_RANDOMIZED_RANGE, randomRange); + + e.putInt(Location.ACCURACY_MODE, option); + e.apply(); + + meAdapter.notifyDataSetChanged(); + } + }); + + builder.create().show(); + } else if (option == Location.ACCURACY_USER) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.title_location_accuracy_user); + + View body = LayoutInflater.from(context).inflate(R.layout.dialog_location_user, null); + builder.setView(body); + + final EditText locationField = (EditText) body.findViewById(R.id.user_location); + + String existingLocation = prefs.getString(Location.ACCURACY_MODE_USER_LOCATION, Location.ACCURACY_MODE_USER_LOCATION_DEFAULT); + + locationField.setText(existingLocation); + + builder.setPositiveButton(R.string.action_continue, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + SharedPreferences.Editor e = prefs.edit(); + + String location = locationField.getText().toString(); + + e.putString(Location.ACCURACY_MODE_USER_LOCATION, location); + + try { + List
results = (new Geocoder(context)).getFromLocationName(location, 1); + + if (results.size() > 0) { + Address match = results.get(0); + + e.putFloat(Location.ACCURACY_MODE_USER_LOCATION_LATITUDE, (float) match.getLatitude()); + e.putFloat(Location.ACCURACY_MODE_USER_LOCATION_LONGITUDE, (float) match.getLongitude()); + } else { + Toast.makeText(context, R.string.toast_location_lookup_failed, Toast.LENGTH_LONG).show(); + + me.onCheckedChanged(compoundButton, checked); + } + } catch (IOException e1) { + e1.printStackTrace(); + } + + e.putInt(Location.ACCURACY_MODE, option); + e.apply(); + + meAdapter.notifyDataSetChanged(); + } + }); + + builder.create().show(); + } else if (option == Location.ACCURACY_DISABLED) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.title_location_accuracy_disabled); + builder.setMessage(R.string.message_location_accuracy_disabled); + + builder.setPositiveButton(R.string.action_continue, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + SharedPreferences.Editor e = prefs.edit(); + e.putInt(Location.ACCURACY_MODE, option); + e.apply(); + + meAdapter.notifyDataSetChanged(); + } + }); + + builder.create().show(); + } + + } + }); + + return convertView; + } + }; + + listView.setAdapter(accuracyAdapter); + + accuracy.view = listView; + + actions.add(accuracy); + + return actions; + } + + public static View getDisclosureDataView(final GeneratorViewHolder holder) { + final Context context = holder.itemView.getContext(); + + if (Location.useKindleLocationServices()) + { + // TODO + throw new RuntimeException("Throw rocks at developer to implement Kindle support."); + } + else if (Location.useGoogleLocationServices(holder.itemView.getContext())) + { + final MapView mapView = new MapView(context); + + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + mapView.setLayoutParams(params); + + mapView.onCreate(null); + + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + final boolean useHybrid = prefs.getBoolean(Location.SETTING_DISPLAY_HYBRID_MAP, Location.SETTING_DISPLAY_HYBRID_MAP_DEFAULT); + + IconGenerator iconGen = new IconGenerator(context); + + Drawable shapeDrawable = ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_location_heatmap_marker, null); + iconGen.setBackground(shapeDrawable); + + View view = new View(context); + view.setLayoutParams(new ViewGroup.LayoutParams(8, 8)); + iconGen.setContentView(view); + + final Bitmap bitmap = iconGen.makeIcon(); + + mapView.getMapAsync(new OnMapReadyCallback() { + public void onMapReady(GoogleMap googleMap) { + if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED || + ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { + googleMap.setMyLocationEnabled(true); + } + + if (useHybrid) { + googleMap.setMapType(GoogleMap.MAP_TYPE_HYBRID); + } else { + googleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL); + } + + googleMap.getUiSettings().setZoomControlsEnabled(true); + googleMap.getUiSettings().setMyLocationButtonEnabled(false); + googleMap.getUiSettings().setMapToolbarEnabled(false); + googleMap.getUiSettings().setAllGesturesEnabled(false); + + Location me = Location.getInstance(context); + + double lastLatitude = 0.0; + double lastLongitude = 0.0; + long timestamp = 0; + + final List locations = new ArrayList<>(); + + String where = Location.HISTORY_OBSERVED + " > ?"; + String[] args = { "" + (System.currentTimeMillis() - (1000 * 60 * 60 * 24)) }; + + Cursor c = me.mDatabase.query(Location.TABLE_HISTORY, null, where, args, null, null, Location.HISTORY_OBSERVED); + + while (c.moveToNext()) { + lastLatitude = c.getDouble(c.getColumnIndex(Location.HISTORY_LATITUDE)); + lastLongitude = c.getDouble(c.getColumnIndex(Location.HISTORY_LONGITUDE)); + + LatLng location = new LatLng(lastLatitude, lastLongitude); + + locations.add(location); + } + + c.close(); + + LatLngBounds.Builder builder = new LatLngBounds.Builder(); + + for (LatLng latlng : locations) { + builder.include(latlng); + } + + final DisplayMetrics metrics = new DisplayMetrics(); + ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(metrics); + + if (locations.size() > 0) { + googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), (int) (16 * metrics.density))); + } + + for (LatLng latLng : locations) { + googleMap.addMarker(new MarkerOptions() + .position(latLng) + .icon(BitmapDescriptorFactory.fromBitmap(bitmap))); + } + + mapView.onResume(); + } + }); + + return mapView; + } + else + { + // TODO + throw new RuntimeException("Throw rocks at developer to implement generic location support."); + } + } + + public static void bindDisclosureViewHolder(final GeneratorViewHolder holder) { + Class currentClass = new Object() { }.getClass().getEnclosingClass(); + + String identifier = currentClass.getCanonicalName(); + + TextView generatorLabel = (TextView) holder.itemView.findViewById(R.id.label_generator); + + generatorLabel.setText(Location.getGeneratorTitle(holder.itemView.getContext())); + } + public android.location.Location getLastKnownLocation() { + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + int selected = prefs.getInt(Location.ACCURACY_MODE, Location.ACCURACY_BEST); + + if (selected == Location.ACCURACY_USER) { + double latitude = prefs.getFloat(Location.ACCURACY_MODE_USER_LOCATION_LATITUDE, 0); + double longitude = prefs.getFloat(Location.ACCURACY_MODE_USER_LOCATION_LONGITUDE, 0); + + android.location.Location location = new android.location.Location(""); + location.setLatitude(latitude); + location.setLongitude(longitude); + + return location; + } else if (selected == Location.ACCURACY_DISABLED) { + android.location.Location location = new android.location.Location(""); + location.setLatitude(41.8781); + location.setLongitude(-87.6298); + + return location; + } + if (this.mLastLocation != null) { return this.mLastLocation; } @@ -380,26 +935,55 @@ public android.location.Location getLastKnownLocation() { android.location.Location last = null; - if (ContextCompat.checkSelfPermission(this.mContext, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED && + if (ContextCompat.checkSelfPermission(this.mContext, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this.mContext, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) { - Log.e("FC", "LOCATION PERMISSIONS GRANTED..."); - last = locations.getLastKnownLocation(LocationManager.GPS_PROVIDER); - Log.e("FC", "GPS: " + last); - if (last == null) { last = locations.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); - - Log.e("FC", "NETWORK: " + last); } } + if (selected == Location.ACCURACY_RANDOMIZED) { + // http://gis.stackexchange.com/a/68275/10230 + + double latitude = last.getLatitude(); + double longitude = last.getLongitude(); + + double radius = prefs.getLong(Location.ACCURACY_MODE_RANDOMIZED_RANGE, Location.ACCURACY_MODE_RANDOMIZED_RANGE_DEFAULT); + + double radiusInDegrees = radius / 111000; + + Random r = new SecureRandom(); + + double u = r.nextDouble(); + double v = r.nextDouble(); + + double w = radiusInDegrees * Math.sqrt(u); + double t = 2 * Math.PI * v; + double x = w * Math.cos(t); + double y = w * Math.sin(t); + + // Adjust the x-coordinate for the shrinking of the east-west distances + longitude = longitude + (x / Math.cos(latitude)); + latitude = y + latitude; + + last.setLongitude(longitude); + last.setLatitude(latitude); + } + return last; } - public static void broadcastLatestDataPoint(Context context) { - Generators.getInstance(context).transmitData(Location.GENERATOR_IDENTIFIER, new Bundle()); + public void setUpdateInterval(long interval) { + this.mUpdateInterval = interval; + + this.stopGenerator(); + this.startGenerator(); + } + + public Cursor queryHistory(String[] cols, String where, String[] args, String orderBy) { + return this.mDatabase.query(Location.TABLE_HISTORY, cols, where, args, null, null, orderBy); } } diff --git a/src/com/audacious_software/passive_data_kit/generators/device/ScreenState.java b/src/com/audacious_software/passive_data_kit/generators/device/ScreenState.java index 4d314aa..d8a7432 100755 --- a/src/com/audacious_software/passive_data_kit/generators/device/ScreenState.java +++ b/src/com/audacious_software/passive_data_kit/generators/device/ScreenState.java @@ -1,13 +1,15 @@ package com.audacious_software.passive_data_kit.generators.device; import android.content.BroadcastReceiver; +import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; import android.os.Build; import android.os.Bundle; -import android.preference.PreferenceManager; import android.view.Display; import android.view.LayoutInflater; import android.view.View; @@ -16,20 +18,18 @@ import android.widget.LinearLayout; import android.widget.TextView; +import com.audacious_software.passive_data_kit.PassiveDataKit; import com.audacious_software.passive_data_kit.activities.generators.DataPointViewHolder; import com.audacious_software.passive_data_kit.diagnostics.DiagnosticAction; import com.audacious_software.passive_data_kit.generators.Generator; import com.audacious_software.passive_data_kit.generators.Generators; import com.audacious_software.pdk.passivedatakit.R; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - +import java.io.File; import java.text.DateFormat; import java.util.ArrayList; import java.util.Calendar; -import java.util.Date; +import java.util.List; public class ScreenState extends Generator{ private static final String GENERATOR_IDENTIFIER = "pdk-screen-state"; @@ -37,20 +37,25 @@ public class ScreenState extends Generator{ private static final String ENABLED = "com.audacious_software.passive_data_kit.generators.device.ScreenState.ENABLED"; private static final boolean ENABLED_DEFAULT = true; - private static final String SCREEN_STATE_KEY = "screen_state"; - private static final String STATE_DOZE = "doze"; - private static final String STATE_DOZE_SUSPEND = "doze_suspend"; - private static final String STATE_ON = "on"; - private static final String STATE_OFF = "off"; - private static final String STATE_UNKNOWN = "unknown"; - private static final String SCREEN_HISTORY_KEY = "com.audacious_software.passive_data_kit.generators.device.ScreenState.SCREEN_HISTORY_KEY";; - private static final String SCREEN_HISTORY_TIMESTAMP = "ts"; - private static final String SCREEN_HISTORY_STATE = "state"; + public static final String STATE_DOZE = "doze"; + public static final String STATE_DOZE_SUSPEND = "doze_suspend"; + public static final String STATE_ON = "on"; + public static final String STATE_OFF = "off"; + public static final String STATE_UNKNOWN = "unknown"; + + private static final String DATABASE_PATH = "pdk-screen-state.sqlite"; + private static final int DATABASE_VERSION = 2; + + public static final String HISTORY_OBSERVED = "observed"; + public static final String HISTORY_STATE = "state"; + public static final String TABLE_HISTORY = "history"; private static ScreenState sInstance = null; private BroadcastReceiver mReceiver = null; + private SQLiteDatabase mDatabase = null; + public static ScreenState getInstance(Context context) { if (ScreenState.sInstance == null) { ScreenState.sInstance = new ScreenState(context.getApplicationContext()); @@ -68,10 +73,18 @@ public static void start(final Context context) { } private void startGenerator() { + final ScreenState me = this; + this.mReceiver = new BroadcastReceiver() { @Override - public void onReceive(Context context, Intent intent) { - Bundle bundle = new Bundle(); + public void onReceive(final Context context, Intent intent) { + long now = System.currentTimeMillis(); + + ContentValues values = new ContentValues(); + values.put(ScreenState.HISTORY_OBSERVED, now); + + Bundle update = new Bundle(); + update.putLong(ScreenState.HISTORY_OBSERVED, now); WindowManager window = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Display display = window.getDefaultDisplay(); @@ -81,62 +94,36 @@ public void onReceive(Context context, Intent intent) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { switch (display.getState()) { case Display.STATE_DOZE: - bundle.putString(ScreenState.SCREEN_STATE_KEY, ScreenState.STATE_DOZE); + values.put(ScreenState.HISTORY_STATE, ScreenState.STATE_DOZE); break; case Display.STATE_DOZE_SUSPEND: - bundle.putString(ScreenState.SCREEN_STATE_KEY, ScreenState.STATE_DOZE_SUSPEND); + values.put(ScreenState.HISTORY_STATE, ScreenState.STATE_DOZE_SUSPEND); break; case Display.STATE_ON: - bundle.putString(ScreenState.SCREEN_STATE_KEY, ScreenState.STATE_ON); + values.put(ScreenState.HISTORY_STATE, ScreenState.STATE_ON); break; case Display.STATE_OFF: - bundle.putString(ScreenState.SCREEN_STATE_KEY, ScreenState.STATE_OFF); + values.put(ScreenState.HISTORY_STATE, ScreenState.STATE_OFF); break; case Display.STATE_UNKNOWN: - bundle.putString(ScreenState.SCREEN_STATE_KEY, ScreenState.STATE_UNKNOWN); + values.put(ScreenState.HISTORY_STATE, ScreenState.STATE_UNKNOWN); break; } } else { if (Intent.ACTION_SCREEN_OFF.equals(action)) { - bundle.putString(ScreenState.SCREEN_STATE_KEY, ScreenState.STATE_OFF); + values.put(ScreenState.HISTORY_STATE, ScreenState.STATE_OFF); } else if (Intent.ACTION_SCREEN_ON.equals(action)) { - bundle.putString(ScreenState.SCREEN_STATE_KEY, ScreenState.STATE_ON); + values.put(ScreenState.HISTORY_STATE, ScreenState.STATE_ON); } else { - bundle.putString(ScreenState.SCREEN_STATE_KEY, ScreenState.STATE_UNKNOWN); + values.put(ScreenState.HISTORY_STATE, ScreenState.STATE_UNKNOWN); } } - Generators.getInstance(context).transmitData(ScreenState.GENERATOR_IDENTIFIER, bundle); - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - - try { - JSONArray history = new JSONArray(prefs.getString(ScreenState.SCREEN_HISTORY_KEY, "[]")); - - JSONObject latest = new JSONObject(); - latest.put(ScreenState.SCREEN_HISTORY_TIMESTAMP, System.currentTimeMillis()); - - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { - latest.put(ScreenState.SCREEN_HISTORY_STATE, display.getState()); - } else { - if (Intent.ACTION_SCREEN_OFF.equals(action)) { - latest.put(ScreenState.SCREEN_HISTORY_STATE, 0x01); - } else if (Intent.ACTION_SCREEN_ON.equals(action)) { - latest.put(ScreenState.SCREEN_HISTORY_STATE, 0x02); - } else { - latest.put(ScreenState.SCREEN_HISTORY_STATE, 0x00); - } - } - - history.put(latest); + me.mDatabase.insert(ScreenState.TABLE_HISTORY, null, values); - SharedPreferences.Editor e = prefs.edit(); + update.putString(ScreenState.HISTORY_STATE, values.getAsString(ScreenState.HISTORY_STATE)); - e.putString(ScreenState.SCREEN_HISTORY_KEY, history.toString()); - e.apply(); - } catch (JSONException e) { - e.printStackTrace(); - } + Generators.getInstance(context).notifyGeneratorUpdated(ScreenState.GENERATOR_IDENTIFIER, update); } }; @@ -147,7 +134,23 @@ public void onReceive(Context context, Intent intent) { Generators.getInstance(this.mContext).registerCustomViewClass(ScreenState.GENERATOR_IDENTIFIER, ScreenState.class); - this.mReceiver.onReceive(this.mContext, null); + File path = PassiveDataKit.getGeneratorsStorage(this.mContext); + + path = new File(path, ScreenState.DATABASE_PATH); + + this.mDatabase = SQLiteDatabase.openOrCreateDatabase(path, null); + + int version = this.getDatabaseVersion(this.mDatabase); + + switch (version) { + case 0: + this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_screen_state_create_history_table)); + case 1: + this.mDatabase.execSQL("DROP TABLE history"); + this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_screen_state_create_history_table)); + } + + this.setDatabaseVersion(this.mDatabase, ScreenState.DATABASE_VERSION); } public static boolean isEnabled(Context context) { @@ -168,95 +171,111 @@ public static ArrayList diagnostics(Context context) { return new ArrayList<>(); } - public static void bindViewHolder(DataPointViewHolder holder, final Bundle dataPoint) { + public static void bindViewHolder(DataPointViewHolder holder) { final Context context = holder.itemView.getContext(); - try { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - JSONArray history = new JSONArray(prefs.getString(ScreenState.SCREEN_HISTORY_KEY, "[]")); - -// Log.e("PDK", "SCREEN HISTORY: " + history.toString(2)); + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); - Calendar cal = Calendar.getInstance(); - cal.set(Calendar.HOUR_OF_DAY, 0); - cal.set(Calendar.MINUTE, 0); - cal.set(Calendar.SECOND, 0); - cal.set(Calendar.MILLISECOND, 0); + long zeroStart = cal.getTimeInMillis(); + cal.add(Calendar.DATE, -1); - long zeroStart = cal.getTimeInMillis(); - cal.add(Calendar.DATE, -1); + LinearLayout zeroTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_zero_value); - LinearLayout zeroTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_zero_value); + ScreenState.populateTimeline(context, zeroTimeline, zeroStart); - ScreenState.populateTimeline(context, zeroTimeline, zeroStart, history); + long oneStart = cal.getTimeInMillis(); + cal.add(Calendar.DATE, -1); - long oneStart = cal.getTimeInMillis(); - cal.add(Calendar.DATE, -1); + LinearLayout oneTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_one_value); - LinearLayout oneTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_one_value); + ScreenState.populateTimeline(context, oneTimeline, oneStart); - ScreenState.populateTimeline(context, oneTimeline, oneStart, history); + long twoStart = cal.getTimeInMillis(); - long twoStart = cal.getTimeInMillis(); + LinearLayout twoTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_two_value); - LinearLayout twoTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_two_value); + ScreenState.populateTimeline(context, twoTimeline, twoStart); - ScreenState.populateTimeline(context, twoTimeline, twoStart, history); - } catch (JSONException e) { - e.printStackTrace(); - } + ScreenState generator = ScreenState.getInstance(context); - double timestamp = dataPoint.getBundle(Generator.PDK_METADATA).getDouble(Generator.TIMESTAMP); + Cursor c = generator.mDatabase.query(ScreenState.TABLE_HISTORY, null, null, null, null, null, ScreenState.HISTORY_OBSERVED + " DESC"); + View cardContent = holder.itemView.findViewById(R.id.card_content); + View cardEmpty = holder.itemView.findViewById(R.id.card_empty); TextView dateLabel = (TextView) holder.itemView.findViewById(R.id.generator_data_point_date); - dateLabel.setText(Generator.formatTimestamp(context, timestamp)); + if (c.moveToNext()) { + cardContent.setVisibility(View.VISIBLE); + cardEmpty.setVisibility(View.GONE); - Calendar cal = Calendar.getInstance(); - DateFormat format = android.text.format.DateFormat.getDateFormat(context); + long timestamp = c.getLong(c.getColumnIndex(ScreenState.HISTORY_OBSERVED)) / 1000; - TextView zeroDayLabel = (TextView) holder.itemView.findViewById(R.id.day_zero_label); - zeroDayLabel.setText(format.format(cal.getTime())); + dateLabel.setText(Generator.formatTimestamp(context, timestamp)); - cal.add(Calendar.DATE, -1); + cal = Calendar.getInstance(); + DateFormat format = android.text.format.DateFormat.getDateFormat(context); - TextView oneDayLabel = (TextView) holder.itemView.findViewById(R.id.day_one_label); - oneDayLabel.setText(format.format(cal.getTime())); + TextView zeroDayLabel = (TextView) holder.itemView.findViewById(R.id.day_zero_label); + zeroDayLabel.setText(format.format(cal.getTime())); - cal.add(Calendar.DATE, -1); + cal.add(Calendar.DATE, -1); + + TextView oneDayLabel = (TextView) holder.itemView.findViewById(R.id.day_one_label); + oneDayLabel.setText(format.format(cal.getTime())); + + cal.add(Calendar.DATE, -1); + + TextView twoDayLabel = (TextView) holder.itemView.findViewById(R.id.day_two_label); + twoDayLabel.setText(format.format(cal.getTime())); + } else { + cardContent.setVisibility(View.GONE); + cardEmpty.setVisibility(View.VISIBLE); + + dateLabel.setText(R.string.label_never_pdk); + } - TextView twoDayLabel = (TextView) holder.itemView.findViewById(R.id.day_two_label); - twoDayLabel.setText(format.format(cal.getTime())); + c.close(); } - private static void populateTimeline(Context context, LinearLayout timeline, long start, JSONArray history) { + private static void populateTimeline(Context context, LinearLayout timeline, long start) { timeline.removeAllViews(); + ScreenState generator = ScreenState.getInstance(context); + long end = start + (24 * 60 * 60 * 1000); - long now = System.currentTimeMillis(); + String where = ScreenState.HISTORY_OBSERVED + " >= ? AND " + ScreenState.HISTORY_OBSERVED + " < ?"; + String[] args = { "" + start, "" + end }; - int lastState = -1; + Cursor c = generator.mDatabase.query(ScreenState.TABLE_HISTORY, null, where, args, null, null, ScreenState.HISTORY_OBSERVED); - ArrayList activeStates = new ArrayList<>(); + ArrayList activeStates = new ArrayList<>(); ArrayList activeTimestamps = new ArrayList<>(); - for (int i = 0; i < history.length(); i++) { - try { - JSONObject point = history.getJSONObject(i); + while (c.moveToNext()) { + long timestamp = c.getLong(c.getColumnIndex(ScreenState.HISTORY_OBSERVED)); - long timestamp = point.getLong(ScreenState.SCREEN_HISTORY_TIMESTAMP); - int state = point.getInt(ScreenState.SCREEN_HISTORY_STATE); + activeTimestamps.add(timestamp); - if (timestamp < start) { - lastState = state; - } else if (timestamp < end) { - activeStates.add(state); - activeTimestamps.add(timestamp); - } - } catch (JSONException e) { - e.printStackTrace(); - } + String state = c.getString(c.getColumnIndex(ScreenState.HISTORY_STATE)); + activeStates.add(state); + } + + c.close(); + + String lastState = ScreenState.STATE_UNKNOWN; + + String lastWhere = ScreenState.HISTORY_OBSERVED + " < ?"; + String[] lastArgs = { "" + start }; + + c = generator.mDatabase.query(ScreenState.TABLE_HISTORY, null, lastWhere, lastArgs, null, null, ScreenState.HISTORY_OBSERVED + " DESC"); + + if (c.moveToNext()) { + lastState = c.getString(c.getColumnIndex(ScreenState.HISTORY_STATE)); } if (activeStates.size() > 0) { @@ -265,25 +284,24 @@ private static void populateTimeline(Context context, LinearLayout timeline, lon View startView = new View(context); - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { - if (lastState == -1) { + if (ScreenState.STATE_UNKNOWN.equals(lastState)) { - } else if (firstState == Display.STATE_ON) { + } else if (ScreenState.STATE_ON.equals(lastState)) { startView.setBackgroundColor(0xff4CAF50); - } else if (firstState == Display.STATE_OFF) { + } else if (ScreenState.STATE_OFF.equals(lastState)) { startView.setBackgroundColor(0xff263238); - } else if (firstState == Display.STATE_DOZE) { + } else if (ScreenState.STATE_DOZE.equals(lastState)) { startView.setBackgroundColor(0xff1b5e20); - } else if (firstState == Display.STATE_DOZE_SUSPEND) { + } else if (ScreenState.STATE_DOZE_SUSPEND.equals(lastState)) { startView.setBackgroundColor(0xff1b5e20); } } else { - if (lastState == -1) { + if (ScreenState.STATE_UNKNOWN.equals(lastState)) { - } else if (firstState == 0x02) { + } else if (ScreenState.STATE_ON.equals(lastState)) { startView.setBackgroundColor(0xff4CAF50); - } else if (firstState == 0x01) { + } else if (ScreenState.STATE_OFF.equals(lastState)) { startView.setBackgroundColor(0xff263238); } } @@ -293,28 +311,30 @@ private static void populateTimeline(Context context, LinearLayout timeline, lon timeline.addView(startView); + long now = System.currentTimeMillis(); + if (activeStates.size() == 1) { View v = new View(context); if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { - if (firstState == Display.STATE_ON) { + if (ScreenState.STATE_ON.equals(firstState)) { v.setBackgroundColor(0xff4CAF50); - } else if (firstState == Display.STATE_OFF) { + } else if (ScreenState.STATE_OFF.equals(firstState)) { v.setBackgroundColor(0xff263238); - } else if (firstState == Display.STATE_DOZE) { + } else if (ScreenState.STATE_DOZE.equals(firstState)) { v.setBackgroundColor(0xff3f51b5); - } else if (firstState == Display.STATE_DOZE_SUSPEND) { + } else if (ScreenState.STATE_DOZE_SUSPEND.equals(firstState)) { v.setBackgroundColor(0xff3f51b5); } } else { - if (firstState == 0x02) { + if (ScreenState.STATE_ON.equals(firstState)) { v.setBackgroundColor(0xff4CAF50); - } else if (firstState == 0x01) { + } else if (ScreenState.STATE_OFF.equals(firstState)) { v.setBackgroundColor(0xff263238); } } - if (end > now) { + if (end > System.currentTimeMillis()) { params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, now - firstTimestamp); v.setLayoutParams(params); } else { @@ -328,24 +348,24 @@ private static void populateTimeline(Context context, LinearLayout timeline, lon long currentTimestamp = activeTimestamps.get(i); long priorTimestamp = activeTimestamps.get(i - 1); - long priorState = activeStates.get(i - 1); + String priorState = activeStates.get(i - 1); View v = new View(context); if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { - if (priorState == Display.STATE_ON) { + if (ScreenState.STATE_ON.equals(priorState)) { v.setBackgroundColor(0xff4CAF50); - } else if (priorState == Display.STATE_OFF) { + } else if (ScreenState.STATE_OFF.equals(priorState)) { v.setBackgroundColor(0xff263238); - } else if (priorState == Display.STATE_DOZE) { + } else if (ScreenState.STATE_DOZE.equals(priorState)) { v.setBackgroundColor(0xff3f51b5); - } else if (priorState == Display.STATE_DOZE_SUSPEND) { + } else if (ScreenState.STATE_DOZE_SUSPEND.equals(priorState)) { v.setBackgroundColor(0xff3f51b5); } } else { - if (priorState == 0x02) { + if (ScreenState.STATE_ON.equals(priorState)) { v.setBackgroundColor(0xff4CAF50); - } else if (priorState == 0x01) { + } else if (ScreenState.STATE_OFF.equals(priorState)) { v.setBackgroundColor(0xff263238); } } @@ -357,17 +377,17 @@ private static void populateTimeline(Context context, LinearLayout timeline, lon } long finalTimestamp = activeTimestamps.get(activeTimestamps.size() - 1); - long finalState = activeStates.get(activeStates.size() - 1); + String finalState = activeStates.get(activeStates.size() - 1); View v = new View(context); - if (finalState == Display.STATE_ON) { + if (ScreenState.STATE_ON.equals(finalState)) { v.setBackgroundColor(0xff4CAF50); - } else if (finalState == Display.STATE_OFF) { + } else if (ScreenState.STATE_OFF.equals(finalState)) { v.setBackgroundColor(0xff263238); - } else if (finalState == Display.STATE_DOZE) { + } else if (ScreenState.STATE_DOZE.equals(finalState)) { v.setBackgroundColor(0xff3f51b5); - } else if (finalState == Display.STATE_DOZE_SUSPEND) { + } else if (ScreenState.STATE_DOZE_SUSPEND.equals(finalState)) { v.setBackgroundColor(0xff3f51b5); } @@ -390,6 +410,8 @@ private static void populateTimeline(Context context, LinearLayout timeline, lon timeline.addView(v); } + } else { + } } @@ -398,7 +420,28 @@ public static View fetchView(ViewGroup parent) return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_screen_state, parent, false); } - public static void broadcastLatestDataPoint(Context context) { - Generators.getInstance(context).transmitData(ScreenState.GENERATOR_IDENTIFIER, new Bundle()); + @Override + public List fetchPayloads() { + return new ArrayList<>(); + } + + public static long latestPointGenerated(Context context) { + long timestamp = 0; + + ScreenState me = ScreenState.getInstance(context); + + Cursor c = me.mDatabase.query(ScreenState.TABLE_HISTORY, null, null, null, null, null, ScreenState.HISTORY_OBSERVED + " DESC"); + + if (c.moveToNext()) { + timestamp = c.getLong(c.getColumnIndex(ScreenState.HISTORY_OBSERVED)); + } + + c.close(); + + return timestamp; + } + + public Cursor queryHistory(String[] cols, String where, String[] args, String orderBy) { + return this.mDatabase.query(ScreenState.TABLE_HISTORY, cols, where, args, null, null, orderBy); } } diff --git a/src/com/audacious_software/passive_data_kit/generators/diagnostics/AppEvent.java b/src/com/audacious_software/passive_data_kit/generators/diagnostics/AppEvent.java new file mode 100755 index 0000000..5eab4d1 --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/generators/diagnostics/AppEvent.java @@ -0,0 +1,441 @@ +package com.audacious_software.passive_data_kit.generators.diagnostics; + +import android.content.ContentValues; +import android.content.Context; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.audacious_software.passive_data_kit.PassiveDataKit; +import com.audacious_software.passive_data_kit.activities.generators.DataPointViewHolder; +import com.audacious_software.passive_data_kit.diagnostics.DiagnosticAction; +import com.audacious_software.passive_data_kit.generators.Generator; +import com.audacious_software.passive_data_kit.generators.Generators; +import com.audacious_software.pdk.passivedatakit.R; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class AppEvent extends Generator{ + private static final String GENERATOR_IDENTIFIER = "pdk-app-event"; + + private static final String ENABLED = "com.audacious_software.passive_data_kit.generators.diagnostics.AppEvent.ENABLED"; + private static final boolean ENABLED_DEFAULT = true; + + private static final String DATABASE_PATH = "pdk-app-event.sqlite"; + private static final int DATABASE_VERSION = 1; + + public static final String HISTORY_OBSERVED = "observed"; + public static final String HISTORY_EVENT_NAME = "event_name"; + public static final String HISTORY_EVENT_DETAILS = "event_details"; + public static final String TABLE_HISTORY = "history"; + + private static AppEvent sInstance = null; + + private SQLiteDatabase mDatabase = null; + + public static AppEvent getInstance(Context context) { + if (AppEvent.sInstance == null) { + AppEvent.sInstance = new AppEvent(context.getApplicationContext()); + } + + return AppEvent.sInstance; + } + + public AppEvent(Context context) { + super(context); + } + + public static void start(final Context context) { + AppEvent.getInstance(context).startGenerator(); + } + + private void startGenerator() { + final AppEvent me = this; + + Generators.getInstance(this.mContext).registerCustomViewClass(AppEvent.GENERATOR_IDENTIFIER, AppEvent.class); + + File path = PassiveDataKit.getGeneratorsStorage(this.mContext); + + path = new File(path, AppEvent.DATABASE_PATH); + + this.mDatabase = SQLiteDatabase.openOrCreateDatabase(path, null); + + int version = this.getDatabaseVersion(this.mDatabase); + + switch (version) { + case 0: + this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_app_events_create_history_table)); + } + + this.setDatabaseVersion(this.mDatabase, AppEvent.DATABASE_VERSION); + } + + public static boolean isEnabled(Context context) { + SharedPreferences prefs = Generators.getInstance(context).getSharedPreferences(context); + + return prefs.getBoolean(AppEvent.ENABLED, AppEvent.ENABLED_DEFAULT); + } + + public static boolean isRunning(Context context) { + return (AppEvent.sInstance != null); + } + + public static ArrayList diagnostics(Context context) { + return new ArrayList<>(); + } + + public static void bindViewHolder(DataPointViewHolder holder) { +/* final Context context = holder.itemView.getContext(); + + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + + long zeroStart = cal.getTimeInMillis(); + cal.add(Calendar.DATE, -1); + + LinearLayout zeroTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_zero_value); + + AppEvents.populateTimeline(context, zeroTimeline, zeroStart); + + long oneStart = cal.getTimeInMillis(); + cal.add(Calendar.DATE, -1); + + LinearLayout oneTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_one_value); + + AppEvents.populateTimeline(context, oneTimeline, oneStart); + + long twoStart = cal.getTimeInMillis(); + + LinearLayout twoTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_two_value); + + AppEvents.populateTimeline(context, twoTimeline, twoStart); + + AppEvents generator = AppEvents.getInstance(context); + + Cursor c = generator.mDatabase.query(AppEvents.TABLE_HISTORY, null, null, null, null, null, AppEvents.HISTORY_OBSERVED + " DESC"); + + View cardContent = holder.itemView.findViewById(R.id.card_content); + View cardEmpty = holder.itemView.findViewById(R.id.card_empty); + TextView dateLabel = (TextView) holder.itemView.findViewById(R.id.generator_data_point_date); + + if (c.moveToNext()) { + cardContent.setVisibility(View.VISIBLE); + cardEmpty.setVisibility(View.GONE); + + long timestamp = c.getLong(c.getColumnIndex(AppEvents.HISTORY_OBSERVED)) / 1000; + + dateLabel.setText(Generator.formatTimestamp(context, timestamp)); + + cal = Calendar.getInstance(); + DateFormat format = android.text.format.DateFormat.getDateFormat(context); + + TextView zeroDayLabel = (TextView) holder.itemView.findViewById(R.id.day_zero_label); + zeroDayLabel.setText(format.format(cal.getTime())); + + cal.add(Calendar.DATE, -1); + + TextView oneDayLabel = (TextView) holder.itemView.findViewById(R.id.day_one_label); + oneDayLabel.setText(format.format(cal.getTime())); + + cal.add(Calendar.DATE, -1); + + TextView twoDayLabel = (TextView) holder.itemView.findViewById(R.id.day_two_label); + twoDayLabel.setText(format.format(cal.getTime())); + } else { + cardContent.setVisibility(View.GONE); + cardEmpty.setVisibility(View.VISIBLE); + + dateLabel.setText(R.string.label_never_pdk); + } + + c.close(); + */ + } + + /* + private static void populateTimeline(Context context, LinearLayout timeline, long start) { + timeline.removeAllViews(); + + AppEvents generator = AppEvents.getInstance(context); + + long end = start + (24 * 60 * 60 * 1000); + + String where = AppEvents.HISTORY_OBSERVED + " >= ? AND " + AppEvents.HISTORY_OBSERVED + " < ?"; + String[] args = { "" + start, "" + end }; + + Cursor c = generator.mDatabase.query(AppEvents.TABLE_HISTORY, null, where, args, null, null, AppEvents.HISTORY_OBSERVED); + + ArrayList activeStates = new ArrayList<>(); + ArrayList activeTimestamps = new ArrayList<>(); + + while (c.moveToNext()) { + long timestamp = c.getLong(c.getColumnIndex(AppEvents.HISTORY_OBSERVED)); + + activeTimestamps.add(timestamp); + + String state = c.getString(c.getColumnIndex(AppEvents.HISTORY_STATE)); + activeStates.add(state); + } + + c.close(); + + String lastState = AppEvents.STATE_UNKNOWN; + + String lastWhere = AppEvents.HISTORY_OBSERVED + " < ?"; + String[] lastArgs = { "" + start }; + + c = generator.mDatabase.query(AppEvents.TABLE_HISTORY, null, lastWhere, lastArgs, null, null, AppEvents.HISTORY_OBSERVED + " DESC"); + + if (c.moveToNext()) { + lastState = c.getString(c.getColumnIndex(AppEvents.HISTORY_STATE)); + } + + if (activeStates.size() > 0) { + long firstTimestamp = activeTimestamps.get(0); + long firstState = activeTimestamps.get(0); + + View startView = new View(context); + + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { + if (AppEvents.STATE_UNKNOWN.equals(lastState)) { + + } else if (AppEvents.STATE_ON.equals(lastState)) { + startView.setBackgroundColor(0xff4CAF50); + } else if (AppEvents.STATE_OFF.equals(lastState)) { + startView.setBackgroundColor(0xff263238); + } else if (AppEvents.STATE_DOZE.equals(lastState)) { + startView.setBackgroundColor(0xff1b5e20); + } else if (AppEvents.STATE_DOZE_SUSPEND.equals(lastState)) { + startView.setBackgroundColor(0xff1b5e20); + } + } else { + if (AppEvents.STATE_UNKNOWN.equals(lastState)) { + + } else if (AppEvents.STATE_ON.equals(lastState)) { + startView.setBackgroundColor(0xff4CAF50); + } else if (AppEvents.STATE_OFF.equals(lastState)) { + startView.setBackgroundColor(0xff263238); + } + } + + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, firstTimestamp - start); + startView.setLayoutParams(params); + + timeline.addView(startView); + + long now = System.currentTimeMillis(); + + if (activeStates.size() == 1) { + View v = new View(context); + + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { + if (AppEvents.STATE_ON.equals(firstState)) { + v.setBackgroundColor(0xff4CAF50); + } else if (AppEvents.STATE_OFF.equals(firstState)) { + v.setBackgroundColor(0xff263238); + } else if (AppEvents.STATE_DOZE.equals(firstState)) { + v.setBackgroundColor(0xff3f51b5); + } else if (AppEvents.STATE_DOZE_SUSPEND.equals(firstState)) { + v.setBackgroundColor(0xff3f51b5); + } + } else { + if (AppEvents.STATE_ON.equals(firstState)) { + v.setBackgroundColor(0xff4CAF50); + } else if (AppEvents.STATE_OFF.equals(firstState)) { + v.setBackgroundColor(0xff263238); + } + } + + if (end > System.currentTimeMillis()) { + params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, now - firstTimestamp); + v.setLayoutParams(params); + } else { + params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, end - firstTimestamp); + v.setLayoutParams(params); + } + + timeline.addView(v); + } else { + for (int i = 1; i < activeStates.size(); i++) { + long currentTimestamp = activeTimestamps.get(i); + + long priorTimestamp = activeTimestamps.get(i - 1); + String priorState = activeStates.get(i - 1); + + View v = new View(context); + + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { + if (AppEvents.STATE_ON.equals(priorState)) { + v.setBackgroundColor(0xff4CAF50); + } else if (AppEvents.STATE_OFF.equals(priorState)) { + v.setBackgroundColor(0xff263238); + } else if (AppEvents.STATE_DOZE.equals(priorState)) { + v.setBackgroundColor(0xff3f51b5); + } else if (AppEvents.STATE_DOZE_SUSPEND.equals(priorState)) { + v.setBackgroundColor(0xff3f51b5); + } + } else { + if (AppEvents.STATE_ON.equals(priorState)) { + v.setBackgroundColor(0xff4CAF50); + } else if (AppEvents.STATE_OFF.equals(priorState)) { + v.setBackgroundColor(0xff263238); + } + } + + params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, currentTimestamp - priorTimestamp); + v.setLayoutParams(params); + + timeline.addView(v); + } + + long finalTimestamp = activeTimestamps.get(activeTimestamps.size() - 1); + String finalState = activeStates.get(activeStates.size() - 1); + + View v = new View(context); + + if (AppEvents.STATE_ON.equals(finalState)) { + v.setBackgroundColor(0xff4CAF50); + } else if (AppEvents.STATE_OFF.equals(finalState)) { + v.setBackgroundColor(0xff263238); + } else if (AppEvents.STATE_DOZE.equals(finalState)) { + v.setBackgroundColor(0xff3f51b5); + } else if (AppEvents.STATE_DOZE_SUSPEND.equals(finalState)) { + v.setBackgroundColor(0xff3f51b5); + } + + if (end > now) { + params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, now - finalTimestamp); + v.setLayoutParams(params); + } else { + params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, end - finalTimestamp); + v.setLayoutParams(params); + } + + timeline.addView(v); + } + + if (end > now) { + View v = new View(context); + + params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, end - now); + v.setLayoutParams(params); + + timeline.addView(v); + } + } else { + + } + } + +*/ + public static View fetchView(ViewGroup parent) + { + return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_generic, parent, false); + } + + @Override + public List fetchPayloads() { + return new ArrayList<>(); + } + + public static long latestPointGenerated(Context context) { + long timestamp = 0; + + AppEvent me = AppEvent.getInstance(context); + + Cursor c = me.mDatabase.query(AppEvent.TABLE_HISTORY, null, null, null, null, null, AppEvent.HISTORY_OBSERVED + " DESC"); + + if (c.moveToNext()) { + timestamp = c.getLong(c.getColumnIndex(AppEvent.HISTORY_OBSERVED)); + } + + c.close(); + + return timestamp; + } + + public Cursor queryHistory(String[] cols, String where, String[] args, String orderBy) { + return this.mDatabase.query(AppEvent.TABLE_HISTORY, cols, where, args, null, null, orderBy); + } + + public boolean logEvent(String eventName, Map eventDetails) { + try { + long now = System.currentTimeMillis(); + + ContentValues values = new ContentValues(); + values.put(AppEvent.HISTORY_OBSERVED, now); + values.put(AppEvent.HISTORY_EVENT_NAME, eventName); + + Bundle detailsBundle = new Bundle(); + JSONObject detailsJson = new JSONObject(); + + for (String key : eventDetails.keySet()) { + Object value = eventDetails.get(key); + + if (value instanceof Double) { + Double doubleValue = ((Double) value); + + detailsBundle.putDouble(key, doubleValue); + detailsJson.put(key, doubleValue.doubleValue()); + } else if (value instanceof Float) { + Float floatValue = ((Float) value); + + detailsBundle.putDouble(key, floatValue.doubleValue()); + detailsJson.put(key, floatValue.doubleValue()); + } else if (value instanceof Long) { + Long longValue = ((Long) value); + + detailsBundle.putLong(key, longValue.longValue()); + detailsJson.put(key, longValue.longValue()); + } else if (value instanceof Integer) { + Integer intValue = ((Integer) value); + + detailsBundle.putLong(key, intValue.longValue()); + detailsJson.put(key, intValue.longValue()); + } else if (value instanceof String) { + detailsBundle.putString(key, value.toString()); + detailsJson.put(key, value.toString()); + } else { + detailsBundle.putString(key, "Unknown Class: " + value.getClass().getCanonicalName()); + detailsJson.put(key, "Unknown Class: " + value.getClass().getCanonicalName()); + } + } + + values.put(AppEvent.HISTORY_EVENT_DETAILS, detailsJson.toString(2)); + + this.mDatabase.insert(AppEvent.TABLE_HISTORY, null, values); + + Bundle update = new Bundle(); + update.putLong(AppEvent.HISTORY_OBSERVED, now); + update.putString(AppEvent.HISTORY_EVENT_NAME, values.getAsString(AppEvent.HISTORY_EVENT_NAME)); + update.putBundle(AppEvent.HISTORY_EVENT_DETAILS, detailsBundle); + + Generators.getInstance(this.mContext).notifyGeneratorUpdated(AppEvent.GENERATOR_IDENTIFIER, update); + + return true; + } catch (JSONException e) { + e.printStackTrace(); + } + + return false; + } + + public void logThrowable(Throwable t) { + + } +} diff --git a/src/com/audacious_software/passive_data_kit/generators/services/GoogleAwareness.java b/src/com/audacious_software/passive_data_kit/generators/services/GoogleAwareness.java index 796b1c2..0688e7d 100755 --- a/src/com/audacious_software/passive_data_kit/generators/services/GoogleAwareness.java +++ b/src/com/audacious_software/passive_data_kit/generators/services/GoogleAwareness.java @@ -19,15 +19,13 @@ import com.audacious_software.passive_data_kit.generators.Generators; import com.audacious_software.pdk.passivedatakit.R; import com.google.android.gms.awareness.Awareness; -import com.google.android.gms.awareness.snapshot.DetectedActivityResult; import com.google.android.gms.awareness.snapshot.HeadphoneStateResult; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.location.ActivityRecognitionResult; -import com.google.android.gms.location.DetectedActivity; import java.util.ArrayList; +import java.util.List; /** * Created by cjkarr on 6/28/2016. @@ -105,7 +103,7 @@ private ArrayList runDiagostics() { int permissionCheck = ContextCompat.checkSelfPermission(this.mContext, Manifest.permission.ACCESS_FINE_LOCATION); if (permissionCheck != PackageManager.PERMISSION_GRANTED) { - actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_location_permission), new Runnable() { + actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_location_permission_title), me.mContext.getString(R.string.diagnostic_missing_location_permission), new Runnable() { @Override public void run() { handler.post(new Runnable() { @@ -129,7 +127,7 @@ public void run() { if (permissionCheck != PackageManager.PERMISSION_GRANTED) { Log.e("PDK", "3.3"); - actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_activity_recognition_permission), new Runnable() { + actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_activity_recognition_permission_title), me.mContext.getString(R.string.diagnostic_missing_activity_recognition_permission), new Runnable() { @Override public void run() { handler.post(new Runnable() { @@ -219,4 +217,9 @@ public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { Log.e("PDK", "GA onConnectionFailed"); this.mGoogleApiClient = null; } + + @Override + public List fetchPayloads() { + return new ArrayList<>(); + } } diff --git a/src/com/audacious_software/passive_data_kit/generators/vr/DaydreamViewController.java b/src/com/audacious_software/passive_data_kit/generators/vr/DaydreamViewController.java index 6cbc304..531ed71 100755 --- a/src/com/audacious_software/passive_data_kit/generators/vr/DaydreamViewController.java +++ b/src/com/audacious_software/passive_data_kit/generators/vr/DaydreamViewController.java @@ -14,10 +14,11 @@ import com.audacious_software.passive_data_kit.generators.Generator; import com.audacious_software.passive_data_kit.generators.Generators; import com.audacious_software.pdk.passivedatakit.R; -import com.google.vr.sdk.controller.Controller; -import com.google.vr.sdk.controller.ControllerManager; +// import com.google.vr.sdk.controller.Controller; +/// import com.google.vr.sdk.controller.ControllerManager; import java.util.ArrayList; +import java.util.List; /** * Created by cjkarr on 11/20/2016. @@ -32,8 +33,8 @@ public class DaydreamViewController extends Generator { private static DaydreamViewController sInstance = null; private BroadcastReceiver mReceiver = null; - private ControllerManager mControllerManager = null; - private Controller mController = null; +// private ControllerManager mControllerManager = null; +// private Controller mController = null; public static DaydreamViewController getInstance(Context context) { if (DaydreamViewController.sInstance == null) { @@ -49,7 +50,7 @@ public DaydreamViewController(Context context) { public static void start(final Context context) { DaydreamViewController.getInstance(context).startGenerator(); - +/* DaydreamViewController.sInstance.mControllerManager = new ControllerManager(context, new ControllerManager.EventListener() { @Override public void onApiStatusChanged(int status) { @@ -78,6 +79,8 @@ public void onUpdate() { }); DaydreamViewController.sInstance.mControllerManager.start(); + +*/ } private void startGenerator() { @@ -103,9 +106,9 @@ public static ArrayList diagnostics(Context context) { } public static void bindViewHolder(DataPointViewHolder holder, final Bundle dataPoint) { + /* final Context context = holder.itemView.getContext(); - /* try { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); JSONArray history = new JSONArray(prefs.getString(ScreenState.SCREEN_HISTORY_KEY, "[]")); @@ -171,8 +174,12 @@ public static View fetchView(ViewGroup parent) return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_daydream_vr_controller, parent, false); } - public static void broadcastLatestDataPoint(Context context) { - Generators.getInstance(context).transmitData(DaydreamViewController.GENERATOR_IDENTIFIER, new Bundle()); + @Override + public List fetchPayloads() { + return null; } + public static void broadcastLatestDataPoint(Context context) { +// Generators.getInstance(context).transmitData(DaydreamViewController.GENERATOR_IDENTIFIER, new Bundle()); + } } diff --git a/src/com/audacious_software/passive_data_kit/generators/wearables/MicrosoftBand.java b/src/com/audacious_software/passive_data_kit/generators/wearables/MicrosoftBand.java index 295aade..246e7d3 100755 --- a/src/com/audacious_software/passive_data_kit/generators/wearables/MicrosoftBand.java +++ b/src/com/audacious_software/passive_data_kit/generators/wearables/MicrosoftBand.java @@ -65,6 +65,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.List; @SuppressWarnings("unused") public class MicrosoftBand extends Generator @@ -543,7 +544,7 @@ private void transmitData(BandSensorManager sensors) { this.mHeartRateVariabilityDataPoints.clear(); } - Generators.getInstance(this.mContext).transmitData(MicrosoftBand.GENERATOR_IDENTIFIER, bundle); +// Generators.getInstance(this.mContext).transmitData(MicrosoftBand.GENERATOR_IDENTIFIER, bundle); } public static ArrayList diagnostics(Context context) @@ -560,7 +561,7 @@ private ArrayList runDiagostics() { final MicrosoftBand me = this; if (MicrosoftBand.sInstance.mBandClient == null) { - actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_msft_band_client), new Runnable() { + actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_msft_band_client_title), me.mContext.getString(R.string.diagnostic_missing_msft_band_client), new Runnable() { @Override public void run() { @@ -574,7 +575,7 @@ public void run() { } })); } else if (!MicrosoftBand.sInstance.mBandClient.isConnected()) { - actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_msft_band_client), new Runnable() { + actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_msft_band_client_title), me.mContext.getString(R.string.diagnostic_missing_msft_band_client), new Runnable() { @Override public void run() { @@ -595,7 +596,7 @@ public void run() { if (this.canAccessSensor(sensors, MicrosoftBand.HeartRateDataPoint.class) || this.canAccessSensor(sensors, MicrosoftBand.HeartRateVariabilityDataPoint.class)) { if (sensors.getCurrentHeartRateConsent() != UserConsent.GRANTED) { - actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_msft_band_auth), new Runnable() { + actions.add(new DiagnosticAction(me.mContext.getString(R.string.diagnostic_missing_msft_band_auth_title), me.mContext.getString(R.string.diagnostic_missing_msft_band_auth), new Runnable() { @Override public void run() { @@ -1429,4 +1430,9 @@ public static void bindViewHolder(DataPointViewHolder holder, Bundle dataPoint) uvLevel.setText(context.getString(R.string.generator_value_not_applicable)); } } + + @Override + public List fetchPayloads() { + return new ArrayList<>(); + } } diff --git a/src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java b/src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java new file mode 100755 index 0000000..8aaab44 --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java @@ -0,0 +1,1175 @@ +package com.audacious_software.passive_data_kit.generators.wearables; + +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.preference.PreferenceManager; +import android.util.Base64; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.audacious_software.passive_data_kit.PassiveDataKit; +import com.audacious_software.passive_data_kit.activities.generators.DataPointViewHolder; +import com.audacious_software.passive_data_kit.diagnostics.DiagnosticAction; +import com.audacious_software.passive_data_kit.generators.Generator; +import com.audacious_software.passive_data_kit.generators.Generators; +import com.audacious_software.passive_data_kit.generators.diagnostics.AppEvent; +import com.audacious_software.pdk.passivedatakit.R; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.Charset; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.UUID; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class WithingsDevice extends Generator { + private static final String GENERATOR_IDENTIFIER = "pdk-withings-device"; + + private static final String ENABLED = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.ENABLED"; + private static final boolean ENABLED_DEFAULT = true; + + private static final String DATASTREAM = "datastream"; + private static final String DATASTREAM_ACTIVITY_MEASURES = "activity-measures"; + + private static final String TABLE_ACTIVITY_MEASURE_HISTORY = "activity_measure_history"; + private static final String ACTIVITY_MEASURE_HISTORY_DATE_START = "date_start"; + private static final String ACTIVITY_MEASURE_HISTORY_TIMEZONE = "timezone"; + private static final String ACTIVITY_MEASURE_STEPS = "steps"; + private static final String ACTIVITY_MEASURE_DISTANCE = "distance"; + private static final String ACTIVITY_MEASURE_ACTIVE_CALORIES = "active_calories"; + private static final String ACTIVITY_MEASURE_TOTAL_CALORIES = "total_calories"; + private static final String ACTIVITY_MEASURE_ELEVATION = "elevation"; + private static final String ACTIVITY_MEASURE_SOFT_ACTIVITY_DURATION = "soft_activity_duration"; + private static final String ACTIVITY_MEASURE_MODERATE_ACTIVITY_DURATION = "moderate_activity_duration"; + private static final String ACTIVITY_MEASURE_INTENSE_ACTIVITY_DURATION = "intense_activity_duration"; + + private static final String TABLE_BODY_MEASURE_HISTORY = "body_measure_history"; + private static final String BODY_MEASURE_STATUS_UNKNOWN = "unknown"; + private static final String BODY_MEASURE_STATUS_USER_DEVICE = "user-device"; + private static final String BODY_MEASURE_STATUS_SHARED_DEVICE = "shared-device"; + private static final String BODY_MEASURE_STATUS_MANUAL_ENTRY = "manual-entry"; + private static final String BODY_MEASURE_STATUS_MANUAL_ENTRY_CREATION = "manual-entry-creation"; + private static final String BODY_MEASURE_STATUS_AUTO_DEVICE = "auto-device"; + private static final String BODY_MEASURE_STATUS_MEASURE_CONFIRMED = "measure-confirmed"; + + private static final String BODY_MEASURE_CATEGORY_UNKNOWN = "unknown"; + private static final String BODY_MEASURE_CATEGORY_REAL_MEASUREMENTS = "real-measurements"; + private static final String BODY_MEASURE_CATEGORY_USER_OBJECTIVES = "user-objectives"; + + private static final String BODY_MEASURE_TYPE_UNKNOWN = "unknown"; + private static final String BODY_MEASURE_TYPE_WEIGHT = "weight"; + private static final String BODY_MEASURE_TYPE_HEIGHT = "height"; + private static final String BODY_MEASURE_TYPE_FAT_FREE_MASS = "fat-free-mass"; + private static final String BODY_MEASURE_TYPE_FAT_RATIO = "fat-ratio"; + private static final String BODY_MEASURE_TYPE_FAT_MASS_WEIGHT = "fat-mass-weight"; + private static final String BODY_MEASURE_TYPE_DIASTOLIC_BLOOD_PRESSURE = "diastolic-blood-pressure"; + private static final String BODY_MEASURE_TYPE_SYSTOLIC_BLOOD_PRESSURE = "systolic-blood-pressure"; + private static final String BODY_MEASURE_TYPE_HEART_PULSE = "heart-pulse"; + private static final String BODY_MEASURE_TYPE_TEMPERATURE = "temperature"; + private static final String BODY_MEASURE_TYPE_OXYGEN_SATURATION = "oxygen-saturation"; + private static final String BODY_MEASURE_TYPE_BODY_TEMPERATURE = "body-temperature"; + private static final String BODY_MEASURE_TYPE_SKIN_TEMPERATURE = "skin-temperature"; + private static final String BODY_MEASURE_TYPE_MUSCLE_MASS = "muscle-mass"; + private static final String BODY_MEASURE_TYPE_HYDRATION = "hydration"; + private static final String BODY_MEASURE_TYPE_BONE_MASS = "bone-mass"; + private static final String BODY_MEASURE_TYPE_PULSE_WAVE_VELOCITY = "pulse-wave-velocity"; + + private static final String BODY_MEASURE_HISTORY_DATE = "measure_date"; + private static final String BODY_MEASURE_HISTORY_STATUS = "measure_status"; + private static final String BODY_MEASURE_HISTORY_CATEGORY = "measure_category"; + private static final String BODY_MEASURE_HISTORY_TYPE = "measure_type"; + private static final String BODY_MEASURE_HISTORY_VALUE = "measure_value"; + + private static final String TABLE_INTRADAY_ACTIVITY_HISTORY = "intraday_activity_history"; + + private static final String TABLE_SLEEP_MEASURE_HISTORY = "sleep_measure_history"; + private static final String SLEEP_MEASURE_MODEL_UNKNOWN = "unknown"; + private static final String SLEEP_MEASURE_MODEL_ACTIVITY_TRACKER = "activity-tracker"; + private static final String SLEEP_MEASURE_MODEL_AURA = "aura"; + + private static final String SLEEP_MEASURE_STATE_UNKNOWN = "unknown"; + private static final String SLEEP_MEASURE_STATE_AWAKE = "awake"; + private static final String SLEEP_MEASURE_STATE_LIGHT_SLEEP = "light-sleep"; + private static final String SLEEP_MEASURE_STATE_DEEP_SLEEP = "deep-sleep"; + private static final String SLEEP_MEASURE_STATE_REM_SLEEP = "rem-sleep"; + + private static final String SLEEP_MEASURE_START_DATE = "start_date"; + private static final String SLEEP_MEASURE_END_DATE = "end_date"; + private static final String SLEEP_MEASURE_STATE = "state"; + private static final String SLEEP_MEASURE_MEASUREMENT_DEVICE = "measurement_device"; + + + private static final String TABLE_SLEEP_SUMMARY_HISTORY = "sleep_summary_history"; + private static final String SLEEP_SUMMARY_MODEL_UNKNOWN = "unknown"; + private static final String SLEEP_SUMMARY_MODEL_ACTIVITY_TRACKER = "activity-tracker"; + private static final String SLEEP_SUMMARY_MODEL_AURA = "aura"; + + private static final String SLEEP_SUMMARY_START_DATE = "start_date"; + private static final String SLEEP_SUMMARY_END_DATE = "end_date"; + private static final String SLEEP_SUMMARY_TIMEZONE = "timezone"; + private static final String SLEEP_SUMMARY_MEASUREMENT_DEVICE = "measurement_device"; + private static final String SLEEP_SUMMARY_WAKE_DURATION = "wake_duration"; + private static final String SLEEP_SUMMARY_LIGHT_SLEEP_DURATION = "light_sleep_duration"; + private static final String SLEEP_SUMMARY_DEEP_SLEEP_DURATION = "deep_sleep_duration"; + private static final String SLEEP_SUMMARY_TO_SLEEP_DURATION = "to_sleep_duration"; + private static final String SLEEP_SUMMARY_WAKE_COUNT = "wake_count"; + private static final String SLEEP_SUMMARY_REM_SLEEP_DURATION = "rem_sleep_duration"; + private static final String SLEEP_SUMMARY_TO_WAKE_DURATION = "to_wake_duration"; + + private static final String TABLE_WORKOUT_HISTORY = "workout_history"; + + private static final String HISTORY_OBSERVED = "observed"; + private static final String DATABASE_PATH = "pdk-withings-device.sqlite";; + private static final int DATABASE_VERSION = 1; + + private static final String LAST_DATA_FETCH = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.LAST_DATA_FETCH"; + + private static final String DATA_FETCH_INTERVAL = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.DATA_FETCH_INTERVAL"; + private static final long DATA_FETCH_INTERVAL_DEFAULT = (60 * 60 * 1000); + + private static final String ACTIVITY_MEASURES_ENABLED = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.ACTIVITY_MEASURES_ENABLED"; + private static final boolean ACTIVITY_MEASURES_ENABLED_DEFAULT = true; + + private static final String BODY_MEASURES_ENABLED = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.BODY_MEASURES_ENABLED"; + private static final boolean BODY_MEASURES_ENABLED_DEFAULT = true; + + private static final String INTRADAY_ACTIVITY_ENABLED = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.INTRADAY_ACTIVITY_ENABLED"; + private static final boolean INTRADAY_ACTIVITY_ENABLED_DEFAULT = false; + + private static final String SLEEP_MEASURES_ENABLED = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.SLEEP_MEASURES_ENABLED"; + private static final boolean SLEEP_MEASURES_ENABLED_DEFAULT = true; + + private static final String SLEEP_SUMMARY_ENABLED = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.SLEEP_SUMMARY_ENABLED"; + private static final boolean SLEEP_SUMMARY_ENABLED_DEFAULT = true; + + private static final String WORKOUTS_ENABLED = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.WORKOUTS_ENABLED"; + private static final boolean WORKOUTS_ENABLED_DEFAULT = true; + + public static final String OPTION_OAUTH_CALLBACK_URL = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.OPTION_CALLBACK_URL"; + public static final String OPTION_OAUTH_CONSUMER_KEY = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.OPTION_OAUTH_CONSUMER_KEY"; + public static final String OPTION_OAUTH_CONSUMER_SECRET = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.OPTION_OAUTH_CONSUMER_SECRET"; + private static final String OPTION_OAUTH_ACCESS_TOKEN = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN"; + private static final String OPTION_OAUTH_ACCESS_TOKEN_SECRET = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN_SECRET"; + private static final String OPTION_OAUTH_REQUEST_SECRET = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.OPTION_OAUTH_REQUEST_SECRET"; + private static final String OPTION_OAUTH_ACCESS_USER_ID = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.OPTION_OAUTH_ACCESS_USER_ID"; + + private static final String API_ACTION_ACTIVITY_URL = "https://wbsapi.withings.net/v2/measure?action=getactivity"; + private static final String API_ACTION_BODY_MEASURES_URL = "https://wbsapi.withings.net/measure?action=getmeas"; + private static final String API_ACTION_INTRADAY_ACTIVITY_URL = "https://wbsapi.withings.net/v2/measure?action=getintradayactivity"; + private static final String API_ACTION_SLEEP_MEASURES_URL = "https://wbsapi.withings.net/v2/sleep?action=get"; + private static final String API_ACTION_SLEEP_SUMMARY_URL = "https://wbsapi.withings.net/v2/sleep?action=getsummary"; + private static final String API_ACTION_WORKOUTS_URL = "https://wbsapi.withings.net/v2/measure?action=getworkouts"; + public static final String API_OAUTH_CALLBACK_PATH = "/oauth/withings"; + + private static WithingsDevice sInstance = null; + private Context mContext = null; + private SQLiteDatabase mDatabase = null; + private Handler mHandler = null; + private Map mProperties = new HashMap<>(); + + public static WithingsDevice getInstance(Context context) { + if (WithingsDevice.sInstance == null) { + WithingsDevice.sInstance = new WithingsDevice(context.getApplicationContext()); + } + + return WithingsDevice.sInstance; + } + + public WithingsDevice(Context context) { + super(context); + + this.mContext = context.getApplicationContext(); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + + SharedPreferences.Editor e = prefs.edit(); + e.remove(WithingsDevice.LAST_DATA_FETCH); + e.apply(); + } + + public static void start(final Context context) { + WithingsDevice.getInstance(context).startGenerator(); + } + + private void startGenerator() { + final WithingsDevice me = this; + + if (this.mHandler != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + this.mHandler.getLooper().quitSafely(); + } else { + this.mHandler.getLooper().quit(); + } + + this.mHandler = null; + } + + final Runnable fetchData = new Runnable() { + @Override + public void run() { + Log.e("PDK", "WITHINGS FETCH DATA"); + + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(me.mContext); + long fetchInterval = prefs.getLong(WithingsDevice.DATA_FETCH_INTERVAL, WithingsDevice.DATA_FETCH_INTERVAL_DEFAULT); + + if (me.approvalGranted()) { + Log.e("PDK", "WITHINGS APPROVED"); + + long lastFetch = prefs.getLong(WithingsDevice.LAST_DATA_FETCH, 0); + + long now = System.currentTimeMillis(); + + if (now - lastFetch > fetchInterval) { + Log.e("PDK", "TIME TO FETCH"); + + Runnable r = new Runnable() { + @Override + public void run() { + if (prefs.getBoolean(WithingsDevice.ACTIVITY_MEASURES_ENABLED, WithingsDevice.ACTIVITY_MEASURES_ENABLED_DEFAULT)) { + me.fetchActivityMeasures(); + } + + if (prefs.getBoolean(WithingsDevice.BODY_MEASURES_ENABLED, WithingsDevice.BODY_MEASURES_ENABLED_DEFAULT)) { + me.fetchBodyMeasures(); + } + + if (prefs.getBoolean(WithingsDevice.INTRADAY_ACTIVITY_ENABLED, WithingsDevice.INTRADAY_ACTIVITY_ENABLED_DEFAULT)) { + me.fetchIntradayActivities(); + } + + if (prefs.getBoolean(WithingsDevice.SLEEP_MEASURES_ENABLED, WithingsDevice.SLEEP_MEASURES_ENABLED_DEFAULT)) { + me.fetchSleepMeasures(); + } + + if (prefs.getBoolean(WithingsDevice.SLEEP_SUMMARY_ENABLED, WithingsDevice.SLEEP_SUMMARY_ENABLED_DEFAULT)) { + me.fetchSleepSummary(); + } + + if (prefs.getBoolean(WithingsDevice.WORKOUTS_ENABLED, WithingsDevice.WORKOUTS_ENABLED_DEFAULT)) { + me.fetchWorkouts(); + } + } + }; + + Thread t = new Thread(r); + t.start(); + + SharedPreferences.Editor e = prefs.edit(); + e.putLong(WithingsDevice.LAST_DATA_FETCH, now); + e.apply(); + } else { + Log.e("PDK", "NOT TIME TO FETCH"); + } + } else { + Log.e("PDK", "WITHINGS NOT APPROVED"); + } + + if (me.mHandler != null) { + me.mHandler.postDelayed(this, fetchInterval); + } + } + }; + + File path = PassiveDataKit.getGeneratorsStorage(this.mContext); + + path = new File(path, WithingsDevice.DATABASE_PATH); + + this.mDatabase = SQLiteDatabase.openOrCreateDatabase(path, null); + + int version = this.getDatabaseVersion(this.mDatabase); + + switch (version) { + case 0: + this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_withings_create_activity_measure_history_table)); + this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_withings_create_body_measure_history_table)); + this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_withings_create_intraday_activity_history_table)); + this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_withings_create_sleep_measure_history_table)); + this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_withings_create_sleep_summary_history_table)); + this.mDatabase.execSQL(this.mContext.getString(R.string.pdk_generator_withings_create_workout_history_table)); + } + + this.setDatabaseVersion(this.mDatabase, WithingsDevice.DATABASE_VERSION); + + Runnable r = new Runnable() { + @Override + public void run() { + Looper.prepare(); + + me.mHandler = new Handler(); + + Looper.loop(); + } + }; + + Thread t = new Thread(r); + t.start(); + + try { + Thread.sleep(50); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + me.mHandler.post(fetchData); + + Generators.getInstance(this.mContext).registerCustomViewClass(WithingsDevice.GENERATOR_IDENTIFIER, WithingsDevice.class); + } + + private String getProperty(String key) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + + if (WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN.equals(key)) { + return prefs.getString(WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN, null); + } else if (WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN_SECRET.equals(key)) { + return prefs.getString(WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN_SECRET, null); + } else if (WithingsDevice.OPTION_OAUTH_ACCESS_USER_ID.equals(key)) { + return prefs.getString(WithingsDevice.OPTION_OAUTH_ACCESS_USER_ID, null); + } + + return this.mProperties.get(key); + } + + private JSONObject queryApi(String apiUrl) { + final WithingsDevice me = this; + + String apiKey = this.getProperty(WithingsDevice.OPTION_OAUTH_CONSUMER_KEY); + String apiSecret = this.getProperty(WithingsDevice.OPTION_OAUTH_CONSUMER_SECRET); + String token = this.getProperty(WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN); + String tokenSecret = this.getProperty(WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN_SECRET); + + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + + String startDate = null; + String endDate = null; + + long startTime = 0; + long endTime = 0; + + if (apiKey != null && apiSecret != null && token != null && tokenSecret != null) { + if (WithingsDevice.API_ACTION_ACTIVITY_URL.equals(apiUrl) || + WithingsDevice.API_ACTION_SLEEP_SUMMARY_URL.equals(apiUrl) || + WithingsDevice.API_ACTION_WORKOUTS_URL.equals(apiUrl)) { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + + startDate = format.format(cal.getTime()); + + cal.add(Calendar.DATE, 1); + + endDate = format.format(cal.getTime()); + } else if (WithingsDevice.API_ACTION_BODY_MEASURES_URL.equals(apiUrl) || + WithingsDevice.API_ACTION_INTRADAY_ACTIVITY_URL.equals(apiUrl) || + WithingsDevice.API_ACTION_SLEEP_MEASURES_URL.equals(apiUrl)) { + + startTime = cal.getTimeInMillis() / 1000; + + cal.add(Calendar.DATE, 1); + + endTime = cal.getTimeInMillis() / 1000; + } + Uri apiUri = Uri.parse(apiUrl); + + Uri.Builder builder = new Uri.Builder(); + builder.scheme(apiUri.getScheme()); + builder.authority(apiUri.getAuthority()); + builder.path(apiUri.getPath()); + + try { + String signature = "GET&" + URLEncoder.encode(builder.build().toString(), "UTF-8"); + + String action = apiUri.getQueryParameter("action"); + String nonce = UUID.randomUUID().toString(); + + builder.appendQueryParameter("action", action); + + if (endTime != 0) { + builder.appendQueryParameter("enddate", "" + endTime); + } + + if (endDate != null) { + builder.appendQueryParameter("enddateymd", endDate); + } + + builder.appendQueryParameter("oauth_consumer_key", apiKey); + builder.appendQueryParameter("oauth_nonce", nonce); + builder.appendQueryParameter("oauth_signature_method", "HMAC-SHA1"); + builder.appendQueryParameter("oauth_timestamp", "" + (System.currentTimeMillis() / 1000)); + builder.appendQueryParameter("oauth_token", token); + builder.appendQueryParameter("oauth_version", "1.0"); + + if (startTime != 0) { + builder.appendQueryParameter("startdate", "" + startTime); + } + + if (startDate != null) { + builder.appendQueryParameter("startdateymd", startDate); + } + + builder.appendQueryParameter("userid", this.getProperty(WithingsDevice.OPTION_OAUTH_ACCESS_USER_ID)); + + Uri baseUri = builder.build(); + + signature += "&" + URLEncoder.encode(baseUri.getEncodedQuery(), "UTF-8"); + + String key = apiSecret + "&" + tokenSecret; + + SecretKeySpec secret = new SecretKeySpec(key.getBytes(), "HmacSHA1"); + Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(secret); + + byte[] bytes = mac.doFinal(signature.getBytes(Charset.forName("UTF-8"))); + + signature = Base64.encodeToString(bytes, Base64.DEFAULT); + + builder.appendQueryParameter("oauth_signature", signature.trim()); + + Uri uri = builder.build(); + + OkHttpClient client = new OkHttpClient(); + + Request request = new Request.Builder() + .url(uri.toString()) + .build(); + + Response response = client.newCall(request).execute(); + + if (response.isSuccessful()) { + return new JSONObject(response.body().string()); + } + } catch (NoSuchAlgorithmException e) { + AppEvent.getInstance(me.mContext).logThrowable(e); + } catch (UnsupportedEncodingException e) { + AppEvent.getInstance(me.mContext).logThrowable(e); + } catch (IOException e) { + AppEvent.getInstance(me.mContext).logThrowable(e); + } catch (InvalidKeyException e) { + AppEvent.getInstance(me.mContext).logThrowable(e); + } catch (JSONException e) { + AppEvent.getInstance(me.mContext).logThrowable(e); + } + } + + return null; + } + + private void fetchActivityMeasures() { + JSONObject response = this.queryApi(WithingsDevice.API_ACTION_ACTIVITY_URL); + + if (response != null) { + try { + if (response.getInt("status") == 0) { + JSONObject body = response.getJSONObject("body"); + JSONArray activities = body.getJSONArray("activities"); + + for (int i = 0; i < activities.length(); i++) { + JSONObject activity = activities.getJSONObject(i); + + Calendar cal = Calendar.getInstance(); + + String[] tokens = activity.getString("date").split("-"); + + cal.set(Calendar.YEAR, Integer.parseInt(tokens[0])); + cal.set(Calendar.MONTH, Integer.parseInt(tokens[1])); + cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(tokens[2])); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + + ContentValues values = new ContentValues(); + values.put(WithingsDevice.HISTORY_OBSERVED, System.currentTimeMillis()); + + values.put(WithingsDevice.ACTIVITY_MEASURE_HISTORY_DATE_START, cal.getTimeInMillis()); + values.put(WithingsDevice.ACTIVITY_MEASURE_HISTORY_TIMEZONE, activity.getString("timezone")); + values.put(WithingsDevice.ACTIVITY_MEASURE_STEPS, activity.getDouble("steps")); + values.put(WithingsDevice.ACTIVITY_MEASURE_DISTANCE, activity.getDouble("distance")); + values.put(WithingsDevice.ACTIVITY_MEASURE_ACTIVE_CALORIES, activity.getDouble("calories")); + values.put(WithingsDevice.ACTIVITY_MEASURE_TOTAL_CALORIES, activity.getDouble("totalcalories")); + values.put(WithingsDevice.ACTIVITY_MEASURE_ELEVATION, activity.getDouble("elevation")); + values.put(WithingsDevice.ACTIVITY_MEASURE_SOFT_ACTIVITY_DURATION, activity.getDouble("soft")); + values.put(WithingsDevice.ACTIVITY_MEASURE_MODERATE_ACTIVITY_DURATION, activity.getDouble("moderate")); + values.put(WithingsDevice.ACTIVITY_MEASURE_INTENSE_ACTIVITY_DURATION, activity.getDouble("intense")); + + this.mDatabase.insert(WithingsDevice.TABLE_ACTIVITY_MEASURE_HISTORY, null, values); + + Bundle updated = new Bundle(); + + updated.putLong(WithingsDevice.HISTORY_OBSERVED, System.currentTimeMillis()); + updated.putLong(WithingsDevice.ACTIVITY_MEASURE_HISTORY_DATE_START, cal.getTimeInMillis()); + updated.putString(WithingsDevice.ACTIVITY_MEASURE_HISTORY_TIMEZONE, activity.getString("timezone")); + updated.putDouble(WithingsDevice.ACTIVITY_MEASURE_STEPS, activity.getDouble("steps")); + updated.putDouble(WithingsDevice.ACTIVITY_MEASURE_DISTANCE, activity.getDouble("distance")); + updated.putDouble(WithingsDevice.ACTIVITY_MEASURE_ACTIVE_CALORIES, activity.getDouble("calories")); + updated.putDouble(WithingsDevice.ACTIVITY_MEASURE_TOTAL_CALORIES, activity.getDouble("totalcalories")); + updated.putDouble(WithingsDevice.ACTIVITY_MEASURE_ELEVATION, activity.getDouble("elevation")); + updated.putDouble(WithingsDevice.ACTIVITY_MEASURE_SOFT_ACTIVITY_DURATION, activity.getDouble("soft")); + updated.putDouble(WithingsDevice.ACTIVITY_MEASURE_MODERATE_ACTIVITY_DURATION, activity.getDouble("moderate")); + updated.putDouble(WithingsDevice.ACTIVITY_MEASURE_INTENSE_ACTIVITY_DURATION, activity.getDouble("intense")); + updated.putString(WithingsDevice.DATASTREAM, WithingsDevice.DATASTREAM_ACTIVITY_MEASURES); + + Generators.getInstance(this.mContext).notifyGeneratorUpdated(WithingsDevice.GENERATOR_IDENTIFIER, updated); + } + } + } catch (JSONException e) { + AppEvent.getInstance(this.mContext).logThrowable(e); + } + } + } + + private void fetchBodyMeasures() { + JSONObject response = this.queryApi(WithingsDevice.API_ACTION_BODY_MEASURES_URL); + + if (response != null) { + try { + if (response.getInt("status") == 0) { + JSONObject body = response.getJSONObject("body"); + JSONArray measureGroups = body.getJSONArray("measuregrps"); + + for (int i = 0; i < measureGroups.length(); i++) { + JSONObject measureGroup = measureGroups.getJSONObject(i); + + long measureDate = measureGroup.getLong("date"); + long now = System.currentTimeMillis(); + + String status = WithingsDevice.BODY_MEASURE_STATUS_UNKNOWN; + + switch (measureGroup.getInt("attrib")) { + case 0: + status = WithingsDevice.BODY_MEASURE_STATUS_USER_DEVICE; + break; + case 1: + status = WithingsDevice.BODY_MEASURE_STATUS_SHARED_DEVICE; + break; + case 2: + status = WithingsDevice.BODY_MEASURE_STATUS_MANUAL_ENTRY; + break; + case 4: + status = WithingsDevice.BODY_MEASURE_STATUS_MANUAL_ENTRY_CREATION; + break; + case 5: + status = WithingsDevice.BODY_MEASURE_STATUS_AUTO_DEVICE; + break; + case 7: + status = WithingsDevice.BODY_MEASURE_STATUS_MEASURE_CONFIRMED; + break; + } + + String category = WithingsDevice.BODY_MEASURE_CATEGORY_UNKNOWN; + + switch (measureGroup.getInt("category")) { + case 1: + category = WithingsDevice.BODY_MEASURE_CATEGORY_REAL_MEASUREMENTS; + break; + case 2: + category = WithingsDevice.BODY_MEASURE_CATEGORY_USER_OBJECTIVES; + break; + } + + JSONArray measures = measureGroup.getJSONArray("measures"); + + for (int j = 0; j < measures.length(); j++) { + JSONObject measure = measures.optJSONObject(j); + + ContentValues values = new ContentValues(); + values.put(WithingsDevice.HISTORY_OBSERVED, now); + + String type = WithingsDevice.BODY_MEASURE_TYPE_UNKNOWN; + + switch (measure.getInt("type")) { + case 1: + type = WithingsDevice.BODY_MEASURE_TYPE_WEIGHT; + break; + case 4: + type = WithingsDevice.BODY_MEASURE_TYPE_HEIGHT; + break; + case 5: + type = WithingsDevice.BODY_MEASURE_TYPE_FAT_FREE_MASS; + break; + case 6: + type = WithingsDevice.BODY_MEASURE_TYPE_FAT_RATIO; + break; + case 8: + type = WithingsDevice.BODY_MEASURE_TYPE_FAT_MASS_WEIGHT; + break; + case 9: + type = WithingsDevice.BODY_MEASURE_TYPE_DIASTOLIC_BLOOD_PRESSURE; + break; + case 10: + type = WithingsDevice.BODY_MEASURE_TYPE_SYSTOLIC_BLOOD_PRESSURE; + break; + case 11: + type = WithingsDevice.BODY_MEASURE_TYPE_HEART_PULSE; + break; + case 12: + type = WithingsDevice.BODY_MEASURE_TYPE_TEMPERATURE; + break; + case 54: + type = WithingsDevice.BODY_MEASURE_TYPE_OXYGEN_SATURATION; + break; + case 71: + type = WithingsDevice.BODY_MEASURE_TYPE_BODY_TEMPERATURE; + break; + case 73: + type = WithingsDevice.BODY_MEASURE_TYPE_SKIN_TEMPERATURE; + break; + case 76: + type = WithingsDevice.BODY_MEASURE_TYPE_MUSCLE_MASS; + break; + case 77: + type = WithingsDevice.BODY_MEASURE_TYPE_HYDRATION; + break; + case 88: + type = WithingsDevice.BODY_MEASURE_TYPE_BONE_MASS; + break; + case 91: + type = WithingsDevice.BODY_MEASURE_TYPE_PULSE_WAVE_VELOCITY; + break; + } + + double value = measure.getDouble("value") * Math.pow(10, measure.getDouble("unit")); + + values.put(WithingsDevice.BODY_MEASURE_HISTORY_DATE, measureDate); + values.put(WithingsDevice.BODY_MEASURE_HISTORY_STATUS, status); + values.put(WithingsDevice.BODY_MEASURE_HISTORY_CATEGORY, category); + values.put(WithingsDevice.BODY_MEASURE_HISTORY_TYPE, type); + values.put(WithingsDevice.BODY_MEASURE_HISTORY_VALUE, value); + + this.mDatabase.insert(WithingsDevice.TABLE_BODY_MEASURE_HISTORY, null, values); + + Bundle updated = new Bundle(); + + updated.putLong(WithingsDevice.HISTORY_OBSERVED, System.currentTimeMillis()); + updated.putLong(WithingsDevice.BODY_MEASURE_HISTORY_DATE, measureDate); + updated.putString(WithingsDevice.BODY_MEASURE_HISTORY_STATUS, status); + updated.putString(WithingsDevice.BODY_MEASURE_HISTORY_CATEGORY, category); + updated.putString(WithingsDevice.BODY_MEASURE_HISTORY_TYPE, type); + updated.putDouble(WithingsDevice.BODY_MEASURE_HISTORY_VALUE, value); + + Generators.getInstance(this.mContext).notifyGeneratorUpdated(WithingsDevice.GENERATOR_IDENTIFIER, updated); + } + } + } + } catch (JSONException e) { + AppEvent.getInstance(this.mContext).logThrowable(e); + } + } + } + + private void fetchIntradayActivities() { + JSONObject response = this.queryApi(WithingsDevice.API_ACTION_INTRADAY_ACTIVITY_URL); + + if (response != null) { + // TODO + } + } + + private void fetchSleepMeasures() { + JSONObject response = this.queryApi(WithingsDevice.API_ACTION_SLEEP_MEASURES_URL); + + if (response != null) { + try { + if (response.getInt("status") == 0) { + JSONObject body = response.getJSONObject("body"); + + String model = WithingsDevice.SLEEP_MEASURE_MODEL_UNKNOWN; + + switch(body.getInt("model")) { + case 16: + model = WithingsDevice.SLEEP_MEASURE_MODEL_ACTIVITY_TRACKER; + break; + case 32: + model = WithingsDevice.SLEEP_MEASURE_MODEL_AURA; + break; + } + + JSONArray series = body.getJSONArray("series"); + + for (int i = 0; i < series.length(); i++) { + JSONObject item = series.getJSONObject(i); + + long now = System.currentTimeMillis(); + + String state = WithingsDevice.SLEEP_MEASURE_STATE_UNKNOWN; + + switch (item.getInt("state")) { + case 0: + state = WithingsDevice.SLEEP_MEASURE_STATE_AWAKE; + break; + case 1: + state = WithingsDevice.SLEEP_MEASURE_STATE_LIGHT_SLEEP; + break; + case 2: + state = WithingsDevice.SLEEP_MEASURE_STATE_DEEP_SLEEP; + break; + case 3: + state = WithingsDevice.SLEEP_MEASURE_STATE_REM_SLEEP; + break; + } + + ContentValues values = new ContentValues(); + values.put(WithingsDevice.HISTORY_OBSERVED, now); + values.put(WithingsDevice.SLEEP_MEASURE_START_DATE, item.getLong("startdate")); + values.put(WithingsDevice.SLEEP_MEASURE_END_DATE, item.getLong("enddate")); + values.put(WithingsDevice.SLEEP_MEASURE_STATE, state); + values.put(WithingsDevice.SLEEP_MEASURE_MEASUREMENT_DEVICE, model); + + this.mDatabase.insert(WithingsDevice.TABLE_SLEEP_MEASURE_HISTORY, null, values); + + Bundle updated = new Bundle(); + updated.putLong(WithingsDevice.HISTORY_OBSERVED, System.currentTimeMillis()); + updated.putLong(WithingsDevice.SLEEP_MEASURE_START_DATE, item.getLong("startdate")); + updated.putLong(WithingsDevice.SLEEP_MEASURE_END_DATE, item.getLong("enddate")); + updated.putString(WithingsDevice.SLEEP_MEASURE_STATE, state); + updated.putString(WithingsDevice.SLEEP_MEASURE_MEASUREMENT_DEVICE, model); + + Generators.getInstance(this.mContext).notifyGeneratorUpdated(WithingsDevice.GENERATOR_IDENTIFIER, updated); + } + } + } catch (JSONException e) { + AppEvent.getInstance(this.mContext).logThrowable(e); + } + } + } + + private void fetchSleepSummary() { + JSONObject response = this.queryApi(WithingsDevice.API_ACTION_SLEEP_SUMMARY_URL); + + if (response != null) { + try { + if (response.getInt("status") == 0) { + JSONObject body = response.getJSONObject("body"); + + JSONArray series = body.getJSONArray("series"); + + long now = System.currentTimeMillis(); + + for (int i = 0; i < series.length(); i++) { + JSONObject item = series.getJSONObject(i); + + String timezone = body.getString("timezone"); + + String model = WithingsDevice.SLEEP_SUMMARY_MODEL_UNKNOWN; + + switch(body.getInt("model")) { + case 16: + model = WithingsDevice.SLEEP_SUMMARY_MODEL_ACTIVITY_TRACKER; + break; + case 32: + model = WithingsDevice.SLEEP_SUMMARY_MODEL_AURA; + break; + } + + JSONObject data = item.getJSONObject("data"); + + ContentValues values = new ContentValues(); + values.put(WithingsDevice.HISTORY_OBSERVED, now); + values.put(WithingsDevice.SLEEP_SUMMARY_START_DATE, item.getLong("startdate")); + values.put(WithingsDevice.SLEEP_SUMMARY_END_DATE, item.getLong("enddate")); + values.put(WithingsDevice.SLEEP_SUMMARY_TIMEZONE, timezone); + values.put(WithingsDevice.SLEEP_SUMMARY_MEASUREMENT_DEVICE, model); + values.put(WithingsDevice.SLEEP_SUMMARY_WAKE_DURATION, data.getDouble("wakeupduration")); + values.put(WithingsDevice.SLEEP_SUMMARY_LIGHT_SLEEP_DURATION, data.getDouble("lightsleepduration")); + values.put(WithingsDevice.SLEEP_SUMMARY_DEEP_SLEEP_DURATION, data.getDouble("deepsleepduration")); + values.put(WithingsDevice.SLEEP_SUMMARY_TO_SLEEP_DURATION, data.getDouble("durationtosleep")); + values.put(WithingsDevice.SLEEP_SUMMARY_WAKE_COUNT, data.getDouble("wakeupcount")); + + if (data.has("remsleepduration")) { + values.put(WithingsDevice.SLEEP_SUMMARY_REM_SLEEP_DURATION, data.getDouble("remsleepduration")); + } + + if (data.has("durationtowakeup")) { + values.put(WithingsDevice.SLEEP_SUMMARY_TO_WAKE_DURATION, data.getDouble("durationtowakeup")); + } + + this.mDatabase.insert(WithingsDevice.TABLE_SLEEP_MEASURE_HISTORY, null, values); + + Bundle updated = new Bundle(); + updated.putLong(WithingsDevice.HISTORY_OBSERVED, now); + updated.putLong(WithingsDevice.SLEEP_SUMMARY_START_DATE, item.getLong("startdate")); + updated.putLong(WithingsDevice.SLEEP_SUMMARY_END_DATE, item.getLong("enddate")); + updated.putString(WithingsDevice.SLEEP_SUMMARY_TIMEZONE, timezone); + updated.putString(WithingsDevice.SLEEP_SUMMARY_MEASUREMENT_DEVICE, model); + updated.putDouble(WithingsDevice.SLEEP_SUMMARY_WAKE_DURATION, data.getDouble("wakeupduration")); + updated.putDouble(WithingsDevice.SLEEP_SUMMARY_LIGHT_SLEEP_DURATION, data.getDouble("lightsleepduration")); + updated.putDouble(WithingsDevice.SLEEP_SUMMARY_DEEP_SLEEP_DURATION, data.getDouble("deepsleepduration")); + updated.putDouble(WithingsDevice.SLEEP_SUMMARY_TO_SLEEP_DURATION, data.getDouble("durationtosleep")); + updated.putDouble(WithingsDevice.SLEEP_SUMMARY_WAKE_COUNT, data.getDouble("wakeupcount")); + + if (data.has("remsleepduration")) { + updated.putDouble(WithingsDevice.SLEEP_SUMMARY_REM_SLEEP_DURATION, data.getDouble("remsleepduration")); + } + + if (data.has("durationtowakeup")) { + updated.putDouble(WithingsDevice.SLEEP_SUMMARY_TO_WAKE_DURATION, data.getDouble("durationtowakeup")); + } + + Generators.getInstance(this.mContext).notifyGeneratorUpdated(WithingsDevice.GENERATOR_IDENTIFIER, updated); + } + } + } catch (JSONException e) { + AppEvent.getInstance(this.mContext).logThrowable(e); + } + } + } + + private void fetchWorkouts() { + JSONObject response = this.queryApi(WithingsDevice.API_ACTION_WORKOUTS_URL); + + if (response != null) { + + } + } + + private boolean approvalGranted() { + String apiToken = this.getProperty(WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN); + + if (apiToken != null) { + return true; + } + + return false; + } + + public static boolean isEnabled(Context context) { + SharedPreferences prefs = Generators.getInstance(context).getSharedPreferences(context); + + return prefs.getBoolean(WithingsDevice.ENABLED, WithingsDevice.ENABLED_DEFAULT); + } + + public static boolean isRunning(Context context) { + if (WithingsDevice.sInstance == null) { + return false; + } + + return WithingsDevice.sInstance.mHandler != null; + } + + public static ArrayList diagnostics(final Context context) { + final WithingsDevice me = WithingsDevice.getInstance(context); + + ArrayList actions = new ArrayList<>(); + + actions.add(new DiagnosticAction(context.getString(R.string.diagnostic_withings_auth_required_title), context.getString(R.string.diagnostic_withings_auth_required), new Runnable() { + + @Override + public void run() { + Runnable r = new Runnable() { + @Override + public void run() { + try { + String requestUrl = "https://oauth.withings.com/account/request_token"; + + Uri apiUri = Uri.parse(requestUrl); + + Uri.Builder builder = new Uri.Builder(); + builder.scheme(apiUri.getScheme()); + builder.authority(apiUri.getAuthority()); + builder.path(apiUri.getPath()); + + String signature = "GET&" + URLEncoder.encode(builder.build().toString(), "UTF-8"); + + String callbackUrl = me.getProperty(WithingsDevice.OPTION_OAUTH_CALLBACK_URL); + String apiKey = me.getProperty(WithingsDevice.OPTION_OAUTH_CONSUMER_KEY); + String apiSecret = me.getProperty(WithingsDevice.OPTION_OAUTH_CONSUMER_SECRET); + + String nonce = UUID.randomUUID().toString(); + + builder.appendQueryParameter("oauth_callback", callbackUrl); + builder.appendQueryParameter("oauth_consumer_key", apiKey); + builder.appendQueryParameter("oauth_nonce", nonce); + builder.appendQueryParameter("oauth_signature_method", "HMAC-SHA1"); + builder.appendQueryParameter("oauth_timestamp", "" + (System.currentTimeMillis() / 1000)); + builder.appendQueryParameter("oauth_version", "1.0"); + + Uri baseUri = builder.build(); + + signature += "&" + URLEncoder.encode(baseUri.getEncodedQuery(), "UTF-8"); + + String key = apiSecret + "&"; + + SecretKeySpec secret = new SecretKeySpec(key.getBytes(), "HmacSHA1"); + Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(secret); + + byte[] bytes = mac.doFinal(signature.getBytes()); + + signature = Base64.encodeToString(bytes, Base64.DEFAULT).trim(); + + builder.appendQueryParameter("oauth_signature", signature); + + Uri uri = builder.build(); + + OkHttpClient client = new OkHttpClient(); + + Request request = new Request.Builder() + .url(uri.toString()) + .build(); + + Response response = client.newCall(request).execute(); + + String responseBody = response.body().string(); + + StringTokenizer st = new StringTokenizer(responseBody, "&"); + + String requestToken = null; + String requestSecret = null; + + while (st.hasMoreTokens()) { + String authToken = st.nextToken(); + + if (authToken.startsWith("oauth_token=")) { + requestToken = authToken.replace("oauth_token=", ""); + } else if (authToken.startsWith("oauth_token_secret=")) { + requestSecret = authToken.replace("oauth_token_secret=", ""); + } + } + + key = apiSecret + "&" + requestSecret; + + me.setProperty(WithingsDevice.OPTION_OAUTH_REQUEST_SECRET, requestSecret); + + builder = new Uri.Builder(); + builder.scheme("https"); + builder.authority("oauth.withings.com"); + builder.path("/account/authorize"); + + signature = "GET&" + URLEncoder.encode(builder.build().toString(), "UTF-8"); + + nonce = UUID.randomUUID().toString(); + + builder.appendQueryParameter("oauth_consumer_key", apiKey); + builder.appendQueryParameter("oauth_nonce", nonce); + builder.appendQueryParameter("oauth_signature_method", "HMAC-SHA1"); + builder.appendQueryParameter("oauth_timestamp", "" + (System.currentTimeMillis() / 1000)); + builder.appendQueryParameter("oauth_token", requestToken); + builder.appendQueryParameter("oauth_version", "1.0"); + + baseUri = builder.build(); + + signature += "&" + URLEncoder.encode(baseUri.getEncodedQuery(), "UTF-8"); + + secret = new SecretKeySpec(key.getBytes(), "HmacSHA1"); + mac = Mac.getInstance("HmacSHA1"); + mac.init(secret); + + bytes = mac.doFinal(signature.getBytes(Charset.forName("UTF-8"))); + + signature = Base64.encodeToString(bytes, Base64.DEFAULT); + + builder.appendQueryParameter("oauth_signature", signature.trim()); + + Intent intent = new Intent(Intent.ACTION_VIEW, builder.build()); + context.startActivity(intent); + } catch (NoSuchAlgorithmException e) { + AppEvent.getInstance(context).logThrowable(e); + } catch (InvalidKeyException e) { + AppEvent.getInstance(context).logThrowable(e); + } catch (UnsupportedEncodingException e) { + AppEvent.getInstance(context).logThrowable(e); + } catch (IOException e) { + AppEvent.getInstance(context).logThrowable(e); + } + } + }; + + Thread t = new Thread(r); + t.start(); + } + })); + + return actions; + } + + public void finishAuthentication(final Uri responseUri) { + final WithingsDevice me = this; + + Runnable r = new Runnable() { + @Override + public void run() { + try { + String requestUrl = "https://oauth.withings.com/account/access_token"; + + Uri apiUri = Uri.parse(requestUrl); + + Uri.Builder builder = new Uri.Builder(); + builder.scheme(apiUri.getScheme()); + builder.authority(apiUri.getAuthority()); + builder.path(apiUri.getPath()); + + String signature = "GET&" + URLEncoder.encode(builder.build().toString(), "UTF-8"); + + String callbackUrl = me.getProperty(WithingsDevice.OPTION_OAUTH_CALLBACK_URL); + String apiKey = me.getProperty(WithingsDevice.OPTION_OAUTH_CONSUMER_KEY); + String apiSecret = me.getProperty(WithingsDevice.OPTION_OAUTH_CONSUMER_SECRET); + + String nonce = UUID.randomUUID().toString(); + + builder.appendQueryParameter("oauth_consumer_key", apiKey); + builder.appendQueryParameter("oauth_nonce", nonce); + builder.appendQueryParameter("oauth_signature_method", "HMAC-SHA1"); + builder.appendQueryParameter("oauth_timestamp", "" + (System.currentTimeMillis() / 1000)); + builder.appendQueryParameter("oauth_token", responseUri.getQueryParameter("oauth_token")); + builder.appendQueryParameter("oauth_version", "1.0"); + + Uri baseUri = builder.build(); + + signature += "&" + URLEncoder.encode(baseUri.getEncodedQuery(), "UTF-8"); + + String key = apiSecret + "&" + me.getProperty(WithingsDevice.OPTION_OAUTH_REQUEST_SECRET); + + SecretKeySpec secret = new SecretKeySpec(key.getBytes(), "HmacSHA1"); + Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(secret); + + byte[] bytes = mac.doFinal(signature.getBytes()); + + signature = Base64.encodeToString(bytes, Base64.DEFAULT).trim(); + + builder.appendQueryParameter("oauth_signature", signature); + + Uri uri = builder.build(); + + OkHttpClient client = new OkHttpClient(); + + Request request = new Request.Builder() + .url(uri.toString()) + .build(); + + Response response = client.newCall(request).execute(); + + String responseBody = response.body().string(); + + StringTokenizer st = new StringTokenizer(responseBody, "&"); + + while (st.hasMoreTokens()) { + String tokenString = st.nextToken(); + + if (tokenString.startsWith("oauth_token=")) { + me.setProperty(WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN, tokenString.replace("oauth_token=", "")); + } else if (tokenString.startsWith("oauth_token_secret=")) { + me.setProperty(WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN_SECRET, tokenString.replace("oauth_token_secret=", "")); + } else if (tokenString.startsWith("userid=")) { + me.setProperty(WithingsDevice.OPTION_OAUTH_ACCESS_USER_ID, tokenString.replace("userid=", "")); + } + } + + me.fetchActivityMeasures(); + + } catch (UnsupportedEncodingException e) { + AppEvent.getInstance(me.mContext).logThrowable(e); + } catch (NoSuchAlgorithmException e) { + AppEvent.getInstance(me.mContext).logThrowable(e); + } catch (IOException e) { + AppEvent.getInstance(me.mContext).logThrowable(e); + } catch (InvalidKeyException e) { + AppEvent.getInstance(me.mContext).logThrowable(e); + } + } + }; + + Thread t = new Thread(r); + t.start(); + } + + public static void bindViewHolder(DataPointViewHolder holder) { +/* + final Context context = holder.itemView.getContext(); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + long lastTimestamp = 0; + long lastDuration = 0; + String callType = null; + + long totalIncoming = 0; + long totalOutgoing = 0; + long totalMissed = 0; + long total = 0; + + WithingsDevice generator = WithingsDevice.getInstance(holder.itemView.getContext()); + + View cardContent = holder.itemView.findViewById(R.id.card_content); + View cardEmpty = holder.itemView.findViewById(R.id.card_empty); + TextView dateLabel = (TextView) holder.itemView.findViewById(R.id.generator_data_point_date); +*/ + } + + @Override + public List fetchPayloads() { + return new ArrayList<>(); + } + + public static View fetchView(ViewGroup parent) + { + return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_withings_device, parent, false); + } + + public static long latestPointGenerated(Context context) { + long timestamp = 0; + + WithingsDevice me = WithingsDevice.getInstance(context); + +// Cursor c = me.mDatabase.query(WithingsDevice.TABLE_HISTORY, null, null, null, null, null, WithingsDevice.HISTORY_OBSERVED + " DESC"); +// +// if (c.moveToNext()) { +// timestamp = c.getLong(c.getColumnIndex(WithingsDevice.HISTORY_OBSERVED)); +// } +// +// c.close(); + + return timestamp; + } + + public void setProperty(String key, String value) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + + if (WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN.equals(key)) { + SharedPreferences.Editor e = prefs.edit(); + e.putString(WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN, value); + e.apply(); + } else if (WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN_SECRET.equals(key)) { + SharedPreferences.Editor e = prefs.edit(); + e.putString(WithingsDevice.OPTION_OAUTH_ACCESS_TOKEN_SECRET, value); + e.apply(); + } else if (WithingsDevice.OPTION_OAUTH_ACCESS_USER_ID.equals(key)) { + SharedPreferences.Editor e = prefs.edit(); + e.putString(WithingsDevice.OPTION_OAUTH_ACCESS_USER_ID, value); + e.apply(); + } + + this.mProperties.put(key, value); + } +} diff --git a/src/com/audacious_software/passive_data_kit/transmitters/HttpTransmitter.java b/src/com/audacious_software/passive_data_kit/transmitters/HttpTransmitter.java index 93f74d3..1d9a2cb 100755 --- a/src/com/audacious_software/passive_data_kit/transmitters/HttpTransmitter.java +++ b/src/com/audacious_software/passive_data_kit/transmitters/HttpTransmitter.java @@ -46,7 +46,7 @@ import okhttp3.Response; import okio.Buffer; -public class HttpTransmitter extends Transmitter implements Generators.NewDataPointListener { +public class HttpTransmitter extends Transmitter implements Generators.GeneratorUpdatedListener { public static final String UPLOAD_URI = "com.audacious_software.passive_data_kit.transmitters.HttpTransmitter.UPLOAD_URI"; public static final String USER_ID = "com.audacious_software.passive_data_kit.transmitters.HttpTransmitter.USER_ID"; private static final String HASH_ALGORITHM = "com.audacious_software.passive_data_kit.transmitters.HttpTransmitter.HASH_ALGORITHM"; @@ -133,16 +133,23 @@ else if (!options.containsKey(HttpTransmitter.USER_ID)) { this.mContext = context.getApplicationContext(); - Generators.getInstance(this.mContext).addNewDataPointListener(this); + Generators.getInstance(this.mContext).addNewGeneratorUpdatedListener(this); } - private boolean shouldAttemptUpload() { + private boolean shouldAttemptUpload(boolean force) { + if (force) { + return true; + } if (this.mWifiOnly) { - return DeviceInformation.wifiAvailable(this.mContext); + if (DeviceInformation.wifiAvailable(this.mContext) == false) { + return false; + } } if (this.mChargingOnly) { - return DeviceInformation.isPluggedIn(this.mContext); + if (DeviceInformation.isPluggedIn(this.mContext) == false) { + return false; + } } return true; @@ -156,7 +163,7 @@ public void transmit(boolean force) { this.mLastAttempt = 0; } - if (now - this.mLastAttempt < this.mUploadInterval || !this.shouldAttemptUpload()) { + if (now - this.mLastAttempt < this.mUploadInterval || !this.shouldAttemptUpload(force)) { return; } @@ -314,7 +321,6 @@ private int transmitHttpPayload(String payload) { builder = builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"payload\""), RequestBody.create(null, payload)); - RequestBody requestBody = builder.build(); if (this.mUserAgent == null) { @@ -418,12 +424,25 @@ public long transmittedSize() { } @Override - public void onNewDataPoint(String identifier, Bundle data) { + public void onGeneratorUpdated(String identifier, long timestamp, Bundle data) { if (data.keySet().size() > 1) { // Only transmit non-empty bundles... + timestamp = timestamp / 1000; // Convert to seconds... + + Generators generators = Generators.getInstance(this.mContext); + + Bundle metadata = new Bundle(); + if (data.containsKey(Generator.PDK_METADATA)) { - data.getBundle(Generator.PDK_METADATA).putString(Generator.SOURCE, this.mUserId); + metadata = data.getBundle(Generator.PDK_METADATA); } + metadata.putString(Generator.IDENTIFIER, identifier); + metadata.putDouble(Generator.TIMESTAMP, timestamp); + metadata.putString(Generator.GENERATOR, generators.getGeneratorFullName(identifier)); + metadata.putString(Generator.SOURCE, generators.getSource()); + metadata.putString(Generator.SOURCE, this.mUserId); + data.putBundle(Generator.PDK_METADATA, metadata); + if (this.mJsonGenerator == null) { this.mCurrentFile = new File(this.getPendingFolder(), System.currentTimeMillis() + HttpTransmitter.TEMP_EXTENSION); @@ -591,6 +610,10 @@ else if (value instanceof Bundle) { } } + public void setUserId(String userId) { + this.mUserId = userId; + } + public static class IncompleteConfigurationException extends RuntimeException { public IncompleteConfigurationException(String message) { super(message); From 24898e246a5b4861d1c51d86ebd4d4676a5666f0 Mon Sep 17 00:00:00 2001 From: "Chris J. Karr" Date: Mon, 17 Apr 2017 18:11:21 -0500 Subject: [PATCH 03/10] Generator work. * Implemented most of the device battery generator. * Created placeholder files for Accelerometer and Ambient Light. --- res/layout/card_generator_app_event.xml | 63 ++++ res/layout/card_generator_device_battery.xml | 63 ++++ res/values/databases.xml | 3 + res/values/generators.xml | 8 + .../generators/DataPointsAdapter.java | 5 + .../generators/Generators.java | 3 + .../generators/device/Battery.java | 311 ++++++++++++++++++ .../generators/diagnostics/AppEvent.java | 230 +------------ .../generators/sensors/Accelerometer.java | 8 + .../generators/sensors/AmbientLight.java | 8 + 10 files changed, 480 insertions(+), 222 deletions(-) create mode 100755 res/layout/card_generator_app_event.xml create mode 100755 res/layout/card_generator_device_battery.xml create mode 100755 src/com/audacious_software/passive_data_kit/generators/device/Battery.java create mode 100755 src/com/audacious_software/passive_data_kit/generators/sensors/Accelerometer.java create mode 100755 src/com/audacious_software/passive_data_kit/generators/sensors/AmbientLight.java diff --git a/res/layout/card_generator_app_event.xml b/res/layout/card_generator_app_event.xml new file mode 100755 index 0000000..02f65b2 --- /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_device_battery.xml b/res/layout/card_generator_device_battery.xml new file mode 100755 index 0000000..3244646 --- /dev/null +++ b/res/layout/card_generator_device_battery.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/databases.xml b/res/values/databases.xml index 672f1cd..57f13d5 100755 --- a/res/values/databases.xml +++ b/res/values/databases.xml @@ -16,4 +16,7 @@ CREATE TABLE history(_id INTEGER PRIMARY KEY AUTOINCREMENT, fetched INTEGER, transmitted INTEGER, observed INTEGER, event_name TEXT, event_details 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); + \ No newline at end of file diff --git a/res/values/generators.xml b/res/values/generators.xml index a6e8f0e..03c8ad8 100755 --- a/res/values/generators.xml +++ b/res/values/generators.xml @@ -116,4 +116,12 @@ No phone calls have been made or received on this device. + + Device Battery + No battery levels have been reported yet. + + + App Event History + No app events have been logged yet. + \ No newline at end of file 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..b60d419 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 @@ -2,6 +2,7 @@ import android.content.Context; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -36,6 +37,8 @@ public DataPointViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { Method fetchView = generatorClass.getDeclaredMethod("fetchView", ViewGroup.class); View view = (View) fetchView.invoke(null, parent); + Log.e("PDK", "GENERATOR CLASS: " + generatorClass); + return new DataPointViewHolder(view); } catch (NoSuchMethodException e1) { Logger.getInstance(parent.getContext()).logThrowable(e1); @@ -67,6 +70,8 @@ public void onBindViewHolder(final DataPointViewHolder holder, int position) { Method bindViewHolder = generatorClass.getDeclaredMethod("bindViewHolder", DataPointViewHolder.class); + Log.e("PDK", "GENERATOR CLASS: " + generatorClass); + bindViewHolder.invoke(null, holder); } catch (NoSuchMethodException e1) { Logger.getInstance(holder.itemView.getContext()).logThrowable(e1); 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..8924585 100755 --- a/src/com/audacious_software/passive_data_kit/generators/Generators.java +++ b/src/com/audacious_software/passive_data_kit/generators/Generators.java @@ -233,6 +233,9 @@ public List> activeGenerators() { for (String className : this.mActiveGenerators) { try { active.add((Class) Class.forName(className)); + + Log.e("PDK", "ACTIVE GENERATOR CLASS: " + className); + } catch (ClassNotFoundException e) { e.printStackTrace(); } 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..c99b1c1 --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/generators/device/Battery.java @@ -0,0 +1,311 @@ +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.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 java.io.File; +import java.util.ArrayList; +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; + + 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; + + this.mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(final Context context, Intent intent) { + long now = System.currentTimeMillis(); + + 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); + } + }; + + 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); + + Cursor c = generator.mDatabase.query(Battery.TABLE_HISTORY, null, null, null, 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)); + + TextView lastLevel = (TextView) holder.itemView.findViewById(R.id.card_last_battery_level); + lastLevel.setText("TODO: LEVEL " + c.getInt(c.getColumnIndex(Battery.HISTORY_LEVEL))); + } 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) { + long timestamp = 0; + + Battery me = Battery.getInstance(context); + + Cursor c = me.mDatabase.query(Battery.TABLE_HISTORY, null, null, null, null, null, Battery.HISTORY_OBSERVED + " DESC"); + + if (c.moveToNext()) { + timestamp = c.getLong(c.getColumnIndex(Battery.HISTORY_OBSERVED)); + } + + c.close(); + + return timestamp; + } + + 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/diagnostics/AppEvent.java b/src/com/audacious_software/passive_data_kit/generators/diagnostics/AppEvent.java index e92d291..75c420f 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 @@ -9,6 +9,7 @@ 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; @@ -96,37 +97,11 @@ public static ArrayList diagnostics(Context context) { } public static void bindViewHolder(DataPointViewHolder holder) { -/* final Context context = holder.itemView.getContext(); + 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); + AppEvent generator = AppEvent.getInstance(context); - long zeroStart = cal.getTimeInMillis(); - cal.add(Calendar.DATE, -1); - - LinearLayout zeroTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_zero_value); - - AppEvents.populateTimeline(context, zeroTimeline, zeroStart); - - long oneStart = cal.getTimeInMillis(); - cal.add(Calendar.DATE, -1); - - LinearLayout oneTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_one_value); - - AppEvents.populateTimeline(context, oneTimeline, oneStart); - - long twoStart = cal.getTimeInMillis(); - - LinearLayout twoTimeline = (LinearLayout) holder.itemView.findViewById(R.id.day_two_value); - - AppEvents.populateTimeline(context, twoTimeline, twoStart); - - AppEvents generator = AppEvents.getInstance(context); - - Cursor c = generator.mDatabase.query(AppEvents.TABLE_HISTORY, null, null, null, null, null, AppEvents.HISTORY_OBSERVED + " DESC"); + 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 cardEmpty = holder.itemView.findViewById(R.id.card_empty); @@ -136,25 +111,13 @@ public static void bindViewHolder(DataPointViewHolder holder) { cardContent.setVisibility(View.VISIBLE); cardEmpty.setVisibility(View.GONE); - 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 eventCount = (TextView) holder.itemView.findViewById(R.id.card_app_event_count); - TextView twoDayLabel = (TextView) holder.itemView.findViewById(R.id.day_two_label); - twoDayLabel.setText(format.format(cal.getTime())); + eventCount.setText("TODO: EVENT VIEW - " + c.getCount()); } else { cardContent.setVisibility(View.GONE); cardEmpty.setVisibility(View.VISIBLE); @@ -163,188 +126,11 @@ public static void bindViewHolder(DataPointViewHolder holder) { } c.close(); - */ - } - - /* - private static void populateTimeline(Context context, LinearLayout timeline, long start) { - timeline.removeAllViews(); - - AppEvents generator = AppEvents.getInstance(context); - - long end = start + (24 * 60 * 60 * 1000); - - String where = AppEvents.HISTORY_OBSERVED + " >= ? AND " + AppEvents.HISTORY_OBSERVED + " < ?"; - String[] args = { "" + start, "" + end }; - - Cursor c = generator.mDatabase.query(AppEvents.TABLE_HISTORY, null, where, args, null, null, AppEvents.HISTORY_OBSERVED); - - ArrayList activeStates = new ArrayList<>(); - ArrayList activeTimestamps = new ArrayList<>(); - - while (c.moveToNext()) { - long timestamp = c.getLong(c.getColumnIndex(AppEvents.HISTORY_OBSERVED)); - - activeTimestamps.add(timestamp); - - String state = c.getString(c.getColumnIndex(AppEvents.HISTORY_STATE)); - activeStates.add(state); - } - - c.close(); - - String lastState = AppEvents.STATE_UNKNOWN; - - String lastWhere = AppEvents.HISTORY_OBSERVED + " < ?"; - String[] lastArgs = { "" + start }; - - c = generator.mDatabase.query(AppEvents.TABLE_HISTORY, null, lastWhere, lastArgs, null, null, AppEvents.HISTORY_OBSERVED + " DESC"); - - if (c.moveToNext()) { - lastState = c.getString(c.getColumnIndex(AppEvents.HISTORY_STATE)); - } - - if (activeStates.size() > 0) { - long firstTimestamp = activeTimestamps.get(0); - long firstState = activeTimestamps.get(0); - - View startView = new View(context); - - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { - if (AppEvents.STATE_UNKNOWN.equals(lastState)) { - - } else if (AppEvents.STATE_ON.equals(lastState)) { - startView.setBackgroundColor(0xff4CAF50); - } else if (AppEvents.STATE_OFF.equals(lastState)) { - startView.setBackgroundColor(0xff263238); - } else if (AppEvents.STATE_DOZE.equals(lastState)) { - startView.setBackgroundColor(0xff1b5e20); - } else if (AppEvents.STATE_DOZE_SUSPEND.equals(lastState)) { - startView.setBackgroundColor(0xff1b5e20); - } - } else { - if (AppEvents.STATE_UNKNOWN.equals(lastState)) { - - } else if (AppEvents.STATE_ON.equals(lastState)) { - startView.setBackgroundColor(0xff4CAF50); - } else if (AppEvents.STATE_OFF.equals(lastState)) { - startView.setBackgroundColor(0xff263238); - } - } - - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, firstTimestamp - start); - startView.setLayoutParams(params); - - timeline.addView(startView); - - long now = System.currentTimeMillis(); - - if (activeStates.size() == 1) { - View v = new View(context); - - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { - if (AppEvents.STATE_ON.equals(firstState)) { - v.setBackgroundColor(0xff4CAF50); - } else if (AppEvents.STATE_OFF.equals(firstState)) { - v.setBackgroundColor(0xff263238); - } else if (AppEvents.STATE_DOZE.equals(firstState)) { - v.setBackgroundColor(0xff3f51b5); - } else if (AppEvents.STATE_DOZE_SUSPEND.equals(firstState)) { - v.setBackgroundColor(0xff3f51b5); - } - } else { - if (AppEvents.STATE_ON.equals(firstState)) { - v.setBackgroundColor(0xff4CAF50); - } else if (AppEvents.STATE_OFF.equals(firstState)) { - v.setBackgroundColor(0xff263238); - } - } - - if (end > System.currentTimeMillis()) { - params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, now - firstTimestamp); - v.setLayoutParams(params); - } else { - params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, end - firstTimestamp); - v.setLayoutParams(params); - } - - timeline.addView(v); - } else { - for (int i = 1; i < activeStates.size(); i++) { - long currentTimestamp = activeTimestamps.get(i); - - long priorTimestamp = activeTimestamps.get(i - 1); - String priorState = activeStates.get(i - 1); - - View v = new View(context); - - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { - if (AppEvents.STATE_ON.equals(priorState)) { - v.setBackgroundColor(0xff4CAF50); - } else if (AppEvents.STATE_OFF.equals(priorState)) { - v.setBackgroundColor(0xff263238); - } else if (AppEvents.STATE_DOZE.equals(priorState)) { - v.setBackgroundColor(0xff3f51b5); - } else if (AppEvents.STATE_DOZE_SUSPEND.equals(priorState)) { - v.setBackgroundColor(0xff3f51b5); - } - } else { - if (AppEvents.STATE_ON.equals(priorState)) { - v.setBackgroundColor(0xff4CAF50); - } else if (AppEvents.STATE_OFF.equals(priorState)) { - v.setBackgroundColor(0xff263238); - } - } - - params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, currentTimestamp - priorTimestamp); - v.setLayoutParams(params); - - timeline.addView(v); - } - - long finalTimestamp = activeTimestamps.get(activeTimestamps.size() - 1); - String finalState = activeStates.get(activeStates.size() - 1); - - View v = new View(context); - - if (AppEvents.STATE_ON.equals(finalState)) { - v.setBackgroundColor(0xff4CAF50); - } else if (AppEvents.STATE_OFF.equals(finalState)) { - v.setBackgroundColor(0xff263238); - } else if (AppEvents.STATE_DOZE.equals(finalState)) { - v.setBackgroundColor(0xff3f51b5); - } else if (AppEvents.STATE_DOZE_SUSPEND.equals(finalState)) { - v.setBackgroundColor(0xff3f51b5); - } - - if (end > now) { - params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, now - finalTimestamp); - v.setLayoutParams(params); - } else { - params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, end - finalTimestamp); - v.setLayoutParams(params); - } - - timeline.addView(v); - } - - if (end > now) { - View v = new View(context); - - params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, end - now); - v.setLayoutParams(params); - - timeline.addView(v); - } - } else { - - } } -*/ public static View fetchView(ViewGroup parent) { - return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_generic, parent, false); + return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_app_event, parent, false); } @Override 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..3828e56 --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/generators/sensors/Accelerometer.java @@ -0,0 +1,8 @@ +package com.audacious_software.passive_data_kit.generators.sensors; + +/** + * Created by cjkarr on 4/17/2017. + */ + +public class Accelerometer { +} 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..09c7f3c --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/generators/sensors/AmbientLight.java @@ -0,0 +1,8 @@ +package com.audacious_software.passive_data_kit.generators.sensors; + +/** + * Created by cjkarr on 4/17/2017. + */ + +public class AmbientLight { +} From 97e8033918eb462a5e8627a102b57fc6b6e1342d Mon Sep 17 00:00:00 2001 From: "Chris J. Karr" Date: Wed, 26 Apr 2017 00:14:56 -0500 Subject: [PATCH 04/10] Withings probe visualization. * Battery probe visualization. * Card UI work --- res/drawable-hdpi/ic_app_events.png | Bin 0 -> 1505 bytes res/drawable-hdpi/ic_device_battery.png | Bin 0 -> 1378 bytes res/drawable-mdpi/ic_app_events.png | Bin 0 -> 1304 bytes res/drawable-mdpi/ic_device_battery.png | Bin 0 -> 1265 bytes res/drawable-xhdpi/ic_app_events.png | Bin 0 -> 1696 bytes res/drawable-xhdpi/ic_device_battery.png | Bin 0 -> 1481 bytes res/drawable-xxhdpi/ic_app_events.png | Bin 0 -> 2094 bytes res/drawable-xxhdpi/ic_device_battery.png | Bin 0 -> 1728 bytes res/drawable-xxxhdpi/ic_app_events.png | Bin 0 -> 2274 bytes res/drawable-xxxhdpi/ic_device_battery.png | Bin 0 -> 1869 bytes res/layout/card_generator_app_event.xml | 4 +- res/layout/card_generator_device_battery.xml | 14 +- res/layout/card_generator_withings_device.xml | 109 +++++- res/values/generators.xml | 18 + .../generators/device/Battery.java | 103 +++++- .../generators/wearables/WithingsDevice.java | 317 ++++++++++++------ 16 files changed, 453 insertions(+), 112 deletions(-) create mode 100755 res/drawable-hdpi/ic_app_events.png create mode 100755 res/drawable-hdpi/ic_device_battery.png create mode 100755 res/drawable-mdpi/ic_app_events.png create mode 100755 res/drawable-mdpi/ic_device_battery.png create mode 100755 res/drawable-xhdpi/ic_app_events.png create mode 100755 res/drawable-xhdpi/ic_device_battery.png create mode 100755 res/drawable-xxhdpi/ic_app_events.png create mode 100755 res/drawable-xxhdpi/ic_device_battery.png create mode 100755 res/drawable-xxxhdpi/ic_app_events.png create mode 100755 res/drawable-xxxhdpi/ic_device_battery.png diff --git a/res/drawable-hdpi/ic_app_events.png b/res/drawable-hdpi/ic_app_events.png new file mode 100755 index 0000000000000000000000000000000000000000..508e84542ff3f2bf151d58e9289968bad84ce2e0 GIT binary patch literal 1505 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBBuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFl%InM3hAM`dB6B=jtVb)aX^@765fKFxc2v6eK2Rr0UTq__OB&@Hb09I0xZL0)vRD^GUf^&XRs)DJWnQpS7v4w)UrJkXw zrG=4+j)IYap_#scrM{twu7RPIfu)s!p#l^r0c|TvNwW%aaf8|g1^l#~=$>Fbx5 zm+O@q>*W`v>l<2HTIw4Z=^Gj80#)c1SLT%@R_NvxE5l51Ni9w;$}A|!%+FH*nV6WA zUs__T1av9H3%LbwWAlok!2}F2{ffi_eM3D1ke6TzeSPsO&CP|YE-nd5MYtEM!Nnn! z1*!T$sm1xFMajU3OH&3}Rbb^@l$uzQUlfv`p92fUfQY#ZiqFjoo3sy}I{#PUg`r0B z({zPbTNm)Hi(!n~BNCz~cp=kp8QUEzeMf&)v5BqbKF3O$%`0^UWqZ_G4ouSP5AQNe z+`zSI!L6i!^&4JYVCWNVvTN)}@v${eJF!kd`?SVUea(4So6$CkF;(z>+p+Dn!m+E#Lfx9q z@wZ|Za{OK@rNL&J5dLGS=e6>n4F5vOnPDmi>{cWfXA8Z!8#wFu4t`sv#Sf;KG@X7R zwcMKdU4!feuC$%$^#^kW*s|3;KS;}Hc2C)5sIweRz(_++Z+|v}L+= zaAKzfSMS0uA-mIS*7ZJ)`6=MTc|N{DZej1QoSP@sPmW=&W&G@Pn`QGUj$e6qpT0_| zm6zB!`NN;NQOVU$LLR*}`=K{efmKdR?LXJMje9HX+1X#%?mw`-;qKzOi@n2-JM8=1 z#`N3~ zw;+L|(I48Urq8|cblL*>H$GezTTkmc9!pOW*7@yv!TrMfE9(yZcmBV0fiWW+!;Ow@ Uf0*h-et-&HPgg&ebxsLQ04^#mng9R* literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/ic_device_battery.png b/res/drawable-hdpi/ic_device_battery.png new file mode 100755 index 0000000000000000000000000000000000000000..000f85c49252bd5ef5900e2d54a40e331331f722 GIT binary patch literal 1378 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBBuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFl%InM3hAM`dB6B=jtVb)aX^@765fKFxc2v6eK2Rr0UTq__OB&@Hb09I0xZL0)vRD^GUf^&XRs)DJWnQpS7v4w)UrJkXw zrG=4+j)IYap_#scrM{twu7RPIfu)s!p#l^r0c|TvNwW%aaf8|g1^l#~=$>Fbx5 zm+O@q>*W`v>l<2HTIw4Z=^Gj80#)c1SLT%@R_NvxE5l51Ni9w;$}A|!%+FH*nV6WA zUs__T1av9H3%LbwWAlok!2}F2{ffi_eM3D1ke6TzeSPsO&CP|YE-nd5MYtEM!Nnn! z1*!T$sm1xFMajU3OH&3}Rbb^@l$uzQUlfv`p92fUfQEaktaqG;Cz23}@0!NDt>zcW^%C0Y&5E51Kb@6jfMIpVD$x^Ra z4li(hc+0(v@KS%NvWB-@ctEwkVJipdT;UuU2kF;eh->arA za&d~*7vl}q|A8Rl|tOgVLddB)<PQPUEnQz%^lQ>obFH-}@=jsO+f=#G#K)prfz66PFT ziphN?xA{dynU3cs@RykE+7YID*z$?f*6C6UMPImVeb07#*6of!=c{~$ygWMs-lhii zUp_at;9!8%6Q6ZmAxHA0RpTGeoA-`qs_tI4&=TI*Xrq#)(N>op-24!oE%)Wc-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxOgGuk*h0bFQqR!T z(!$6@N5ROz&`jUJQs2--*TB%qz|zXVPyq^*fVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5 zFD$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;NpiyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr7I$DAddqG<*}2 zGxI=#nqXbNzE+-j#U+V($*G<$wn{*A^fEJ3tXxb@Eey>Z9gU343=Iuk%`M#AEG^6p zU5(6~oXpJ(Twr=#@{>z*Q}aq-dQ%X3op9;}C5YStpv^9+MVV!(DQ-pixe8#9TV>*Q ziv>>epn6kqyTuZxUVWfr^g+>!6x}c(U>X83;fWW>fhYgeJYbqH0w(SqvHMpsFfg`x zx;TbZ+)DcM|Gz!6E~7AWFQX#&oW>aw!^Aq6KJq9`TRxTbox>WYBXwIgDX?{_*{^e0 z$-y1Q_DKG};sbes*@=OVIgi*_d^v2WH@%h7(0U4Q*OLg7$~1*}9w8qRH%^u8(Esbs zdcI)>zh|#v$R85{%w`rnU1_~s1P;LaF{5^6521f<@3U=^onT+^=!=2C{)SDCCqD@a$hYr$qA2jb&25d^ByN7jLVJPj4?FJd zclguV7~t0Uh(TxFX%#p9jba6iADvYW?{{qOYrHe3hi{*wvNT&CkheG!qui;hd(n!czc}pg)#~xFmN1PV52g(C>~T+ NdAjc-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxOgGuk*h0bFQqR!T z(!$6@N5ROz&`jUJQs2--*TB%qz|zXVPyq^*fVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5 zFD$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;NpiyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr7I$DAddqG<*}2 zGxI=#nqXbNzE+-j#U+V($*G<$wn{*A^fEJ3tlSKoEi7Cu%#4i83=9oj%`M#AEG^6p zU5(6~oXpJ(Twr=#@{>z*Q}aq-dQ%X3&2j1lC5YStpv^9+MVV!(DQ-pixe8#9TV>*Q ziwREipn6kqyTufzUVWfr^g+>!6x}c(U>X83;fWW>fhYgeJYbqH0w!)z#Xv^}2F7?# z7srr_TSsmJiu}E#sPb$S&=0YesUZL0VD`Vq?EZ~2c;`#{ zu+)5*%JDOP3EM<=f$C-(_9J2}Is(j(Rt9`iRhY+oq&`qdKzEYk#7fzYtCA;}wH#{1 zBO5wfM4DxEZUph4Seo;q$wx(PueigVro)2&7tUCc(#6Ns_`_0g-;vkMF$uN;>lu>j zf?{0OuquS7PkyMeNHpD9xACYT&;JE&teY1eWM=-|5Fy=hwPDBP1$kT6oMkz3ePUPB z<^aQ-qYW$q_Krcn#XC$dTov?kJ>#&4qvK{{h4B>L)B4;E5wn8i6`EIiyyDt$BKa$$ zrsgB{Dc6`Eg+)DSzJFl>qi9CVmJJLcY*QIFYHI#$n6l^)dxxo~Zd_!+5}BL>Q#vjr zsED{Mxbt(?nOzU01@@~i^V`P$Xt%?k*(`s=I*K=@dbr7)6$|Lp^lW-<&-l?eM~H!u ZjbXAugYHj@O}9XWkf*Dk%Q~loCIHK>tK>?5hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|8hm3bwJ6}oxF$}kgLQj3#|G7CyF^YauyCMG83 zmzLNn0bL65LT&-v*t}wBFaZNhzap_f-%!s0^MGl-2$;C1wYr}K<{3Uu z7srr_TW@B0W{WzCwEpIGT)aq)#n3`0Mxs}Rf6f6;$AT{Au0TUU^P4Zi=Kpv6-(VeU z@vtFKplL>H;9{4HtrL2k!n%&Rlq_zUvCvjL&pB}AE3J6`_1r}dn%}Lenrl^?AAUD) z(&yUy#gUcg3(raw+&o~k;ZiEI?1P%Eyic@ZT2K6tzQA#<;r=?;1)@_dddqgra8h^R zm7iaD_5f3XkHvl7upKE0))BgMt~V~$lYDVyi)f62!|HQ(uD{tfoctu!ec)J@qy>YN z$Xiz5l#oSNUu(^nzOB(FKh8>)@vGSNn}<(-__&344Rf^fuj$Qu+p;fQx_#`p_<=hn zycc9=Crdb2JDrO-cH~%1;^>em6A6)h|1CAZh}8bJxGX3mP%Wwlh8)zM5z_U9v+k z;>7M8+szU=57{HGWq%O#Y2Mvcx9eP@W^Gqnb>y?4rG7J-``Q-F5x;*T?{iCYo2J=P zulaffz5SV`RmZg_tO)<3^W?|dv&;VdWICoBG<#ho7f;8-7vJBSFirDX_$IJt@svz9 z-v{gE_m`d2G)eq8x#C^fg-=U2C*Cm#Uu3`PZ>rxl5%+^j(hGg<@|Yd7 z{uuRCPH5$iwhD~<*7l{IS$|WG!*+F+MXm`Zui54``_J0>?5hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|8hm3bwJ6}oxF$}kgLQj3#|G7CyF^YauyCMG83 zmzLNn0bL65LT&-v*t}wBFaZNhzap_f-%!s0?28M>N<`!;lmKNrQ zu101~PUdC?E-<|=`N^fZsd*(Zy(tL2ZaDRV5=3qR&}Ns^qRg_?6t|-MTm`VltunE= z#nr;xz}eEm5~q1ky(w7S0@dq?Q?EYIG5VnBMT%~i5HJmanDE34}B|@u*BOz*@9(hWZ!#+qe+Zgau%vdPxg=4?I&*_ zlJl^`k8vGGd>P9hjUWFb)-4K<%-FDIpMN96JLi8(-kz|(@90n`|3Kk4yZsKfi7JN_ zvTkjk@wEJB{=TNv2NP#V8>{#|s=cs0v3h%hx2Rpf;*<8;A(n+9lN)AS4)nOTBlzAf zQ}Ia~Spziv@``T0kc?n-6}kM9-$1M8gmd75Ej|(@s~eIx_-$3H7PGADS=HeA!C|Y~ zJ?7mZ-M`#7{!-a36u9yI{Gb^9lHZ=A#pTXJXY{riwZ9g?{=iGh3MS^=+PFT>`7){-swBMXRbm&^>Aq$9#V0 z!OqnO{kpQxK2UQtxy7WKXV+Hxo_rhdAKOXO}!eVqgk0{$Wf&cmU4fAuZ7;pNt+nfbmd$wEM64< zIQ6FO){-}T5@(L8DJ!hV%!^;eGAC<>P5xhHkQ7KDi-8gh2uLX~gye-pOkT(WK`14NJp$5# z)u~EQz*12`+kzreF*urlbAnam!>?HW)nO_?#_?z-;$7cBO(s0r=TOo<2P^bbC(PAhE1Y+gFTrdd~ zii`4xzz77w!e5r0rb*++(?yURFEq#Cb#f()Mj#>>I;BvQ2Wo&EFjuCC#4h$+!U8gJ zBz7B>Pvk3Epj5`&sRFm|Oh^{(%oEYXSVj~Op`*hDa!?}#bn+bvHC-2pea%aUZL^zz z1zv|}@*=Un7nQ~r04zua0#tlxu!uwn2f}E05|tKC4haItL{cb$NF$I!f{7$Lkwzzy zfHw~o&ZZJe=t*qun_TcM5-Zhclym~2u&@wcNWnv@Tmp$kqY;Q?0+}2PM+B>j6dIu} zSfR$PF|a|kNF`HhWRLb|e<&!OLV~It!jNB_@PTCWn$pBu-d3 zmrDx|BXP)~F)?AGMD`k%4T-dJP@!4FivPfJ*2S6yAy>kg*`P{R0E)RPNDjPioGx3x zmY8+<-eAS+*TPyCOMr_ZnAiJX>t1ug8Zw)2#S35F${$p~npeT%jtx8VAp(Ii#ImE4 zb(8lprl1Xpo=01}&z;utohq7LqeD*iwO6*LR$lOp3yBEGFM4RL+sD&AXggBzesgk0 zy>q??y8S%LfyXkPrzW3T`egazhJ@>f)DxKv6SPaSUyY0!4yot67J3>wCIrRBJ)m*3 z15WQWXd_0Ek(OH<<8h9E4lwRAZooPERF^$9Qn^5As>L$|rtAc_anuu?5i`(jJOrC* z2t)jUMcMFPW<$fHxPfjv<4bO%{i96X&pgBN}pZ?Zh>FfyOv z6W{A^Lbh8h7j%D4OH3D7dfR6NNh#I`T||syt%RHCig{{^$%=BIe!g8E}>2% zKl8n&Y?UxBmc3{E{uQnI)lE?>y~RbmiC&d((rC-b%-?PN=*slI z`|box$jF_vmBjuH(-A}GWU~uF2{mDTr;u~8vr&qTJ-qKTP`u_nfA!qwC3)tMc+b zUBSob^s_&@@mwRl+Xs8o4pelEy7@KYNBf&Y7ya{}uA>J39AYu>RRUt55Gf zVUs#!`ujz|lzzEu(yHy7nR?^S%a8u5GAso(2YvJ9%wnqEY7EoebfhA>1eN8~Igl*e zn2Y9sdOO@*`>Q|BSmhpkm#4iBai>zJRnsAtDS2g!@}pl^boKnOV^7S}fIGPDSsd?0 zO3iao%j`qU7MJHapFaPSVJ58Vne2ACpWw5(m%C6U(q6e0CozxXe!obHbtwXi$E*(* z{p2w{?1^+-?sm|9PTfU*PqN47PFa`dLLds+QuivHd9+%@i?Q}!#bNgra*o#>o6;L;v{Z2h+;0QCq@`_>MEbzGN!i}SG zFV6@ZFPa7nz@-Dqulw@jQu9Xwt3K$9h!$?DirS(t*3Y#4(p9%Vw^kwTi@sUA2k&7i zf6y{uJ9(oi=!FugMfXqj5xq+M#_prgE!7e-*yrUO==3BPJsKemn8PvLB*5xd!$uF= z-jf3L^mSRun6w%r!`yQGq+v-&UHS*7@s&PjlSHeuIX`eFG2Z=WS4q9gfl^rUPsd|x zp1u2F+Xmm=%sm+6nW~k>zdUhw)e~25q>OZIw=7CcLO-s)x-WES*+IYZB5Qjq@`cH7 z=v?WhHXE&Ykg=7%Z(Uu9TW1T+|4R<7zso+InalB z_6$!wT_B_!cdx$0a!%9Vk&mthyB+5OKFX;JDnjzSIq8y?kpCaZL^Y=2j08UVN>kEVhqWr-aMbgPInE&9hoCNminC$ZZ0B)*2 A<^TWy literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_device_battery.png b/res/drawable-xxhdpi/ic_device_battery.png new file mode 100755 index 0000000000000000000000000000000000000000..f8a2355c57046b737d42ee292b983e9f6620a60c GIT binary patch literal 1728 zcmaJ?X;2eq7>-(ka#XD?m$Y4j7C~}sBDo;YkOKk+LqJ6b2R6x)1V}bbHYG$Dg=hz_ zj!IzwDI)fOij_-&0wN*>DuT|`q99a9fhtidR6N>ZHwf4tr8~3xeeXB#^Ssyd&F+^( zhB`ZVIN)$NXHghmimhR`cPSqGZh2G_iY@M_AQp{A646vh597EBLe=ExYiIa$t95Q2k%Koc7y(7-4Jm^3L`1KY$Q%J_Ib9 zUXjR_@`bapFpEP_p{R~cCa0#Rl2U0TM6V=+EEbDQp^~XoBIZFfq-jyeMARC*<{0>} zL9SQpP&J|jY>bc$F`^s-mg(yfG`e|OtzouJSi{IBNJj=q6kADij$-luyJ|G^-Ud_( zzsdJMi48GnI+!el4Tw=M#|D?^WecTa^Yk!;BKjBvNtx@SM1`P;L51i59?#Y_2C!ZX z$<4@DIh3dFoi z8B2z28}GlyJ$Hm@$hMsqFJ{ckAJ$@;*JI*d#@#8w?hHx9=f;@&two!&#ZjIey~BsO zdy7|Yuas^I7dh7ej_|(DylP6RlS}xj7dh?EPXsSpeXB}B>y%aa2-G12$CSMyH{Op= z|DNgzne1t5cs97JH(+$=*SetQs}GtCErWjsv>1BEhX5zuI**Eo^c$;%l$nvx`HR>2b}vJjCZXV}TkXvn=9P~03u23d|Ktrr)k(Me zpG{|TPmPgGYw!h6J+1a_vLh^7V$UVwjgP>x9m>`WiXaO;SFd1>^w-C^FYOBtXx+1E zGV%SW+-Qd`i<24G6H%&iSw78fWq_R(q-*2|=NfJ1>1r3+m^1TE_<3s~x2?aZuey*i z=22fBcvNF~X-wEElbmDbjxRbvs2wi9?Bwd?yZx`T2TRDZf$;?Ue7ewY07oBgEeXA& zJ2Vw&vbvuVyL)4u@>FNIZh<3sG}5rb$gUlNng(W!L=x&p_hJ~2Ib+K zm+Xq}$@8}4(3SOmmOt!%x9dIw=Dw0Vp?8@^Ye(V2@t;#Al2guC_6889>V%_F4PBNI zzAF0jo30zeBQ5&{{f|pZi;5@SzMq$p?1lf0x8{2#}7ui@0P5EtD2q{h%G}|wBr0W^PW}T>8BI@!)TMu z)_PXO&}c(k{ISSm>A~3})kzle3sO{WU)#0U nBk>)kXQtb2ey5%P()%J#8FZk+Ec6_({ZmAONdEUB30eODWGtRA literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_app_events.png b/res/drawable-xxxhdpi/ic_app_events.png new file mode 100755 index 0000000000000000000000000000000000000000..99432559133e801f2a86ee01b1ade7c29d017f0e GIT binary patch literal 2274 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 - diff --git a/res/layout/card_generator_device_battery.xml b/res/layout/card_generator_device_battery.xml index 3244646..7239d93 100755 --- a/res/layout/card_generator_device_battery.xml +++ b/res/layout/card_generator_device_battery.xml @@ -18,10 +18,10 @@ - @@ -45,12 +45,12 @@ android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" - android:padding="8dp"> - + + android:layout_height="160dp" + android:layout_marginBottom="0dp"/> - + android:layout_height="wrap_content" + android:padding="8dp"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/res/values/generators.xml b/res/values/generators.xml index 0d0357e..20e06bd 100755 --- a/res/values/generators.xml +++ b/res/values/generators.xml @@ -120,10 +120,28 @@ Withings Device + Soft Activities + Moderate Activities + Intense Activities + #757575 + #039BE5 + #43A047 + No activity data has been downloaded yet. + + Steps + %1$d steps + + Distance + %1$.2f km + + Elevation + %1$.2f m Device Battery No battery levels have been reported yet. + #66BB6A + #1B5E20 App Event History 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 index c99b1c1..4d91714 100755 --- a/src/com/audacious_software/passive_data_kit/generators/device/Battery.java +++ b/src/com/audacious_software/passive_data_kit/generators/device/Battery.java @@ -10,6 +10,8 @@ import android.database.sqlite.SQLiteDatabase; import android.os.BatteryManager; import android.os.Bundle; +import android.support.v4.content.ContextCompat; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -21,9 +23,19 @@ 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; /** @@ -253,12 +265,20 @@ public static void bindViewHolder(DataPointViewHolder holder) { Battery generator = Battery.getInstance(context); - Cursor c = generator.mDatabase.query(Battery.TABLE_HISTORY, null, null, null, null, null, Battery.HISTORY_OBSERVED + " DESC"); + 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); + Log.e("SLEEP-SIGHT", "BATT VALUES COUNT 1: " + c.getCount()); + if (c.moveToNext()) { cardContent.setVisibility(View.VISIBLE); cardEmpty.setVisibility(View.GONE); @@ -267,8 +287,85 @@ public static void bindViewHolder(DataPointViewHolder holder) { dateLabel.setText(Generator.formatTimestamp(context, timestamp)); - TextView lastLevel = (TextView) holder.itemView.findViewById(R.id.card_last_battery_level); - lastLevel.setText("TODO: LEVEL " + c.getInt(c.getColumnIndex(Battery.HISTORY_LEVEL))); + 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; + + while (c.moveToNext()) { + long when = c.getLong(c.getColumnIndex(Battery.HISTORY_OBSERVED)); + long level = c.getLong(c.getColumnIndex(Battery.HISTORY_LEVEL)); + + if (level != lastLevel) { + values.add(0, new Entry(when, level)); + lastLevel = level; + + Log.e("SLEEP-SIGHT", "VALUE: " + level + " -- " + (when - start)); + } + } + + Log.e("SLEEP-SIGHT", "BATT VALUES COUNT 2: " + values.size()); + + 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); 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 index 8aaab44..7f47926 100755 --- a/src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java +++ b/src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java @@ -4,7 +4,9 @@ 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; @@ -16,6 +18,7 @@ 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; @@ -24,6 +27,13 @@ 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.PieChart; +import com.github.mikephil.charting.data.Entry; +import com.github.mikephil.charting.data.PieData; +import com.github.mikephil.charting.data.PieDataSet; +import com.github.mikephil.charting.data.PieEntry; +import com.github.mikephil.charting.formatter.IValueFormatter; +import com.github.mikephil.charting.utils.ViewPortHandler; import org.json.JSONArray; import org.json.JSONException; @@ -39,6 +49,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -155,7 +166,7 @@ public class WithingsDevice extends Generator { private static final String LAST_DATA_FETCH = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.LAST_DATA_FETCH"; private static final String DATA_FETCH_INTERVAL = "com.audacious_software.passive_data_kit.generators.wearables.WithingsDevice.DATA_FETCH_INTERVAL"; - private static final long DATA_FETCH_INTERVAL_DEFAULT = (60 * 60 * 1000); + private static final 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; @@ -256,26 +267,32 @@ public void run() { @Override public void run() { if (prefs.getBoolean(WithingsDevice.ACTIVITY_MEASURES_ENABLED, WithingsDevice.ACTIVITY_MEASURES_ENABLED_DEFAULT)) { + Log.e("SLEEP-SIGHT", "FETCH ACTIVITY MEASURES"); me.fetchActivityMeasures(); } if (prefs.getBoolean(WithingsDevice.BODY_MEASURES_ENABLED, WithingsDevice.BODY_MEASURES_ENABLED_DEFAULT)) { + Log.e("SLEEP-SIGHT", "FETCH BODY MEASURES"); me.fetchBodyMeasures(); } if (prefs.getBoolean(WithingsDevice.INTRADAY_ACTIVITY_ENABLED, WithingsDevice.INTRADAY_ACTIVITY_ENABLED_DEFAULT)) { + Log.e("SLEEP-SIGHT", "FETCH INTRADAY ACTIVITY"); me.fetchIntradayActivities(); } if (prefs.getBoolean(WithingsDevice.SLEEP_MEASURES_ENABLED, WithingsDevice.SLEEP_MEASURES_ENABLED_DEFAULT)) { + Log.e("SLEEP-SIGHT", "FETCH SLEEP MEASURES"); me.fetchSleepMeasures(); } if (prefs.getBoolean(WithingsDevice.SLEEP_SUMMARY_ENABLED, WithingsDevice.SLEEP_SUMMARY_ENABLED_DEFAULT)) { + Log.e("SLEEP-SIGHT", "FETCH SLEEP SUMMARY"); me.fetchSleepSummary(); } if (prefs.getBoolean(WithingsDevice.WORKOUTS_ENABLED, WithingsDevice.WORKOUTS_ENABLED_DEFAULT)) { + Log.e("SLEEP-SIGHT", "FETCH WORKOUTS"); me.fetchWorkouts(); } } @@ -552,6 +569,12 @@ private void fetchActivityMeasures() { private void fetchBodyMeasures() { JSONObject response = this.queryApi(WithingsDevice.API_ACTION_BODY_MEASURES_URL); + try { + Log.e("SLEEP-SIGHT", "BODY JSON: " + response.toString(2)); + } catch (JSONException e) { + e.printStackTrace(); + } + if (response != null) { try { if (response.getInt("status") == 0) { @@ -699,6 +722,12 @@ private void fetchIntradayActivities() { private void fetchSleepMeasures() { JSONObject response = this.queryApi(WithingsDevice.API_ACTION_SLEEP_MEASURES_URL); + try { + Log.e("SLEEP-SIGHT", "SLEEP JSON: " + response.toString(2)); + } catch (JSONException e) { + e.printStackTrace(); + } + if (response != null) { try { if (response.getInt("status") == 0) { @@ -767,6 +796,12 @@ private void fetchSleepMeasures() { private void fetchSleepSummary() { JSONObject response = this.queryApi(WithingsDevice.API_ACTION_SLEEP_SUMMARY_URL); + try { + Log.e("SLEEP-SIGHT", "SLEEP SUMMARY JSON: " + response.toString(2)); + } catch (JSONException e) { + e.printStackTrace(); + } + if (response != null) { try { if (response.getInt("status") == 0) { @@ -882,133 +917,135 @@ public static ArrayList diagnostics(final Context context) { ArrayList actions = new ArrayList<>(); - actions.add(new DiagnosticAction(context.getString(R.string.diagnostic_withings_auth_required_title), context.getString(R.string.diagnostic_withings_auth_required), new Runnable() { + 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"; + @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 apiUri = Uri.parse(requestUrl); - Uri.Builder builder = new Uri.Builder(); - builder.scheme(apiUri.getScheme()); - builder.authority(apiUri.getAuthority()); - builder.path(apiUri.getPath()); + 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 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 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(); + 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"); + 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(); + Uri baseUri = builder.build(); - signature += "&" + URLEncoder.encode(baseUri.getEncodedQuery(), "UTF-8"); + signature += "&" + URLEncoder.encode(baseUri.getEncodedQuery(), "UTF-8"); - String key = apiSecret + "&"; + String key = apiSecret + "&"; - SecretKeySpec secret = new SecretKeySpec(key.getBytes(), "HmacSHA1"); - Mac mac = Mac.getInstance("HmacSHA1"); - mac.init(secret); + SecretKeySpec secret = new SecretKeySpec(key.getBytes(), "HmacSHA1"); + Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(secret); - byte[] bytes = mac.doFinal(signature.getBytes()); + byte[] bytes = mac.doFinal(signature.getBytes()); - signature = Base64.encodeToString(bytes, Base64.DEFAULT).trim(); + signature = Base64.encodeToString(bytes, Base64.DEFAULT).trim(); - builder.appendQueryParameter("oauth_signature", signature); + builder.appendQueryParameter("oauth_signature", signature); - Uri uri = builder.build(); + Uri uri = builder.build(); - OkHttpClient client = new OkHttpClient(); + OkHttpClient client = new OkHttpClient(); - Request request = new Request.Builder() - .url(uri.toString()) - .build(); + Request request = new Request.Builder() + .url(uri.toString()) + .build(); - Response response = client.newCall(request).execute(); + Response response = client.newCall(request).execute(); - String responseBody = response.body().string(); + String responseBody = response.body().string(); - StringTokenizer st = new StringTokenizer(responseBody, "&"); + StringTokenizer st = new StringTokenizer(responseBody, "&"); - String requestToken = null; - String requestSecret = null; + String requestToken = null; + String requestSecret = null; - while (st.hasMoreTokens()) { - String authToken = st.nextToken(); + 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=", ""); + 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; + key = apiSecret + "&" + requestSecret; - me.setProperty(WithingsDevice.OPTION_OAUTH_REQUEST_SECRET, 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"); + 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"); + signature = "GET&" + URLEncoder.encode(builder.build().toString(), "UTF-8"); - nonce = UUID.randomUUID().toString(); + 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"); + 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(); + baseUri = builder.build(); - signature += "&" + URLEncoder.encode(baseUri.getEncodedQuery(), "UTF-8"); + signature += "&" + URLEncoder.encode(baseUri.getEncodedQuery(), "UTF-8"); - secret = new SecretKeySpec(key.getBytes(), "HmacSHA1"); - mac = Mac.getInstance("HmacSHA1"); - mac.init(secret); + secret = new SecretKeySpec(key.getBytes(), "HmacSHA1"); + mac = Mac.getInstance("HmacSHA1"); + mac.init(secret); - bytes = mac.doFinal(signature.getBytes(Charset.forName("UTF-8"))); + bytes = mac.doFinal(signature.getBytes(Charset.forName("UTF-8"))); - signature = Base64.encodeToString(bytes, Base64.DEFAULT); + signature = Base64.encodeToString(bytes, Base64.DEFAULT); - builder.appendQueryParameter("oauth_signature", signature.trim()); + builder.appendQueryParameter("oauth_signature", signature.trim()); - Intent intent = new Intent(Intent.ACTION_VIEW, builder.build()); - context.startActivity(intent); - } catch (NoSuchAlgorithmException e) { - AppEvent.getInstance(context).logThrowable(e); - } catch (InvalidKeyException e) { - AppEvent.getInstance(context).logThrowable(e); - } catch (UnsupportedEncodingException e) { - AppEvent.getInstance(context).logThrowable(e); - } catch (IOException e) { - AppEvent.getInstance(context).logThrowable(e); + Intent intent = new Intent(Intent.ACTION_VIEW, builder.build()); + context.startActivity(intent); + } catch (NoSuchAlgorithmException e) { + AppEvent.getInstance(context).logThrowable(e); + } catch (InvalidKeyException e) { + AppEvent.getInstance(context).logThrowable(e); + } catch (UnsupportedEncodingException e) { + AppEvent.getInstance(context).logThrowable(e); + } catch (IOException e) { + AppEvent.getInstance(context).logThrowable(e); + } } - } - }; + }; - Thread t = new Thread(r); - t.start(); - } - })); + Thread t = new Thread(r); + t.start(); + } + })); + } return actions; } @@ -1105,26 +1142,112 @@ public void run() { } public static void bindViewHolder(DataPointViewHolder holder) { -/* final Context context = holder.itemView.getContext(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); long lastTimestamp = 0; - long lastDuration = 0; - String callType = null; - long totalIncoming = 0; - long totalOutgoing = 0; - long totalMissed = 0; - long total = 0; + double steps = 0; + double distance = 0; + double elevation = 0; + + double softActivity = 0; + double moderateActivity = 0; + double intenseActivity = 0; WithingsDevice generator = WithingsDevice.getInstance(holder.itemView.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 = 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 (lastTimestamp > 0) { + cardContent.setVisibility(View.VISIBLE); + cardEmpty.setVisibility(View.GONE); + + dateLabel.setText(Generator.formatTimestamp(context, lastTimestamp)); + + PieChart pieChart = (PieChart) holder.itemView.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) holder.itemView.findViewById(R.id.field_steps); + stepsValue.setText(context.getString(R.string.generator_withings_steps_value, (int) steps)); + + TextView distanceValue = (TextView) holder.itemView.findViewById(R.id.field_distance); + distanceValue.setText(context.getString(R.string.generator_withings_distance_value, (distance / 1000))); + + TextView elevationValue = (TextView) holder.itemView.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); + } } @Override From a1de0d5d1ef1eb0eb8d1291d14bd5df530ea9f92 Mon Sep 17 00:00:00 2001 From: "Chris J. Karr" Date: Sat, 29 Apr 2017 19:01:02 -0500 Subject: [PATCH 05/10] Withings device work * Card for app events. --- res/drawable-hdpi/ic_page_indicator.png | Bin 0 -> 1340 bytes res/drawable-mdpi/ic_page_indicator.png | Bin 0 -> 1190 bytes res/drawable-xhdpi/ic_page_indicator.png | Bin 0 -> 1489 bytes res/drawable-xxhdpi/ic_page_indicator.png | Bin 0 -> 1856 bytes res/drawable-xxxhdpi/ic_page_indicator.png | Bin 0 -> 2035 bytes res/layout/card_generator_app_event.xml | 20 +- res/layout/card_generator_app_event_page.xml | 186 ++++++ .../card_generator_withings_activity_page.xml | 114 ++++ .../card_generator_withings_body_page.xml | 13 + res/layout/card_generator_withings_device.xml | 121 +--- .../card_generator_withings_info_page.xml | 13 + .../card_generator_withings_intraday_page.xml | 71 +++ .../card_generator_withings_sleep_page.xml | 13 + ..._generator_withings_sleep_summary_page.xml | 13 + .../card_generator_withings_workouts_page.xml | 13 + res/values/generators.xml | 5 + .../activities/views/ViewPagerIndicator.java | 92 +++ .../generators/device/Battery.java | 3 - .../generators/diagnostics/AppEvent.java | 144 ++++- .../generators/sensors/AmbientLight.java | 312 +++++++++- .../generators/wearables/WithingsDevice.java | 554 ++++++++++++++++-- .../transmitters/HttpTransmitter.java | 3 +- 22 files changed, 1513 insertions(+), 177 deletions(-) create mode 100755 res/drawable-hdpi/ic_page_indicator.png create mode 100755 res/drawable-mdpi/ic_page_indicator.png create mode 100755 res/drawable-xhdpi/ic_page_indicator.png create mode 100755 res/drawable-xxhdpi/ic_page_indicator.png create mode 100755 res/drawable-xxxhdpi/ic_page_indicator.png create mode 100755 res/layout/card_generator_app_event_page.xml create mode 100755 res/layout/card_generator_withings_activity_page.xml create mode 100755 res/layout/card_generator_withings_body_page.xml create mode 100755 res/layout/card_generator_withings_info_page.xml create mode 100755 res/layout/card_generator_withings_intraday_page.xml create mode 100755 res/layout/card_generator_withings_sleep_page.xml create mode 100755 res/layout/card_generator_withings_sleep_summary_page.xml create mode 100755 res/layout/card_generator_withings_workouts_page.xml create mode 100755 src/com/audacious_software/passive_data_kit/activities/views/ViewPagerIndicator.java diff --git a/res/drawable-hdpi/ic_page_indicator.png b/res/drawable-hdpi/ic_page_indicator.png new file mode 100755 index 0000000000000000000000000000000000000000..0bc147b66e91b3edae08ccc1f295fc1c3cef03cb GIT binary patch literal 1340 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@%o>>?5hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|8hm3bwJ6}oxF$}kgLQj3#|G7CyF^YauyCMG83 zmzLNn0bL65LT&-v*t}wBFaZNhzap_f-%!s0*lEl2^R8JRMC7?NanVBh8W(KYnZYE|XZZ5_KhK8=Djs})47M2E< zhL&a~u7;M5FugAM$)&lec_lEtDG0r$IQ4=OL~a4lW|!2W%(B!Jx1#)91+d4hGI6`b z2&Z{ay(zfeVvJL-KF~4xpy)-4ZkP}-4S|^O#0%uWlYeR+FwGYM6ZdBh4L$}2##Np! zjv*DdlJ-=8*JC#O@cO!b+xqzZi#j%L+-O)(R0K@1hKVcEMMOkg1b_Vgt}k)>)-9us zj*bi_)051tk&QpTPS=lL7rUe2;pNZI&r4cZSV(+eDl02%>+9?DSnz1^e<1iL&sp>P z+uMWR-rm0L>*M3{L2A*GB}XbhJvrI>pOaz1={yxRwOI+KE$!{?FR!c&?*1>#`R~uq z&qpp^4CFn$;M=!vGybbIS(m@ddHn2|TZ@gny!`9U>F3Y=Q)g~zZN1rWhGBMETG}#Q zP0f|Z9oRm6{`|Q?n#D)D%b}}Prg_JX9VQRi8ZSvP|7?)W6EI-hr+i&x!9DSU=5;YU ziTWB0r7@qAeT1o;HgU~8?oT}N_?p*dZnQ7Lr+1c0uCk@-B4OD}(GbfuNor^QG z+1aq#rf>S76fp%;1z(i5*hU|G(9q4phSCO;1;L;XidK=hihZc1MU(N&HkcPFb=bmR5`c z1cD|NVGc@4<<)Dj55t0CH9z8vh%a)op%IdgAzaNwYz*t`cTGv2fDS0YqN=CxKfkQu zKvh!sv4lv8W(JO{ho&reWNIicPff_Ig7-fU`dkhPXwZ>>tDVqo&Q0MhT@LO2Fo}Z} z#F3arDA;^(l+9Ac zxEw#YnG2m#__*Vk97&eTWuhD-468`eEX$Hql#E6r2obR>x+A#}-QL$$;Gr#Bs_CeP z4tzzaV3eE`jw-#^f@W^X>h|WC(7;GnGD(`C{Fd54QT%_Xrfs2ZCkOA>`#@nkUojz> zgSJt!WOQ+3`}|ZUm$9Ja7*^gePPC^uJZ?CKJ#LsFlW9#2ghfeKbwAkCVu>Or=(Z#2 zG8Fg}j&ulBRXB!@W-`44%pf15XgZrnQ!L-hQY_6T<5`+bx4FC_mo%t5ZLV^k+q*5- zH-u)Q%sjNzNvI53h6Y-VbLsAb7U&ZAbl&~0u-0tK*tF+)v8uQ6{`BnZ?DUy4k33xu)x9gP zuU}tSUS3|P*F#4io|z65i^V_Y=jY>sAjH7g^`*1q>P+PL=kD@3_TZjIx>Bix8Z#^2 zr>EZCe{AxzxjE2vkmG*C7Hi(s;Dw79AN_IfH=i75ZuAFk-dg*5samZ*0aoTluT<|I zc&hWHx3T_VAOtSQg7o7-X<v$Alm(P-=*7<%GV)x+>`iO8i{Fc7Im`;ZH zWAR$gGu_=EciaL?&DG0K)?WMjv7c)mRXK$PvC%hoe}8`E_AmaQL&y&Cmj+%r{Vyu~ Bq}>1j literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_page_indicator.png b/res/drawable-xhdpi/ic_page_indicator.png new file mode 100755 index 0000000000000000000000000000000000000000..a1900c614ebce2509a6f9fe978414647aa90a046 GIT binary patch literal 1489 zcmaJ>ZA=qq96#s^LPL}xBI?F*>MUw|*H`F4p^O&oSPN?#z`11{?NuuD?z}st=mzpu zC+Zv+9m^u1e4xY)7;YdN2u?8IHj){Wjk$%v)Q_x4qLN{|D+2Dr*pu8n&-2gk_xr!* zIhkYJ6A`{C8~}g_{a&q!+cyTjH6h&pk5cthZi{7g`K+0=v!xh~1L-!h5C`>6>;P`U zFFlFCpyg36`f(t;nLDlaj8s9(g$FPN~MAkDJ+$WIE08PBUsEOBABhq3R;}8 z(hiDskOUY|#0ts7tQz7XeV&4oT9GA~r802^gIyQ}OTcH22@WlEJj#yy;cozI%0>zhAI(VQl?yqWNKxIM50rqBPwl@3QG)oD}C-i_?xHxGj?=o#0~PsAJVw6uMRQmbkW6V^L(T%7wXL z;6Qu-)$U~zH$#Er%J6cRmGQ?3ZsuujxX(0(d;+-%EkruJ(_`C(yU(%jr!V?#qjuBIb+-PNGkWbium z=>8aQe=ngv;#-)n*mms4u`&JAyY1$SL*Vm~F2QEPoOlY@g<2E&=!7XHM<#6wL)r|(=)$2)z}bIr}ov29a5Jw4O!4VDkgHwSmo z4{k#_!&bm(G`d^&oPGSPZA<^$+?;u&Yv|4$Q>1YILimP-$?G2+i*fVKZAQbh?Cro0 zF?p4d$c48iwJ{%QZDMJL~vE_Y<;5UapENY7%zyHK|)SpFb6!yI$b) zJ^Q<|&Ru@Ut^AHdDWT29Q^8zM%awDCe#3M{eARPd+p~{5VjE9k;w! z@;YBVL{U^IUI^NESj^IlcjHS4Vy4?QJH0QvDWxxh-qwAo!naUXHa&c@dgyk$hFg43 z)7sCbu9Xy4?F=b9|4M#bmM}O7`Kac&s)^s2Fj&qtZ=zNZGsAn3#+xNUU)XSk=F`)^ xywMzaFmjh%hd}KoVjsmEO#1NF+q1lvHUhfZRwmA}y)*F0>UBnK$Lj@E{{Sb`F)siB literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_page_indicator.png b/res/drawable-xxhdpi/ic_page_indicator.png new file mode 100755 index 0000000000000000000000000000000000000000..af86b8c9318758363f9dcf8ce81e088736bf388c GIT binary patch literal 1856 zcmaJ?X;c$e6b>khAY~B|Xw+#y3nIxRge8%%m_#S`buFfkJ^|g(DgT*H#KzK@sf)Mf*cL=ghqK?z!K0zq`CMTjPX_ z9PB*p2n2!yKZ+y5S8wxcYmI+RgrXvR@xZuCu>>Ru(@0b>AreB?zyMz%NrpwR1j<-@ z84e{7Ci%z`mtso=F-$3I@|&(fTXZUK0!) zSS?k_P)vp>0W+gy4U&$rNVwA1DJamlv`Y1xGT{ZIXeB6xN(Rj-O*je!|L>|$y!BRN zBKV!&_Y$iUGfA?g$a1=#HIssTO% ziBzUE8-2%70s)h+RAUmQ6y|eSB%FsVlR?ZNkQ>6I2eNsb0FX-M21bGmP6z{JP#HmV zE|n2Ef#o35bOo%$Ca};uEa!t*^FSz2JTeDX$=1OTPlYIe@x+<3_h_N>IP4I5FqOxl z@!qfHH5Pi0mN!`12eA}f48`2u|FnC;gwK$9`*wKo!`tzPmH5o7@Zok`aKRV9GZlPJ zWTLjaxoA~XLOiii^J8_mqJo-yB3y2r)>I#LyvUSI+vww@iQ9uPF$y%#XEMYU4<8&>YsPLi>i+t3f9u$O z^`_bZ51o_Ci`Yn;>^kWf;FdoHq7_GYyYv(FF>{A2L`P3)W82!>PuT2^O%#jmQ&UsF zI?~23>F(*NY^QJB_kF2-p=T6z{w9~91ycu=a`}e6#l=%b#$Mj?Ike;ls}(C&bocl7 z+vxTBd~mHRE&HsOgU3gcEgu<;#-8-_bl7EU-;>p;X=!r^D#y8lC0}2BGBPqEDBvZ8 z#Kgw#@O8B_#+jV;2M%m%@9e}>N3E@`$w{WxKZ*{zDIDXJ8j82D|rgV07EwRcT3ni39 zc@Nd4p59jUjcGY~N8@An!5KZmBOSl)Uv;@#Afe)7PSve8Fu-q0i2!a+5{qBwfkYSHP%yRn|R& z!O$8pnkU{127Q}#TE3v5uCLJZK8wNGJI52djoaS zvU?v*HHDy-@$LmYzq3ro*$+0iWLRg$wkvIWmaM`z!9Ol%f67=&k{A6&T-?Wa97eDR>itXW^!sc*hC zFr#NaU0L5b-7UZWN_~BOPXffZYz{CkSJn@I>UiK}aYA!@?k|Aej0RPVA$r!;lnjCLSc>CxO|oA zq~e9617u0FGOA0fS}o>~K8}q(_xE0VMUb9fc~zV>yTRIifZ=Ix>5%C+Ye$|~EUrCP zUY>J##MR!XW%1<4R%;r-)U8v9SM`xqWns&Xxqh~D#Sm*=->XmVSj$u$-th%<#S*vW jxqW260Aobah~GIvUzqLHrUAKw`Jcw;3ON-ES8w?jEVtqj literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_page_indicator.png b/res/drawable-xxxhdpi/ic_page_indicator.png new file mode 100755 index 0000000000000000000000000000000000000000..6a92866f6cd93dbbf698796a44c62dc630d916b9 GIT binary patch literal 2035 zcmaJ?Yg7|w8XiE(MJS4Z;nFfhL~O|oLSh1uO9Ca3fFd_5EQVx&NG21L5dvZh+8}r- zSd;>`QhN$2EZP8KsiJ^dD`k^%5%5-^utJMjV=bBr(w$IY_s6nx&dm3H?|Gl+y*=NV zk}c8i*;>0;0{~#ljo`#$E7|y3zJYxcP5(HEEzW3I61ojZM{@*n2nZ1&X%NVT1sPB* zBoO89z6}KefSI>AE(uNI^O!;eCJKx`L?tZ4&;Sq^q?8GSSr7`QK^bBx3;*QuH9RO5 zvG9rhd@^6ghBCzwd2(o5UUZx=FH6V};e$4Vfl4Mu07Iw%RKgOef~jQT7kHW2-e@M_ z!37sIi-rFysU-dukd4S8(4R;n2+1@$NM{f!{&YH(?gRRgDgGp~AIXnQ@bzO-$xNy* z`1-+P(d43ZW-KTCbu8?}!e^qWj7cKpFc@Px2QdFQM zNEPcA890zaC>P66F(L(xjDj>|56Z%0nf_G*EL)kdY`vvazH^M?U}m zyTb62w*rlY{*&+jBv!=b${B;g-o2MM*q0=i+#0vz z_2-MX559N5b!bznso9_3?KRkpbmmYj6zh8(&2C*3@?v5v)|A{}ziCi0s6Cy@2GAp~ zO(nTtn3|fhdV6m5WJFUR!75e1U2{Z~*zV~75( zB3bDW?MASVhimQi7cXA8bG^!uB$bVNRdd{GXM;wgIq+aCD?z~K-T0}&@~BrHoldVI ze`=%d9F5~V-#_C+AUqThT|Z2JV1BrKrE6-vGY)5|Kd4Q-WIpPFvsOj-J>jpB z)+slt>~eMLbMG+J;s9DJuRnQfeDG%4cHr=>@tSzYV2Mvnfuy;lv9WPqTs)953pRB- zcep;En3xENH|Ozq1^=jME_2-PG9HnVl0rTaNoYv0D72F7{7P6yL_EBk)6_}3cJF?A zVpoHm*Vl#WADx;A`2djoxan65>MO&Awzjsl(#~hFo9Tg;mw8)Z$8S^2_jiuAMR?k0pFZsvE+Maq+6tf0edpxv zol?4PAAvwvo0yn5l3@J-1VJ_w@wD59iZ5MuFdD<|Mb6}}I9Xk-(P}hB-L|E;VpXNX z{U - - + + - + android:layout_height="match_parent"> + + 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_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..91befe4 --- /dev/null +++ b/res/layout/card_generator_withings_body_page.xml @@ -0,0 +1,13 @@ + + + + diff --git a/res/layout/card_generator_withings_device.xml b/res/layout/card_generator_withings_device.xml index 6675ceb..96f1fec 100755 --- a/res/layout/card_generator_withings_device.xml +++ b/res/layout/card_generator_withings_device.xml @@ -26,127 +26,30 @@ android:layout_marginRight="8dp" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:layout_height="160dp"> + + + 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..3b1086d --- /dev/null +++ b/res/layout/card_generator_withings_intraday_page.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + 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/values/generators.xml b/res/values/generators.xml index 20e06bd..c304b38 100755 --- a/res/values/generators.xml +++ b/res/values/generators.xml @@ -127,6 +127,11 @@ #039BE5 #43A047 No activity data has been downloaded yet. + Info coming soon… + Steps + Distance + Elevation + Calories Steps %1$d steps 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/device/Battery.java b/src/com/audacious_software/passive_data_kit/generators/device/Battery.java index 4d91714..f940c35 100755 --- a/src/com/audacious_software/passive_data_kit/generators/device/Battery.java +++ b/src/com/audacious_software/passive_data_kit/generators/device/Battery.java @@ -43,7 +43,6 @@ */ 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"; @@ -277,8 +276,6 @@ public static void bindViewHolder(DataPointViewHolder holder) { View cardEmpty = holder.itemView.findViewById(R.id.card_empty); TextView dateLabel = (TextView) holder.itemView.findViewById(R.id.generator_data_point_date); - Log.e("SLEEP-SIGHT", "BATT VALUES COUNT 1: " + c.getCount()); - if (c.moveToNext()) { cardContent.setVisibility(View.VISIBLE); cardEmpty.setVisibility(View.GONE); 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 ccb26e2..0319140 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,9 +6,12 @@ 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.widget.LinearLayout; import android.widget.TextView; import com.audacious_software.passive_data_kit.PassiveDataKit; @@ -23,7 +26,6 @@ import java.io.File; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -41,10 +43,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()); @@ -99,33 +106,158 @@ public static ArrayList diagnostics(Context context) { public static void bindViewHolder(DataPointViewHolder holder) { final Context context = holder.itemView.getContext(); - AppEvent generator = AppEvent.getInstance(context); + final AppEvent generator = AppEvent.getInstance(context); + + final ArrayList events = new ArrayList<>(); 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(AppEvent.HISTORY_OBSERVED)) / 1000; dateLabel.setText(Generator.formatTimestamp(context, timestamp)); - - TextView eventCount = (TextView) holder.itemView.findViewById(R.id.card_app_event_count); - - eventCount.setText("TODO: EVENT VIEW - " + c.getCount()); } else { cardContent.setVisibility(View.GONE); cardEmpty.setVisibility(View.VISIBLE); + cardSizer.setVisibility(View.INVISIBLE); dateLabel.setText(R.string.label_never_pdk); } + c.moveToPrevious(); + + while (c.moveToNext()) { + String event = c.getString(c.getColumnIndex(AppEvent.HISTORY_EVENT_NAME)); + long timestamp = c.getLong(c.getColumnIndex(AppEvent.HISTORY_OBSERVED)); + + Object[] values = { event, timestamp }; + + events.add(values); + } + c.close(); + + if (eventCount > AppEvent.CARD_PAGE_SIZE * AppEvent.CARD_MAX_PAGES) { + eventCount = AppEvent.CARD_PAGE_SIZE * AppEvent.CARD_MAX_PAGES; + } + + final int pages = (int) Math.ceil(((double) eventCount) / AppEvent.CARD_PAGE_SIZE); + + final AppEvent appEvent = AppEvent.getInstance(holder.itemView.getContext()); + + ViewPager pager = (ViewPager) holder.itemView.findViewById(R.id.content_pager); + + PagerAdapter adapter = new PagerAdapter() { + @Override + public int getCount() { + return pages; + } + + @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) { + 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; 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; + } + + TextView eventName = (TextView) row.findViewById(R.id.app_event_row_event_name); + TextView eventWhen = (TextView) row.findViewById(R.id.app_event_row_event_when); + + eventName.setText(event); + eventWhen.setText(Generator.formatTimestamp(context, timestamp / 1000)); + } + + list.setTag("" + position); + + container.addView(list); + + return "" + list.getTag(); + } + }; + + pager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + appEvent.mPage = position; + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + }); + + pager.setAdapter(adapter); + + pager.setCurrentItem(appEvent.mPage); } public static View fetchView(ViewGroup parent) 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 index 09c7f3c..4dbedf8 100755 --- a/src/com/audacious_software/passive_data_kit/generators/sensors/AmbientLight.java +++ b/src/com/audacious_software/passive_data_kit/generators/sensors/AmbientLight.java @@ -1,8 +1,318 @@ package com.audacious_software.passive_data_kit.generators.sensors; +import android.content.Context; +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.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.passive_data_kit.generators.device.Battery; +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 { +public class AmbientLight extends Generator 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 DATABASE_PATH = "pdk-sensor-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 AmbientLight sInstance = null; + private static Handler sHandler = null; + + private SQLiteDatabase mDatabase = null; + + private Sensor mSensor = null; + + 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 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; + + if (AmbientLight.isEnabled(this.mContext)) { + this.mSensor = sensors.getDefaultSensor(Sensor.TYPE_LIGHT); + + Runnable r = new Runnable() + { + public void run() + { + Looper.prepare(); + + AmbientLight.sHandler = new Handler(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) + sensors.registerListener(me, me.mSensor, SensorManager.SENSOR_DELAY_NORMAL, 0, AmbientLight.sHandler); + else + sensors.registerListener(me, me.mSensor, SensorManager.SENSOR_DELAY_NORMAL, AmbientLight.sHandler); + + Looper.loop(); + } + }; + + Thread t = new Thread(r, "ambient-light"); + t.start(); + } else { + if (this.mSensor != null) { + if (AmbientLight.sHandler != null) { + Looper loop = AmbientLight.sHandler.getLooper(); + loop.quit(); + + AmbientLight.sHandler = null; + } + + sensors.unregisterListener(this, this.mSensor); + + this.mSensor = null; + } + } + + + Generators.getInstance(this.mContext).registerCustomViewClass(AmbientLight.GENERATOR_IDENTIFIER, Battery.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_device_ambient_light_create_history_table)); + } + + this.setDatabaseVersion(this.mDatabase, AmbientLight.DATABASE_VERSION); + } + + 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(Context context) { + return new ArrayList<>(); + } + + public static void bindViewHolder(DataPointViewHolder holder) { + final Context context = holder.itemView.getContext(); + + AmbientLight generator = AmbientLight.getInstance(context); + + long now = System.currentTimeMillis(); + long start = now - (24 * 60 * 60 * 1000); + + String where = AmbientLight.HISTORY_OBSERVED + " >= ?"; + String[] args = { "" + start }; + + Cursor c = generator.mDatabase.query(AmbientLight.TABLE_HISTORY, null, where, args, null, null, AmbientLight.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(AmbientLight.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; + + while (c.moveToNext()) { + long when = c.getLong(c.getColumnIndex(AmbientLight.HISTORY_OBSERVED)); + long level = c.getLong(c.getColumnIndex(AmbientLight.HISTORY_LEVEL)); + + if (level != lastLevel) { + values.add(0, new Entry(when, level)); + lastLevel = level; + + Log.e("SLEEP-SIGHT", "VALUE: " + level + " -- " + (when - start)); + } + } + + Log.e("SLEEP-SIGHT", "LIGHT VALUES COUNT 2: " + values.size()); + + LineDataSet set = new LineDataSet(values, "Light Level"); + 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) { + long timestamp = 0; + + AmbientLight me = AmbientLight.getInstance(context); + + Cursor c = me.mDatabase.query(AmbientLight.TABLE_HISTORY, null, null, null, null, null, AmbientLight.HISTORY_OBSERVED + " DESC"); + + if (c.moveToNext()) { + timestamp = c.getLong(c.getColumnIndex(AmbientLight.HISTORY_OBSERVED)); + } + + c.close(); + + return timestamp; + } + + 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) { + + } + + @Override + public void onAccuracyChanged(Sensor sensor, int newAccuracy) { + + } } 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 index 7f47926..b56eeb9 100755 --- a/src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java +++ b/src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java @@ -13,11 +13,14 @@ 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; @@ -27,8 +30,12 @@ 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; @@ -49,8 +56,8 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; -import java.util.Date; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; @@ -71,6 +78,10 @@ public class WithingsDevice extends Generator { 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"; @@ -122,6 +133,14 @@ public class WithingsDevice extends Generator { 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"; @@ -186,6 +205,9 @@ public class WithingsDevice extends Generator { 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"; @@ -202,12 +224,18 @@ public class WithingsDevice extends Generator { 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()); @@ -248,51 +276,39 @@ private void startGenerator() { final Runnable fetchData = new Runnable() { @Override public void run() { - Log.e("PDK", "WITHINGS FETCH DATA"); - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(me.mContext); long fetchInterval = prefs.getLong(WithingsDevice.DATA_FETCH_INTERVAL, WithingsDevice.DATA_FETCH_INTERVAL_DEFAULT); if (me.approvalGranted()) { - Log.e("PDK", "WITHINGS APPROVED"); - long lastFetch = prefs.getLong(WithingsDevice.LAST_DATA_FETCH, 0); long now = System.currentTimeMillis(); if (now - lastFetch > fetchInterval) { - Log.e("PDK", "TIME TO FETCH"); - Runnable r = new Runnable() { @Override public void run() { if (prefs.getBoolean(WithingsDevice.ACTIVITY_MEASURES_ENABLED, WithingsDevice.ACTIVITY_MEASURES_ENABLED_DEFAULT)) { - Log.e("SLEEP-SIGHT", "FETCH ACTIVITY MEASURES"); me.fetchActivityMeasures(); } if (prefs.getBoolean(WithingsDevice.BODY_MEASURES_ENABLED, WithingsDevice.BODY_MEASURES_ENABLED_DEFAULT)) { - Log.e("SLEEP-SIGHT", "FETCH BODY MEASURES"); me.fetchBodyMeasures(); } if (prefs.getBoolean(WithingsDevice.INTRADAY_ACTIVITY_ENABLED, WithingsDevice.INTRADAY_ACTIVITY_ENABLED_DEFAULT)) { - Log.e("SLEEP-SIGHT", "FETCH INTRADAY ACTIVITY"); me.fetchIntradayActivities(); } if (prefs.getBoolean(WithingsDevice.SLEEP_MEASURES_ENABLED, WithingsDevice.SLEEP_MEASURES_ENABLED_DEFAULT)) { - Log.e("SLEEP-SIGHT", "FETCH SLEEP MEASURES"); me.fetchSleepMeasures(); } if (prefs.getBoolean(WithingsDevice.SLEEP_SUMMARY_ENABLED, WithingsDevice.SLEEP_SUMMARY_ENABLED_DEFAULT)) { - Log.e("SLEEP-SIGHT", "FETCH SLEEP SUMMARY"); me.fetchSleepSummary(); } if (prefs.getBoolean(WithingsDevice.WORKOUTS_ENABLED, WithingsDevice.WORKOUTS_ENABLED_DEFAULT)) { - Log.e("SLEEP-SIGHT", "FETCH WORKOUTS"); me.fetchWorkouts(); } } @@ -304,11 +320,7 @@ public void run() { SharedPreferences.Editor e = prefs.edit(); e.putLong(WithingsDevice.LAST_DATA_FETCH, now); e.apply(); - } else { - Log.e("PDK", "NOT TIME TO FETCH"); } - } else { - Log.e("PDK", "WITHINGS NOT APPROVED"); } if (me.mHandler != null) { @@ -557,6 +569,8 @@ private void fetchActivityMeasures() { 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); } } @@ -569,12 +583,6 @@ private void fetchActivityMeasures() { private void fetchBodyMeasures() { JSONObject response = this.queryApi(WithingsDevice.API_ACTION_BODY_MEASURES_URL); - try { - Log.e("SLEEP-SIGHT", "BODY JSON: " + response.toString(2)); - } catch (JSONException e) { - e.printStackTrace(); - } - if (response != null) { try { if (response.getInt("status") == 0) { @@ -700,6 +708,9 @@ private void fetchBodyMeasures() { 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); } @@ -715,19 +726,90 @@ private void fetchIntradayActivities() { JSONObject response = this.queryApi(WithingsDevice.API_ACTION_INTRADAY_ACTIVITY_URL); if (response != null) { - // TODO + 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); - try { - Log.e("SLEEP-SIGHT", "SLEEP JSON: " + response.toString(2)); - } catch (JSONException e) { - e.printStackTrace(); - } - if (response != null) { try { if (response.getInt("status") == 0) { @@ -784,6 +866,10 @@ private void fetchSleepMeasures() { 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); } } @@ -796,12 +882,6 @@ private void fetchSleepMeasures() { private void fetchSleepSummary() { JSONObject response = this.queryApi(WithingsDevice.API_ACTION_SLEEP_SUMMARY_URL); - try { - Log.e("SLEEP-SIGHT", "SLEEP SUMMARY JSON: " + response.toString(2)); - } catch (JSONException e) { - e.printStackTrace(); - } - if (response != null) { try { if (response.getInt("status") == 0) { @@ -871,6 +951,10 @@ private void fetchSleepSummary() { 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); } } @@ -880,6 +964,20 @@ private void fetchSleepSummary() { } } + 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); @@ -1141,10 +1239,94 @@ public void run() { t.start(); } - public static void bindViewHolder(DataPointViewHolder holder) { - final Context context = holder.itemView.getContext(); + 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 7; + } + + @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(); - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + LinearLayout card = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.card_generator_withings_activity_page, null); + card.setTag("" + position); long lastTimestamp = 0; @@ -1156,7 +1338,7 @@ public static void bindViewHolder(DataPointViewHolder holder) { double moderateActivity = 0; double intenseActivity = 0; - WithingsDevice generator = WithingsDevice.getInstance(holder.itemView.getContext()); + 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"); @@ -1176,8 +1358,8 @@ public static void bindViewHolder(DataPointViewHolder holder) { c.close(); - View cardContent = holder.itemView.findViewById(R.id.card_content); - View cardEmpty = holder.itemView.findViewById(R.id.card_empty); + 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) { @@ -1186,7 +1368,7 @@ public static void bindViewHolder(DataPointViewHolder holder) { dateLabel.setText(Generator.formatTimestamp(context, lastTimestamp)); - PieChart pieChart = (PieChart) holder.itemView.findViewById(R.id.chart_phone_calls); + PieChart pieChart = (PieChart) card.findViewById(R.id.chart_phone_calls); pieChart.getLegend().setEnabled(false); pieChart.setEntryLabelColor(android.R.color.transparent); @@ -1234,13 +1416,13 @@ public String getFormattedValue(float value, Entry entry, int dataSetIndex, View dateLabel.setText(Generator.formatTimestamp(context, lastTimestamp / 1000)); - TextView stepsValue = (TextView) holder.itemView.findViewById(R.id.field_steps); + TextView stepsValue = (TextView) card.findViewById(R.id.field_steps); stepsValue.setText(context.getString(R.string.generator_withings_steps_value, (int) steps)); - TextView distanceValue = (TextView) holder.itemView.findViewById(R.id.field_distance); + TextView distanceValue = (TextView) card.findViewById(R.id.field_distance); distanceValue.setText(context.getString(R.string.generator_withings_distance_value, (distance / 1000))); - TextView elevationValue = (TextView) holder.itemView.findViewById(R.id.field_elevation); + 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); @@ -1248,6 +1430,10 @@ public String getFormattedValue(float value, Entry entry, int dataSetIndex, View dateLabel.setText(R.string.label_never_pdk); } + + container.addView(card); + + return "" + card.getTag(); } @Override @@ -1295,4 +1481,278 @@ public void setProperty(String key, String value) { 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(); + + LinearLayout card = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.card_generator_withings_body_page, null); + card.setTag("" + position); + + 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..ed939ea 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"; @@ -159,6 +158,8 @@ private boolean shouldAttemptUpload(boolean force) { public void transmit(boolean force) { long now = System.currentTimeMillis(); + Log.e("PDK", "TRANSMIT: " + force); + if (force) { this.mLastAttempt = 0; } From fa714a97158886d3d15021af37cf318339fd660a Mon Sep 17 00:00:00 2001 From: "Chris J. Karr" Date: Thu, 11 May 2017 18:06:42 -0500 Subject: [PATCH 06/10] Implemented ambient light sensor. * Implemented accelerometer sensor. * Added lock functionality to Data Stream view. * Updated Foreground App generator to only count when screen is active. --- res/drawable-hdpi/ic_ambient_light.png | Bin 0 -> 1424 bytes res/drawable-hdpi/ic_pdk_accelerometer.png | Bin 0 -> 1721 bytes res/drawable-hdpi/ic_pdk_action_lock.png | Bin 0 -> 1338 bytes res/drawable-hdpi/ic_pdk_action_unlock.png | Bin 0 -> 1350 bytes res/drawable-mdpi/ic_ambient_light.png | Bin 0 -> 1271 bytes res/drawable-mdpi/ic_pdk_accelerometer.png | Bin 0 -> 1404 bytes res/drawable-mdpi/ic_pdk_action_lock.png | Bin 0 -> 1244 bytes res/drawable-mdpi/ic_pdk_action_unlock.png | Bin 0 -> 1248 bytes res/drawable-xhdpi/ic_ambient_light.png | Bin 0 -> 1569 bytes res/drawable-xhdpi/ic_pdk_accelerometer.png | Bin 0 -> 1997 bytes res/drawable-xhdpi/ic_pdk_action_lock.png | Bin 0 -> 1461 bytes res/drawable-xhdpi/ic_pdk_action_unlock.png | Bin 0 -> 1468 bytes res/drawable-xxhdpi/ic_ambient_light.png | Bin 0 -> 1880 bytes res/drawable-xxhdpi/ic_pdk_accelerometer.png | Bin 0 -> 2681 bytes res/drawable-xxhdpi/ic_pdk_action_lock.png | Bin 0 -> 1754 bytes res/drawable-xxhdpi/ic_pdk_action_unlock.png | Bin 0 -> 1784 bytes res/drawable-xxxhdpi/ic_ambient_light.png | Bin 0 -> 2016 bytes res/drawable-xxxhdpi/ic_pdk_accelerometer.png | Bin 0 -> 3185 bytes res/drawable-xxxhdpi/ic_pdk_action_lock.png | Bin 0 -> 1950 bytes res/drawable-xxxhdpi/ic_pdk_action_unlock.png | Bin 0 -> 1945 bytes .../card_generator_sensors_accelerometer.xml | 63 ++ .../card_generator_sensors_ambient_light.xml | 63 ++ res/menu/activity_data_stream.xml | 8 + res/values/databases.xml | 8 +- res/values/generators.xml | 21 +- res/values/strings.xml | 2 + .../activities/DataStreamActivity.java | 52 ++ .../generators/DataPointsAdapter.java | 92 ++- .../generators/device/Battery.java | 5 - .../device/ForegroundApplication.java | 34 +- .../generators/sensors/Accelerometer.java | 652 +++++++++++++++++- .../generators/sensors/AmbientLight.java | 279 ++++++-- .../generators/sensors/SensorGenerator.java | 74 ++ 33 files changed, 1244 insertions(+), 109 deletions(-) create mode 100755 res/drawable-hdpi/ic_ambient_light.png create mode 100755 res/drawable-hdpi/ic_pdk_accelerometer.png create mode 100755 res/drawable-hdpi/ic_pdk_action_lock.png create mode 100755 res/drawable-hdpi/ic_pdk_action_unlock.png create mode 100755 res/drawable-mdpi/ic_ambient_light.png create mode 100755 res/drawable-mdpi/ic_pdk_accelerometer.png create mode 100755 res/drawable-mdpi/ic_pdk_action_lock.png create mode 100755 res/drawable-mdpi/ic_pdk_action_unlock.png create mode 100755 res/drawable-xhdpi/ic_ambient_light.png create mode 100755 res/drawable-xhdpi/ic_pdk_accelerometer.png create mode 100755 res/drawable-xhdpi/ic_pdk_action_lock.png create mode 100755 res/drawable-xhdpi/ic_pdk_action_unlock.png create mode 100755 res/drawable-xxhdpi/ic_ambient_light.png create mode 100755 res/drawable-xxhdpi/ic_pdk_accelerometer.png create mode 100755 res/drawable-xxhdpi/ic_pdk_action_lock.png create mode 100755 res/drawable-xxhdpi/ic_pdk_action_unlock.png create mode 100755 res/drawable-xxxhdpi/ic_ambient_light.png create mode 100755 res/drawable-xxxhdpi/ic_pdk_accelerometer.png create mode 100755 res/drawable-xxxhdpi/ic_pdk_action_lock.png create mode 100755 res/drawable-xxxhdpi/ic_pdk_action_unlock.png create mode 100755 res/layout/card_generator_sensors_accelerometer.xml create mode 100755 res/layout/card_generator_sensors_ambient_light.xml create mode 100755 res/menu/activity_data_stream.xml create mode 100755 src/com/audacious_software/passive_data_kit/generators/sensors/SensorGenerator.java diff --git a/res/drawable-hdpi/ic_ambient_light.png b/res/drawable-hdpi/ic_ambient_light.png new file mode 100755 index 0000000000000000000000000000000000000000..01cd42fb19e2ed29ccf679acc743bedd90f76e74 GIT binary patch literal 1424 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 Mr>mdKI;Vst0E!ypSO5S3 literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_ambient_light.png b/res/drawable-mdpi/ic_ambient_light.png new file mode 100755 index 0000000000000000000000000000000000000000..183e05399ae0057f754d05a38211d8a298bd1c35 GIT binary patch literal 1271 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m^Cs(B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxOgGuk*h0bFQqR!T z(!$6@N5ROz&`jUJQs2--*TB%qz|zXVPyq^*fVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5 zFD$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;NpiyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr7I$DAddqG<*}2 zGxI=#nqXbNzE+-j#U+V($*G<$wn{*A^fEJ3tXvG74NT2V9gSQqO$-fP%^eNRoL!t< z4Gm1pO$>lMm|mCsATTySbngC`LrJCZ4FdpC0*k zmCW(PZMo^?6U04)Sq|P`mt4SFd~))<=c#edk0KAYB^sQopY{8J(5nJQ;Z=$k)wh0% zu753{=6ElR_1uExCk`kvUf#gfZ@~OqWZT7c4YI%POseMJeWAry;sWoA3v1>-n03z9 znR!*hYqpvTIqZopZ|!?lFOM{TAndn5{#^opULsfCgN30E;f=NyO^FeUzou_-IW4d~ zdGh`QS+SQ9``C|f;AOwCx|-ejOVZg(A8mA0Z}~smmvg{w%?07bT9(OcRUd8930)Sr z_S=KGnPTbxW9ByaF}&6@m^b~`s*cAI4Sz(X&r1H6at+)Z(vUgpQ0ZOPKgKJb_SA3I gd9AAdAi9B(!M(M}Xwu9i3s6zy>FVdQ&MBb@08q@o0RR91 literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_pdk_accelerometer.png b/res/drawable-mdpi/ic_pdk_accelerometer.png new file mode 100755 index 0000000000000000000000000000000000000000..557004955426c3704b6ae075ec1046c91b11782a GIT binary patch literal 1404 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m^Cs(B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxOgGuk*h0bFQqR!T z(!$6@N5ROz&`jUJQs2--*TB%qz|zXVPyq^*fVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5 zFD$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;NpiyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr7I$DAddqG<*}2 zGxI=#nqXbNzE+-j#U+V($*G<$wn{*A^fEJ3tV}E&P0gL1U5#9wTn!Cf%^eNRoL!t< z4Gm1pO$>lMm|mCsATTyX)sZwmMII{`Tz)4F|k26za$1}*x- z$|df#Z?f-C4c8RWAdgcYeQb@apUv64w(M2-^747#lhf`Q_Z~LrS>e(5qW+h)gO7@E zNQ3kP_P7UJ>W)kqEK_Igv^HQYbI`xR@J^xCf|)z$;Ixm{w{*80eIR<~RN3kWFBAG3 z%^e%=%Zl0EeZYI074vk#{a=x%kM zw3l(t#(=ky6)(21P0E=xr78PWt=IFJWq~#ki!S$RnPr~Ue&t>wHFxsl-}=YwPHgZD zV$oaD+{^XUUcmF&ixQSiIg_3=tG_z_V!f||TJ+Di48|o^j620Rk9@1~(v4c}ec{Z> zbiLol?OcCrYKJ!aOVzt?owdMAa89#{<_U|pi&(jri27wWeSdv!LFZbvbxpAbXI49Y z?~nOpKjRY1`>oa~f;C6e)*tMye}V9o4{4#L zaqFD!{Lc;3U1PlKpiM=3shse0r^)NM4kdnE<-xd8fkEz2ov6#No1b(KFnqP2{qPvm RUr=$%;OXk;vd$@?2>@#*{>}gZ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..53377c1e3f1a432613480fdc96fbc0bd4c5e49c7 GIT binary patch literal 1244 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m^Cs(B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxOgGuk*h0bFQqR!T z(!$6@N5ROz&`jUJQs2--*TB%qz|zXVPyq^*fVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5 zFD$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;NpiyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr7I$DAddqG<*}2 zGxI=#nqXbNzE+-j#U+V($*G<$wn{*A^fEJ3tPBl}EsfmFOpIKejSLN4%`MCz*Q}aq-dQ%X3O>yc4C5YStpv^9+MVV!(DQ-pixe8#9TV>*Q zixE!qpn6kqyTur%UVWfr^g+>!6x}c(U>X83;fWW>fhYgeJYbqH0w(Ur4S(wy7#O`g zT^vIyZf%*eU!TQMMoDqo*+N0;ZA)E1YNlM`1v zN>>@Yz3jovYoK%feBU4IJty`><%rL;|EAhH&D43%>JX& z_xy?t;@SMu5;@*IJp8dUO{JbWx?tJf4Dn?(Hjc5EUCVN{lH7WoVt-HGuf$VyeZPjW zuY4k})P+?^G4l^=#vG~>W$V6Vs=Q&9?kO*cGlxV=5;ny%-=E#fxo?(>a!P)j`J(Qs z56et%w3RLV>J;%%w&Fq`m$b0KI-A<-s&Ym@rvDI2VDOxEV!zs>0vAwuvd$@? F2>_BEt!n@P literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..542703db3ba8f0e33df2ca671afc21406d6cd0b0 GIT binary patch literal 1248 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m^Cs(B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxOgGuk*h0bFQqR!T z(!$6@N5ROz&`jUJQs2--*TB%qz|zXVPyq^*fVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5 zFD$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;NpiyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr7I$DAddqG<*}2 zGxI=#nqXbNzE+-j#U+V($*G<$wn{*A^fEJ3tW3;|oGe|<&5T@~jSLN4%`MCz*Q}aq-dQ%X3&2j1lC5YStpv^9+MVV!(DQ-pixe8#9TV>*Q ziwREipn6kqyTufzUVWfr^g+>!6x}c(U>X83;fWW>fhYgeJYbqH0w(TM*Gvlr21Y+m z7srr_TU)NJ_hxYvIq>mz>QrGtk-ch*4lX*n=;5rB3QY=btN*tqTuKpa4xH_|A@Ypc zQ-?L_1{_WMF9?g~AAf&SZH#VUOdu~m&tS(}Sbzi>BJJNQ59yzO@r=g?KY z6IiR%^lzWsw(;8r4f}(#`?!VnB#Nisa@%e2^f-6cEUit~)s)U{O}w%t{bIgL)19yJ zOH`R`l6gx_cGb?hRoE9CleAx)^O<3*;+hQcR8=9L$6UHS<+BcK-z@krdU6KS&2ztd zRxC2J+tRzOi+#(~H^TY1MOVLAXx|q2bo+MeeebVDyRQ7Get=;Uk8no0VE$Q9nd9l| K=d#Wzp$Py}U$t5Q literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_ambient_light.png b/res/drawable-xhdpi/ic_ambient_light.png new file mode 100755 index 0000000000000000000000000000000000000000..64185f1622836de342e8f05709e7a8d9351b4772 GIT binary patch literal 1569 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 Q2&!N`UHx3vIVCg!0Q~D0ZU6uP literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..093cf20ca3defd36c82a9e9d664c5f2b72dec283 GIT binary patch literal 1461 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@%o>>?5hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|8hm3bwJ6}oxF$}kgLQj3#|G7CyF^YauyCMG83 zmzLNn0bL65LT&-v*t}wBFaZNhzap_f-%!s0#9CcUNA2%eZ%@_WRkj)vq@1SYLi` z?|t*{cZ$!~J`M=o>XA|`#+et?aQE4^2QzyUBzTzQKQx33FfkUK-eRuXAsNVR<{0z$ zK2yS+(EV%J?laH3^2V1@`amQH)2@Tvvu3}2c6-0_d7A*{JO}ntTP8gRftp4!1I}fQ z-Mh*qWp0TwN3-RB?KskW@vpFg*WLLnXUlvRvR&GFk8jh5&(c5E-c(FzlyZ>>NqxE7 z>t~yTr{3ce>Jxbb-WE?dbt$~Nf62PLUXNO`)s|VCOkNg~pA{U;Mx=6|mwPd;0(TOZD6DzR;P$oV*~aJa+Ytdp-J4eU7bVZY~^#twQx{k~9NZ zd?aJCnQ|9#T>?5hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|8hm3bwJ6}oxF$}kgLQj3#|G7CyF^YauyCMG83 zmzLNn0bL65LT&-v*t}wBFaZNhzap_f-%!s0l?YJBS?bT^lA?@9oIGRe?im1xM+N1swkzN`f2<1k5iw{%3P66r3LA z;#v^=!C%12L-6QAg}$X4TXjPYK3dR}`G;*?K%Yit(atXl{C0=AciyR8|NY%vZ!OKP zAhk_Xr*r1@G3-8@d(imy0SyiY`40`|0<4JzhqsuUc1T`iDRT(=dH+ztq|p6q*z8&4 zMEXc&)yQMZ) zc|3A_{ZI3Q&)xrAR&OGl)+#vjsx4W3x4~_Uz7#i^zcj`F& ze6Fy1^I9qSnN0h;k8oc1@hOk+Y4U1}XpCo`Rmr_#*`I!;+x3BUOYf#Do_xZx!|SQe zlIvlP&W-Y-z7A84I?t&(AegY2Vdf|Mw*m=IOsx3D4zwK8`;=GYbS0&NY2F%(-c?7B zHzw`f%dlVc>j9Y^PqrB49pqT4I^*a8rQ=KAve&L@zH!>v%uzO*(ej*h&!MP>$tPTd z3;3rUw77BE;{WtFtB?M9aGa}aj`jzRzTSDiEB`b7mJ+=sDX4fv$m{RM?o?MngT;kw zWlOr15_s1%a6S5c;@9^sp(CLWL~eyzz$*Geyv@XrH hueM-1b8|fdGsBJx_Kqi7vcG`JQ%_evmvv4FO#pZ17hC`U literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_ambient_light.png b/res/drawable-xxhdpi/ic_ambient_light.png new file mode 100755 index 0000000000000000000000000000000000000000..84d9cbbab2dfb900a7c2a82b94feb548ecf08b77 GIT binary patch literal 1880 zcmaJ?X;2eq7>*)`C?X(Q1#yi6f{Z#tua|h44YlDizEuA1Qjji zP}C8jo`^z6QHx?lJgb6(fI5nUqA-96h*+?VXYGcH_D6ANcE9ia=6#;`dcN7MlEtAm z)-$Xr6pD=~42mMxFw<*gL4KF^{~Agz(+Q!Jh(_ay6qy#L2o&fV7!V<{1UL$oDfCHo zFqc9xo2QDA5>jylM~)&inaPKyLogCep>X*+OeR;u1h589P-%G7f$ED?K&9YOm-~tt zVk{U|s>0T5;pp{?W8~}Aa<+oX4+OY64oQH(gbdIji5i@v<57osIpp4SOs4|FE`*v# z{WmG8SONs2S{U%9`FYDhp8&w0O#^+|0Zf(`z+`}abOxIavb-4}hr#ABK;Y9uC8KE- z@ti0qEVs*HH1v(t-XZHm?P5QgiIrcMG%il^3YT&1t%!PA7lwaAf}Mb0zsj_0Q3unf`uT1<bS4eESyL6`r0*WjA$Eb*IZvros~ zGV{Ali(OE6CO$R&ssG`g;?_dY*ic{iq$1GwXCLF^7YPj?@$U6k+D~j}WYO=rPqfG^ zudaT0uV@~Pwk&X8V_`w`_(iT_vH1AMn;8p%E~A}mS4+(*l$n)do-MFdnqRbMKj`>F zvBWW)`jq0i?(Raqj^dYr7B*Sv@1}lWV$)-O;7TaFGP3tGn#~`NEYO* z?xDP~*&n#v92rw>q26#=B`HuFJTI38RPbgLcl7RdFr=nlm9zBwek;n$dDA92Zq;Jh z%j$V@c@Z}G{r%2?&ZO=445gVho6kG6*%{Y*?@8qcSZsdirQ}z6oJ^Wl+}_ACJT`mm zR32Az%%H41A6fAC&g!8izG{}VR8yF-sLjgB-1nPOU-!#u@mp^OGvmwsSSK1U zxn2clyI2#d>mlk^FQLXN?(p;*O(n0qp3Q7KJFTtMEzM##k_}X}$M4NtkmQ{1_?KXe|9`XteNPO(Ykb646wW zGJW%ySode{uaAl5BS=`3J{2FWu${Mei7VJ9*b`2v zcSoaAw)Qf%EJG1A-wdP_)l}^Ok3qu>7UUs$ZaZBHg=HPE{%_fBe7{JZdl)yE8P zS%(s>TiPEu-`Kb#;M1& literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_pdk_accelerometer.png b/res/drawable-xxhdpi/ic_pdk_accelerometer.png new file mode 100755 index 0000000000000000000000000000000000000000..d9970ce495ac8ec2c2a46db37b473e19fa136c42 GIT binary patch literal 2681 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 NHlD72F6*2UngFwcojU*k literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0ffdc6048a3149133a4407456595ad1e3b14245c GIT binary patch literal 1784 zcmaJ?X;c$e7>%M>5EKy=K|EvV0Yu405|Y&D8Ik}ggoY55A_|javH;11WQ2fJkSJxb zxZ$ZHEx3RSqAbU~F0m}4fXZsI6txAhrhrSeXw^;-us=%YoSFCDcfNbS`|kVZyc}U@ zpuMe&Ee?mX=Lc~`*cfDaKe5KX5r5PKV#7j|7m0==ax@vz!8lNcNML}kh7_;}hGZ#; zjqnN_&dN(Ajzl8`AuK7PCP0=Pfh6+EDlvGHqY z0+K+>ft9MDR2>|i8Y-5i#!Hzp{Hm3}3IhuxP{Sw$7}NdCXg&!8c!4m{y$Z%p2*gt zBKV`;|0LFnQ?xKq1nZFvIw^K=a(7E9EsLXrAr#Sx5hP*UMWGTw5xo-80vwLTH9x>h z07+FEOK|BZN+4kIHF^}%NMSygjmLNhDwT}o%V6;6{sCMHk3%7od43E!lfm()lF6I^ zKYu2TI*#Qc(hX`@gN|cmAF;eiv6hBVYq82)Sf@&YWdS-w4UBG_rJ7ue|D=3lSlQ%S zIFn+Dm>8nvc>i_W;~}hvEYpeR#TFCo4{NZV*I~`QIBzf=`)A~QE+{s1n~TleOaG$}j_O4ze^*D!1%Dg<{bhu%&KK6W8m92@OLf@y6 zvImRWVlVV3uE{XQmNbK@IOVkPwxTZELesL_Hd&h~LeHUcmzc|4;*NRh;*xKk4Y(^2il4su)}?+BH#Dyy z>#k&n_r;EgoMi7!y|>D~Y@!kS=JC?S=CJPHdU7T7xPo%4W4Se8PeAR+oL`+SCR@Cw zKs$~*{UzTIvYrFSuk;&T9++d!tMar-VtwhBJr@g| zZfq}O9w9VYhanH-+b?F9y|IUi>-sb2mU#EwIRyXg8*As88!8Hl4}NjlvwI}Fv3Mpk z%-rc^(uCob_i4mF@18Ztbt=)40?tn{$8pF>Gl$I``mg&Z%kxK)p2N pe(lu2L+(y{iScg9{ojjmR=E0J2Km{NQU}Xj#OH-_Px{AZ{sSTSxKaQB literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_ambient_light.png b/res/drawable-xxxhdpi/ic_ambient_light.png new file mode 100755 index 0000000000000000000000000000000000000000..d5c326912d85a1f090ff6f800a8873e4182c7d07 GIT binary patch literal 2016 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 + + + + + + + + + + + + + + + \ 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..62dd685 --- /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/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 9e41810..0b74d73 100755 --- a/res/values/databases.xml +++ b/res/values/databases.xml @@ -27,8 +27,14 @@ 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); diff --git a/res/values/generators.xml b/res/values/generators.xml index b4037cd..0150e72 100755 --- a/res/values/generators.xml +++ b/res/values/generators.xml @@ -158,4 +158,23 @@ Most Used (Last 24 Hours) Recently Used %1$s: %2$.0fm - \ No newline at end of file + + + 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 + + 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..c3a33f4 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,16 @@ 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.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 +20,7 @@ public class DataStreamActivity extends AppCompatActivity implements Generators.GeneratorUpdatedListener { private DataPointsAdapter mAdapter = null; + private Menu mMenu = null; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -70,4 +76,50 @@ public void run() { } }); } + + 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; + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_pdk_toggle_sort_lock) { + this.toggleSortLock(); + + return true; + } + + return true; + } + + private void toggleSortLock() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + + boolean locked = prefs.getBoolean(DataPointsAdapter.SORT_BY_UPDATED, DataPointsAdapter.SORT_BY_UPDATED_DEFAULT); + + MenuItem lockedItem = this.mMenu.findItem(R.id.action_pdk_toggle_sort_lock); + + if (locked) { + lockedItem.setIcon(R.drawable.ic_pdk_action_unlock); + } else { + lockedItem.setIcon(R.drawable.ic_pdk_action_lock); + } + + SharedPreferences.Editor e = prefs.edit(); + e.putBoolean(DataPointsAdapter.SORT_BY_UPDATED, (locked == false)); + e.apply(); + + this.mAdapter.notifyDataSetChanged(); + } } 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 b934071..38cffc3 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,6 +1,8 @@ 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; @@ -18,6 +20,9 @@ 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; @Override @@ -85,46 +90,57 @@ public int getItemCount() { } 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(); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + if (prefs.getBoolean(DataPointsAdapter.SORT_BY_UPDATED, DataPointsAdapter.SORT_BY_UPDATED_DEFAULT)) { + 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; + + 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; } - - 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(); + }); + } else { + Collections.sort(generators, new Comparator>() { + @Override + public int compare(Class one, Class two) { + return one.getCanonicalName().compareTo(two.getCanonicalName()); } - - if (oneUpdated < twoUpdated) { - return 1; - } else if (oneUpdated > twoUpdated) { - return -1; - } - - return 0; - } - }); + }); + } } public int getItemViewType (int position) { 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 index f940c35..6fead94 100755 --- a/src/com/audacious_software/passive_data_kit/generators/device/Battery.java +++ b/src/com/audacious_software/passive_data_kit/generators/device/Battery.java @@ -11,7 +11,6 @@ import android.os.BatteryManager; import android.os.Bundle; import android.support.v4.content.ContextCompat; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -342,13 +341,9 @@ public String getFormattedValue(float value, AxisBase axis) { if (level != lastLevel) { values.add(0, new Entry(when, level)); lastLevel = level; - - Log.e("SLEEP-SIGHT", "VALUE: " + level + " -- " + (when - start)); } } - Log.e("SLEEP-SIGHT", "BATT VALUES COUNT 2: " + values.size()); - LineDataSet set = new LineDataSet(values, "Battery"); set.setAxisDependency(YAxis.AxisDependency.LEFT); set.setLineWidth(2.0f); 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 index 4eb5410..90f4620 100755 --- a/src/com/audacious_software/passive_data_kit/generators/device/ForegroundApplication.java +++ b/src/com/audacious_software/passive_data_kit/generators/device/ForegroundApplication.java @@ -13,9 +13,11 @@ 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; @@ -46,12 +48,13 @@ public class ForegroundApplication extends Generator{ 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 = 2; + 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; @@ -94,14 +97,26 @@ private void startGenerator() { this.mAppChecker.other(new AppChecker.Listener() { @Override public void onForeground(String process) { - Log.e("PDK", "PROCESS: " + 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); @@ -130,6 +145,8 @@ public void onForeground(String process) { 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); @@ -200,8 +217,8 @@ public static void bindViewHolder(DataPointViewHolder holder) { ArrayList latest = new ArrayList<>(); - String where = ForegroundApplication.HISTORY_OBSERVED + " > ?"; - String[] args = { "" + yesterday }; + 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); @@ -298,16 +315,19 @@ public int compare(HashMap mapOne, HashMap mapTw 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(); - double minutes = duration / (1000 * 60); - 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; 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 index 3828e56..93e805d 100755 --- a/src/com/audacious_software/passive_data_kit/generators/sensors/Accelerometer.java +++ b/src/com/audacious_software/passive_data_kit/generators/sensors/Accelerometer.java @@ -1,8 +1,658 @@ package com.audacious_software.passive_data_kit.generators.sensors; +import android.app.Activity; +import android.content.ContentValues; +import android.content.Context; +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.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 Accelerometer { +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 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; + + 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(); + } + + 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); + + Looper.loop(); + } + }; + + Thread t = new Thread(r, "accelerometer"); + t.start(); + } else { + if (this.mSensor != null) { + sensors.unregisterListener(this, this.mSensor); + + if (Accelerometer.sHandler != null) { + Looper loop = Accelerometer.sHandler.getLooper(); + loop.quit(); + + Accelerometer.sHandler = null; + } + + me.mXValueBuffers = null; + me.mYValueBuffers = null; + me.mZValueBuffers = null; + me.mAccuracyBuffers = null; + me.mRawTimestampBuffers = null; + me.mTimestampBuffers = null; + + me.mActiveBuffersIndex = 0; + me.mCurrentBufferIndex = 0; + + this.mSensor = null; + } + } + } + + 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(Context context) { + return new ArrayList<>(); + } + + public static void bindViewHolder(final DataPointViewHolder holder) { + if (Accelerometer.sIsDrawing) { + Log.e("PDK", "IS DRAWING"); + return; + } + + final long drawStart = System.currentTimeMillis(); + + if (drawStart - Accelerometer.sLastDrawStart < (30 * 1000)) { + Log.e("PDK", "TOO SOON"); + return; + } + + Accelerometer.sLastDrawStart = drawStart; + + Accelerometer.sIsDrawing = true; + + Log.e("PDK", "HOLDER " + holder.hashCode()); + + 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); + + Log.e("PDK", "START QUERY: " + (System.currentTimeMillis() - drawStart)); + + Cursor c = generator.mDatabase.query(Accelerometer.TABLE_HISTORY, null, null, null, null, null, Accelerometer.HISTORY_OBSERVED + " DESC"); + + Log.e("PDK", "END QUERY: " + (System.currentTimeMillis() - drawStart)); + + 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); + + Log.e("PDK", "ACCEL PREP: " + (System.currentTimeMillis() - drawStart) + " -- COUNT: " + c.getCount()); + + if (c.moveToNext() && (context instanceof Activity)) { + cardContent.setVisibility(View.VISIBLE); + cardEmpty.setVisibility(View.GONE); + + long timestamp = c.getLong(c.getColumnIndex(Accelerometer.HISTORY_OBSERVED)) / (1000 * 1000 * 1000); + + dateLabel.setText(Generator.formatTimestamp(context, timestamp)); + + Runnable r = new Runnable() { + @Override + public void run() { + Log.e("PDK", "THREAD START: " + (System.currentTimeMillis() - drawStart)); + + 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 = { "" + start }; + + 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); + + Log.e("PDK", "COUNT: " + c.getCount()); + Log.e("PDK", "ACCEL START BUILD: " + (System.currentTimeMillis() - drawStart)); + + 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; + } + } + + Log.e("PDK", "ACCEL END BUILD BUILD: " + (System.currentTimeMillis() - drawStart)); + + Log.e("PDK", "DATA COUNT: " + xLowValues.size()); + + 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; + + Log.e("PDK", "THREAD HANDOFF: " + (System.currentTimeMillis() - drawStart)); + + 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() { + Log.e("PDK", "UI START: " + (System.currentTimeMillis() - drawStart)); + + 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); + } + + Log.e("PDK", "ACCEL START GRAPH: " + (System.currentTimeMillis() - drawStart)); + + 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); + } + + Log.e("PDK", "UI END: " + (System.currentTimeMillis() - drawStart)); + + Accelerometer.sIsDrawing = false; + } + }); + } + }; + + Thread t = new Thread(r, "render_accelerometer_graph"); + t.start(); + + c.close(); + } else { + cardContent.setVisibility(View.GONE); + cardEmpty.setVisibility(View.VISIBLE); + + dateLabel.setText(R.string.label_never_pdk); + } + + c.close(); + + Log.e("PDK", "MAIN DONE: " + (System.currentTimeMillis() - drawStart)); + } + + 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"); + + 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; + } + } + +// Log.e("PDK", "ACCEL[" + this.mCurrentBufferIndex + "/" + this.mActiveBuffersIndex + "] = " + sensorEvent.values[0] + " -- " + sensorEvent.values[1] + " -- " + sensorEvent.values[2]); + + 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 - (2 * 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 index 4dbedf8..cba3bc2 100755 --- a/src/com/audacious_software/passive_data_kit/generators/sensors/AmbientLight.java +++ b/src/com/audacious_software/passive_data_kit/generators/sensors/AmbientLight.java @@ -1,5 +1,6 @@ package com.audacious_software.passive_data_kit.generators.sensors; +import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; import android.database.Cursor; @@ -13,7 +14,6 @@ import android.os.Handler; import android.os.Looper; import android.support.v4.content.ContextCompat; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -24,7 +24,6 @@ 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.device.Battery; import com.audacious_software.pdk.passivedatakit.R; import com.github.mikephil.charting.charts.LineChart; import com.github.mikephil.charting.components.AxisBase; @@ -45,19 +44,21 @@ * Created by cjkarr on 4/17/2017. */ -public class AmbientLight extends Generator implements SensorEventListener { +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 DATABASE_PATH = "pdk-sensor-light.sqlite"; + 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; @@ -66,6 +67,23 @@ public class AmbientLight extends Generator implements SensorEventListener { 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()); @@ -87,6 +105,23 @@ private void startGenerator() { 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); @@ -96,12 +131,20 @@ 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_NORMAL, 0, AmbientLight.sHandler); + sensors.registerListener(me, me.mSensor, SensorManager.SENSOR_DELAY_FASTEST, 0, AmbientLight.sHandler); else - sensors.registerListener(me, me.mSensor, SensorManager.SENSOR_DELAY_NORMAL, AmbientLight.sHandler); + sensors.registerListener(me, me.mSensor, SensorManager.SENSOR_DELAY_FASTEST, AmbientLight.sHandler); Looper.loop(); } @@ -111,6 +154,8 @@ public void run() t.start(); } else { if (this.mSensor != null) { + sensors.unregisterListener(this, this.mSensor); + if (AmbientLight.sHandler != null) { Looper loop = AmbientLight.sHandler.getLooper(); loop.quit(); @@ -118,30 +163,17 @@ public void run() AmbientLight.sHandler = null; } - sensors.unregisterListener(this, this.mSensor); + me.mValueBuffers = null; + me.mAccuracyBuffers = null; + me.mRawTimestampBuffers = null; + me.mTimestampBuffers = null; + + me.mActiveBuffersIndex = 0; + me.mCurrentBufferIndex = 0; this.mSensor = null; } } - - - Generators.getInstance(this.mContext).registerCustomViewClass(AmbientLight.GENERATOR_IDENTIFIER, Battery.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_device_ambient_light_create_history_table)); - } - - this.setDatabaseVersion(this.mDatabase, AmbientLight.DATABASE_VERSION); } public static boolean isEnabled(Context context) { @@ -167,8 +199,8 @@ public static void bindViewHolder(DataPointViewHolder holder) { AmbientLight generator = AmbientLight.getInstance(context); - long now = System.currentTimeMillis(); - long start = now - (24 * 60 * 60 * 1000); + long now = System.currentTimeMillis() / (1000 * 60 * 5); + long start = now - (24 * 12); // * 60); String where = AmbientLight.HISTORY_OBSERVED + " >= ?"; String[] args = { "" + start }; @@ -183,13 +215,13 @@ public static void bindViewHolder(DataPointViewHolder holder) { cardContent.setVisibility(View.VISIBLE); cardEmpty.setVisibility(View.GONE); - long timestamp = c.getLong(c.getColumnIndex(AmbientLight.HISTORY_OBSERVED)) / 1000; + long timestamp = c.getLong(c.getColumnIndex(AmbientLight.HISTORY_OBSERVED)) / (1000 * 1000 * 1000); dateLabel.setText(Generator.formatTimestamp(context, timestamp)); c.moveToPrevious(); - final LineChart chart = (LineChart) holder.itemView.findViewById(R.id.battery_level_chart); + final LineChart chart = (LineChart) holder.itemView.findViewById(R.id.light_chart); chart.setViewPortOffsets(0,0,0,0); chart.setHighlightPerDragEnabled(false); chart.setHighlightPerTapEnabled(false); @@ -213,7 +245,7 @@ public static void bindViewHolder(DataPointViewHolder holder) { xAxis.setValueFormatter(new IAxisValueFormatter() { @Override public String getFormattedValue(float value, AxisBase axis) { - Date date = new Date((long) value); + Date date = new Date((long) value * 5 * 60 * 1000); return timeFormat.format(date); } @@ -224,8 +256,6 @@ public String getFormattedValue(float value, AxisBase axis) { 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(); @@ -234,38 +264,88 @@ public String getFormattedValue(float value, AxisBase axis) { chart.getLegend().setEnabled(false); chart.getDescription().setEnabled(false); - ArrayList values = new ArrayList<>(); + ArrayList lowValues = new ArrayList<>(); + ArrayList highValues = new ArrayList<>(); - long lastLevel = -1; + long lastTimestamp = -1; + + float maxValue = 0; + float minValue = 0; + + float lowLevel = -1; + float highLevel = -1; while (c.moveToNext()) { long when = c.getLong(c.getColumnIndex(AmbientLight.HISTORY_OBSERVED)); - long level = c.getLong(c.getColumnIndex(AmbientLight.HISTORY_LEVEL)); - if (level != lastLevel) { - values.add(0, new Entry(when, level)); - lastLevel = level; + when = when / (1000 * 1000); + when = when / (1000 * 6 * 50); + + float level = c.getFloat(c.getColumnIndex(AmbientLight.HISTORY_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; + } - Log.e("SLEEP-SIGHT", "VALUE: " + level + " -- " + (when - start)); + if (level < minValue) { + minValue = level; } } - Log.e("SLEEP-SIGHT", "LIGHT VALUES COUNT 2: " + values.size()); + if (lastTimestamp != -1) { + lowValues.add(0, new Entry(lastTimestamp, lowLevel)); + highValues.add(0, new Entry(lastTimestamp, highLevel)); + } - LineDataSet set = new LineDataSet(values, "Light Level"); + LineDataSet set = new LineDataSet(lowValues, "Low Light Levels"); set.setAxisDependency(YAxis.AxisDependency.LEFT); - set.setLineWidth(2.0f); + set.setLineWidth(1.0f); set.setDrawCircles(false); set.setFillAlpha(192); set.setDrawFilled(false); set.setDrawValues(true); - set.setColor(ContextCompat.getColor(context, R.color.generator_battery_plot)); + set.setColor(ContextCompat.getColor(context, R.color.generator_ambient_light_low)); set.setDrawCircleHole(false); set.setDrawValues(false); set.setMode(LineDataSet.Mode.LINEAR); - chart.setVisibleYRange(0, 120, YAxis.AxisDependency.LEFT); - chart.setData(new LineData(set)); + 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(minValue) - 1, (float) Math.ceil(maxValue) + 1, YAxis.AxisDependency.LEFT); + chart.setData(chartData); } else { cardContent.setVisibility(View.GONE); cardEmpty.setVisibility(View.VISIBLE); @@ -278,7 +358,7 @@ public String getFormattedValue(float value, AxisBase axis) { public static View fetchView(ViewGroup parent) { - return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_device_battery, parent, false); + return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_sensors_ambient_light, parent, false); } @Override @@ -287,19 +367,19 @@ public List fetchPayloads() { } public static long latestPointGenerated(Context context) { - long timestamp = 0; - AmbientLight me = AmbientLight.getInstance(context); - Cursor c = me.mDatabase.query(AmbientLight.TABLE_HISTORY, null, null, null, null, null, AmbientLight.HISTORY_OBSERVED + " DESC"); + if (me.mLatestTimestamp == 0) { + Cursor c = me.mDatabase.query(AmbientLight.TABLE_HISTORY, null, null, null, null, null, Accelerometer.HISTORY_OBSERVED + " DESC"); - if (c.moveToNext()) { - timestamp = c.getLong(c.getColumnIndex(AmbientLight.HISTORY_OBSERVED)); - } + if (c.moveToNext()) { + me.mLatestTimestamp = c.getLong(c.getColumnIndex(Accelerometer.HISTORY_OBSERVED) / (1000 * 1000)); + } - c.close(); + c.close(); + } - return timestamp; + return me.mLatestTimestamp; } public Cursor queryHistory(String[] cols, String where, String[] args, String orderBy) { @@ -308,11 +388,98 @@ public Cursor queryHistory(String[] cols, String where, String[] args, String or @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 - (2 * 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()); + } + } + } +} From b0ccf25954b2931d65981ce8f32192e77d66fbf4 Mon Sep 17 00:00:00 2001 From: "Chris J. Karr" Date: Fri, 12 May 2017 23:55:18 -0500 Subject: [PATCH 07/10] Visualization performance improvements --- build.gradle | 2 +- .../activities/DataStreamActivity.java | 47 ++- .../generators/DataPointsAdapter.java | 46 +-- .../generators/device/Battery.java | 34 +- .../device/ForegroundApplication.java | 17 +- .../generators/sensors/Accelerometer.java | 47 +-- .../generators/sensors/AmbientLight.java | 290 ++++++++++-------- 7 files changed, 268 insertions(+), 215 deletions(-) diff --git a/build.gradle b/build.gradle index 3dbd2ce..713328b 100755 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.3.1' + classpath 'com.android.tools.build:gradle:2.3.2' } } 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 c3a33f4..a0f4808 100755 --- a/src/com/audacious_software/passive_data_kit/activities/DataStreamActivity.java +++ b/src/com/audacious_software/passive_data_kit/activities/DataStreamActivity.java @@ -9,6 +9,7 @@ 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; @@ -21,6 +22,7 @@ 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); @@ -28,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); @@ -67,12 +72,21 @@ 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; } }); } @@ -83,20 +97,29 @@ public boolean onCreateOptionsMenu(Menu 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) { - // Handle action bar item clicks here. The action bar will - // automatically handle clicks on the Home/Up button, so long - // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); - //noinspection SimplifiableIfStatement - if (id == R.id.action_pdk_toggle_sort_lock) { + if (id == android.R.id.home) { + this.finish(); + return true; + } else if (id == R.id.action_pdk_toggle_sort_lock) { this.toggleSortLock(); - return true; } @@ -106,20 +129,20 @@ public boolean onOptionsItemSelected(MenuItem item) { private void toggleSortLock() { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - boolean locked = prefs.getBoolean(DataPointsAdapter.SORT_BY_UPDATED, DataPointsAdapter.SORT_BY_UPDATED_DEFAULT); + 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 (locked) { - lockedItem.setIcon(R.drawable.ic_pdk_action_unlock); - } else { + 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, (locked == false)); + e.putBoolean(DataPointsAdapter.SORT_BY_UPDATED, (sortEnabled == false)); e.apply(); - this.mAdapter.notifyDataSetChanged(); + this.mAdapter.sortGenerators(); } } 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 38cffc3..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 @@ -24,6 +24,7 @@ public class DataPointsAdapter extends RecyclerView.Adapter 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) { @@ -55,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); @@ -86,14 +95,24 @@ public void onBindViewHolder(final DataPointViewHolder holder, int position) { @Override public int getItemCount() { - return Generators.getInstance(null).activeGenerators().size(); + this.getGenerators(this.mContext); + + return this.mActiveGenerators.size(); } - private void sortGenerators(final Context context, List> generators) { + public void sortGenerators() { + final Context context = this.mContext; + + if (this.mActiveGenerators == null) { + this.mActiveGenerators = Generators.getInstance(this.mContext).activeGenerators(); + } + + final DataPointsAdapter me = this; + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); if (prefs.getBoolean(DataPointsAdapter.SORT_BY_UPDATED, DataPointsAdapter.SORT_BY_UPDATED_DEFAULT)) { - Collections.sort(generators, new Comparator>() { + Collections.sort(me.mActiveGenerators, new Comparator>() { @Override public int compare(Class one, Class two) { long oneUpdated = 0; @@ -133,22 +152,13 @@ public int compare(Class one, Class tw return 0; } }); - } else { - Collections.sort(generators, new Comparator>() { - @Override - public int compare(Class one, Class two) { - return one.getCanonicalName().compareTo(two.getCanonicalName()); - } - }); } } 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/generators/device/Battery.java b/src/com/audacious_software/passive_data_kit/generators/device/Battery.java index 6fead94..d652be9 100755 --- a/src/com/audacious_software/passive_data_kit/generators/device/Battery.java +++ b/src/com/audacious_software/passive_data_kit/generators/device/Battery.java @@ -11,6 +11,7 @@ import android.os.BatteryManager; import android.os.Bundle; import android.support.v4.content.ContextCompat; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -91,6 +92,8 @@ public class Battery extends Generator { private SQLiteDatabase mDatabase = null; + private long mLastTimestamp = 0; + public static Battery getInstance(Context context) { if (Battery.sInstance == null) { Battery.sInstance = new Battery(context.getApplicationContext()); @@ -110,11 +113,13 @@ public static void start(final Context context) { 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) { - long now = System.currentTimeMillis(); - ContentValues values = new ContentValues(); values.put(Battery.HISTORY_OBSERVED, now); @@ -271,6 +276,8 @@ public static void bindViewHolder(DataPointViewHolder holder) { Cursor c = generator.mDatabase.query(Battery.TABLE_HISTORY, null, where, args, null, null, Battery.HISTORY_OBSERVED + " DESC"); + Log.e("PDK", "BATTERY COUNT: " + c.getCount()); + 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); @@ -334,9 +341,12 @@ public String getFormattedValue(float value, AxisBase axis) { long lastLevel = -1; + int observedIndex = c.getColumnIndex(Battery.HISTORY_OBSERVED); + int levelIndex = c.getColumnIndex(Battery.HISTORY_LEVEL); + while (c.moveToNext()) { - long when = c.getLong(c.getColumnIndex(Battery.HISTORY_OBSERVED)); - long level = c.getLong(c.getColumnIndex(Battery.HISTORY_LEVEL)); + long when = c.getLong(observedIndex); + long level = c.getLong(levelIndex); if (level != lastLevel) { values.add(0, new Entry(when, level)); @@ -379,19 +389,19 @@ public List fetchPayloads() { } public static long latestPointGenerated(Context context) { - long timestamp = 0; - Battery me = Battery.getInstance(context); - Cursor c = me.mDatabase.query(Battery.TABLE_HISTORY, null, null, null, null, null, Battery.HISTORY_OBSERVED + " DESC"); + if (me.mLastTimestamp == 0) { + Cursor c = me.mDatabase.query(Battery.TABLE_HISTORY, null, null, null, null, null, Battery.HISTORY_OBSERVED + " DESC"); - if (c.moveToNext()) { - timestamp = c.getLong(c.getColumnIndex(Battery.HISTORY_OBSERVED)); - } + if (c.moveToNext()) { + me.mLastTimestamp = c.getLong(c.getColumnIndex(Battery.HISTORY_OBSERVED)); + } - c.close(); + c.close(); + } - return timestamp; + return me.mLastTimestamp; } public Cursor queryHistory(String[] cols, String where, String[] args, String 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 index 90f4620..c664a3a 100755 --- a/src/com/audacious_software/passive_data_kit/generators/device/ForegroundApplication.java +++ b/src/com/audacious_software/passive_data_kit/generators/device/ForegroundApplication.java @@ -65,6 +65,7 @@ public class ForegroundApplication extends Generator{ 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) { @@ -389,18 +390,18 @@ public static View fetchView(ViewGroup parent) } public static long latestPointGenerated(Context context) { - long timestamp = 0; - ForegroundApplication me = ForegroundApplication.getInstance(context); - Cursor c = me.mDatabase.query(ForegroundApplication.TABLE_HISTORY, null, null, null, null, null, ForegroundApplication.HISTORY_OBSERVED + " DESC"); + if (me.mLastTimestamp == 0) { + Cursor c = me.mDatabase.query(ForegroundApplication.TABLE_HISTORY, null, null, null, null, null, ForegroundApplication.HISTORY_OBSERVED + " DESC"); - if (c.moveToNext()) { - timestamp = c.getLong(c.getColumnIndex(ForegroundApplication.HISTORY_OBSERVED)); - } + if (c.moveToNext()) { + me.mLastTimestamp = c.getLong(c.getColumnIndex(ForegroundApplication.HISTORY_OBSERVED)); + } - c.close(); + c.close(); + } - return timestamp; + return me.mLastTimestamp; } } 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 index 93e805d..823e776 100755 --- a/src/com/audacious_software/passive_data_kit/generators/sensors/Accelerometer.java +++ b/src/com/audacious_software/passive_data_kit/generators/sensors/Accelerometer.java @@ -210,14 +210,12 @@ public static ArrayList diagnostics(Context context) { public static void bindViewHolder(final DataPointViewHolder holder) { if (Accelerometer.sIsDrawing) { - Log.e("PDK", "IS DRAWING"); return; } final long drawStart = System.currentTimeMillis(); if (drawStart - Accelerometer.sLastDrawStart < (30 * 1000)) { - Log.e("PDK", "TOO SOON"); return; } @@ -225,8 +223,6 @@ public static void bindViewHolder(final DataPointViewHolder holder) { Accelerometer.sIsDrawing = true; - Log.e("PDK", "HOLDER " + holder.hashCode()); - final Context context = holder.itemView.getContext(); final View itemView = holder.itemView; @@ -235,31 +231,19 @@ public static void bindViewHolder(final DataPointViewHolder holder) { final long now = System.currentTimeMillis() / (1000 * 60 * 5); final long start = now - (24 * 12); // * 60); - Log.e("PDK", "START QUERY: " + (System.currentTimeMillis() - drawStart)); - - Cursor c = generator.mDatabase.query(Accelerometer.TABLE_HISTORY, null, null, null, null, null, Accelerometer.HISTORY_OBSERVED + " DESC"); - - Log.e("PDK", "END QUERY: " + (System.currentTimeMillis() - drawStart)); - 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); - Log.e("PDK", "ACCEL PREP: " + (System.currentTimeMillis() - drawStart) + " -- COUNT: " + c.getCount()); - - if (c.moveToNext() && (context instanceof Activity)) { + if (context instanceof Activity) { cardContent.setVisibility(View.VISIBLE); cardEmpty.setVisibility(View.GONE); - long timestamp = c.getLong(c.getColumnIndex(Accelerometer.HISTORY_OBSERVED)) / (1000 * 1000 * 1000); - - dateLabel.setText(Generator.formatTimestamp(context, timestamp)); + dateLabel.setText(Generator.formatTimestamp(context, Accelerometer.latestPointGenerated(generator.mContext) / 1000)); Runnable r = new Runnable() { @Override public void run() { - Log.e("PDK", "THREAD START: " + (System.currentTimeMillis() - drawStart)); - final ArrayList xLowValues = new ArrayList<>(); final ArrayList xHighValues = new ArrayList<>(); @@ -270,7 +254,7 @@ public void run() { final ArrayList zHighValues = new ArrayList<>(); final String where = Accelerometer.HISTORY_OBSERVED + " >= ? AND _id % 1024 = 0"; - final String[] args = { "" + start }; + 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"); @@ -293,9 +277,6 @@ public void run() { int yIndex = c.getColumnIndex(Accelerometer.HISTORY_Y); int zIndex = c.getColumnIndex(Accelerometer.HISTORY_Z); - Log.e("PDK", "COUNT: " + c.getCount()); - Log.e("PDK", "ACCEL START BUILD: " + (System.currentTimeMillis() - drawStart)); - while (c.moveToNext()) { long when = c.getLong(whenIndex); @@ -379,10 +360,6 @@ public void run() { } } - Log.e("PDK", "ACCEL END BUILD BUILD: " + (System.currentTimeMillis() - drawStart)); - - Log.e("PDK", "DATA COUNT: " + xLowValues.size()); - if (lastTimestamp != -1) { xLowValues.add(0, new Entry(lastTimestamp, lowX)); xHighValues.add(0, new Entry(lastTimestamp, highX)); @@ -401,8 +378,6 @@ public void run() { final float finalMaxValue = maxValue; final float finalMinValue = minValue; - Log.e("PDK", "THREAD HANDOFF: " + (System.currentTimeMillis() - drawStart)); - final List> data = new ArrayList<>(); data.add(xLowValues); data.add(xHighValues); @@ -414,8 +389,6 @@ public void run() { activity.runOnUiThread(new Runnable() { @Override public void run() { - Log.e("PDK", "UI START: " + (System.currentTimeMillis() - drawStart)); - int[] colors = { R.color.generator_accelerometer_x_low, R.color.generator_accelerometer_x_high, @@ -447,8 +420,6 @@ public void run() { chartData.addDataSet(set); } - Log.e("PDK", "ACCEL START GRAPH: " + (System.currentTimeMillis() - drawStart)); - final LineChart chart = (LineChart) itemView.findViewById(R.id.accelerometer_chart); if (chart != null) { @@ -498,18 +469,14 @@ public String getFormattedValue(float value, AxisBase axis) { chart.setData(chartData); } - Log.e("PDK", "UI END: " + (System.currentTimeMillis() - drawStart)); - Accelerometer.sIsDrawing = false; } }); } }; - Thread t = new Thread(r, "render_accelerometer_graph"); + Thread t = new Thread(r, "render-accelerometer-graph"); t.start(); - - c.close(); } else { cardContent.setVisibility(View.GONE); cardEmpty.setVisibility(View.VISIBLE); @@ -517,8 +484,6 @@ public String getFormattedValue(float value, AxisBase axis) { dateLabel.setText(R.string.label_never_pdk); } - c.close(); - Log.e("PDK", "MAIN DONE: " + (System.currentTimeMillis() - drawStart)); } @@ -536,10 +501,10 @@ 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"); + 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)); + me.mLatestTimestamp = c.getLong(c.getColumnIndex(Accelerometer.HISTORY_OBSERVED) / 1000); } c.close(); 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 index cba3bc2..eee9a3b 100755 --- a/src/com/audacious_software/passive_data_kit/generators/sensors/AmbientLight.java +++ b/src/com/audacious_software/passive_data_kit/generators/sensors/AmbientLight.java @@ -1,5 +1,6 @@ package com.audacious_software.passive_data_kit.generators.sensors; +import android.app.Activity; import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; @@ -14,6 +15,7 @@ import android.os.Handler; import android.os.Looper; import android.support.v4.content.ContextCompat; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -62,6 +64,8 @@ public class AmbientLight extends SensorGenerator implements SensorEventListener private static AmbientLight sInstance = null; private static Handler sHandler = null; + private static boolean sIsDrawing = false; + private static long sLastDrawStart = 0; private SQLiteDatabase mDatabase = null; @@ -194,166 +198,206 @@ public static ArrayList diagnostics(Context context) { return new ArrayList<>(); } - public static void bindViewHolder(DataPointViewHolder holder) { - final Context context = holder.itemView.getContext(); + public static void bindViewHolder(final DataPointViewHolder holder) { + if (AmbientLight.sIsDrawing) { + return; + } - AmbientLight generator = AmbientLight.getInstance(context); + final long drawStart = System.currentTimeMillis(); + + if (drawStart - AmbientLight.sLastDrawStart < (30 * 1000)) { + return; + } - long now = System.currentTimeMillis() / (1000 * 60 * 5); - long start = now - (24 * 12); // * 60); + AmbientLight.sLastDrawStart = drawStart; + + AmbientLight.sIsDrawing = true; + + final Context context = holder.itemView.getContext(); - String where = AmbientLight.HISTORY_OBSERVED + " >= ?"; - String[] args = { "" + start }; + final AmbientLight generator = AmbientLight.getInstance(context); - Cursor c = generator.mDatabase.query(AmbientLight.TABLE_HISTORY, null, where, args, null, null, AmbientLight.HISTORY_OBSERVED + " DESC"); + 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 (c.moveToNext()) { + if (context instanceof Activity) { cardContent.setVisibility(View.VISIBLE); cardEmpty.setVisibility(View.GONE); - long timestamp = c.getLong(c.getColumnIndex(AmbientLight.HISTORY_OBSERVED)) / (1000 * 1000 * 1000); - - dateLabel.setText(Generator.formatTimestamp(context, timestamp)); - - c.moveToPrevious(); - - 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() { + dateLabel.setText(Generator.formatTimestamp(context, AmbientLight.latestPointGenerated(context) / 1000)); + + Runnable r = new Runnable() { @Override - public String getFormattedValue(float value, AxisBase axis) { - Date date = new Date((long) value * 5 * 60 * 1000); + public void run() { + final ArrayList lowValues = new ArrayList<>(); + final ArrayList highValues = new ArrayList<>(); - return timeFormat.format(date); - } - }); + long lastTimestamp = -1; - 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)); + float maxValue = 0; + float minValue = 0; - YAxis rightAxis = chart.getAxisRight(); - rightAxis.setEnabled(false); + float lowLevel = -1; + float highLevel = -1; - chart.getLegend().setEnabled(false); - chart.getDescription().setEnabled(false); + final String where = Accelerometer.HISTORY_OBSERVED + " >= ? AND _id"; + final String[] args = { "" + (System.currentTimeMillis() - (24 * 60 * 60 * 1000)) }; - ArrayList lowValues = new ArrayList<>(); - ArrayList highValues = new ArrayList<>(); + Cursor c = generator.mDatabase.query(AmbientLight.TABLE_HISTORY, null, where, args, null, null, AmbientLight.HISTORY_OBSERVED + " DESC"); - long lastTimestamp = -1; + while (c.moveToNext()) { + long when = c.getLong(c.getColumnIndex(AmbientLight.HISTORY_OBSERVED)); - float maxValue = 0; - float minValue = 0; + when = when / (1000 * 1000); + when = when / (1000 * 6 * 50); - float lowLevel = -1; - float highLevel = -1; + float level = c.getFloat(c.getColumnIndex(AmbientLight.HISTORY_LEVEL)); - while (c.moveToNext()) { - long when = c.getLong(c.getColumnIndex(AmbientLight.HISTORY_OBSERVED)); + if (level < 0.001) { + level = 0.001f; + } - when = when / (1000 * 1000); - when = when / (1000 * 6 * 50); + level = (float) Math.log10(level); - float level = c.getFloat(c.getColumnIndex(AmbientLight.HISTORY_LEVEL)); + if (lastTimestamp != when) { + if (lastTimestamp != -1) { + lowValues.add(0, new Entry(lastTimestamp, lowLevel)); + highValues.add(0, new Entry(lastTimestamp, highLevel)); + } - 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; + } - lastTimestamp = when; - lowLevel = level; - highLevel = level; - } else { - if (level < lowLevel) { - lowLevel = level; - } + if (level > highLevel) { + highLevel = level; + } + } - if (level > highLevel) { - highLevel = level; + if (level > maxValue) { + maxValue = level; + } + + if (level < minValue) { + minValue = level; + } } - } - if (level > maxValue) { - maxValue = level; - } + if (lastTimestamp != -1) { + lowValues.add(0, new Entry(lastTimestamp, lowLevel)); + highValues.add(0, new Entry(lastTimestamp, highLevel)); + } - if (level < minValue) { - minValue = level; + 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; + } + }); } - } - - if (lastTimestamp != -1) { - lowValues.add(0, new Entry(lastTimestamp, lowLevel)); - highValues.add(0, new Entry(lastTimestamp, highLevel)); - } + }; - 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(minValue) - 1, (float) Math.ceil(maxValue) + 1, YAxis.AxisDependency.LEFT); - chart.setData(chartData); + 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); } - - c.close(); } public static View fetchView(ViewGroup parent) @@ -370,7 +414,7 @@ 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"); + 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(Accelerometer.HISTORY_OBSERVED) / (1000 * 1000)); From b09253314e75b8f6f0d8642e3df8279c2f07103e Mon Sep 17 00:00:00 2001 From: "Chris J. Karr" Date: Mon, 15 May 2017 11:28:14 -0500 Subject: [PATCH 08/10] Fixed various crashing bugs related to launching external apps on Android 6. * Added support for grabbing wake locks in sensor generators. * Added ability to run Accelerometer in a sampling (as opposed to continuous) mode --- AndroidManifest.xml | 1 + res/values/diagnostics.xml | 3 + .../generators/Generators.java | 24 +++ .../device/ForegroundApplication.java | 4 +- .../generators/sensors/Accelerometer.java | 174 +++++++++++++++--- .../generators/sensors/AmbientLight.java | 92 +++++++-- .../generators/wearables/WithingsDevice.java | 1 + 7 files changed, 254 insertions(+), 45 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 279ecb5..5ad723f 100755 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -6,6 +6,7 @@ + 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/src/com/audacious_software/passive_data_kit/generators/Generators.java b/src/com/audacious_software/passive_data_kit/generators/Generators.java index a90f321..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(); } 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 index c664a3a..85c58de 100755 --- a/src/com/audacious_software/passive_data_kit/generators/device/ForegroundApplication.java +++ b/src/com/audacious_software/passive_data_kit/generators/device/ForegroundApplication.java @@ -180,7 +180,9 @@ public static ArrayList diagnostics(final Context context) { 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() { - context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)); + Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); } })); } 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 index 823e776..d15e5e8 100755 --- a/src/com/audacious_software/passive_data_kit/generators/sensors/Accelerometer.java +++ b/src/com/audacious_software/passive_data_kit/generators/sensors/Accelerometer.java @@ -3,6 +3,7 @@ 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; @@ -14,8 +15,10 @@ 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; @@ -52,6 +55,15 @@ public class Accelerometer extends SensorGenerator implements SensorEventListene 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; @@ -93,6 +105,7 @@ public class Accelerometer extends SensorGenerator implements SensorEventListene long mBaseTimestamp = 0; private long mLatestTimestamp = 0; + private Thread mIntervalThread = null; public static Accelerometer getInstance(Context context) { if (Accelerometer.sInstance == null) { @@ -110,6 +123,39 @@ 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); @@ -158,36 +204,95 @@ public void run() 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 { - if (this.mSensor != null) { - sensors.unregisterListener(this, this.mSensor); + this.stopGenerator(); + } + } - if (Accelerometer.sHandler != null) { - Looper loop = Accelerometer.sHandler.getLooper(); - loop.quit(); + private void stopGenerator() { + final SensorManager sensors = (SensorManager) this.mContext.getSystemService(Context.SENSOR_SERVICE); - Accelerometer.sHandler = null; - } + if (this.mSensor != null) { + if (this.mIntervalThread != null) { + this.mIntervalThread.interrupt(); + this.mIntervalThread = null; + } - me.mXValueBuffers = null; - me.mYValueBuffers = null; - me.mZValueBuffers = null; - me.mAccuracyBuffers = null; - me.mRawTimestampBuffers = null; - me.mTimestampBuffers = null; + sensors.unregisterListener(this, this.mSensor); - me.mActiveBuffersIndex = 0; - me.mCurrentBufferIndex = 0; + if (Accelerometer.sHandler != null) { + Looper loop = Accelerometer.sHandler.getLooper(); + loop.quit(); - this.mSensor = null; + 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) { @@ -204,8 +309,30 @@ public static boolean isRunning(Context context) { return Accelerometer.sInstance.mSensor != null; } - public static ArrayList diagnostics(Context context) { - return new ArrayList<>(); + 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) { @@ -483,12 +610,9 @@ public String getFormattedValue(float value, AxisBase axis) { dateLabel.setText(R.string.label_never_pdk); } - - Log.e("PDK", "MAIN DONE: " + (System.currentTimeMillis() - drawStart)); } - public static View fetchView(ViewGroup parent) - { + public static View fetchView(ViewGroup parent) { return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_sensors_accelerometer, parent, false); } @@ -539,8 +663,6 @@ public void onSensorChanged(SensorEvent sensorEvent) { } } -// Log.e("PDK", "ACCEL[" + this.mCurrentBufferIndex + "/" + this.mActiveBuffersIndex + "] = " + sensorEvent.values[0] + " -- " + sensorEvent.values[1] + " -- " + sensorEvent.values[2]); - 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]; @@ -602,7 +724,7 @@ public void run() { if (now - me.mLastCleanup > me.mCleanupInterval) { me.mLastCleanup = now; - long start = (now - (2 * 24 * 60 * 60 * 1000)) * 1000 * 1000; + long start = (now - (24 * 60 * 60 * 1000)) * 1000 * 1000; String where = Accelerometer.HISTORY_OBSERVED + " < ?"; String[] args = { "" + start }; 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 index eee9a3b..145cf6e 100755 --- a/src/com/audacious_software/passive_data_kit/generators/sensors/AmbientLight.java +++ b/src/com/audacious_software/passive_data_kit/generators/sensors/AmbientLight.java @@ -3,6 +3,7 @@ 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; @@ -14,6 +15,9 @@ 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; @@ -52,6 +56,9 @@ public class AmbientLight extends SensorGenerator implements SensorEventListener 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; @@ -100,6 +107,17 @@ 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(); } @@ -156,28 +174,44 @@ public void run() Thread t = new Thread(r, "ambient-light"); t.start(); - } else { - if (this.mSensor != null) { - sensors.unregisterListener(this, this.mSensor); - if (AmbientLight.sHandler != null) { - Looper loop = AmbientLight.sHandler.getLooper(); - loop.quit(); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); - AmbientLight.sHandler = null; - } + 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(); + } + } - me.mValueBuffers = null; - me.mAccuracyBuffers = null; - me.mRawTimestampBuffers = null; - me.mTimestampBuffers = null; + private void stopGenerator() { + final SensorManager sensors = (SensorManager) this.mContext.getSystemService(Context.SENSOR_SERVICE); - me.mActiveBuffersIndex = 0; - me.mCurrentBufferIndex = 0; + if (this.mSensor != null) { + sensors.unregisterListener(this, this.mSensor); - this.mSensor = null; + 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) { @@ -194,8 +228,30 @@ public static boolean isRunning(Context context) { return AmbientLight.sInstance.mSensor != null; } - public static ArrayList diagnostics(Context context) { - return new ArrayList<>(); + 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) { @@ -508,7 +564,7 @@ public void run() { if (now - me.mLastCleanup > me.mCleanupInterval) { me.mLastCleanup = now; - long start = (now - (2 * 24 * 60 * 60 * 1000)) * 1000 * 1000; + long start = (now - (24 * 60 * 60 * 1000)) * 1000 * 1000; String where = AmbientLight.HISTORY_OBSERVED + " < ?"; String[] args = { "" + start }; 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 index b56eeb9..0d09ce2 100755 --- a/src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java +++ b/src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java @@ -1126,6 +1126,7 @@ public void run() { 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); From 563551910a407e83031c984fda198567b7ab9674 Mon Sep 17 00:00:00 2001 From: "Chris J. Karr" Date: Mon, 15 May 2017 15:58:54 -0500 Subject: [PATCH 09/10] Fixing timestamp display in sensor generator cards. --- .../passive_data_kit/generators/sensors/Accelerometer.java | 2 +- .../passive_data_kit/generators/sensors/AmbientLight.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 index d15e5e8..7f4f8a2 100755 --- a/src/com/audacious_software/passive_data_kit/generators/sensors/Accelerometer.java +++ b/src/com/audacious_software/passive_data_kit/generators/sensors/Accelerometer.java @@ -628,7 +628,7 @@ public static long latestPointGenerated(Context context) { 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); + me.mLatestTimestamp = c.getLong(c.getColumnIndex(Accelerometer.HISTORY_OBSERVED)) / (1000 * 1000); } c.close(); 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 index 145cf6e..5018de4 100755 --- a/src/com/audacious_software/passive_data_kit/generators/sensors/AmbientLight.java +++ b/src/com/audacious_software/passive_data_kit/generators/sensors/AmbientLight.java @@ -473,7 +473,7 @@ public static long latestPointGenerated(Context context) { 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(Accelerometer.HISTORY_OBSERVED) / (1000 * 1000)); + me.mLatestTimestamp = c.getLong(c.getColumnIndex(AmbientLight.HISTORY_OBSERVED) / (1000 * 1000)); } c.close(); From 45c5dd88462ca91cac73163c8772d7e8800db722 Mon Sep 17 00:00:00 2001 From: "Chris J. Karr" Date: Wed, 17 May 2017 21:46:07 -0500 Subject: [PATCH 10/10] Wrapping up initial Withings work. * Added System Status generator. --- ...ent_light.png => ic_pdk_ambient_light.png} | Bin ...c_app_events.png => ic_pdk_app_events.png} | Bin ..._battery.png => ic_pdk_device_battery.png} | Bin ...oft_band.png => ic_pdk_microsoft_band.png} | Bin ...phone_calls.png => ic_pdk_phone_calls.png} | Bin ...reen_state.png => ic_pdk_screen_state.png} | Bin res/drawable-hdpi/ic_pdk_system_status.png | Bin 0 -> 1501 bytes ..._messages.png => ic_pdk_text_messages.png} | Bin ..._device.png => ic_pdk_withings_device.png} | Bin ...ent_light.png => ic_pdk_ambient_light.png} | Bin ...c_app_events.png => ic_pdk_app_events.png} | Bin ..._battery.png => ic_pdk_device_battery.png} | Bin ...oft_band.png => ic_pdk_microsoft_band.png} | Bin ...phone_calls.png => ic_pdk_phone_calls.png} | Bin ...reen_state.png => ic_pdk_screen_state.png} | Bin res/drawable-mdpi/ic_pdk_system_status.png | Bin 0 -> 1294 bytes ..._messages.png => ic_pdk_text_messages.png} | Bin ..._device.png => ic_pdk_withings_device.png} | Bin ...ent_light.png => ic_pdk_ambient_light.png} | Bin ...c_app_events.png => ic_pdk_app_events.png} | Bin ..._battery.png => ic_pdk_device_battery.png} | Bin ...oft_band.png => ic_pdk_microsoft_band.png} | Bin ...phone_calls.png => ic_pdk_phone_calls.png} | Bin ...reen_state.png => ic_pdk_screen_state.png} | Bin res/drawable-xhdpi/ic_pdk_system_status.png | Bin 0 -> 1696 bytes ..._messages.png => ic_pdk_text_messages.png} | Bin ..._device.png => ic_pdk_withings_device.png} | Bin ...ent_light.png => ic_pdk_ambient_light.png} | Bin ...c_app_events.png => ic_pdk_app_events.png} | Bin ..._battery.png => ic_pdk_device_battery.png} | Bin ...oft_band.png => ic_pdk_microsoft_band.png} | Bin ...phone_calls.png => ic_pdk_phone_calls.png} | Bin ...reen_state.png => ic_pdk_screen_state.png} | Bin res/drawable-xxhdpi/ic_pdk_system_status.png | Bin 0 -> 2094 bytes ..._messages.png => ic_pdk_text_messages.png} | Bin ..._device.png => ic_pdk_withings_device.png} | Bin ...ent_light.png => ic_pdk_ambient_light.png} | Bin ...c_app_events.png => ic_pdk_app_events.png} | Bin ..._battery.png => ic_pdk_device_battery.png} | Bin ...oft_band.png => ic_pdk_microsoft_band.png} | Bin ...phone_calls.png => ic_pdk_phone_calls.png} | Bin ...reen_state.png => ic_pdk_screen_state.png} | Bin res/drawable-xxxhdpi/ic_pdk_system_status.png | Bin 0 -> 2274 bytes ..._messages.png => ic_pdk_text_messages.png} | Bin ..._device.png => ic_pdk_withings_device.png} | Bin res/layout/card_generator_app_event.xml | 2 +- res/layout/card_generator_device_battery.xml | 2 +- ...rd_generator_diagnostics_system_status.xml | 70 +++ res/layout/card_generator_microsoft_band.xml | 2 +- res/layout/card_generator_phone_calls.xml | 2 +- res/layout/card_generator_screen_state.xml | 2 +- .../card_generator_sensors_ambient_light.xml | 2 +- res/layout/card_generator_text_messages.xml | 2 +- .../card_generator_withings_body_page.xml | 174 ++++++- res/layout/card_generator_withings_device.xml | 2 +- .../card_generator_withings_intraday_page.xml | 3 +- res/values/databases.xml | 3 + res/values/generators.xml | 18 + .../generators/device/Battery.java | 16 +- .../generators/diagnostics/SystemStatus.java | 465 ++++++++++++++++++ .../generators/wearables/WithingsDevice.java | 72 ++- .../transmitters/HttpTransmitter.java | 4 +- 62 files changed, 811 insertions(+), 30 deletions(-) rename res/drawable-hdpi/{ic_ambient_light.png => ic_pdk_ambient_light.png} (100%) rename res/drawable-hdpi/{ic_app_events.png => ic_pdk_app_events.png} (100%) rename res/drawable-hdpi/{ic_device_battery.png => ic_pdk_device_battery.png} (100%) rename res/drawable-hdpi/{ic_microsoft_band.png => ic_pdk_microsoft_band.png} (100%) rename res/drawable-hdpi/{ic_phone_calls.png => ic_pdk_phone_calls.png} (100%) rename res/drawable-hdpi/{ic_screen_state.png => ic_pdk_screen_state.png} (100%) create mode 100755 res/drawable-hdpi/ic_pdk_system_status.png rename res/drawable-hdpi/{ic_text_messages.png => ic_pdk_text_messages.png} (100%) rename res/drawable-hdpi/{ic_withings_device.png => ic_pdk_withings_device.png} (100%) rename res/drawable-mdpi/{ic_ambient_light.png => ic_pdk_ambient_light.png} (100%) rename res/drawable-mdpi/{ic_app_events.png => ic_pdk_app_events.png} (100%) rename res/drawable-mdpi/{ic_device_battery.png => ic_pdk_device_battery.png} (100%) rename res/drawable-mdpi/{ic_microsoft_band.png => ic_pdk_microsoft_band.png} (100%) rename res/drawable-mdpi/{ic_phone_calls.png => ic_pdk_phone_calls.png} (100%) rename res/drawable-mdpi/{ic_screen_state.png => ic_pdk_screen_state.png} (100%) create mode 100755 res/drawable-mdpi/ic_pdk_system_status.png rename res/drawable-mdpi/{ic_text_messages.png => ic_pdk_text_messages.png} (100%) rename res/drawable-mdpi/{ic_withings_device.png => ic_pdk_withings_device.png} (100%) rename res/drawable-xhdpi/{ic_ambient_light.png => ic_pdk_ambient_light.png} (100%) rename res/drawable-xhdpi/{ic_app_events.png => ic_pdk_app_events.png} (100%) rename res/drawable-xhdpi/{ic_device_battery.png => ic_pdk_device_battery.png} (100%) rename res/drawable-xhdpi/{ic_microsoft_band.png => ic_pdk_microsoft_band.png} (100%) rename res/drawable-xhdpi/{ic_phone_calls.png => ic_pdk_phone_calls.png} (100%) rename res/drawable-xhdpi/{ic_screen_state.png => ic_pdk_screen_state.png} (100%) create mode 100755 res/drawable-xhdpi/ic_pdk_system_status.png rename res/drawable-xhdpi/{ic_text_messages.png => ic_pdk_text_messages.png} (100%) rename res/drawable-xhdpi/{ic_withings_device.png => ic_pdk_withings_device.png} (100%) rename res/drawable-xxhdpi/{ic_ambient_light.png => ic_pdk_ambient_light.png} (100%) rename res/drawable-xxhdpi/{ic_app_events.png => ic_pdk_app_events.png} (100%) rename res/drawable-xxhdpi/{ic_device_battery.png => ic_pdk_device_battery.png} (100%) rename res/drawable-xxhdpi/{ic_microsoft_band.png => ic_pdk_microsoft_band.png} (100%) rename res/drawable-xxhdpi/{ic_phone_calls.png => ic_pdk_phone_calls.png} (100%) rename res/drawable-xxhdpi/{ic_screen_state.png => ic_pdk_screen_state.png} (100%) create mode 100755 res/drawable-xxhdpi/ic_pdk_system_status.png rename res/drawable-xxhdpi/{ic_text_messages.png => ic_pdk_text_messages.png} (100%) rename res/drawable-xxhdpi/{ic_withings_device.png => ic_pdk_withings_device.png} (100%) rename res/drawable-xxxhdpi/{ic_ambient_light.png => ic_pdk_ambient_light.png} (100%) rename res/drawable-xxxhdpi/{ic_app_events.png => ic_pdk_app_events.png} (100%) rename res/drawable-xxxhdpi/{ic_device_battery.png => ic_pdk_device_battery.png} (100%) rename res/drawable-xxxhdpi/{ic_microsoft_band.png => ic_pdk_microsoft_band.png} (100%) rename res/drawable-xxxhdpi/{ic_phone_calls.png => ic_pdk_phone_calls.png} (100%) rename res/drawable-xxxhdpi/{ic_screen_state.png => ic_pdk_screen_state.png} (100%) create mode 100755 res/drawable-xxxhdpi/ic_pdk_system_status.png rename res/drawable-xxxhdpi/{ic_text_messages.png => ic_pdk_text_messages.png} (100%) rename res/drawable-xxxhdpi/{ic_withings_device.png => ic_pdk_withings_device.png} (100%) create mode 100755 res/layout/card_generator_diagnostics_system_status.xml create mode 100755 src/com/audacious_software/passive_data_kit/generators/diagnostics/SystemStatus.java diff --git a/res/drawable-hdpi/ic_ambient_light.png b/res/drawable-hdpi/ic_pdk_ambient_light.png similarity index 100% rename from res/drawable-hdpi/ic_ambient_light.png rename to res/drawable-hdpi/ic_pdk_ambient_light.png diff --git a/res/drawable-hdpi/ic_app_events.png b/res/drawable-hdpi/ic_pdk_app_events.png similarity index 100% rename from res/drawable-hdpi/ic_app_events.png rename to res/drawable-hdpi/ic_pdk_app_events.png diff --git a/res/drawable-hdpi/ic_device_battery.png b/res/drawable-hdpi/ic_pdk_device_battery.png similarity index 100% rename from res/drawable-hdpi/ic_device_battery.png rename to res/drawable-hdpi/ic_pdk_device_battery.png 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 0000000000000000000000000000000000000000..303b8b1252b34c49204b43c944fc7490dcd92784 GIT binary patch literal 1501 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBBuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFl%InM3hAM`dB6B=jtVb)aX^@765fKFxc2v6eK2Rr0UTq__OB&@Hb09I0xZL0)vRD^GUf^&XRs)DJWnQpS7v4w)UrJkXw zrG=4+j)IYap_#scrM{twu7RPIfu)s!p#l^r0c|TvNwW%aaf8|g1^l#~=$>Fbx5 zm+O@q>*W`v>l<2HTIw4Z=^Gj80#)c1SLT%@R_NvxE5l51Ni9w;$}A|!%+FH*nV6WA zUs__T1av9H3%LbwWAlok!2}F2{ffi_eM3D1ke6TzeSPsO&CP|YE-nd5MYtEM!Nnn! z1*!T$sm1xFMajU3OH&3}Rbb^@l$uzQUlfv`p92fUfQj_j!pzOU0H)U^Ke;qFHLnDwHwB^B6sKNLg2*iZ+U$~Alv$RV;#QQOs{r=6RVHq? z7~wPzsy79RVB$W~%k-Infyvv` z#WAGf)|(me8mfUZ$KP-LzVAnHo{DPy^iS${ZfvU%krNUB=u~lVx2}Jj#?#2k$BwzT z?tW{Xpl*}vy-THANcHJKrT>%uL_Yp~IdJ*r=Vv60X9d=MU1^;DY|hVfKj+=IG=6;5 z{7TKqI5 z;N_A9BApFl&1RJfueY*X_0&<~zFcyvq~V@8uVAjMl}m%GuiV`(GrJG$cN+TH{{C;j z$a?gFpJE2<57peR9cIpHtdfy#n*SUN*9lDVc;y^3J8VMYb(t=vuU60ZPCDu9l-2U} zb$4v>3bzd}1bDbh*%kZ}uLs98lvwLzi}lqv%M~2Es@jnCZbwv-VQR+on}VMU9fDKX|YBeQZq?*RAbEJCir<2TVA+cr*(aNKMttC5e_`jIa5}jx^LfVQD~2B~)vc{5^=aHc zsY>c8r@CFU#_MCMUuP%WT={;}`mTw)3%xEdMTi=u9`b4rW4+6nEpk(|>{swF^&Kfs zGquZZs%A`I>nXRo?B>tSjoUhHH#`w@EM9PbRqUT@AOF?AILlEbkicM4xteF%)#LX- O1+J&7pUXO@geCwWmol9I literal 0 HcmV?d00001 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_withings_device.png b/res/drawable-hdpi/ic_pdk_withings_device.png similarity index 100% rename from res/drawable-hdpi/ic_withings_device.png rename to res/drawable-hdpi/ic_pdk_withings_device.png diff --git a/res/drawable-mdpi/ic_ambient_light.png b/res/drawable-mdpi/ic_pdk_ambient_light.png similarity index 100% rename from res/drawable-mdpi/ic_ambient_light.png rename to res/drawable-mdpi/ic_pdk_ambient_light.png diff --git a/res/drawable-mdpi/ic_app_events.png b/res/drawable-mdpi/ic_pdk_app_events.png similarity index 100% rename from res/drawable-mdpi/ic_app_events.png rename to res/drawable-mdpi/ic_pdk_app_events.png diff --git a/res/drawable-mdpi/ic_device_battery.png b/res/drawable-mdpi/ic_pdk_device_battery.png similarity index 100% rename from res/drawable-mdpi/ic_device_battery.png rename to res/drawable-mdpi/ic_pdk_device_battery.png 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 0000000000000000000000000000000000000000..4deaf7c31edad6ee548c22d64c6d20096b99f2d6 GIT binary patch literal 1294 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m^Cs(B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxOgGuk*h0bFQqR!T z(!$6@N5ROz&`jUJQs2--*TB%qz|zXVPyq^*fVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5 zFD$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;NpiyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr7I$DAddqG<*}2 zGxI=#nqXbNzE+-j#U+V($*G<$wn{*A^fEJ3tQ;*&U7cJUEsTv#j0_E3&7E9K&D@+U zf#Gj%VdiFF0MqM|pIn-onpXnTn}X15iBm5qLF5(yZFWg5$}CGwaVyHtRRDY3DigO` z%y60q)tiFbE#^4&>H{644~kx-=!OXa(-4RWPrN`5Jo%^Q0n>aDFmZQ3{I!vRfw9KZ z#WAGfR??sU|LvJ|8HJg985OzbG|re9Cf339kw;oJV$LBu#R7(|Y8)JC~h&P_u?qr>%HLz0=K)9#wxPUI`Htc2RiG z&9sq8z`niezor1UV>2U59m7Twfoz74@mmyn7?K!QNS>H6ll9f2uMTTCkK{P$@NBK- z7TC}HDD=Vt2G=Tq5ZhhnpF3Yw>i*A|Ygw7^H&#fSzHZLHaH6d-`ND$5S*@E{q}C~H=o2%&<*H!E>&tPw z>?5hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|8hm3bwJ6}oxF$}kgLQj3#|G7CyF^YauyCMG83 zmzLNn0bL65LT&-v*t}wBFaZNhzap_f-%!s0Fmp37fa!I~PcF?(%`1WFO+n~2#HkmQAaV^MGl-2$;Bk zOnLtlm}mGrT^vIyZoQf1nJwxl()yd%aq%KG7DEf27>QmL{y7IY9Sgdcy8;ab&2PR4 zoB!YOe}i?b#lwa`fu z_km+sk`@e7B5zrJQ$iM9eXTWP`nE=!{5UIF#;;=6ZyrAV;o}zGHO$e@zos|uZOguJ z>GrYX;s@@S@LrIeoh;#8?R0K}oo>yYL%%(>*s>gVAKf9lKy%4|0S&I_6Bj3BCrDqn zQ9H|j@~xDa!gQ6lnG!ETmb9(ovRS!vwd%YLYc{!NxfyM6srcZsm;G+nw=_T1NOsfN zv6@~YIXqi7az=hXkm{^cr-(Pl8()Udfy3owRu#0vqddFhVT#f3He{^TsS7Do=E$c5$5o3P9Wp6!o)dNrM!?GV3eX{Bt zl7AnN_x-P1aPHIDH;V3?--QtvnK*(b|W<%)M@7d|cBoOs6|e3AXCzo~xLMBEQ9 zNiX!Z%VT!T`eW2nIiZz5+A1*aTicg_2PYJDr*vMxFaY z?uT!wPcNQn{-ChcYF^1NmCOWzFX}sgspK}49hW}z)8o!+|4VEhwoX@X9Y5a?%%*W) zUVZDYCBWkQmJE&e^`l0_`B0DY=8!^RAn(TkN!8}{RmdKI;Vst0Iz6~LI3~& literal 0 HcmV?d00001 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_withings_device.png b/res/drawable-xhdpi/ic_pdk_withings_device.png similarity index 100% rename from res/drawable-xhdpi/ic_withings_device.png rename to res/drawable-xhdpi/ic_pdk_withings_device.png diff --git a/res/drawable-xxhdpi/ic_ambient_light.png b/res/drawable-xxhdpi/ic_pdk_ambient_light.png similarity index 100% rename from res/drawable-xxhdpi/ic_ambient_light.png rename to res/drawable-xxhdpi/ic_pdk_ambient_light.png diff --git a/res/drawable-xxhdpi/ic_app_events.png b/res/drawable-xxhdpi/ic_pdk_app_events.png similarity index 100% rename from res/drawable-xxhdpi/ic_app_events.png rename to res/drawable-xxhdpi/ic_pdk_app_events.png diff --git a/res/drawable-xxhdpi/ic_device_battery.png b/res/drawable-xxhdpi/ic_pdk_device_battery.png similarity index 100% rename from res/drawable-xxhdpi/ic_device_battery.png rename to res/drawable-xxhdpi/ic_pdk_device_battery.png 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 0000000000000000000000000000000000000000..ce6df18f8cd4e83f0d140acbd13f78d06b552c56 GIT binary patch literal 2094 zcmaJ?dsGv57EVKWgowP~XS-uU3kc*%z@$I|Br#Be0RbrmmXJ(H#AHG;fgqF;#2x`@ z!Rl5eC}63mplv}BSuwhB06`3Fw=Livsl3)l#j1efYbRFh{;}+wGxNK@d+zt$*LTiT z2;(=~*?QTcP$;_?J|_`5`R3Qg3i&4g+P)b%JYjAsycLqd1tK+wij+XvAP}Pv<$#Hx zNK%+L2!^3h7Jl-iR5(=-#}q>fqR1RW)G1U58ifjD=~NQ=oCAY8m2BB|C*PH*k(5g z54;Y6bHnk!7nLdy0@08f1n9(&ATfou2?%8nDRjmrYH%PxB~wC3WCn>697Lut$qXi$ z0=#+fNH(=Z%1q?&-sD1V;dmJgtC%EGK|ukrfJTJWIV1{$!61>TBq}usi3rjZDq)c> zNU0&LGH^hRSS?qLiLtbsd+OdWMCwnNJXL$$>x?;0|kQr zAF5Eijn=@4;2-t=Phw3{p$a4=f*MGx79)$363nSo%xESkXWk#mGCN7@&`6}O{`fE3Kf!>1FGfupoFJ}6u|4onew%3 zp{&XG1}j;+7V4T<5>gDwyx#v>_o@rgklB1IUgYvt{-6@kyc!YrFD}yh&Jo08J& zobueUZRaue{Akm8deW)IPnJHekH3CMGoDdD&bT!5)$oYnkY=uPzPrACTv$}p4H`Gw z6ZDP)HWCaKZMn4}j^OZTf8#FWI)a0DRq0bBod<-ZSUf}FN>A__Mm(_TQT<)ULx`D* zGQ{;;ln(7>*VjLa?eDTPzT`Emf3z{*Fod4-)H4m5gLx&V9R13wiK0*AOqPf3hv(A0 z<9hr|=r)U`{I1U#328!0ul4DHGMe>4XEEzo3+X1de2$)MvZCE}r7X4M&R>Jji{Jr#VJmRq`@IQoS}XZPQB?1_5ne}}L=lk2rW zt9~wSo_UDd;`}`O)8~IOOovuJlix1$6@E7Rau=pp)}yfECggJ6?ib4N&V^vnsP*B( zAKj;hJkSnHUG}=q>AR@!N%wf)Ded%_55S8D$N?yTLPQ=^piM&7L1Lv*)P zJZSE>ow(5$_(Fx&V*4h0$)3f&qxUh`<|-)_?DcdCaD0-39SM{9&k|U!Qefq)A)~u( z&q<+X>bksmR91zP<8C>8QoksoFaDF;@JgS(QL0thobNxA5a;%ji?q)9KnbGwr(-eI z&))s8b)C;{_8y$^OyzRJU!J(R=*cTLl84*3TNb7yVjovs-4`;rWUpU-k-5DE{lers zc&^iR;{9s2ozo#Ld@k8H`n!|6jyWm5MfG?k=}ieNr;e_#YsP)+**Dt;4xeo)X)oJY z!_}q}<|k;CS$a1c!9C{uglBa*^)}{8{k9+7wBC&Qr3Rd&mJNB(?q(L)4+o$d?U_UT z^(+rPQz)VxcdNP-?Ubs&qZnBUay`xiyj7DI#@trw_Ode)qDzin4X}84n7yTSAV0o- zo0ZGMDVA{ei86?eeu|n~$lJ^20R&;etuIg(sImv6G-(IRVE%*0aN{|rqq55W1B?+r AHvj+t literal 0 HcmV?d00001 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_withings_device.png b/res/drawable-xxhdpi/ic_pdk_withings_device.png similarity index 100% rename from res/drawable-xxhdpi/ic_withings_device.png rename to res/drawable-xxhdpi/ic_pdk_withings_device.png diff --git a/res/drawable-xxxhdpi/ic_ambient_light.png b/res/drawable-xxxhdpi/ic_pdk_ambient_light.png similarity index 100% rename from res/drawable-xxxhdpi/ic_ambient_light.png rename to res/drawable-xxxhdpi/ic_pdk_ambient_light.png diff --git a/res/drawable-xxxhdpi/ic_app_events.png b/res/drawable-xxxhdpi/ic_pdk_app_events.png similarity index 100% rename from res/drawable-xxxhdpi/ic_app_events.png rename to res/drawable-xxxhdpi/ic_pdk_app_events.png diff --git a/res/drawable-xxxhdpi/ic_device_battery.png b/res/drawable-xxxhdpi/ic_pdk_device_battery.png similarity index 100% rename from res/drawable-xxxhdpi/ic_device_battery.png rename to res/drawable-xxxhdpi/ic_pdk_device_battery.png 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 0000000000000000000000000000000000000000..595b4592fe07b671f4f298786adf39d7c530e8aa GIT binary patch literal 2274 zcmaJ@dsGuw9-f3mAw22>!ovr{uwp2X$%97{fglM8Xfy=0phqPn6G$z00Q=}Dp#)!(EU)zx{5x2t=irMg7LkC(p$bed)gr(`Ihu@s(JE;QB0;3` z4eKu;!2n=IQ^qG?38E!D8LFa7Ef{)+N`tcjAef(_k;>8#3`|B+lxATTskv=%VGKZf-D9z5N2>;roSJ9$zyPN3?}&D zfpBkHxq>GVMt<;xpF*Hi4AbynSg+U9^=vw-O@WzQE*EC7U>3^{NBHSBs4;1VpIYZR zsUSpjGObdBDN!|OQIsa5=~xJaNBU<9D$Pe(weCZi@Pfe^QVqOZykD^s0LbEK;llWg z-bbq+ra3KgGzM%%eP~}Ej5VFBIPBsi5*#kw)f;%@M#@OlUY8McB;QN?q|Nx1S5fb^ zU`k}n{^o;G4F~6uR{0B8#bz%oq(GtKJI^a`x0r6<&eALNipTqY8O?XU(7u5E=rVe_ zoc;H+1%zA_8I>N6qKg|?V z>8(vlQfkSjy}3iaVJFSI`)L!Kx}NiGr_E@-6bcrG^x15=W%C(fPfIADyn;A^*qVyx zaZ8I21H%euM~4dAY~%e@ny+&_T{e+OZtNy(lG&QJd{!8rbKofGOOR0FX3k6m7L-kS zd(-pB_yI4f*};@v92eDnzRENw>E6nQuN~04_5)7MfbQ^$BX1|%1Tz+$ZziWcVXiQ( zU*=DE*FI*$n~&VG|K~g3+vL+#BtkQBKB==`5pC#9!~qh*=$#YkMY&@MluG01FIln2 ziL;0UR^78`wLgYGymBDqGO@<4v`KL4RQ7|Zq6p~38xF-VRKD`p3WvUFAivSvo_IGi zYc@Ic>-;)NQO)wcWmNZqAHu~)NQ{tzvoZDlk8&WV>E(_|+hz;PPoGg3hihXz<~07? z?&M6kdUt-}$wieO>2__#&SzvVa_TXxm{VlBm^XHT__F?n#5rl7VqVoTj#t8w`#F%5 ze25gKY~4C;ZgP7NbSd=wiLncVo+XKF(A!Ym6T61IZ%R&M$<#4wmtc-4)L5Ng4>Z)+ zO3uZWBuEK`&r|$bsw#k;{u_s3^QFcs zMptUqV)MPk`7B$GoOHA1Coa^g3!SigyQ--P#_A8nM26*k<3w@_OZjco!vq&E{{0%w z>gArml^VV}8)7}Q-VgSlKhnN!!}c#kjhbdhcd~Pp^FQ&Z8Dw0#u)X z{kmRHhY9OcrCIK;Hs+@FQ=X3|XVfbkM%=sgoIkQ-uNq`&pC52(wA$pWUEN_nv|#!5 zzZ5{egdGH9*2&TZZbSb#4rJbcaq=DM)mM=lI5+EcEk-x$w~QR}8c9@lU81_7_(t*g zt)?2At4GTms>?izv`w{NVpG=UQ>*tzQAS*DR+p^lpsXPbU*F!7mAmDDoZS2`ai!*Z zkD?$e93^)69CGh+;ScEA=^e9O93RDmyJW_*$5#*76g@-Slv9x*PluQ%$$#Xj3Cmu0 zb|VjR`mTd}YSMSl9-15EM&iF(hm<|8V6>gF{>eD{I&OOQy{ghskYc{bO)>>ze4m}c z(?;)XzD8Zew;dn^*T0!*E6(&`5N7wSRuG=$K0Nnwu&V#*gysfAGOo3p7U|#(TZc0ArP5wIgpid&$wKbrgfyer>g5?c6~KP_KQvqjjRz*U-6M;K?E;6T-ib z9#CBz-qH#T#|6mA-@0sW&Fzq6{q5y47ZX6u<(CBGm)DH$sV2aVY9J?>0AC=V3>G>Q a?SM7+pL^c!@ou*Kbwo!j6*h(^<^3CWg^17q literal 0 HcmV?d00001 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_withings_device.png b/res/drawable-xxxhdpi/ic_pdk_withings_device.png similarity index 100% rename from res/drawable-xxxhdpi/ic_withings_device.png rename to res/drawable-xxxhdpi/ic_pdk_withings_device.png diff --git a/res/layout/card_generator_app_event.xml b/res/layout/card_generator_app_event.xml index 3d822c9..472985c 100755 --- a/res/layout/card_generator_app_event.xml +++ b/res/layout/card_generator_app_event.xml @@ -21,7 +21,7 @@ android:background="#424242" android:padding="8dp" android:baselineAligned="false"> - diff --git a/res/layout/card_generator_device_battery.xml b/res/layout/card_generator_device_battery.xml index 7239d93..ef96661 100755 --- a/res/layout/card_generator_device_battery.xml +++ b/res/layout/card_generator_device_battery.xml @@ -21,7 +21,7 @@ android:background="@color/generator_battery_header" android:padding="8dp" android:baselineAligned="false"> - 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_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_ambient_light.xml b/res/layout/card_generator_sensors_ambient_light.xml index 62dd685..0176f72 100755 --- a/res/layout/card_generator_sensors_ambient_light.xml +++ b/res/layout/card_generator_sensors_ambient_light.xml @@ -21,7 +21,7 @@ android:background="@color/generator_ambient_light_header" android:padding="8dp" android:baselineAligned="false"> - 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_body_page.xml b/res/layout/card_generator_withings_body_page.xml index 91befe4..2eefb66 100755 --- a/res/layout/card_generator_withings_body_page.xml +++ b/res/layout/card_generator_withings_body_page.xml @@ -1,13 +1,173 @@ - + android:layout_margin="8dp"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/card_generator_withings_device.xml b/res/layout/card_generator_withings_device.xml index 96f1fec..7f5993e 100755 --- a/res/layout/card_generator_withings_device.xml +++ b/res/layout/card_generator_withings_device.xml @@ -20,7 +20,7 @@ android:background="#009BBF" android:padding="8dp" android:baselineAligned="false"> - diff --git a/res/layout/card_generator_withings_intraday_page.xml b/res/layout/card_generator_withings_intraday_page.xml index 3b1086d..ddf6b27 100755 --- a/res/layout/card_generator_withings_intraday_page.xml +++ b/res/layout/card_generator_withings_intraday_page.xml @@ -45,8 +45,7 @@ + android:layout_marginTop="8dp"> 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/generators.xml b/res/values/generators.xml index 0150e72..85bd3b0 100755 --- a/res/values/generators.xml +++ b/res/values/generators.xml @@ -7,7 +7,12 @@ + 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 @@ -142,6 +147,9 @@ Elevation %1$.2f m + - + Body Measures + Device Battery No battery levels have been reported yet. @@ -176,5 +184,15 @@ #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/src/com/audacious_software/passive_data_kit/generators/device/Battery.java b/src/com/audacious_software/passive_data_kit/generators/device/Battery.java index d652be9..48b4159 100755 --- a/src/com/audacious_software/passive_data_kit/generators/device/Battery.java +++ b/src/com/audacious_software/passive_data_kit/generators/device/Battery.java @@ -11,7 +11,6 @@ import android.os.BatteryManager; import android.os.Bundle; import android.support.v4.content.ContextCompat; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -93,6 +92,8 @@ public class Battery extends Generator { 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) { @@ -220,6 +221,17 @@ public void onReceive(final Context context, Intent intent) { 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); + } } }; @@ -276,8 +288,6 @@ public static void bindViewHolder(DataPointViewHolder holder) { Cursor c = generator.mDatabase.query(Battery.TABLE_HISTORY, null, where, args, null, null, Battery.HISTORY_OBSERVED + " DESC"); - Log.e("PDK", "BATTERY COUNT: " + c.getCount()); - 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); 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/wearables/WithingsDevice.java b/src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java index 0d09ce2..7cdcff4 100755 --- a/src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java +++ b/src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java @@ -17,6 +17,7 @@ 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; @@ -1248,7 +1249,7 @@ public static void bindViewHolder(final DataPointViewHolder holder) { PagerAdapter adapter = new PagerAdapter() { @Override public int getCount() { - return 7; + return 2; } @Override @@ -1452,13 +1453,13 @@ public static long latestPointGenerated(Context context) { WithingsDevice me = WithingsDevice.getInstance(context); -// Cursor c = me.mDatabase.query(WithingsDevice.TABLE_HISTORY, null, null, null, null, null, WithingsDevice.HISTORY_OBSERVED + " DESC"); -// -// if (c.moveToNext()) { -// timestamp = c.getLong(c.getColumnIndex(WithingsDevice.HISTORY_OBSERVED)); -// } -// -// c.close(); + 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; } @@ -1653,9 +1654,64 @@ private static void populateIntradayChart(Context context, LineChart chart, Arra 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(); 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 ed939ea..939f96a 100755 --- a/src/com/audacious_software/passive_data_kit/transmitters/HttpTransmitter.java +++ b/src/com/audacious_software/passive_data_kit/transmitters/HttpTransmitter.java @@ -158,8 +158,6 @@ private boolean shouldAttemptUpload(boolean force) { public void transmit(boolean force) { long now = System.currentTimeMillis(); - Log.e("PDK", "TRANSMIT: " + force); - if (force) { this.mLastAttempt = 0; } @@ -346,6 +344,8 @@ private int transmitHttpPayload(String payload) { } return HttpTransmitter.RESULT_SUCCESS; + } else { + } } catch (Exception e) { e.printStackTrace();