diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 5dd65d6..45147a5 100755 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,14 +1,18 @@ - + - + + + diff --git a/assets/html/passive_data_kit/generator_location_disclosure.html b/assets/html/passive_data_kit/generator_location_disclosure.html new file mode 100755 index 0000000..583b4d7 --- /dev/null +++ b/assets/html/passive_data_kit/generator_location_disclosure.html @@ -0,0 +1,26 @@ + + + + + + + +

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

+ +

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

+ + \ No newline at end of file diff --git a/res/drawable-hdpi/ic_button_disclosure_setting.png b/res/drawable-hdpi/ic_button_disclosure_setting.png new file mode 100755 index 0000000..9628f97 Binary files /dev/null and b/res/drawable-hdpi/ic_button_disclosure_setting.png differ diff --git a/res/drawable-mdpi/ic_button_disclosure_setting.png b/res/drawable-mdpi/ic_button_disclosure_setting.png new file mode 100755 index 0000000..a252c98 Binary files /dev/null and b/res/drawable-mdpi/ic_button_disclosure_setting.png differ diff --git a/res/drawable-xhdpi/ic_button_disclosure_setting.png b/res/drawable-xhdpi/ic_button_disclosure_setting.png new file mode 100755 index 0000000..c006523 Binary files /dev/null and b/res/drawable-xhdpi/ic_button_disclosure_setting.png differ diff --git a/res/drawable-xxhdpi/ic_button_disclosure_setting.png b/res/drawable-xxhdpi/ic_button_disclosure_setting.png new file mode 100755 index 0000000..8310d1e Binary files /dev/null and b/res/drawable-xxhdpi/ic_button_disclosure_setting.png differ diff --git a/res/drawable-xxxhdpi/ic_button_disclosure_setting.png b/res/drawable-xxxhdpi/ic_button_disclosure_setting.png new file mode 100755 index 0000000..b25919e Binary files /dev/null and b/res/drawable-xxxhdpi/ic_button_disclosure_setting.png differ diff --git a/res/layout/dialog_location_randomized.xml b/res/layout/dialog_location_randomized.xml new file mode 100755 index 0000000..58965d7 --- /dev/null +++ b/res/layout/dialog_location_randomized.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/res/layout/dialog_location_user.xml b/res/layout/dialog_location_user.xml new file mode 100755 index 0000000..17bbf31 --- /dev/null +++ b/res/layout/dialog_location_user.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/res/layout/layout_data_disclosure_detail_pdk.xml b/res/layout/layout_data_disclosure_detail_pdk.xml new file mode 100755 index 0000000..9cd2e4e --- /dev/null +++ b/res/layout/layout_data_disclosure_detail_pdk.xml @@ -0,0 +1,19 @@ + + + + + + + diff --git a/res/layout/layout_data_disclosure_pdk.xml b/res/layout/layout_data_disclosure_pdk.xml new file mode 100755 index 0000000..cc763d0 --- /dev/null +++ b/res/layout/layout_data_disclosure_pdk.xml @@ -0,0 +1,31 @@ + + + + + + + + + + diff --git a/res/layout/row_disclosure_action_pdk.xml b/res/layout/row_disclosure_action_pdk.xml new file mode 100755 index 0000000..a5250e3 --- /dev/null +++ b/res/layout/row_disclosure_action_pdk.xml @@ -0,0 +1,19 @@ + + + + diff --git a/res/layout/row_disclosure_location_accuracy_pdk.xml b/res/layout/row_disclosure_location_accuracy_pdk.xml new file mode 100755 index 0000000..78cd4cc --- /dev/null +++ b/res/layout/row_disclosure_location_accuracy_pdk.xml @@ -0,0 +1,26 @@ + + + + + + + diff --git a/res/layout/row_generator_disclosure_generic.xml b/res/layout/row_generator_disclosure_generic.xml new file mode 100755 index 0000000..200f44e --- /dev/null +++ b/res/layout/row_generator_disclosure_generic.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/res/values/generators.xml b/res/values/generators.xml index 2fbcf3b..a6e8f0e 100755 --- a/res/values/generators.xml +++ b/res/values/generators.xml @@ -43,6 +43,33 @@ Device Location Coordinates: %1$.4f, %2$.4f + Location Accuracy + Tap here to update the accuracy of your data. + + Best Accuracy + Best Available Data From Location Hardware + + Locally Randomized + Location Combined With Random Noise + + User Provided + Static Location Provided By User + + Disabled + App Does Not Use Location Data + + Best Accuracy + This app will use the location hardware on this device to obtain the most accurate location readings. + + Location Disabled + This app will not use your location, but use an placeholder instead. + + Locally Randomized + Please enter the random distance (kilometers) to use to obfuscate your exact location: + + User Provided + Please enter a postal code or city and province to use as your location: + Unable to find your location. Please enter your location another way and try again. Screen State diff --git a/res/values/strings.xml b/res/values/strings.xml index a784812..73b220f 100755 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4,6 +4,7 @@ 0.0.1 Unknown Version Data Stream + Continue %d generator @@ -20,5 +21,11 @@ Diagnostics The app is set up correctly.\n\nNo further actions are needed. + + Passive Data Disclosure + Select a disclosure item for more information… + + Data Collection Description + Tap here to learn how this app uses your data. diff --git a/src/com/audacious_software/passive_data_kit/activities/DataDisclosureActivity.java b/src/com/audacious_software/passive_data_kit/activities/DataDisclosureActivity.java new file mode 100755 index 0000000..70d41f3 --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/activities/DataDisclosureActivity.java @@ -0,0 +1,44 @@ +package com.audacious_software.passive_data_kit.activities; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.MenuItem; +import android.widget.FrameLayout; + +import com.audacious_software.passive_data_kit.activities.generators.GeneratorsAdapter; +import com.audacious_software.pdk.passivedatakit.R; + +public class DataDisclosureActivity extends AppCompatActivity { + private GeneratorsAdapter mAdapter = null; + + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + this.setContentView(R.layout.layout_data_disclosure_pdk); + this.getSupportActionBar().setTitle(R.string.title_data_disclosure); + this.getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + FrameLayout dataView = (FrameLayout) this.findViewById(R.id.data_view); + + this.mAdapter = new GeneratorsAdapter(); + this.mAdapter.setContext(this.getApplicationContext()); + this.mAdapter.setDataView(dataView); + + RecyclerView listView = (RecyclerView) this.findViewById(R.id.list_view); + + listView.setLayoutManager(new LinearLayoutManager(this)); + + listView.setAdapter(this.mAdapter); + } + + public boolean onOptionsItemSelected(MenuItem item) + { + if (item.getItemId() == android.R.id.home) + { + this.finish(); + } + + return true; + } +} diff --git a/src/com/audacious_software/passive_data_kit/activities/DataDisclosureDetailActivity.java b/src/com/audacious_software/passive_data_kit/activities/DataDisclosureDetailActivity.java new file mode 100755 index 0000000..aaf79bb --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/activities/DataDisclosureDetailActivity.java @@ -0,0 +1,123 @@ +package com.audacious_software.passive_data_kit.activities; + +import android.content.Context; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.FrameLayout; +import android.widget.ListView; +import android.widget.TextView; + +import com.audacious_software.passive_data_kit.Logger; +import com.audacious_software.passive_data_kit.generators.Generator; +import com.audacious_software.pdk.passivedatakit.R; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; + +public class DataDisclosureDetailActivity extends AppCompatActivity { + public static class Action { + public String title; + public String subtitle; + + public View view; + } + + public static final String GENERATOR_CLASS_NAME = "com.audacious_software.passive_data_kit.activities.DataDisclosureDetailActivity.GENERATOR_CLASS_NAME"; + + private Class mGeneratorClass = null; + + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final DataDisclosureDetailActivity me = this; + + this.setContentView(R.layout.layout_data_disclosure_detail_pdk); + this.getSupportActionBar().setSubtitle(R.string.title_data_disclosure); + this.getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + try { + this.mGeneratorClass = (Class) Class.forName(this.getIntent().getStringExtra(DataDisclosureDetailActivity.GENERATOR_CLASS_NAME)); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + + if (this.mGeneratorClass != null) { + try { + Method getGeneratorTitle = this.mGeneratorClass.getDeclaredMethod("getGeneratorTitle", Context.class); + + String title = (String) getGeneratorTitle.invoke(null, this); + this.getSupportActionBar().setTitle(title); + + Method getDisclosureActions = this.mGeneratorClass.getDeclaredMethod("getDisclosureActions", Context.class); + + final List actions = (List) getDisclosureActions.invoke(null, this); + + ListView actionsList = (ListView) this.findViewById(R.id.disclosure_actions); + ArrayAdapter adapter = new ArrayAdapter(this, R.layout.row_disclosure_action_pdk, actions) { + public View getView (int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = LayoutInflater.from(me).inflate(R.layout.row_disclosure_action_pdk, null); + } + + Action action = actions.get(position); + + TextView title = (TextView) convertView.findViewById(R.id.action_title); + title.setText(action.title); + + TextView description = (TextView) convertView.findViewById(R.id.action_description); + description.setText(action.subtitle); + + return convertView; + } + }; + + actionsList.setAdapter(adapter); + + actionsList.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView adapterView, View view, int position, long l) { + Log.e("PDK", "TAPPED: " + position); + + Action action = actions.get(position); + + FrameLayout dataView = (FrameLayout) me.findViewById(R.id.data_view); + dataView.removeAllViews(); + + if (action.view != null) { + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + + action.view.setLayoutParams(params); + + dataView.addView(action.view); + } + } + }); + + actionsList.performItemClick(null, 0, 0); + } catch (NoSuchMethodException e1) { + Logger.getInstance(this).logThrowable(e1); + } catch (InvocationTargetException e1) { + Logger.getInstance(this).logThrowable(e1); + } catch (IllegalAccessException e1) { + Logger.getInstance(this).logThrowable(e1); + } + } + } + + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + this.finish(); + } + + return true; + } + +} diff --git a/src/com/audacious_software/passive_data_kit/activities/DataStreamActivity.java b/src/com/audacious_software/passive_data_kit/activities/DataStreamActivity.java index 881a6fe..3e80bb0 100755 --- a/src/com/audacious_software/passive_data_kit/activities/DataStreamActivity.java +++ b/src/com/audacious_software/passive_data_kit/activities/DataStreamActivity.java @@ -6,15 +6,8 @@ import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; -import com.audacious_software.passive_data_kit.PassiveDataKit; import com.audacious_software.passive_data_kit.activities.generators.DataPointsAdapter; -import com.audacious_software.passive_data_kit.diagnostics.DiagnosticAction; import com.audacious_software.passive_data_kit.generators.Generators; import com.audacious_software.pdk.passivedatakit.R; diff --git a/src/com/audacious_software/passive_data_kit/activities/generators/GeneratorViewHolder.java b/src/com/audacious_software/passive_data_kit/activities/generators/GeneratorViewHolder.java new file mode 100755 index 0000000..5726eb2 --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/activities/generators/GeneratorViewHolder.java @@ -0,0 +1,10 @@ +package com.audacious_software.passive_data_kit.activities.generators; + +import android.support.v7.widget.RecyclerView; +import android.view.View; + +public class GeneratorViewHolder extends RecyclerView.ViewHolder { + public GeneratorViewHolder(View itemView) { + super(itemView); + } +} diff --git a/src/com/audacious_software/passive_data_kit/activities/generators/GeneratorsAdapter.java b/src/com/audacious_software/passive_data_kit/activities/generators/GeneratorsAdapter.java new file mode 100755 index 0000000..adf3618 --- /dev/null +++ b/src/com/audacious_software/passive_data_kit/activities/generators/GeneratorsAdapter.java @@ -0,0 +1,129 @@ +package com.audacious_software.passive_data_kit.activities.generators; + +import android.content.Context; +import android.content.Intent; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.Toast; + +import com.audacious_software.passive_data_kit.Logger; +import com.audacious_software.passive_data_kit.activities.DataDisclosureDetailActivity; +import com.audacious_software.passive_data_kit.generators.Generator; +import com.audacious_software.passive_data_kit.generators.Generators; +import com.audacious_software.pdk.passivedatakit.R; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class GeneratorsAdapter extends RecyclerView.Adapter { + private Context mContext = null; + private FrameLayout mDataView = null; + + @Override + public GeneratorViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = Generator.fetchDisclosureView(parent); + + return new GeneratorViewHolder(view); + } + + @Override + public void onBindViewHolder(final GeneratorViewHolder holder, int position) { + final GeneratorsAdapter me = this; + + List> activeGenerators = Generators.getInstance(holder.itemView.getContext()).activeGenerators(); + + this.sortGenerators(this.mContext, activeGenerators); + + Class generatorClass = activeGenerators.get(position); + + Log.e("PDK", "GENERATOR CLASS: " + generatorClass); + + try { + Method bindViewHolder = generatorClass.getDeclaredMethod("bindDisclosureViewHolder", GeneratorViewHolder.class); + bindViewHolder.invoke(null, holder); + } catch (Exception e) { +// e.printStackTrace(); + try { + generatorClass = Generator.class; + + Method bindViewHolder = generatorClass.getDeclaredMethod("bindDisclosureViewHolder", GeneratorViewHolder.class); + + bindViewHolder.invoke(null, holder); + } catch (NoSuchMethodException e1) { + Logger.getInstance(holder.itemView.getContext()).logThrowable(e1); + } catch (InvocationTargetException e1) { + Logger.getInstance(holder.itemView.getContext()).logThrowable(e1); + } catch (IllegalAccessException e1) { + Logger.getInstance(holder.itemView.getContext()).logThrowable(e1); + } + } + + final Class finalClass = generatorClass; + + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + me.mDataView.removeAllViews(); + + try { + Method bindViewHolder = finalClass.getDeclaredMethod("getDisclosureDataView", GeneratorViewHolder.class); + + View dataView = (View) bindViewHolder.invoke(null, holder); + me.mDataView.addView(dataView); + } catch (NoSuchMethodException e1) { + Logger.getInstance(holder.itemView.getContext()).logThrowable(e1); + } catch (InvocationTargetException e1) { + Logger.getInstance(holder.itemView.getContext()).logThrowable(e1); + } catch (IllegalAccessException e1) { + Logger.getInstance(holder.itemView.getContext()).logThrowable(e1); + } + } + }); + + ImageView settingsButton = (ImageView) holder.itemView.findViewById(R.id.button_disclosure_item); + + settingsButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(holder.itemView.getContext(), DataDisclosureDetailActivity.class); + intent.putExtra(DataDisclosureDetailActivity.GENERATOR_CLASS_NAME, finalClass.getCanonicalName()); + + holder.itemView.getContext().startActivity(intent); + } + }); + + } + + @Override + public int getItemCount() { + return Generators.getInstance(null).activeGenerators().size(); + } + + private void sortGenerators(final Context context, List> generators) { + Collections.sort(generators, new Comparator>() { + @Override + public int compare(Class one, Class two) { + return one.getName().compareTo(two.getName()); + } + }); + } + + public int getItemViewType (int position) { + return 0; + } + + public void setContext(Context context) { + this.mContext = context; + } + + public void setDataView(FrameLayout dataView) { + this.mDataView = dataView; + } +} diff --git a/src/com/audacious_software/passive_data_kit/generators/Generator.java b/src/com/audacious_software/passive_data_kit/generators/Generator.java index 1d8835d..66563c0 100755 --- a/src/com/audacious_software/passive_data_kit/generators/Generator.java +++ b/src/com/audacious_software/passive_data_kit/generators/Generator.java @@ -12,6 +12,7 @@ import android.widget.TextView; import com.audacious_software.passive_data_kit.activities.generators.DataPointViewHolder; +import com.audacious_software.passive_data_kit.activities.generators.GeneratorViewHolder; import com.audacious_software.pdk.passivedatakit.R; import java.text.DateFormat; @@ -83,6 +84,20 @@ public static void bindViewHolder(DataPointViewHolder holder) { generatorLabel.setText(identifier); } + public static View fetchDisclosureView(ViewGroup parent) { + return LayoutInflater.from(parent.getContext()).inflate(R.layout.row_generator_disclosure_generic, parent, false); + } + + public static void bindDisclosureViewHolder(GeneratorViewHolder holder) { + Class currentClass = new Object() { }.getClass().getEnclosingClass(); + + String identifier = currentClass.getCanonicalName(); + + TextView generatorLabel = (TextView) holder.itemView.findViewById(R.id.label_generator); + + generatorLabel.setText(identifier); + } + public static String formatTimestamp(Context context, double timestamp) { timestamp *= 1000; 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 fd35553..d33c9f4 100755 --- a/src/com/audacious_software/passive_data_kit/generators/Generators.java +++ b/src/com/audacious_software/passive_data_kit/generators/Generators.java @@ -24,6 +24,7 @@ public class Generators { private Context mContext = null; private boolean mStarted = false; + private ArrayList mGenerators = new ArrayList<>(); private HashSet mActiveGenerators = new HashSet<>(); private SharedPreferences mSharedPreferences = null; 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 12ab3b8..058f4ec 100755 --- a/src/com/audacious_software/passive_data_kit/generators/device/Location.java +++ b/src/com/audacious_software/passive_data_kit/generators/device/Location.java @@ -4,6 +4,7 @@ import android.app.Activity; import android.content.ContentValues; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; @@ -13,6 +14,8 @@ import android.database.sqlite.SQLiteDatabase; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; +import android.location.Address; +import android.location.Geocoder; import android.location.LocationManager; import android.os.Bundle; import android.os.Handler; @@ -21,18 +24,28 @@ import android.support.v4.content.ContextCompat; import android.support.v4.content.res.ResourcesCompat; import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v7.app.AlertDialog; import android.support.v7.widget.SwitchCompat; import android.util.DisplayMetrics; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.webkit.WebView; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.ListView; import android.widget.TextView; +import android.widget.Toast; import com.audacious_software.passive_data_kit.DeviceInformation; import com.audacious_software.passive_data_kit.PassiveDataKit; +import com.audacious_software.passive_data_kit.activities.DataDisclosureDetailActivity; import com.audacious_software.passive_data_kit.activities.generators.DataPointViewHolder; +import com.audacious_software.passive_data_kit.activities.generators.GeneratorViewHolder; import com.audacious_software.passive_data_kit.activities.generators.RequestPermissionActivity; import com.audacious_software.passive_data_kit.diagnostics.DiagnosticAction; import com.audacious_software.passive_data_kit.generators.Generator; @@ -54,8 +67,11 @@ import com.google.maps.android.ui.IconGenerator; import java.io.File; +import java.io.IOException; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; +import java.util.Random; @SuppressWarnings("unused") public class Location extends Generator implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener { @@ -80,6 +96,22 @@ public class Location extends Generator implements GoogleApiClient.ConnectionCal 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 String ACCURACY_MODE = "com.audacious_software.passive_data_kit.generators.device.Location.ACCURACY_MODE"; + + private static int ACCURACY_BEST = 0; + private static int ACCURACY_RANDOMIZED = 1; + private static int ACCURACY_USER = 2; + private static int ACCURACY_DISABLED = 3; + + private static final String ACCURACY_MODE_RANDOMIZED_RANGE = "com.audacious_software.passive_data_kit.generators.device.Location.ACCURACY_MODE_RANDOMIZED_RANGE"; + private static final long ACCURACY_MODE_RANDOMIZED_RANGE_DEFAULT = 100; + + private static final String ACCURACY_MODE_USER_LOCATION = "com.audacious_software.passive_data_kit.generators.device.Location.ACCURACY_MODE_USER_LOCATION"; + private static final String ACCURACY_MODE_USER_LOCATION_DEFAULT = "Chicago, Illinois"; + + private static final String ACCURACY_MODE_USER_LOCATION_LATITUDE = "com.audacious_software.passive_data_kit.generators.device.Location.ACCURACY_MODE_USER_LOCATION_LATITUDE"; + private static final String ACCURACY_MODE_USER_LOCATION_LONGITUDE = "com.audacious_software.passive_data_kit.generators.device.Location.ACCURACY_MODE_USER_LOCATION_LONGITUDE"; + private static Location sInstance = null; private GoogleApiClient mGoogleApiClient = null; private android.location.Location mLastLocation = null; @@ -288,6 +320,37 @@ public void onLocationChanged(android.location.Location location) { long now = System.currentTimeMillis(); + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + int selected = prefs.getInt(Location.ACCURACY_MODE, Location.ACCURACY_BEST); + + if (selected == Location.ACCURACY_RANDOMIZED) { + // http://gis.stackexchange.com/a/68275/10230 + + double latitude = location.getLatitude(); + double longitude = location.getLongitude(); + + double radius = prefs.getLong(Location.ACCURACY_MODE_RANDOMIZED_RANGE, Location.ACCURACY_MODE_RANDOMIZED_RANGE_DEFAULT); + + double radiusInDegrees = radius / 111000; + + Random r = new SecureRandom(); + + double u = r.nextDouble(); + double v = r.nextDouble(); + + double w = radiusInDegrees * Math.sqrt(u); + double t = 2 * Math.PI * v; + double x = w * Math.cos(t); + double y = w * Math.sin(t); + + // Adjust the x-coordinate for the shrinking of the east-west distances + longitude = longitude + (x / Math.cos(latitude)); + latitude = y + latitude; + + location.setLongitude(longitude); + location.setLatitude(latitude); + } + ContentValues values = new ContentValues(); values.put(Location.HISTORY_OBSERVED, System.currentTimeMillis()); values.put(Location.HISTORY_LATITUDE, location.getLatitude()); @@ -523,7 +586,347 @@ else if (Location.useGoogleLocationServices(parent.getContext())) } } + public static String getGeneratorTitle(Context context) { + return context.getString(R.string.generator_location); + } + + public static List getDisclosureActions(final Context context) { + List actions = new ArrayList<>(); + + DataDisclosureDetailActivity.Action disclosure = new DataDisclosureDetailActivity.Action(); + + disclosure.title = context.getString(R.string.label_data_collection_description); + disclosure.subtitle = context.getString(R.string.label_data_collection_description_more); + + WebView disclosureView = new WebView(context); + disclosureView.loadUrl("file:///android_asset/html/passive_data_kit/generator_location_disclosure.html"); + + disclosure.view = disclosureView; + + actions.add(disclosure); + + DataDisclosureDetailActivity.Action accuracy = new DataDisclosureDetailActivity.Action(); + accuracy.title = context.getString(R.string.label_data_collection_location_accuracy); + accuracy.subtitle = context.getString(R.string.label_data_collection_location_accuracy_more); + + final Integer[] options = { Location.ACCURACY_BEST, Location.ACCURACY_RANDOMIZED, Location.ACCURACY_USER, Location.ACCURACY_DISABLED }; + + final ListView listView = new ListView(context); + + final ArrayAdapter accuracyAdapter = new ArrayAdapter(context, R.layout.row_disclosure_location_accuracy_pdk, options) { + public View getView (final int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = LayoutInflater.from(context).inflate(R.layout.row_disclosure_location_accuracy_pdk, null); + } + + final Integer option = options[position]; + + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + int selected = prefs.getInt(Location.ACCURACY_MODE, Location.ACCURACY_BEST); + + CheckBox checked = (CheckBox) convertView.findViewById(R.id.action_checked); + + checked.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { + + } + }); + + checked.setChecked(selected == option); + + TextView title = (TextView) convertView.findViewById(R.id.action_title); + TextView description = (TextView) convertView.findViewById(R.id.action_description); + + if (option == Location.ACCURACY_BEST) { + title.setText(R.string.label_data_collection_location_accuracy_best); + description.setText(R.string.label_data_collection_location_accuracy_best_more); + } else if (option == Location.ACCURACY_RANDOMIZED) { + title.setText(R.string.label_data_collection_location_accuracy_randomized); + description.setText(R.string.label_data_collection_location_accuracy_randomized_more); + } else if (option == Location.ACCURACY_USER) { + title.setText(R.string.label_data_collection_location_accuracy_user); + description.setText(R.string.label_data_collection_location_accuracy_user_more); + } else if (option == Location.ACCURACY_DISABLED) { + title.setText(R.string.label_data_collection_location_accuracy_disabled); + description.setText(R.string.label_data_collection_location_accuracy_disabled_more); + } + + final ArrayAdapter meAdapter = this; + + checked.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(final CompoundButton compoundButton, final boolean checked) { + Log.e("PDK", "CHECK CHANGE!"); + + final CompoundButton.OnCheckedChangeListener me = this; + + if (option == Location.ACCURACY_BEST) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.title_location_accuracy_best); + builder.setMessage(R.string.message_location_accuracy_best); + + builder.setPositiveButton(R.string.action_continue, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + SharedPreferences.Editor e = prefs.edit(); + e.putInt(Location.ACCURACY_MODE, option); + e.apply(); + + meAdapter.notifyDataSetChanged(); + } + }); + + builder.create().show(); + } else if (option == Location.ACCURACY_RANDOMIZED) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.title_location_accuracy_randomized); + + View body = LayoutInflater.from(context).inflate(R.layout.dialog_location_randomized, null); + builder.setView(body); + + final EditText rangeField = (EditText) body.findViewById(R.id.random_range); + + long existingRange = prefs.getLong(Location.ACCURACY_MODE_RANDOMIZED_RANGE, Location.ACCURACY_MODE_RANDOMIZED_RANGE_DEFAULT); + + rangeField.setText("" + existingRange); + + builder.setPositiveButton(R.string.action_continue, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + SharedPreferences.Editor e = prefs.edit(); + + long randomRange = Long.parseLong(rangeField.getText().toString()); + + e.putLong(Location.ACCURACY_MODE_RANDOMIZED_RANGE, randomRange); + + e.putInt(Location.ACCURACY_MODE, option); + e.apply(); + + meAdapter.notifyDataSetChanged(); + } + }); + + builder.create().show(); + } else if (option == Location.ACCURACY_USER) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.title_location_accuracy_user); + + View body = LayoutInflater.from(context).inflate(R.layout.dialog_location_user, null); + builder.setView(body); + + final EditText locationField = (EditText) body.findViewById(R.id.user_location); + + String existingLocation = prefs.getString(Location.ACCURACY_MODE_USER_LOCATION, Location.ACCURACY_MODE_USER_LOCATION_DEFAULT); + + locationField.setText(existingLocation); + + builder.setPositiveButton(R.string.action_continue, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + SharedPreferences.Editor e = prefs.edit(); + + String location = locationField.getText().toString(); + + e.putString(Location.ACCURACY_MODE_USER_LOCATION, location); + + try { + List
results = (new Geocoder(context)).getFromLocationName(location, 1); + + if (results.size() > 0) { + Address match = results.get(0); + + e.putFloat(Location.ACCURACY_MODE_USER_LOCATION_LATITUDE, (float) match.getLatitude()); + e.putFloat(Location.ACCURACY_MODE_USER_LOCATION_LONGITUDE, (float) match.getLongitude()); + } else { + Toast.makeText(context, R.string.toast_location_lookup_failed, Toast.LENGTH_LONG).show(); + + me.onCheckedChanged(compoundButton, checked); + } + } catch (IOException e1) { + e1.printStackTrace(); + } + + e.putInt(Location.ACCURACY_MODE, option); + e.apply(); + + meAdapter.notifyDataSetChanged(); + } + }); + + builder.create().show(); + } else if (option == Location.ACCURACY_DISABLED) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.title_location_accuracy_disabled); + builder.setMessage(R.string.message_location_accuracy_disabled); + + builder.setPositiveButton(R.string.action_continue, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + SharedPreferences.Editor e = prefs.edit(); + e.putInt(Location.ACCURACY_MODE, option); + e.apply(); + + meAdapter.notifyDataSetChanged(); + } + }); + + builder.create().show(); + } + + } + }); + + return convertView; + } + }; + + listView.setAdapter(accuracyAdapter); + + accuracy.view = listView; + + actions.add(accuracy); + + return actions; + } + + public static View getDisclosureDataView(final GeneratorViewHolder holder) { + final Context context = holder.itemView.getContext(); + + if (Location.useKindleLocationServices()) + { + // TODO + throw new RuntimeException("Throw rocks at developer to implement Kindle support."); + } + else if (Location.useGoogleLocationServices(holder.itemView.getContext())) + { + final MapView mapView = new MapView(context); + + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + mapView.setLayoutParams(params); + + mapView.onCreate(null); + + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + final boolean useHybrid = prefs.getBoolean(Location.SETTING_DISPLAY_HYBRID_MAP, Location.SETTING_DISPLAY_HYBRID_MAP_DEFAULT); + + IconGenerator iconGen = new IconGenerator(context); + + Drawable shapeDrawable = ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_location_heatmap_marker, null); + iconGen.setBackground(shapeDrawable); + + View view = new View(context); + view.setLayoutParams(new ViewGroup.LayoutParams(8, 8)); + iconGen.setContentView(view); + + final Bitmap bitmap = iconGen.makeIcon(); + + mapView.getMapAsync(new OnMapReadyCallback() { + public void onMapReady(GoogleMap googleMap) { + if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED || + ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { + googleMap.setMyLocationEnabled(true); + } + + if (useHybrid) { + googleMap.setMapType(GoogleMap.MAP_TYPE_HYBRID); + } else { + googleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL); + } + + googleMap.getUiSettings().setZoomControlsEnabled(true); + googleMap.getUiSettings().setMyLocationButtonEnabled(false); + googleMap.getUiSettings().setMapToolbarEnabled(false); + googleMap.getUiSettings().setAllGesturesEnabled(false); + + Location me = Location.getInstance(context); + + double lastLatitude = 0.0; + double lastLongitude = 0.0; + long timestamp = 0; + + final List locations = new ArrayList<>(); + + String where = Location.HISTORY_OBSERVED + " > ?"; + String[] args = { "" + (System.currentTimeMillis() - (1000 * 60 * 60 * 24)) }; + + Cursor c = me.mDatabase.query(Location.TABLE_HISTORY, null, where, args, null, null, Location.HISTORY_OBSERVED); + + while (c.moveToNext()) { + lastLatitude = c.getDouble(c.getColumnIndex(Location.HISTORY_LATITUDE)); + lastLongitude = c.getDouble(c.getColumnIndex(Location.HISTORY_LONGITUDE)); + + LatLng location = new LatLng(lastLatitude, lastLongitude); + + locations.add(location); + } + + c.close(); + + LatLngBounds.Builder builder = new LatLngBounds.Builder(); + + for (LatLng latlng : locations) { + builder.include(latlng); + } + + final DisplayMetrics metrics = new DisplayMetrics(); + ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(metrics); + + if (locations.size() > 0) { + googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), (int) (16 * metrics.density))); + } + + for (LatLng latLng : locations) { + googleMap.addMarker(new MarkerOptions() + .position(latLng) + .icon(BitmapDescriptorFactory.fromBitmap(bitmap))); + } + + mapView.onResume(); + } + }); + + return mapView; + } + else + { + // TODO + throw new RuntimeException("Throw rocks at developer to implement generic location support."); + } + } + + public static void bindDisclosureViewHolder(final GeneratorViewHolder holder) { + Class currentClass = new Object() { }.getClass().getEnclosingClass(); + + String identifier = currentClass.getCanonicalName(); + + TextView generatorLabel = (TextView) holder.itemView.findViewById(R.id.label_generator); + + generatorLabel.setText(Location.getGeneratorTitle(holder.itemView.getContext())); + } + public android.location.Location getLastKnownLocation() { + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + int selected = prefs.getInt(Location.ACCURACY_MODE, Location.ACCURACY_BEST); + + if (selected == Location.ACCURACY_USER) { + double latitude = prefs.getFloat(Location.ACCURACY_MODE_USER_LOCATION_LATITUDE, 0); + double longitude = prefs.getFloat(Location.ACCURACY_MODE_USER_LOCATION_LONGITUDE, 0); + + android.location.Location location = new android.location.Location(""); + location.setLatitude(latitude); + location.setLongitude(longitude); + + return location; + } else if (selected == Location.ACCURACY_DISABLED) { + android.location.Location location = new android.location.Location(""); + location.setLatitude(41.8781); + location.setLongitude(-87.6298); + + return location; + } + if (this.mLastLocation != null) { return this.mLastLocation; } @@ -542,6 +945,34 @@ public android.location.Location getLastKnownLocation() { } } + if (selected == Location.ACCURACY_RANDOMIZED) { + // http://gis.stackexchange.com/a/68275/10230 + + double latitude = last.getLatitude(); + double longitude = last.getLongitude(); + + double radius = prefs.getLong(Location.ACCURACY_MODE_RANDOMIZED_RANGE, Location.ACCURACY_MODE_RANDOMIZED_RANGE_DEFAULT); + + double radiusInDegrees = radius / 111000; + + Random r = new SecureRandom(); + + double u = r.nextDouble(); + double v = r.nextDouble(); + + double w = radiusInDegrees * Math.sqrt(u); + double t = 2 * Math.PI * v; + double x = w * Math.cos(t); + double y = w * Math.sin(t); + + // Adjust the x-coordinate for the shrinking of the east-west distances + longitude = longitude + (x / Math.cos(latitude)); + latitude = y + latitude; + + last.setLongitude(longitude); + last.setLatitude(latitude); + } + return last; }