diff --git a/AndroidManifest.xml b/AndroidManifest.xml index cff23c6..daf0bdd 100755 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,10 +1,11 @@ - + - + + diff --git a/build.gradle b/build.gradle index 78806aa..e6359b3 100755 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.2.2' + classpath 'com.android.tools.build:gradle:2.2.3' } } @@ -17,7 +17,7 @@ repositories { android { compileSdkVersion 25 - buildToolsVersion "25" + buildToolsVersion "25.0.2" useLibrary 'org.apache.http.legacy' @@ -45,14 +45,15 @@ android { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) // testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:25.0.0' - compile 'com.android.support:recyclerview-v7:25.0.0' - compile 'com.android.support:cardview-v7:25.0.0' - compile 'com.google.android.gms:play-services-location:9.4.0' - compile 'com.google.android.gms:play-services-maps:9.4.0' - compile 'com.google.android.gms:play-services-nearby:9.4.0' - compile 'com.google.android.gms:play-services-places:9.4.0' - compile 'com.google.android.gms:play-services-contextmanager:9.4.0' + compile 'com.android.support:appcompat-v7:25.1.0' + compile 'com.android.support:recyclerview-v7:25.1.0' + compile 'com.android.support:cardview-v7:25.1.0' + compile 'com.google.android.gms:play-services-location:10.0.1' + compile 'com.google.android.gms:play-services-maps:10.0.1' + compile 'com.google.android.gms:play-services-nearby:10.0.1' + compile 'com.google.android.gms:play-services-places:10.0.1' + compile 'com.google.android.gms:play-services-awareness:10.0.1' + 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' diff --git a/res/drawable-hdpi/ic_pdk_diagnostic.png b/res/drawable-hdpi/ic_pdk_diagnostic.png new file mode 100755 index 0000000..e80247a Binary files /dev/null and b/res/drawable-hdpi/ic_pdk_diagnostic.png differ diff --git a/res/drawable-hdpi/ic_text_messages.png b/res/drawable-hdpi/ic_text_messages.png new file mode 100755 index 0000000..ef5d391 Binary files /dev/null and b/res/drawable-hdpi/ic_text_messages.png differ diff --git a/res/drawable-mdpi/ic_pdk_diagnostic.png b/res/drawable-mdpi/ic_pdk_diagnostic.png new file mode 100755 index 0000000..539cf8a Binary files /dev/null and b/res/drawable-mdpi/ic_pdk_diagnostic.png differ diff --git a/res/drawable-mdpi/ic_text_messages.png b/res/drawable-mdpi/ic_text_messages.png new file mode 100755 index 0000000..cdabac0 Binary files /dev/null and b/res/drawable-mdpi/ic_text_messages.png differ diff --git a/res/drawable-xhdpi/ic_pdk_diagnostic.png b/res/drawable-xhdpi/ic_pdk_diagnostic.png new file mode 100755 index 0000000..c9ebaa0 Binary files /dev/null and b/res/drawable-xhdpi/ic_pdk_diagnostic.png differ diff --git a/res/drawable-xhdpi/ic_text_messages.png b/res/drawable-xhdpi/ic_text_messages.png new file mode 100755 index 0000000..a0a8ceb Binary files /dev/null and b/res/drawable-xhdpi/ic_text_messages.png differ diff --git a/res/drawable-xxhdpi/ic_pdk_diagnostic.png b/res/drawable-xxhdpi/ic_pdk_diagnostic.png new file mode 100755 index 0000000..62b411d Binary files /dev/null and b/res/drawable-xxhdpi/ic_pdk_diagnostic.png differ diff --git a/res/drawable-xxhdpi/ic_text_messages.png b/res/drawable-xxhdpi/ic_text_messages.png new file mode 100755 index 0000000..31ceb29 Binary files /dev/null and b/res/drawable-xxhdpi/ic_text_messages.png differ diff --git a/res/drawable-xxxhdpi/ic_pdk_diagnostic.png b/res/drawable-xxxhdpi/ic_pdk_diagnostic.png new file mode 100755 index 0000000..ed16022 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_pdk_diagnostic.png differ diff --git a/res/drawable-xxxhdpi/ic_text_messages.png b/res/drawable-xxxhdpi/ic_text_messages.png new file mode 100755 index 0000000..352d5d5 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_text_messages.png differ diff --git a/res/drawable/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/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/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..a6d7f01 --- /dev/null +++ b/res/values/databases.xml @@ -0,0 +1,15 @@ + + 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); + \ No newline at end of file diff --git a/res/values/diagnostics.xml b/res/values/diagnostics.xml index c0adb6a..a656358 100755 --- a/res/values/diagnostics.xml +++ b/res/values/diagnostics.xml @@ -1,7 +1,19 @@ - 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. diff --git a/res/values/generators.xml b/res/values/generators.xml index c4d60a2..2fbcf3b 100755 --- a/res/values/generators.xml +++ b/res/values/generators.xml @@ -3,6 +3,7 @@ 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.communication.TextMessages com.audacious_software.passive_data_kit.generators.wearables.MicrosoftBand com.audacious_software.passive_data_kit.generators.services.GoogleAwareness @@ -45,6 +46,29 @@ 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 @@ -63,4 +87,6 @@ Direction %1$.2f min. + No phone calls have been made or received on this device. + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 61e918e..a784812 100755 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -14,10 +14,11 @@ Today - On - Off - Doze - Unknown - Legend: + Never + Diagnostics + Diagnostics (%d) + Diagnostics + + The app is set up correctly.\n\nNo further actions are needed. 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/DataStreamActivity.java b/src/com/audacious_software/passive_data_kit/activities/DataStreamActivity.java index 184173a..04e416c 100755 --- a/src/com/audacious_software/passive_data_kit/activities/DataStreamActivity.java +++ b/src/com/audacious_software/passive_data_kit/activities/DataStreamActivity.java @@ -20,7 +20,7 @@ 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 +30,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 +42,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, 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/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/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..abb0938 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,9 @@ package com.audacious_software.passive_data_kit.generators; +import android.content.ContentValues; import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -13,6 +16,7 @@ import java.text.DateFormat; import java.util.Calendar; import java.util.Date; +import java.util.List; @SuppressWarnings("unused") public abstract class Generator @@ -25,6 +29,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 +54,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 +64,18 @@ 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); @@ -87,4 +102,62 @@ public static String formatTimestamp(Context context, double timestamp) { return context.getString(R.string.format_full_timestamp_pdk, date, time); } + + public abstract List fetchPayloads(); + + 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..42b4cd9 100755 --- a/src/com/audacious_software/passive_data_kit/generators/Generators.java +++ b/src/com/audacious_software/passive_data_kit/generators/Generators.java @@ -19,6 +19,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.List; public class Generators { private Context mContext = null; @@ -26,9 +27,9 @@ public class Generators { 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) @@ -148,27 +149,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 +171,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 +196,51 @@ 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, Bundle bundle) { + for (GeneratorUpdatedListener listener : this.mGeneratorUpdatedListeners) { + listener.onGeneratorUpdated(identifier, bundle); + } } private static class GeneratorsHolder { @@ -257,11 +260,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, 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..f72cbe8 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,28 +155,30 @@ 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(); + + String where = CallLog.Calls.DATE + " > ?"; + String[] args = {"" + lastObserved}; Cursor c = me.mContext.getContentResolver().query(CallLog.Calls.CONTENT_URI, null, where, args, CallLog.Calls.DATE); while (c.moveToNext()) { - Bundle bundle = new Bundle(); + 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)); + 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))); @@ -186,6 +200,8 @@ public void run() { } 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; @@ -195,6 +211,9 @@ public void run() { 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; @@ -203,57 +222,53 @@ public void run() { 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.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; - 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; - } - - Log.e("PDK", "------"); - for (int i = 0; i < c.getColumnCount(); i++) { - Log.e("PDK", "CALL LOG: " + c.getColumnName(i) + " --> " + c.getString(i)); - } + values.put(PhoneCalls.HISTORY_CALL_TYPE, type); String[] sensitiveFields = { PhoneCalls.CALL_NUMBER_KEY, PhoneCalls.CALL_POST_DIAL_DIGITS_KEY, - PhoneCalls.CALL_VIA_NUMBER_KEY + PhoneCalls.CALL_VIA_NUMBER_KEY, }; for (String field : sensitiveFields) { @@ -262,38 +277,49 @@ public void run() { } } - Generators.getInstance(me.mContext).transmitData(PhoneCalls.GENERATOR_IDENTIFIER, bundle); - } - - Log.e("PDK", "------"); + String[] valueSensitiveFields = { + PhoneCalls.HISTORY_NUMBER, + PhoneCalls.HISTORY_POST_DIAL_DIGITS, + PhoneCalls.HISTORY_VIA_NUMBER, + }; - total += c.getCount(); + for (String field : valueSensitiveFields) { + if (values.containsKey(field)) { + values.put(field, new String(Hex.encodeHex(DigestUtils.sha256(values.getAsString(field))))); + } + } - c.close(); + me.mDatabase.insert(PhoneCalls.TABLE_HISTORY, null, values); - if (latestCallType != null) { - e.putLong(PhoneCalls.LAST_CALL_TIMESTAMP, latestCallTimestamp); - e.putLong(PhoneCalls.LAST_CALL_DURATION, latestCallDuration); - e.putString(PhoneCalls.LAST_CALL_TYPE, latestCallType); + Generators.getInstance(me.mContext).notifyGeneratorUpdated(PhoneCalls.GENERATOR_IDENTIFIER, bundle); } - 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); - - e.putLong(PhoneCalls.LAST_SAMPLE, now); - e.apply(); + c.close(); } - long sampleInterval = prefs.getLong(PhoneCalls.SAMPLE_INTERVAL, PhoneCalls.SAMPLE_INTERVAL_DEFAULT); - if (me.mHandler != null) { - me.mHandler.postDelayed(this, sampleInterval); + me.mHandler.postDelayed(this, me.mSampleInterval); } } }; + File path = PassiveDataKit.getGeneratorsStorage(this.mContext); + + path = new File(path, PhoneCalls.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_phone_calls_create_history_table)); + case 1: + this.mDatabase.delete(PhoneCalls.TABLE_HISTORY, null, null); + } + + this.setDatabaseVersion(this.mDatabase, PhoneCalls.DATABASE_VERSION); + Runnable r = new Runnable() { @Override public void run() { @@ -340,7 +366,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 +389,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))); } - }); - pieChart.setData(data); - pieChart.invalidate(); + if (totalOutgoing > 0) { + entries.add(new PieEntry(totalOutgoing, context.getString(R.string.generator_phone_calls_outgoing_label))); + } - 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, ""); + if (totalMissed > 0) { + entries.add(new PieEntry(totalMissed, context.getString(R.string.generator_phone_calls_missed_label))); + } + + long other = total - (totalIncoming + totalOutgoing + totalMissed); + + if (other > 0) { + entries.add(new PieEntry(other, context.getString(R.string.generator_phone_calls_other_label))); + } - 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); + PieDataSet set = new PieDataSet(entries, " "); - 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); + 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 + }; - 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); + set.setColors(colors, context); - dateLabel.setText(Generator.formatTimestamp(context, latest / 1000)); + 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_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); + + 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); + + 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) @@ -456,8 +526,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..f00204c 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,25 +1,36 @@ 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.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.content.res.ColorStateList; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; 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.widget.SwitchCompat; import android.util.DisplayMetrics; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.CompoundButton; import android.widget.TextView; import com.audacious_software.passive_data_kit.DeviceInformation; +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; @@ -35,16 +46,20 @@ 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.util.ArrayList; +import java.util.List; @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 +76,27 @@ 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 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 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 final String LAST_KNOWN_TIMESTAMP = "com.audacious_software.passive_data_kit.generators.device.Location.LAST_KNOWN_TIMESTAMP";; + 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 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"; + private static final String HISTORY_OBSERVED = "observed"; + private static final String HISTORY_LATITUDE = "latitude"; + private static final String HISTORY_LONGITUDE = "longitude"; + private static final String HISTORY_ALTITUDE = "altitude"; + private static final String HISTORY_BEARING = "bearing"; + private static final String HISTORY_SPEED = "speed"; + private static final String HISTORY_PROVIDER = "provider"; + private static final String HISTORY_LOCATION_TIMESTAMP = "location_timestamp"; + private static final String HISTORY_ACCURACY = "accuracy"; public static Location getInstance(Context context) { if (Location.sInstance == null) { @@ -114,7 +138,6 @@ else if (Location.useGoogleLocationServices(me.mContext)) me.mGoogleApiClient = builder.build(); me.mGoogleApiClient.connect(); } - } else { @@ -128,6 +151,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 +231,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 +254,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) { @@ -234,83 +282,115 @@ public void onConnectionFailed(ConnectionResult connectionResult) { @Override public void onLocationChanged(android.location.Location location) { + Log.e("PDK", "LOCATION CHANGED"); + if (location == null) return; long now = System.currentTimeMillis(); - Bundle bundle = new Bundle(); + 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.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()); + 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()); - this.mLastLocation = location; + Bundle metadata = new Bundle(); + metadata.putDouble(Generator.LATITUDE, location.getLatitude()); + metadata.putDouble(Generator.LONGITUDE, location.getLongitude()); - if (location.hasAccuracy()) { - bundle.putFloat(Location.ACCURACY_KEY, location.getAccuracy()); - } + 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); + + Generators.getInstance(this.mContext).notifyGeneratorUpdated(Location.GENERATOR_IDENTIFIER, updated); + } - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); - SharedPreferences.Editor e = prefs.edit(); + public static long latestPointGenerated(Context context) { + long timestamp = 0; - 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()); + Location me = Location.getInstance(context); - e.apply(); - } + Cursor c = me.mDatabase.query(Location.TABLE_HISTORY, null, null, null, null, null, Location.HISTORY_OBSERVED + " DESC"); - public static void bindViewHolder(DataPointViewHolder holder, final Bundle dataPoint) { - Log.e("PDK", "DRAWING LOCATION: " + dataPoint); + 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 +402,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); - googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(finalLatitude, finalLongitude), 14)); + LatLngBounds.Builder builder = new LatLngBounds.Builder(); + + 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 +499,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) @@ -380,26 +533,23 @@ 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); } } 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(); } } 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..2107447 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,24 @@ 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"; + + private static final String DATABASE_PATH = "pdk-screen-state.sqlite"; + private static final int DATABASE_VERSION = 2; + private static final String HISTORY_OBSERVED = "observed"; + private static final String HISTORY_STATE = "state"; + private 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 +72,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 +93,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, "[]")); + me.mDatabase.insert(ScreenState.TABLE_HISTORY, null, values); - JSONObject latest = new JSONObject(); - latest.put(ScreenState.SCREEN_HISTORY_TIMESTAMP, System.currentTimeMillis()); + update.putString(ScreenState.HISTORY_STATE, values.getAsString(ScreenState.HISTORY_STATE)); - 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); - - SharedPreferences.Editor e = prefs.edit(); - - 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 +133,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 +170,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 +283,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 +310,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 +347,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 +376,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 +409,8 @@ private static void populateTimeline(Context context, LinearLayout timeline, lon timeline.addView(v); } + } else { + } } @@ -398,7 +419,24 @@ 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; } } 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 423912b..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() { @@ -174,6 +172,7 @@ public static boolean isEnabled(Context context) { @Override public void onConnected(@Nullable Bundle bundle) { Log.e("PDK", "GA onConnected"); + final GoogleAwareness me = this; Runnable r = new Runnable() { @@ -218,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/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/transmitters/HttpTransmitter.java b/src/com/audacious_software/passive_data_kit/transmitters/HttpTransmitter.java index 93f74d3..863d30e 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,26 @@ public long transmittedSize() { } @Override - public void onNewDataPoint(String identifier, Bundle data) { + public void onGeneratorUpdated(String identifier, Bundle data) { if (data.keySet().size() > 1) { // Only transmit non-empty bundles... + double now = (double) System.currentTimeMillis(); + now = now / 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, now); + 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 +611,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);