summaryrefslogtreecommitdiff
path: root/navit/android
diff options
context:
space:
mode:
Diffstat (limited to 'navit/android')
-rw-r--r--navit/android/AndroidManifest.xml3
-rw-r--r--navit/android/build.gradle107
-rw-r--r--navit/android/res/drawable-hdpi/ic_launcher_background.pngbin0 -> 613 bytes
-rw-r--r--navit/android/res/drawable-hdpi/ic_launcher_foreground.pngbin0 -> 3358 bytes
-rw-r--r--navit/android/res/drawable-ldpi/ic_launcher_background.pngbin0 -> 377 bytes
-rw-r--r--navit/android/res/drawable-ldpi/ic_launcher_foreground.pngbin0 -> 1558 bytes
-rw-r--r--navit/android/res/drawable-mdpi/ic_launcher_background.pngbin0 -> 443 bytes
-rw-r--r--navit/android/res/drawable-mdpi/ic_launcher_foreground.pngbin0 -> 1976 bytes
-rw-r--r--navit/android/res/drawable-xhdpi/ic_launcher_background.pngbin0 -> 771 bytes
-rw-r--r--navit/android/res/drawable-xhdpi/ic_launcher_foreground.pngbin0 -> 4582 bytes
-rw-r--r--navit/android/res/drawable-xxhdpi/ic_launcher_background.pngbin0 -> 1242 bytes
-rw-r--r--navit/android/res/drawable-xxhdpi/ic_launcher_foreground.pngbin0 -> 7210 bytes
-rw-r--r--navit/android/res/mipmap-anydpi-v26/ic_launcher.xml5
-rw-r--r--navit/android/res/mipmap-hdpi/ic_launcher.pngbin0 -> 4562 bytes
-rw-r--r--navit/android/res/mipmap-ldpi/ic_launcher.pngbin0 -> 2092 bytes
-rw-r--r--navit/android/res/mipmap-mdpi/ic_launcher.pngbin0 -> 2868 bytes
-rw-r--r--navit/android/res/mipmap-xhdpi/ic_launcher.pngbin0 -> 6201 bytes
-rw-r--r--navit/android/res/mipmap-xxhdpi/ic_launcher.pngbin0 -> 9396 bytes
-rw-r--r--navit/android/src/org/navitproject/navit/Navit.java14
-rw-r--r--navit/android/src/org/navitproject/navit/NavitTraff.java361
20 files changed, 360 insertions, 130 deletions
diff --git a/navit/android/AndroidManifest.xml b/navit/android/AndroidManifest.xml
index 27c7c3359..b9e646097 100644
--- a/navit/android/AndroidManifest.xml
+++ b/navit/android/AndroidManifest.xml
@@ -8,6 +8,7 @@
<supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:resizeable="true" android:anyDensity="true"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
@@ -16,7 +17,7 @@
android:usesCleartextTraffic="true"
android:allowBackup="true"
android:fullBackupContent="@xml/navit_backup_rules"
- android:icon="@drawable/icon"
+ android:icon="@mipmap/ic_launcher"
android:name=".NavitAppConfig">
<activity android:name="Navit"
android:label="@string/app_name"
diff --git a/navit/android/build.gradle b/navit/android/build.gradle
deleted file mode 100644
index dc19f4f5d..000000000
--- a/navit/android/build.gradle
+++ /dev/null
@@ -1,107 +0,0 @@
-apply plugin: 'com.android.application'
-apply from: "$project.rootDir/gradle/scripts/git-scm-version.gradle"
-apply plugin: 'checkstyle'
-
-android {
- compileSdkVersion 29
- buildToolsVersion "29.0.2"
- signingConfigs {
- release {
- // We can leave these in environment variables
- storeFile file(System.getenv("KEYSTORE") ?: "/store")
- keyAlias System.getenv("KEY_ALIAS")
- storePassword System.getenv("STORE_PASS")
- keyPassword System.getenv("KEY_PASS")
- }
- }
- defaultConfig {
- applicationId "org.navitproject.navit"
- minSdkVersion 10
- targetSdkVersion 29
- versionCode gitVersionCode
- versionName gitVersionName
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
- ndk {
- abiFilters 'x86', 'x86_64', 'arm64-v8a', 'armeabi-v7a'
- }
- externalNativeBuild {
- cmake {
- arguments '-DDISABLE_CXX=y', '-DUSE_PLUGINS=n', '-DBUILD_MAPTOOL=n', '-DXSL_PROCESSING=n', '-DSAMPLE_MAP=n'
- }
- }
- }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- if(file(System.getenv("KEYSTORE") ?: "/store").exists()){
- signingConfig signingConfigs.release
- }
- }
- }
- lintOptions {
- disable 'UnusedResources'
- abortOnError true
- }
- sourceSets {
- main {
- manifest.srcFile "AndroidManifest.xml"
- java.srcDirs = ["src"]
- resources.srcDirs = ["src"]
- renderscript.srcDirs = ["src"]
- res.srcDirs = ["res"]
- assets.srcDirs = ["assets"]
- }
- }
- externalNativeBuild {
- cmake {
- path '../../CMakeLists.txt'
- }
- }
-
- task checkstyleMain(type: Checkstyle){
- source 'src'
- include '**/*.java'
- configFile = rootProject.file('checkstyle.xml')
- ignoreFailures = false
- showViolations = true
- // empty classpath
- classpath = files()
- reports {
- include ('**/*.java')
- xml.enabled = true
- html.enabled = true
- xml {
- destination file("checkstyle/checkstyleMain.xml")
- }
- html {
- destination file("checkstyle/checkstyleMain.html")
- }
- }
-
- checkstyle {
- toolVersion = '8.26'
- }
- }
- applicationVariants.all { variant ->
- // create tasks to generate Javadocs
- task("generate${variant.name.capitalize()}Javadoc", type: Javadoc) {
- source = android.sourceSets.main.java.srcDirs
- destinationDir = file("build/outputs/docs/javadoc/")
- title = rootProject.name
- options.memberLevel = JavadocMemberLevel.PACKAGE
- verbose = true
- failOnError false
- }
- }
-
-}
-dependencies {
- implementation fileTree(include: ['*.jar'], dir: 'libs')
- androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
- exclude group: 'com.android.support', module: 'support-annotations'
- })
- testImplementation 'junit:junit:4.12'
- implementation 'ch.acra:acra:4.9.2'
-// implementation 'com.android.support:support-v4:28.0.0'
-}
diff --git a/navit/android/res/drawable-hdpi/ic_launcher_background.png b/navit/android/res/drawable-hdpi/ic_launcher_background.png
new file mode 100644
index 000000000..4d8e2e760
--- /dev/null
+++ b/navit/android/res/drawable-hdpi/ic_launcher_background.png
Binary files differ
diff --git a/navit/android/res/drawable-hdpi/ic_launcher_foreground.png b/navit/android/res/drawable-hdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..316279d11
--- /dev/null
+++ b/navit/android/res/drawable-hdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/navit/android/res/drawable-ldpi/ic_launcher_background.png b/navit/android/res/drawable-ldpi/ic_launcher_background.png
new file mode 100644
index 000000000..8999f7aa1
--- /dev/null
+++ b/navit/android/res/drawable-ldpi/ic_launcher_background.png
Binary files differ
diff --git a/navit/android/res/drawable-ldpi/ic_launcher_foreground.png b/navit/android/res/drawable-ldpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..e06e327bb
--- /dev/null
+++ b/navit/android/res/drawable-ldpi/ic_launcher_foreground.png
Binary files differ
diff --git a/navit/android/res/drawable-mdpi/ic_launcher_background.png b/navit/android/res/drawable-mdpi/ic_launcher_background.png
new file mode 100644
index 000000000..f2e553f55
--- /dev/null
+++ b/navit/android/res/drawable-mdpi/ic_launcher_background.png
Binary files differ
diff --git a/navit/android/res/drawable-mdpi/ic_launcher_foreground.png b/navit/android/res/drawable-mdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..0196097fb
--- /dev/null
+++ b/navit/android/res/drawable-mdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/navit/android/res/drawable-xhdpi/ic_launcher_background.png b/navit/android/res/drawable-xhdpi/ic_launcher_background.png
new file mode 100644
index 000000000..58c6beb60
--- /dev/null
+++ b/navit/android/res/drawable-xhdpi/ic_launcher_background.png
Binary files differ
diff --git a/navit/android/res/drawable-xhdpi/ic_launcher_foreground.png b/navit/android/res/drawable-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..70148579d
--- /dev/null
+++ b/navit/android/res/drawable-xhdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/navit/android/res/drawable-xxhdpi/ic_launcher_background.png b/navit/android/res/drawable-xxhdpi/ic_launcher_background.png
new file mode 100644
index 000000000..df2ea36fb
--- /dev/null
+++ b/navit/android/res/drawable-xxhdpi/ic_launcher_background.png
Binary files differ
diff --git a/navit/android/res/drawable-xxhdpi/ic_launcher_foreground.png b/navit/android/res/drawable-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..ce3ccbc22
--- /dev/null
+++ b/navit/android/res/drawable-xxhdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/navit/android/res/mipmap-anydpi-v26/ic_launcher.xml b/navit/android/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 000000000..6b78462d6
--- /dev/null
+++ b/navit/android/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
diff --git a/navit/android/res/mipmap-hdpi/ic_launcher.png b/navit/android/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..ce18b5780
--- /dev/null
+++ b/navit/android/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/navit/android/res/mipmap-ldpi/ic_launcher.png b/navit/android/res/mipmap-ldpi/ic_launcher.png
new file mode 100644
index 000000000..183e2f829
--- /dev/null
+++ b/navit/android/res/mipmap-ldpi/ic_launcher.png
Binary files differ
diff --git a/navit/android/res/mipmap-mdpi/ic_launcher.png b/navit/android/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..1bde1888e
--- /dev/null
+++ b/navit/android/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/navit/android/res/mipmap-xhdpi/ic_launcher.png b/navit/android/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..041c61c03
--- /dev/null
+++ b/navit/android/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/navit/android/res/mipmap-xxhdpi/ic_launcher.png b/navit/android/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..ad89bd0b5
--- /dev/null
+++ b/navit/android/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/navit/android/src/org/navitproject/navit/Navit.java b/navit/android/src/org/navitproject/navit/Navit.java
index bacc15213..011acd9fe 100644
--- a/navit/android/src/org/navitproject/navit/Navit.java
+++ b/navit/android/src/org/navitproject/navit/Navit.java
@@ -248,10 +248,15 @@ public class Navit extends Activity {
private void verifyPermissions() {
if (ContextCompat.checkSelfPermission(this,
- Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
+ return;
+ } else if (ContextCompat.checkSelfPermission(this,
+ Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED) {
+ return;
+ } else {
Log.d(TAG,"ask for permission(s)");
ActivityCompat.requestPermissions(this, new String[] {
- Manifest.permission.ACCESS_FINE_LOCATION}, MY_PERMISSIONS_REQ_FINE_LOC);
+ Manifest.permission.ACCESS_BACKGROUND_LOCATION}, MY_PERMISSIONS_REQ_FINE_LOC);
}
}
@@ -728,7 +733,7 @@ public class Navit extends Activity {
if (resultCode == RESULT_OK) {
String newDir = data.getStringExtra(FileBrowserActivity.returnDirectoryParameter);
Log.d(TAG, "selected path= " + newDir);
- if (!newDir.contains("/navit")) {
+ if (!(newDir.contains("/navit") || newDir.contains("/org.navitproject.navit"))) {
newDir = newDir + "/navit/";
} else {
newDir = newDir + "/";
@@ -774,7 +779,8 @@ public class Navit extends Activity {
private void setMapLocation() {
Intent fileExploreIntent = new Intent(this,FileBrowserActivity.class);
fileExploreIntent
- .putExtra(FileBrowserActivity.startDirectoryParameter, "/mnt")
+ .putExtra(FileBrowserActivity.startDirectoryParameter,
+ getApplicationContext().getExternalFilesDir(null).toString())
.setAction(FileBrowserActivity.INTENT_ACTION_SELECT_DIR);
startActivityForResult(fileExploreIntent,NavitSelectStorage_id);
}
diff --git a/navit/android/src/org/navitproject/navit/NavitTraff.java b/navit/android/src/org/navitproject/navit/NavitTraff.java
index c82d7d293..fd499b70b 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,46 @@ 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_HEARTBEAT = "org.traffxml.traff.HEARTBEAT";
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[] ERROR_STRINGS = {
+ "unknown (0)",
+ "invalid request (1)",
+ "subscription rejected by the source (2)",
+ "requested area not covered (3)",
+ "requested area partially covered (4)",
+ "subscription ID not recognized by the source (5)",
+ "unknown (6)",
+ "source reported an internal error (7)"
+ };
+ 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 +107,322 @@ public class NavitTraff extends BroadcastReceiver {
*/
NavitTraff(Context context, long cbid) {
this.mCbid = cbid;
+ this.context = context.getApplicationContext();
+
+ /* An intent filter for TraFF 0.7 events. */
+ IntentFilter traffFilter07 = new IntentFilter();
+ traffFilter07.addAction(ACTION_TRAFF_FEED);
- /* 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.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 */
+ /* Broadcast a poll intent to all TraFF 0.7-only receivers */
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);
+ List<ResolveInfo> receivers08 = pm.queryBroadcastReceivers(new Intent(ACTION_TRAFF_GET_CAPABILITIES), 0);
+ if (receivers07 != null) {
+ /* get receivers which support only TraFF 0.7 and poll them */
+ 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);
+ }
+ }
+ }
+
+ 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);
+ }
+
+ void onFilterUpdate(String filterList) {
+ /* change existing subscriptions */
+ for (Map.Entry<String, String> entry : subscriptions.entrySet()) {
+ Log.d(TAG, String.format("changing subscription %s (%s)", entry.getKey(), entry.getValue()));
+ Bundle extras = new Bundle();
+ extras.putString(EXTRA_SUBSCRIPTION_ID, entry.getKey());
+ extras.putString(EXTRA_FILTER_LIST, filterList);
+ sendTraffIntent(context, ACTION_TRAFF_SUBSCRIPTION_CHANGE, null, extras,
+ entry.getValue(),
+ Manifest.permission.ACCESS_COARSE_LOCATION, this);
+ }
+
+ /* set up missing subscriptions */
+ PackageManager pm = this.context.getPackageManager();
+ List<ResolveInfo> receivers = pm.queryBroadcastReceivers(new Intent(ACTION_TRAFF_GET_CAPABILITIES), 0);
+ if (receivers != null) {
+ /* filter out receivers to which we are already subscribed */
+ Iterator<ResolveInfo> iter = receivers.iterator();
+ while (iter.hasNext()) {
+ ResolveInfo receiver = iter.next();
+ if (subscriptions.containsValue(receiver.activityInfo.applicationInfo.packageName))
+ iter.remove();
+ }
+
+ for (ResolveInfo receiver : receivers) {
+ Log.d(TAG, "subscribing to " + receiver.activityInfo.applicationInfo.packageName);
+ Bundle extras = new Bundle();
+ extras.putString(EXTRA_PACKAGE, context.getPackageName());
+ extras.putString(EXTRA_FILTER_LIST, filterList);
+ sendTraffIntent(context, ACTION_TRAFF_SUBSCRIBE, null, extras,
+ receiver.activityInfo.applicationInfo.packageName,
+ Manifest.permission.ACCESS_COARSE_LOCATION, 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.containsKey(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.
+ */
+ Log.d(TAG,
+ String.format("got a feed from %s for unknown subscription %s, URI %s; unsubscribing",
+ intent.getStringExtra(EXTRA_PACKAGE), subscriptionId, uri));
+ 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.
+ * As a side effect of the current implementation, if a “bilingual” TraFF 0.7/0.8
+ * source sends a broadcast feed before we have subscribed to it, we would process
+ * the whole feed first, and then subscribe to a subset of that data.
+ * If that turns out to be an issue, we would need to detect TraFF 0.8-capable
+ * sources and discard broadcast feeds from them.
+ */
+ 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) {
+ Bundle extras = this.getResultExtras(true);
+ if (extras != null)
+ Log.e(this.getClass().getSimpleName(), String.format("subscription to %s failed, %s",
+ extras.getString(EXTRA_PACKAGE), formatTraffError(this.getResultCode())));
+ else
+ Log.e(this.getClass().getSimpleName(), String.format("subscription failed, %s",
+ formatTraffError(this.getResultCode())));
+ return;
+ }
+ Bundle extras = this.getResultExtras(true);
+ String data = this.getResultData();
+ String packageName = extras.getString(EXTRA_PACKAGE);
+ String subscriptionId = extras.getString(EXTRA_SUBSCRIPTION_ID);
+ if (subscriptionId == null) {
+ Log.e(this.getClass().getSimpleName(),
+ String.format("subscription to %s failed: no subscription ID returned", packageName));
+ return;
+ } else if (packageName == null) {
+ Log.e(this.getClass().getSimpleName(), "subscription failed: no package name");
+ return;
+ } else if (data == null) {
+ Log.w(this.getClass().getSimpleName(),
+ String.format("subscription to %s successful (ID: %s) but no content URI was supplied. "
+ + "This is an issue with the source and may result in delayed message retrieval.",
+ packageName, subscriptionId));
+ subscriptions.put(subscriptionId, packageName);
+ return;
+ }
+ Log.d(TAG, "subscription to " + packageName + " successful, ID: " + subscriptionId);
+ subscriptions.put(subscriptionId, packageName);
+ fetchMessages(context, Uri.parse(data));
+ } else if (intent.getAction().equals(ACTION_TRAFF_SUBSCRIPTION_CHANGE)) {
+ if (this.getResultCode() != RESULT_OK) {
+ Bundle extras = this.getResultExtras(true);
+ if (extras != null)
+ Log.e(this.getClass().getSimpleName(),
+ String.format("subscription change for %s failed: %s",
+ extras.getString(EXTRA_SUBSCRIPTION_ID),
+ formatTraffError(this.getResultCode())));
+ else
+ Log.e(this.getClass().getSimpleName(),
+ String.format("subscription change failed: %s",
+ formatTraffError(this.getResultCode())));
+ return;
+ }
+ Bundle extras = intent.getExtras();
+ String data = this.getResultData();
+ String subscriptionId = extras.getString(EXTRA_SUBSCRIPTION_ID);
+ if (subscriptionId == null) {
+ Log.w(this.getClass().getSimpleName(),
+ "subscription change successful but the source did not specify the subscription ID. "
+ + "This is an issue with the source and may result in delayed message retrieval. "
+ + "URI: " + data);
+ return;
+ } else if (data == null) {
+ Log.w(this.getClass().getSimpleName(),
+ String.format("subscription change for %s successful but no content URI was supplied. "
+ + "This is an issue with the source and may result in delayed message retrieval.",
+ subscriptionId));
+ return;
+ } else if (!subscriptions.containsKey(subscriptionId)) {
+ Log.e(this.getClass().getSimpleName(),
+ "subscription change failed: unknown subscription ID " + subscriptionId);
+ return;
+ }
+ Log.d(TAG, "subscription change for " + subscriptionId + " successful");
+ 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.
+ */
+ } else if (intent.getAction().equals(ACTION_TRAFF_HEARTBEAT)) {
+ String subscriptionId = intent.getStringExtra(EXTRA_SUBSCRIPTION_ID);
+ if (subscriptions.containsKey(subscriptionId)) {
+ Log.d(TAG,
+ String.format("got a heartbeat from %s for subscription %s; sending result",
+ intent.getStringExtra(EXTRA_PACKAGE), subscriptionId));
+ this.setResult(RESULT_OK, null, null);
+ } else {
+ /*
+ * If we don’t recognize the subscription, skip reply and unsubscribe.
+ * Note: if EXTRA_PACKAGE is not set, sendTraffIntent() sends the request to every
+ * manifest-declared receiver which handles the request.
+ */
+ Log.d(TAG,
+ String.format("got a heartbeat from %s for unknown subscription %s; unsubscribing",
+ intent.getStringExtra(EXTRA_PACKAGE), subscriptionId));
+ 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);
+ }
+ } // intent.getAction()
+ } // intent != null
+ }
+
+ /**
+ * Fetches messages from a content provider.
+ *
+ * @param context The context to use for the content resolver
+ * @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();
}
}
+
+ /**
+ * Sends a TraFF intent to a source. This encapsulates most of the low-level Android handling.
+ *
+ * <p>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.
+ *
+ * <p>All intents are sent as explicit ordered broadcasts. This means two things:
+ *
+ * <p>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.
+ *
+ * <p>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 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);
+ }
+ }
+
+ private static String formatTraffError(int code) {
+ if ((code < 0) || (code >= ERROR_STRINGS.length))
+ return String.format("unknown (%d)", code);
+ else
+ return ERROR_STRINGS[code];
+ }
}