summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormvglasow <michael -at- vonglasow.com>2020-08-06 22:32:19 +0200
committermvglasow <michael -at- vonglasow.com>2020-08-06 22:32:19 +0200
commite72acd6493f5d074223372b8deebe67816725c56 (patch)
treebfb63fff92e6d37467e00c73c72814641dfc09eb
parente38d453bcf0c8a99b7f9fcee9dcfae8f3fa39054 (diff)
downloadnavit-e72acd6493f5d074223372b8deebe67816725c56.tar.gz
Add:traffic:Add basic TraFF 0.8 support
Signed-off-by: mvglasow <michael -at- vonglasow.com>
-rw-r--r--navit/android/src/org/navitproject/navit/NavitTraff.java238
-rw-r--r--navit/navit.c29
-rw-r--r--navit/traffic.c11
-rw-r--r--navit/traffic.h6
-rw-r--r--navit/traffic/dummy/traffic_dummy.c1
-rw-r--r--navit/traffic/null/traffic_null.c1
-rw-r--r--navit/traffic/traff_android/traffic_traff_android.c16
-rw-r--r--navit/xmlconfig.h2
8 files changed, 283 insertions, 21 deletions
diff --git a/navit/android/src/org/navitproject/navit/NavitTraff.java b/navit/android/src/org/navitproject/navit/NavitTraff.java
index c82d7d293..8ef738724 100644
--- a/navit/android/src/org/navitproject/navit/NavitTraff.java
+++ b/navit/android/src/org/navitproject/navit/NavitTraff.java
@@ -25,11 +25,18 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.IntentFilter.MalformedMimeTypeException;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
import android.util.Log;
+import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
+import java.util.Map;
/**
* The TraFF receiver implementation.
@@ -39,11 +46,35 @@ import java.util.List;
*/
public class NavitTraff extends BroadcastReceiver {
+ private static final String ACTION_TRAFF_GET_CAPABILITIES = "org.traffxml.traff.GET_CAPABILITIES";
private static final String ACTION_TRAFF_FEED = "org.traffxml.traff.FEED";
private static final String ACTION_TRAFF_POLL = "org.traffxml.traff.POLL";
+ private static final String ACTION_TRAFF_SUBSCRIBE = "org.traffxml.traff.SUBSCRIBE";
+ private static final String ACTION_TRAFF_SUBSCRIPTION_CHANGE = "org.traffxml.traff.SUBSCRIPTION_CHANGE";
+ private static final String ACTION_TRAFF_UNSUBSCRIBE = "org.traffxml.traff.UNSUBSCRIBE";
+ private static final String COLUMN_DATA = "data";
+ private static final String CONTENT_SCHEMA = "content";
+ private static final String EXTRA_CAPABILITIES = "capabilities";
private static final String EXTRA_FEED = "feed";
+ private static final String EXTRA_FILTER_LIST = "filter_list";
+ private static final String EXTRA_PACKAGE = "package";
+ private static final String EXTRA_SUBSCRIPTION_ID = "subscription_id";
+ private static final String MIME_TYPE_TRAFF = "vnd.android.cursor.dir/org.traffxml.message";
+ private static final int RESULT_OK = -1;
+ private static final int RESULT_INTERNAL_ERROR = 7;
+ private static final int RESULT_INVALID = 1;
+ private static final int RESULT_SUBSCRIPTION_REJECTED = 2;
+ private static final int RESULT_NOT_COVERED = 3;
+ private static final int RESULT_PARTIALLY_COVERED = 4;
+ private static final int RESULT_SUBSCRIPTION_UNKNOWN = 5;
+ private static final String TAG = "NavitTraff";
private final long mCbid;
+ private final Context context;
+
+ /** Active subscriptions (key is the subscription ID, value is the package ID) */
+ private Map<String, String> subscriptions = new HashMap<String, String>();
+
/**
* Forwards a newly received TraFF feed to the traffic module for processing.
*
@@ -65,39 +96,212 @@ public class NavitTraff extends BroadcastReceiver {
*/
NavitTraff(Context context, long cbid) {
this.mCbid = cbid;
+ this.context = context.getApplicationContext();
- /* An intent filter for TraFF events. */
- IntentFilter traffFilter = new IntentFilter();
- traffFilter.addAction(ACTION_TRAFF_FEED);
- traffFilter.addAction(ACTION_TRAFF_POLL);
+ /* An intent filter for TraFF 0.7 events. */
+ IntentFilter traffFilter07 = new IntentFilter();
+ traffFilter07.addAction(ACTION_TRAFF_FEED);
+
+ /* An intent filter for TraFF 0.8 events. */
+ IntentFilter traffFilter08 = new IntentFilter();
+ traffFilter08.addAction(ACTION_TRAFF_FEED);
+ traffFilter08.addDataScheme(CONTENT_SCHEMA);
+ try {
+ traffFilter08.addDataType(MIME_TYPE_TRAFF);
+ } catch (MalformedMimeTypeException e) {
+ // as long as the constant is a well-formed MIME type, this exception never gets thrown
+ e.printStackTrace();
+ }
- context.registerReceiver(this, traffFilter);
- /* TODO unregister receiver on exit */
+ this.context.registerReceiver(this, traffFilter07);
+ this.context.registerReceiver(this, traffFilter08);
/* Broadcast a poll intent */
Intent outIntent = new Intent(ACTION_TRAFF_POLL);
- PackageManager pm = context.getPackageManager();
- List<ResolveInfo> receivers = pm.queryBroadcastReceivers(outIntent, 0);
- if (receivers != null) {
- for (ResolveInfo receiver : receivers) {
+ PackageManager pm = this.context.getPackageManager();
+ List<ResolveInfo> receivers07 = pm.queryBroadcastReceivers(outIntent, 0);
+ /* receivers with TraFF 0.8 support */
+ List<ResolveInfo> receivers08 = pm.queryBroadcastReceivers(new Intent(ACTION_TRAFF_GET_CAPABILITIES), 0);
+ if (receivers07 != null) {
+ /* get receivers which support only TraFF 0.7 */
+ if (receivers08 != null)
+ receivers07.removeAll(receivers08);
+ for (ResolveInfo receiver : receivers07) {
ComponentName cn = new ComponentName(receiver.activityInfo.applicationInfo.packageName,
receiver.activityInfo.name);
outIntent = new Intent(ACTION_TRAFF_POLL);
outIntent.setComponent(cn);
- context.sendBroadcast(outIntent, Manifest.permission.ACCESS_COARSE_LOCATION);
+ this.context.sendBroadcast(outIntent, Manifest.permission.ACCESS_COARSE_LOCATION);
+ }
+ }
+ if (receivers08 != null) {
+ for (ResolveInfo receiver : receivers08) {
+ Bundle extras = new Bundle();
+ extras.putString(EXTRA_PACKAGE, context.getPackageName());
+ extras.putString(EXTRA_FILTER_LIST, "<filter_list><filter bbox=\"-90.0000 -180.0000 90.0000 180.0000\"/></filter_list>");
+ sendTraffIntent(context, ACTION_TRAFF_SUBSCRIBE, null, extras,
+ receiver.activityInfo.applicationInfo.packageName,
+ Manifest.permission.ACCESS_COARSE_LOCATION, this);
}
}
}
+ void close() {
+ for (Map.Entry<String, String> subscription : subscriptions.entrySet()) {
+ Bundle extras = new Bundle();
+ extras.putString(EXTRA_SUBSCRIPTION_ID, subscription.getKey());
+ sendTraffIntent(this.context, ACTION_TRAFF_UNSUBSCRIBE, null, extras, subscription.getValue(),
+ Manifest.permission.ACCESS_COARSE_LOCATION, this);
+ }
+ this.context.unregisterReceiver(this);
+ }
+
@Override
public void onReceive(Context context, Intent intent) {
- if ((intent != null) && (intent.getAction().equals(ACTION_TRAFF_FEED))) {
- String feed = intent.getStringExtra(EXTRA_FEED);
- if (feed == null) {
- Log.w(this.getClass().getSimpleName(), "empty feed, ignoring");
- } else {
- onFeedReceived(mCbid, feed);
+ if (intent != null) {
+ if (intent.getAction().equals(ACTION_TRAFF_FEED)) {
+ Uri uri = intent.getData();
+ if (uri != null) {
+ /* 0.8 feed */
+ String subscriptionId = intent.getStringExtra(EXTRA_SUBSCRIPTION_ID);
+ if (subscriptions.containsValue(subscriptionId))
+ fetchMessages(context, uri);
+ else {
+ /*
+ * If we don’t recognize the subscription, skip processing and unsubscribe.
+ * Note: if EXTRA_PACKAGE is not set, sendTraffIntent() sends the request to every
+ * manifest-declared receiver which handles the request.
+ */
+ Bundle extras = new Bundle();
+ extras.putString(EXTRA_SUBSCRIPTION_ID, subscriptionId);
+ sendTraffIntent(context, ACTION_TRAFF_UNSUBSCRIBE, null, extras,
+ intent.getStringExtra(EXTRA_PACKAGE),
+ Manifest.permission.ACCESS_COARSE_LOCATION, this);
+ }
+ } else {
+ /* 0.7 feed */
+ String packageName = intent.getStringExtra(EXTRA_PACKAGE);
+ /* if the feed comes from a TraFF 0.8+ source and we are subscribed, skip it */
+ // TODO what if we don’t have a subscription yet? First subscribe, then poll (still no guarantee)
+ if ((packageName != null) && subscriptions.containsValue(packageName))
+ return;
+ String feed = intent.getStringExtra(EXTRA_FEED);
+ if (feed == null) {
+ Log.w(this.getClass().getSimpleName(), "empty feed, ignoring");
+ } else {
+ onFeedReceived(mCbid, feed);
+ }
+ } // uri != null
+ } else if (intent.getAction().equals(ACTION_TRAFF_SUBSCRIBE)) {
+ if (this.getResultCode() != RESULT_OK)
+ return;
+ Bundle extras = this.getResultExtras(true);
+ String data = this.getResultData();
+ String packageName = extras.getString(EXTRA_PACKAGE);
+ String subscriptionId = extras.getString(EXTRA_SUBSCRIPTION_ID);
+ if ((data == null) || (packageName == null) || (subscriptionId == null))
+ return;
+ subscriptions.put(subscriptionId, packageName);
+ fetchMessages(context, Uri.parse(data));
+ } else if (intent.getAction().equals(ACTION_TRAFF_SUBSCRIPTION_CHANGE)) {
+ if (this.getResultCode() != RESULT_OK)
+ return;
+ Bundle extras = this.getResultExtras(true);
+ String data = this.getResultData();
+ String subscriptionId = extras.getString(EXTRA_SUBSCRIPTION_ID);
+ if ((data == null) || (subscriptionId == null) || (!subscriptions.containsKey(subscriptionId)))
+ return;
+ fetchMessages(context, Uri.parse(data));
+ } else if (intent.getAction().equals(ACTION_TRAFF_UNSUBSCRIBE)) {
+ /*
+ * If we ever unsubscribe for reasons other than that we are shutting down or got a feed for
+ * a subscription we don’t recognize, or if we start keeping a persistent list of
+ * subscriptions, we need to delete the subscription from our list. Until then, there is
+ * nothing to do here: either the subscription isn’t in the list, or we are about to shut
+ * down and the whole list is about to get discarded.
+ */
+ } // intent.getAction()
+ } // intent != null
+ }
+
+ /**
+ * @brief Fetches messages from a content provider.
+ *
+ * @param context
+ * @param uri The content provider URI
+ */
+ private void fetchMessages(Context context, Uri uri) {
+ try {
+ Cursor cursor = context.getContentResolver().query(uri, new String[] {COLUMN_DATA}, null, null, null);
+ if (cursor == null)
+ return;
+ if (cursor.getCount() < 1) {
+ cursor.close();
+ return;
}
+ StringBuilder builder = new StringBuilder("<feed>\n");
+ while (cursor.moveToNext())
+ builder.append(cursor.getString(cursor.getColumnIndex(COLUMN_DATA))).append("\n");
+ builder.append("</feed>");
+ cursor.close();
+ onFeedReceived(mCbid, builder.toString());
+ } catch (Exception e) {
+ Log.w(TAG, String.format("Unable to fetch messages from %s", uri.toString()), e);
+ e.printStackTrace();
}
}
+
+ /**
+ * @brief Sends a TraFF intent to a source.
+ *
+ * This encapsulates most of the low-level Android handling.
+ *
+ * If the recipient specified in {@code packageName} declares multiple receivers for the intent in its
+ * manifest, a separate intent will be delivered to each of them. The intent will not be delivered to
+ * receivers registered at runtime.
+ *
+ * All intents are sent as explicit ordered broadcasts. This means two things:
+ *
+ * Any app which declares a matching receiver in its manifest will be woken up to process the intent.
+ * This works even with certain Android 7 builds which restrict intent delivery to apps which are not
+ * currently running.
+ *
+ * It is safe for the recipient to unconditionally set result data. If the recipient does not set result
+ * data, the result will have a result code of {@link #RESULT_INTERNAL_ERROR}, no data and no extras.
+ *
+ * @param context The context
+ * @param action The intent action.
+ * @param data The intent data (for TraFF, this is the content provider URI), or null
+ * @param extras The extras for the intent
+ * @param packageName The package name for the intent recipient, or null to deliver the intent to all matching receivers
+ * @param receiverPermission A permission which the recipient must hold, or null if not required
+ * @param resultReceiver A BroadcastReceiver which will receive the result for the intent
+ */
+ /* From traff-consumer-android, by the same author and re-licensed under GPL2 for Navit */
+ public static void sendTraffIntent(Context context, String action, Uri data, Bundle extras, String packageName,
+ String receiverPermission, BroadcastReceiver resultReceiver) {
+ Intent outIntent = new Intent(action);
+ PackageManager pm = context.getPackageManager();
+ List<ResolveInfo> receivers = pm.queryBroadcastReceivers(outIntent, 0);
+ if (receivers != null)
+ for (ResolveInfo receiver : receivers) {
+ if ((packageName != null) && !packageName.equals(receiver.activityInfo.applicationInfo.packageName))
+ continue;
+ ComponentName cn = new ComponentName(receiver.activityInfo.applicationInfo.packageName,
+ receiver.activityInfo.name);
+ outIntent = new Intent(action);
+ if (data != null)
+ outIntent.setData(data);
+ if (extras != null)
+ outIntent.putExtras(extras);
+ outIntent.setComponent(cn);
+ context.sendOrderedBroadcast (outIntent,
+ receiverPermission,
+ resultReceiver,
+ null, // scheduler,
+ RESULT_INTERNAL_ERROR, // initialCode,
+ null, // initialData,
+ null);
+ }
+ }
}
diff --git a/navit/navit.c b/navit/navit.c
index 50d639896..d87f1c2ae 100644
--- a/navit/navit.c
+++ b/navit/navit.c
@@ -3685,7 +3685,36 @@ int navit_get_blocked(struct navit *this_) {
void navit_destroy(struct navit *this_) {
dbg(lvl_debug,"enter %p",this_);
+ GList *mapsets;
+ struct map * map;
+ struct attr attr;
graphics_draw_cancel(this_->gra, this_->displaylist);
+
+ mapsets = this_->mapsets;
+ while (mapsets) {
+ GList *maps = NULL;
+ struct mapset_handle *msh;
+ msh = mapset_open(mapsets->data);
+ while (msh && (map = mapset_next(msh, 0))) {
+ /* Add traffic map (identified by the `attr_traffic` attribute) to list of maps to remove */
+ if (map_get_attr(map, attr_traffic, &attr, NULL))
+ maps = g_list_append(maps, map);
+ }
+ mapset_close(msh);
+
+ /* Remove traffic maps, if any */
+ while (maps) {
+ attr.type = attr_map;
+ attr.u.map = maps->data;
+ mapset_remove_attr(this_->mapsets, &attr);
+ attr_free_content(&attr);
+ maps = g_list_next(maps);
+ }
+ if (maps)
+ g_list_free(maps);
+ mapsets = g_list_next(mapsets);
+ }
+
callback_list_call_attr_1(this_->attr_cbl, attr_destroy, this_);
attr_list_free(this_->attrs);
diff --git a/navit/traffic.c b/navit/traffic.c
index 9705abbdd..9cf22a371 100644
--- a/navit/traffic.c
+++ b/navit/traffic.c
@@ -4626,7 +4626,6 @@ static struct traffic * traffic_new(struct attr *parent, struct attr **attrs) {
navit_object_destroy((struct navit_object *) this_);
return NULL;
}
- navit_object_ref((struct navit_object *) this_);
dbg(lvl_debug,"return %p", this_);
// TODO do this once and cycle through all plugins
@@ -5800,7 +5799,6 @@ struct map * traffic_get_map(struct traffic *this_) {
attrs[4] = NULL;
this_->shared->map = map_new(NULL, attrs);
- navit_object_ref((struct navit_object *) this_->shared->map);
/* populate map with previously stored messages */
filename = g_strjoin(NULL, navit_get_user_data_directory(TRUE), "/traffic.xml", NULL);
@@ -5938,6 +5936,13 @@ void traffic_set_route(struct traffic *this_, struct route *rt) {
this_->shared->rt = rt;
}
+void traffic_destroy(struct traffic *this_) {
+ if (this_->meth.destroy)
+ this_->meth.destroy(this_->priv);
+ attr_list_free(this_->attrs);
+ g_free(this_);
+}
+
struct object_func traffic_func = {
attr_traffic,
(object_func_new)traffic_new,
@@ -5948,7 +5953,7 @@ struct object_func traffic_func = {
(object_func_add_attr)navit_object_add_attr,
(object_func_remove_attr)navit_object_remove_attr,
(object_func_init)NULL,
- (object_func_destroy)navit_object_destroy,
+ (object_func_destroy)traffic_destroy,
(object_func_dup)NULL,
(object_func_ref)navit_object_ref,
(object_func_unref)navit_object_unref,
diff --git a/navit/traffic.h b/navit/traffic.h
index bf0ca907e..126cbb164 100644
--- a/navit/traffic.h
+++ b/navit/traffic.h
@@ -239,6 +239,7 @@ struct traffic_message_priv;
*/
struct traffic_methods {
struct traffic_message **(* get_messages)(struct traffic_priv * this_); /**< Retrieves new messages from the traffic plugin */
+ void (*destroy)(struct traffic_priv * this_); /**< Destructor for the traffic plugin */
};
/**
@@ -989,6 +990,11 @@ void traffic_set_mapset(struct traffic *this_, struct mapset *ms);
*/
void traffic_set_route(struct traffic *this_, struct route *rt);
+/**
+ * @brief Destructor.
+ */
+void traffic_destroy(struct traffic *this_);
+
/* end of prototypes */
#ifdef __cplusplus
}
diff --git a/navit/traffic/dummy/traffic_dummy.c b/navit/traffic/dummy/traffic_dummy.c
index b838752dc..2ab4073d4 100644
--- a/navit/traffic/dummy/traffic_dummy.c
+++ b/navit/traffic/dummy/traffic_dummy.c
@@ -154,6 +154,7 @@ struct traffic_message ** traffic_dummy_get_messages(struct traffic_priv * this_
*/
static struct traffic_methods traffic_dummy_meth = {
traffic_dummy_get_messages,
+ NULL,
};
/**
diff --git a/navit/traffic/null/traffic_null.c b/navit/traffic/null/traffic_null.c
index 94546a666..02fc461c6 100644
--- a/navit/traffic/null/traffic_null.c
+++ b/navit/traffic/null/traffic_null.c
@@ -65,6 +65,7 @@ struct traffic_message ** traffic_null_get_messages(struct traffic_priv * this_)
*/
static struct traffic_methods traffic_null_meth = {
traffic_null_get_messages,
+ NULL,
};
/**
diff --git a/navit/traffic/traff_android/traffic_traff_android.c b/navit/traffic/traff_android/traffic_traff_android.c
index 266f51a0c..788dd3708 100644
--- a/navit/traffic/traff_android/traffic_traff_android.c
+++ b/navit/traffic/traff_android/traffic_traff_android.c
@@ -54,9 +54,24 @@ struct traffic_priv {
jobject NavitTraff; /**< An instance of `NavitTraff` */
};
+void traffic_traff_android_destroy(struct traffic_priv * this_);
struct traffic_message ** traffic_traff_android_get_messages(struct traffic_priv * this_);
/**
+ * @brief Destructor.
+ */
+void traffic_traff_android_destroy(struct traffic_priv * this_) {
+ jmethodID cid;
+
+ cid = (*jnienv)->GetMethodID(jnienv, this_->NavitTraffClass, "close", "()V");
+ if (cid == NULL) {
+ dbg(lvl_error,"no method found");
+ return; /* exception thrown */
+ }
+ (*jnienv)->CallVoidMethod(jnienv, this_->NavitTraff, cid);
+}
+
+/**
* @brief Returns an empty traffic report.
*
* @return Always `NULL`
@@ -70,6 +85,7 @@ struct traffic_message ** traffic_traff_android_get_messages(struct traffic_priv
*/
static struct traffic_methods traffic_traff_android_meth = {
traffic_traff_android_get_messages,
+ traffic_traff_android_destroy,
};
diff --git a/navit/xmlconfig.h b/navit/xmlconfig.h
index d5697d53c..bcd0ceec9 100644
--- a/navit/xmlconfig.h
+++ b/navit/xmlconfig.h
@@ -116,7 +116,7 @@ extern struct object_func map_func, mapset_func, navit_func, osd_func, tracking_
layout_func, roadprofile_func, vehicleprofile_func, layer_func, config_func, profile_option_func, script_func, log_func,
speech_func, navigation_func, route_func, traffic_func;
-#define HAS_OBJECT_FUNC(x) ((x) == attr_map || (x) == attr_mapset || (x) == attr_navit || (x) == attr_osd || (x) == attr_trackingo || (x) == attr_vehicle || (x) == attr_maps || (x) == attr_layout || (x) == attr_roadprofile || (x) == attr_vehicleprofile || (x) == attr_layer || (x) == attr_config || (x) == attr_profile_option || (x) == attr_script || (x) == attr_log || (x) == attr_speech || (x) == attr_navigation || (x) == attr_route)
+#define HAS_OBJECT_FUNC(x) ((x) == attr_map || (x) == attr_mapset || (x) == attr_navit || (x) == attr_osd || (x) == attr_trackingo || (x) == attr_vehicle || (x) == attr_maps || (x) == attr_layout || (x) == attr_roadprofile || (x) == attr_vehicleprofile || (x) == attr_layer || (x) == attr_config || (x) == attr_profile_option || (x) == attr_script || (x) == attr_log || (x) == attr_speech || (x) == attr_navigation || (x) == attr_route || (x) == attr_traffic)
#define NAVIT_OBJECT struct object_func *func; int refcount; struct attr **attrs;
struct navit_object {