diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7cd7009..8cf2fbd 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,107 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/backend/FiveTStopsFetcher.java b/src/it/reyboz/bustorino/backend/FiveTStopsFetcher.java
index e5bc001..913941e 100644
--- a/src/it/reyboz/bustorino/backend/FiveTStopsFetcher.java
+++ b/src/it/reyboz/bustorino/backend/FiveTStopsFetcher.java
@@ -1,119 +1,119 @@
/*
BusTO (backend components)
Copyright (C) 2016 Ludovico Pavesi
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.backend;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/**
* Once was asynchronous BusStop[] fetcher from a query, code mostly taken from
* AsyncWgetBusStopSuggestions (by Valerio Bozzolan)
*
* @see FiveTScraperFetcher
*/
public class FiveTStopsFetcher implements StopsFinderByName {
@Override
public List FindByName(String name, AtomicReference res) {
// API apparently limited to 20 results
ArrayList busStops = new ArrayList<>(20);
String stopID;
String stopName;
String stopLocation;
//Stop busStop;
- if(name.length() < 3) {
+ if(name.length() < 2) { //some stops are shorter than 3 chars.. "PO" is an example
res.set(Result.QUERY_TOO_SHORT);
return busStops;
}
String responseInDOMFormatBecause5THaveAbsolutelyNoIdeaWhatJSONWas;
URL u;
try {
u = new URL("http://www.5t.torino.it/5t/trasporto/stop-lookup.jsp?action=search&stopShortName=" + URLEncoder.encode(name, "utf-8"));
} catch(Exception e) {
res.set(Result.PARSER_ERROR);
return busStops;
}
responseInDOMFormatBecause5THaveAbsolutelyNoIdeaWhatJSONWas = networkTools.getDOM(u, res);
if (responseInDOMFormatBecause5THaveAbsolutelyNoIdeaWhatJSONWas == null) {
// result already set in getDOM()
return busStops;
}
Document doc = Jsoup.parse(responseInDOMFormatBecause5THaveAbsolutelyNoIdeaWhatJSONWas);
// Find bus stops
Elements lis = doc.getElementsByTag("li");
for(Element li : lis) {
Elements spans = li.getElementsByTag("span");
// busStopID
try {
stopID = FiveTNormalizer.FiveTNormalizeRoute(spans.eq(0).text());
} catch(Exception e) {
//Log.e("Suggestions", "Empty busStopID");
stopID = "";
}
// busStopName
try {
stopName = spans.eq(1).text();
} catch(Exception e) {
//Log.e("Suggestions", "Empty busStopName");
stopName = "";
}
// busStopLocation
try {
stopLocation = (spans.eq(2).text());
} catch(Exception e) {
//Log.e("Suggestions", "Empty busStopLocation");
stopLocation = null;
}
/*
if(stopLocation == null || stopLocation.length() == 0) {
stopLocation = db.getLocationFromID(stopID);
}*/
busStops.add(new Stop(stopName, stopID, stopLocation, null, null));
}
if(busStops.size() == 0) {
res.set(Result.EMPTY_RESULT_SET);
} else {
res.set(Result.OK);
}
Collections.sort(busStops);
// TODO: remove duplicates? (see GTTStopsFetcher)
return busStops;
}
}
diff --git a/src/it/reyboz/bustorino/backend/GTTStopsFetcher.java b/src/it/reyboz/bustorino/backend/GTTStopsFetcher.java
index 1b2df98..29dfc5f 100644
--- a/src/it/reyboz/bustorino/backend/GTTStopsFetcher.java
+++ b/src/it/reyboz/bustorino/backend/GTTStopsFetcher.java
@@ -1,191 +1,191 @@
/*
BusTO (backend components)
Copyright (C) 2016 Ludovico Pavesi
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.backend;
import androidx.annotation.NonNull;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
public class GTTStopsFetcher implements StopsFinderByName {
@Override @NonNull
public List FindByName(String name, AtomicReference res) {
URL url;
// sorting an ArrayList should be faster than a LinkedList and the API is limited to 15 results
List s = new ArrayList<>(15);
List s2 = new ArrayList<>(15);
String fullname;
String content;
String bacino;
String localita;
Route.Type type;
JSONArray json;
int howManyStops, i;
JSONObject thisstop;
- if(name.length() < 3) {
+ if(name.length() < 2) {
res.set(Result.QUERY_TOO_SHORT);
return s;
}
try {
url = new URL("https://www.gtt.to.it/cms/index.php?option=com_gtt&view=palinejson&term=" + URLEncoder.encode(name, "utf-8"));
} catch (Exception e) {
res.set(Result.PARSER_ERROR);
return s;
}
content = networkTools.queryURL(url, res);
if(content == null) {
return s;
}
try {
json = new JSONArray(content);
} catch(JSONException e) {
if(content.contains("[]")) {
// when no results are found, server returns a PHP Warning and an empty array. In case they fix the warning, we're looking for the array.
res.set(Result.EMPTY_RESULT_SET);
} else {
res.set(Result.PARSER_ERROR);
}
return s;
}
howManyStops = json.length();
if(howManyStops == 0) {
res.set(Result.EMPTY_RESULT_SET);
return s;
}
try {
for(i = 0; i < howManyStops; i++) {
thisstop = json.getJSONObject(i);
fullname = thisstop.getString("data");
String ID = thisstop.getString("value");
try {
localita = thisstop.getString("localita");
if(localita.equals("[MISSING]")) {
localita = null;
}
} catch(JSONException e) {
localita = null;
}
/*
if(localita == null || localita.length() == 0) {
localita = db.getLocationFromID(ID);
}
//TODO: find località by ContentProvider
*/
try {
bacino = thisstop.getString("bacino");
} catch (JSONException ignored) {
bacino = "U";
}
if(fullname.startsWith("Metro ")) {
type = Route.Type.METRO;
} else if(fullname.length() >= 6 && fullname.startsWith("S00")) {
type = Route.Type.RAILWAY;
} else if(fullname.startsWith("ST")) {
type = Route.Type.RAILWAY;
} else {
type = FiveTNormalizer.decodeType("", bacino);
}
//TODO: refactor using content provider
s.add(new Stop(fullname, ID, localita, type,null));
}
} catch (JSONException e) {
res.set(Result.PARSER_ERROR);
return s;
}
if(s.size() < 1) {
// shouldn't happen but prevents the next part from catching fire
res.set(Result.EMPTY_RESULT_SET);
return s;
}
Collections.sort(s);
// the next loop won't work with less than 2 items
if(s.size() < 2) {
res.set(Result.OK);
return s;
}
/* There are some duplicate stops returned by this API.
* Long distance buses have stop IDs with 5 digits. Always. They are zero-padded if there
* aren't enough. E.g. stop 631 becomes 00631.
*
* Unfortunately you can't use padded stops to query any API.
* Fortunately, unpadded stops return both normal and long distance bus timetables.
* FiveTNormalizer is already removing padding (there may be some padded stops for which the
* API doesn't return an unpadded equivalent), here we'll remove duplicates by skipping
* padded stops, which also never have a location.
*
* I had to draw a finite state machine on a piece of paper to understand how to implement
* this loop.
*/
for(i = 1; i < howManyStops; ) {
Stop current = s.get(i);
Stop previous = s.get(i-1);
// same stop: let's see which one to keep...
if(current.ID.equals(previous.ID)) {
if(previous.location == null) {
// previous one is useless: discard it, increment
i++;
} else if(current.location == null) {
// this one is useless: add previous and skip one
s2.add(previous);
i += 2;
} else {
// they aren't really identical: to err on the side of caution, keep them both.
s2.add(previous);
i++;
}
} else {
// different: add previous, increment
s2.add(previous);
i++;
}
}
// unless the last one was garbage (i would be howManyStops+1 in that case), add it
if(i == howManyStops) {
s2.add(s.get(i-1));
}
res.set(Result.OK);
return s2;
}
}
diff --git a/src/it/reyboz/bustorino/backend/utils.java b/src/it/reyboz/bustorino/backend/utils.java
index 2d615b9..06b17a0 100644
--- a/src/it/reyboz/bustorino/backend/utils.java
+++ b/src/it/reyboz/bustorino/backend/utils.java
@@ -1,183 +1,185 @@
package it.reyboz.bustorino.backend;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import androidx.annotation.Nullable;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import it.reyboz.bustorino.backend.mato.MatoAPIFetcher;
public abstract class utils {
private static final double EarthRadius = 6371e3;
public static Double measuredistanceBetween(double lat1,double long1,double lat2,double long2){
final double phi1 = Math.toRadians(lat1);
final double phi2 = Math.toRadians(lat2);
final double deltaPhi = Math.toRadians(lat2-lat1);
final double deltaTheta = Math.toRadians(long2-long1);
final double a = Math.sin(deltaPhi/2)*Math.sin(deltaPhi/2)+
Math.cos(phi1)*Math.cos(phi2)*Math.sin(deltaTheta/2)*Math.sin(deltaTheta/2);
final double c = 2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a));
return Math.abs(EarthRadius*c);
}
public static Double angleRawDifferenceFromMeters(double distanceInMeters){
return Math.toDegrees(distanceInMeters/EarthRadius);
}
/*
public static int convertDipToPixels(Context con,float dips)
{
return (int) (dips * con.getResources().getDisplayMetrics().density + 0.5f);
}
*/
public static float convertDipToPixels(Context con, float dp){
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,con.getResources().getDisplayMetrics());
}
/*
public static int calculateNumColumnsFromSize(View containerView, int pixelsize){
int width = containerView.getWidth();
float ncols = ((float)width)/pixelsize;
return (int) Math.floor(ncols);
}
*/
/**
* Check if there is an internet connection
* @param con context object to get the system service
* @return true if we are
*/
public static boolean isConnected(Context con) {
ConnectivityManager connMgr = (ConnectivityManager) con.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
return networkInfo != null && networkInfo.isConnected();
}
///////////////////// INTENT HELPER ////////////////////////////////////////////////////////////
/**
* Try to extract the bus stop ID from a URi
*
* @param uri The URL
* @return bus stop ID or null
*/
public static String getBusStopIDFromUri(Uri uri) {
String busStopID;
// everithing catches fire when passing null to a switch.
String host = uri.getHost();
if (host == null) {
Log.e("ActivityMain", "Not an URL: " + uri);
return null;
}
switch (host) {
case "m.gtt.to.it":
// http://m.gtt.to.it/m/it/arrivi.jsp?n=1254
busStopID = uri.getQueryParameter("n");
if (busStopID == null) {
Log.e("ActivityMain", "Expected ?n from: " + uri);
}
break;
case "www.gtt.to.it":
case "gtt.to.it":
// http://www.gtt.to.it/cms/percorari/arrivi?palina=1254
busStopID = uri.getQueryParameter("palina");
if (busStopID == null) {
Log.e("ActivityMain", "Expected ?palina from: " + uri);
}
break;
default:
Log.e("ActivityMain", "Unexpected intent URL: " + uri);
busStopID = null;
}
return busStopID;
}
public static String toTitleCase(String givenString, boolean lowercaseRest) {
String[] arr = givenString.split(" ");
StringBuilder sb = new StringBuilder();
//Log.d("BusTO chars", "String parsing: "+givenString+" in array: "+ Arrays.toString(arr));
for (int i = 0; i < arr.length; i++) {
if (arr[i].length() > 1) {
sb.append(Character.toUpperCase(arr[i].charAt(0)));
if (lowercaseRest)
sb.append(arr[i].substring(1).toLowerCase(Locale.ROOT));
else
sb.append(arr[i].substring(1));
sb.append(" ");
}
else sb.append(arr[i]);
}
return sb.toString().trim();
}
/**
* Open an URL in the default browser.
*
* @param url URL
*/
public static void openIceweasel(String url, Context context) {
Intent browserIntent1 = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
if (browserIntent1.resolveActivity(context.getPackageManager()) != null) {
//check we have an activity ready to receive intents (otherwise, there will be a crash)
context.startActivity(browserIntent1);
+ } else{
+ Log.e("BusTO","openIceweasel can't find a browser");
}
}
public static ArrivalsFetcher[] getDefaultArrivalsFetchers(){
return new ArrivalsFetcher[]{ new MatoAPIFetcher(),
new FiveTAPIFetcher(), new GTTJSONFetcher(), new FiveTScraperFetcher()};
}
/**
* Print the first i lines of the the trace of an exception
* https://stackoverflow.com/questions/21706722/fetch-only-first-n-lines-of-a-stack-trace
*/
/*
public static String traceCaller(Exception ex, int i) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
StringBuilder sb = new StringBuilder();
ex.printStackTrace(pw);
String ss = sw.toString();
String[] splitted = ss.split("\n");
sb.append("\n");
if(splitted.length > 2 + i) {
for(int x = 2; x < i+2; x++) {
sb.append(splitted[x].trim());
sb.append("\n");
}
return sb.toString();
}
return "Trace too Short.";
}
*/
public static String joinList(@Nullable List dat, String separator){
StringBuilder sb = new StringBuilder();
if(dat==null || dat.size()==0)
return "";
else if(dat.size()==1)
return dat.get(0);
sb.append(dat.get(0));
for (int i=1; i requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback