diff options
author | mvglasow <michael -at- vonglasow.com> | 2020-08-06 22:32:19 +0200 |
---|---|---|
committer | mvglasow <michael -at- vonglasow.com> | 2020-08-06 22:32:19 +0200 |
commit | e72acd6493f5d074223372b8deebe67816725c56 (patch) | |
tree | bfb63fff92e6d37467e00c73c72814641dfc09eb | |
parent | e38d453bcf0c8a99b7f9fcee9dcfae8f3fa39054 (diff) | |
download | navit-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.java | 238 | ||||
-rw-r--r-- | navit/navit.c | 29 | ||||
-rw-r--r-- | navit/traffic.c | 11 | ||||
-rw-r--r-- | navit/traffic.h | 6 | ||||
-rw-r--r-- | navit/traffic/dummy/traffic_dummy.c | 1 | ||||
-rw-r--r-- | navit/traffic/null/traffic_null.c | 1 | ||||
-rw-r--r-- | navit/traffic/traff_android/traffic_traff_android.c | 16 | ||||
-rw-r--r-- | navit/xmlconfig.h | 2 |
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 { |