package de.blau.android.util;
import java.util.ArrayList;
import org.acra.ACRA;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.util.Log;
import de.blau.android.App;
import de.blau.android.Logic;
import de.blau.android.Main;
import de.blau.android.R;
import de.blau.android.exception.OsmException;
import de.blau.android.osm.BoundingBox;
import de.blau.android.osm.Node;
import de.blau.android.osm.OsmElement;
import de.blau.android.osm.Way;
import de.blau.android.prefs.Preferences;
import de.blau.android.tasks.Note;
import de.blau.android.tasks.Task;
/**
* Generate an Android notification for OSM elements that have an issue and for Notes and other QA "bugs"
* @author simon
*
*/
public class IssueAlert {
private final static String GROUP_DATA = "Data";
private final static String GROUP_NOTES = "Notes";
private final static String GROUP_OSMOSE = "Osmose";
private final static int[] bearings = {R.string.bearing_ne,
R.string.bearing_e,
R.string.bearing_se,
R.string.bearing_s,
R.string.bearing_sw,
R.string.bearing_w,
R.string.bearing_nw,
R.string.bearing_n};
private static int bugCount = 0;
/**
* Generate an alert/notification if something is problematic about the OSM object
* @param context
* @param e
*/
public static void alert(Context context, OsmElement e) {
Preferences prefs = new Preferences(context);
if (!prefs.generateAlerts()) { // don't generate alerts
return;
}
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
Location location = null;
try {
location = locationManager.getLastKnownLocation("gps");
} catch (SecurityException sex) {
// can be safely ignored
}
double eLon = 0D;
double eLat = 0D;
if ("node".equals(e.getName())) {
eLon = ((Node)e).getLon()/1E7D;
eLat = ((Node)e).getLat()/1E7D;
} else if ("way".equals(e.getName())) {
double[] result = Logic.centroidLonLat((Way)e);
if (result== null) {
return;
}
eLon = result[0];
eLat = result[1];
} else {
return;
}
String title = context.getString(R.string.alert_data_issue);
String ticker = title;
String message = "";
if (location != null) {
// if we know where we are we can provide better information
long distance = 0;
if ("node".equals(e.getName())) {
distance = Math.round(GeoMath.haversineDistance(location.getLongitude(), location.getLatitude(), eLon, eLat));
} else if ("way".equals(e.getName())) {
ClosestPoint cp = getClosestDistance(location.getLongitude(), location.getLatitude(), (Way)e);
distance = Math.round(cp.distance);
eLon = cp.lon;
eLat = cp.lat;
}
// filter
if (distance > prefs.getMaxAlertDistance()) {
return;
}
long bearing = GeoMath.bearing(location.getLongitude(), location.getLatitude(), eLon, eLat);
int index = (int)(bearing - 22.5);
if (index < 0)
index += 360;
index = index / 45;
// message = "in " + distance + "m " /* + bearing + "° " */ + bearings[index] + "\n";
message = context.getString(R.string.alert_distance_direction, distance, context.getString(bearings[index])) + "\n";
ticker = ticker + " " + message;
}
message = message + e.describeProblem();
NotificationCompat.Builder mBuilder;
try {
mBuilder =
new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.osm_logo)
.setContentTitle(title)
.setContentText(message)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setTicker(ticker)
.setAutoCancel(true)
.setGroup(GROUP_DATA);
} catch (RuntimeException re) {
// NotificationCompat.Builder seems to be flaky instead of crashing we produce a
// crash dump and return
ACRA.getErrorReporter().putCustomData("STATUS","NOCRASH");
ACRA.getErrorReporter().handleException(re);
return;
}
// Creates an explicit intent for an Activity in your app
// Intent resultIntent = new Intent(main, Main.class);
Intent resultIntent = new Intent(Intent.ACTION_VIEW);
// Uri geo = Uri.fromParts("geo", eLat+","+eLon,null);
// resultIntent.setData(geo);
try {
BoundingBox box = GeoMath.createBoundingBoxForCoordinates(eLat, eLon, prefs.getDownloadRadius(), true);
Uri rc = Uri.parse( "http://127.0.0.1:8111/load_and_zoom?left=" + box.getLeft()/1E7D + "&right=" + box.getRight()/1E7D + "&top=" + box.getTop()/1E7D + "&bottom=" + box.getBottom()/1E7D + "&select="+e.getName()+e.getOsmId());
Log.d("IssueAlert", rc.toString());
resultIntent.setData(rc);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
// Adds the back stack for the Intent (but not the Intent itself)
stackBuilder.addParentStack(Main.class);
// Adds the Intent that starts the Activity to the top of the stack
stackBuilder.addNextIntent(resultIntent);
PendingIntent resultPendingIntent =
stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT
);
mBuilder.setContentIntent(resultPendingIntent);
NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
// mId allows you to update the notification later on.
mNotificationManager.notify(id(e), mBuilder.build());
App.getOsmDataNotifications(context).save(mNotificationManager,id(e));
} catch (OsmException e1) {
Log.d("IssueAlert","Illegal BB created from lat " + eLat+ " lon " + eLon + " r " + prefs.getDownloadRadius());
}
}
private static int id(OsmElement e) {
return (e.getName() + e.getOsmId()).hashCode();
}
public static void cancel(Context context, OsmElement e) {
NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
App.getOsmDataNotifications(context).remove(mNotificationManager,id(e));
}
/**
* Generate an alert/notification if we found a task object nearby.
* @param context
* @param b
*/
public static void alert(Context context, Task b) {
Log.d("IssueAlert", "generating alert for " + b.getDescription());
Preferences prefs = new Preferences(context);
if (!prefs.generateAlerts()) { // don't generate alerts
return;
}
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
Location location = null;
try {
location = locationManager.getLastKnownLocation("gps");
} catch (SecurityException sex) {
// can be safely ignored
}
double eLon = b.getLon()/1E7D;
double eLat = b.getLat()/1E7D;
String title = b instanceof Note ? context.getString(R.string.alert_note) : context.getString(R.string.alert_bug);
String ticker = title;
String message = "";
if (location != null) {
// if we know where we are we can provide better information
long distance = Math.round(GeoMath.haversineDistance(location.getLongitude(), location.getLatitude(), eLon, eLat));
// filter
if (distance > Math.sqrt(8*prefs.getBugDownloadRadius()*prefs.getBugDownloadRadius())) { // diagonal of auto download box
return;
}
long bearing = GeoMath.bearing(location.getLongitude(), location.getLatitude(), eLon, eLat);
int index = (int)(bearing - 22.5);
if (index < 0)
index += 360;
index = index / 45;
// message = "in " + distance + "m " /* + bearing + "° " */ + bearings[index] + "\n";
message = context.getString(R.string.alert_distance_direction, distance, context.getString(bearings[index])) + "\n";
ticker = ticker + " " + message;
}
message = message + b.getDescription();
NotificationCompat.Builder mBuilder;
try {
mBuilder =
new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.osm_logo)
.setContentTitle(title)
.setContentText(message)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setTicker(ticker)
.setAutoCancel(true)
.setGroup(b instanceof Note ? GROUP_NOTES : GROUP_OSMOSE);
} catch (RuntimeException re) {
// NotificationCompat.Builder seems to be flaky instead of crashing we produce a
// crash dump and return
ACRA.getErrorReporter().putCustomData("STATUS","NOCRASH");
ACRA.getErrorReporter().handleException(re);
return;
}
// Creates an explicit intent for an Activity in your app
// Intent resultIntent = new Intent(main, Main.class);
Intent resultIntent = new Intent(Intent.ACTION_VIEW);
Uri geo = Uri.fromParts("geo", eLat+","+eLon,null);
resultIntent.setData(geo);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
// Adds the back stack for the Intent (but not the Intent itself)
stackBuilder.addParentStack(Main.class);
// Adds the Intent that starts the Activity to the top of the stack
stackBuilder.addNextIntent(resultIntent);
PendingIntent resultPendingIntent =
stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT
);
mBuilder.setContentIntent(resultPendingIntent);
NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
// mId allows you to update the notification later on.
int id = id(b);
mNotificationManager.notify(id(b), mBuilder.build());
App.getTaskNotifications(context).save(mNotificationManager,id(b));
bugCount++;
}
private static int id(Task b) {
return (b.getClass().getSimpleName() + b.getId()).hashCode();
}
public static void cancel(Context context, Task b) {
NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
App.getTaskNotifications(context).remove(mNotificationManager,id(b)); // cancels and removes from cache
}
static class ClosestPoint{
double distance = Double.MAX_VALUE;
double lat;
double lon;
}
private static ClosestPoint getClosestDistance(double lon, double lat, Way w) {
ClosestPoint closest = new IssueAlert.ClosestPoint();
double ny = GeoMath.latToMercator(lat);
double nx = lon;
ArrayList<Node> nodes = new ArrayList<Node>(w.getNodes());
for (int i = 0;i <= nodes.size()-2;i++) {
double bx = nodes.get(i).getLon()/1E7D;
double by = GeoMath.latE7ToMercator(nodes.get(i).getLat());
double ax = nodes.get(i+1).getLon()/1E7D;
double ay = GeoMath.latE7ToMercator(nodes.get(i+1).getLat());
float[] newClosest = GeoMath.closestPoint((float)nx, (float)ny, (float)bx, (float)by, (float)ax, (float)ay);
double newDistance = GeoMath.haversineDistance(nx, ny, newClosest[0], newClosest[1]);
if (newDistance < closest.distance) {
closest.distance = newDistance;
closest.lon = newClosest[0];
closest.lat = GeoMath.mercatorToLat(newClosest[1]);
}
}
return closest;
}
}