diff --git a/src/it/reyboz/bustorino/adapters/PalinaAdapter.java b/src/it/reyboz/bustorino/adapters/PalinaAdapter.java
index 88d8ac9..ff8a416 100644
--- a/src/it/reyboz/bustorino/adapters/PalinaAdapter.java
+++ b/src/it/reyboz/bustorino/adapters/PalinaAdapter.java
@@ -1,146 +1,147 @@
/*
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.adapters;
import android.content.Context;
import androidx.annotation.NonNull;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import java.util.List;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.Palina;
import it.reyboz.bustorino.backend.Passaggio;
import it.reyboz.bustorino.backend.Route;
+import it.reyboz.bustorino.backend.utils;
/**
* This once was a ListView Adapter for BusLine[].
*
* Thanks to Framentos developers for the guide:
* http://www.framentos.com/en/android-tutorial/2012/07/16/listview-in-android-using-custom-listadapter-and-viewcache/#
*
* @author Valerio Bozzolan
* @author Ludovico Pavesi
*/
public class PalinaAdapter extends ArrayAdapter {
private LayoutInflater li;
private static int row_layout = R.layout.entry_bus_line_passage;
private static final int metroBg = R.drawable.route_background_metro;
private static final int busBg = R.drawable.route_background_bus;
private static final int extraurbanoBg = R.drawable.route_background_bus_long_distance;
private static final int busIcon = R.drawable.bus;
private static final int trainIcon = R.drawable.subway;
private static final int tramIcon = R.drawable.tram;
//private static final int cityIcon = R.drawable.city;
// hey look, a pattern!
private static class ViewHolder {
TextView rowStopIcon;
TextView rowRouteDestination;
TextView rowRouteTimetable;
}
public PalinaAdapter(Context context, Palina p) {
super(context, row_layout, p.queryAllRoutes());
li = LayoutInflater.from(context);
}
/**
* Some parts taken from the AdapterBusLines class.
* Some parts inspired by these enlightening tutorials:
* http://www.simplesoft.it/android/guida-agli-adapter-e-le-listview-in-android.html
* https://www.codeofaninja.com/2013/09/android-viewholder-pattern-example.html
* And some other bits and bobs TIRATI FUORI DAL NULLA CON L'INTUIZIONE INTELLETTUALE PERCHÉ
* SEMBRA CHE NESSUNO ABBIA LA MINIMA IDEA DI COME FUNZIONA UN ADAPTER SU ANDROID.
*/
@NonNull
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
ViewHolder vh;
if(convertView == null) {
// INFLATE!
// setting a parent here is not supported and causes a fatal exception, apparently.
convertView = li.inflate(row_layout, null);
// STORE TEXTVIEWS!
vh = new ViewHolder();
vh.rowStopIcon = (TextView) convertView.findViewById(R.id.routeID);
vh.rowRouteDestination = (TextView) convertView.findViewById(R.id.routeDestination);
vh.rowRouteTimetable = (TextView) convertView.findViewById(R.id.routesThatStopHere);
// STORE VIEWHOLDER IN\ON\OVER\UNDER\ABOVE\BESIDE THE VIEW!
convertView.setTag(vh);
} else {
// RECOVER THIS STUFF!
vh = (ViewHolder) convertView.getTag();
}
Route route = getItem(position);
vh.rowStopIcon.setText(route.getNameForDisplay());
if(route.destinazione==null || route.destinazione.length() == 0) {
vh.rowRouteDestination.setVisibility(View.GONE);
} else {
// View Holder Pattern(R) renders each element from a previous one: if the other one had an invisible rowRouteDestination, we need to make it visible.
vh.rowRouteDestination.setVisibility(View.VISIBLE);
vh.rowRouteDestination.setText(route.destinazione);
}
switch (route.type) {
//UNKNOWN = BUS for the moment
case UNKNOWN:
case BUS:
default:
// convertView could contain another background, reset it
vh.rowStopIcon.setBackgroundResource(busBg);
vh.rowRouteDestination.setCompoundDrawablesWithIntrinsicBounds(busIcon, 0, 0, 0);
break;
case LONG_DISTANCE_BUS:
vh.rowStopIcon.setBackgroundResource(extraurbanoBg);
vh.rowRouteDestination.setCompoundDrawablesWithIntrinsicBounds(busIcon, 0, 0, 0);
break;
case METRO:
vh.rowStopIcon.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
vh.rowStopIcon.setBackgroundResource(metroBg);
vh.rowRouteDestination.setCompoundDrawablesWithIntrinsicBounds(trainIcon, 0, 0, 0);
break;
case RAILWAY:
vh.rowStopIcon.setBackgroundResource(busBg);
vh.rowRouteDestination.setCompoundDrawablesWithIntrinsicBounds(trainIcon, 0, 0, 0);
break;
case TRAM: // never used but whatever.
vh.rowStopIcon.setBackgroundResource(busBg);
vh.rowRouteDestination.setCompoundDrawablesWithIntrinsicBounds(tramIcon, 0, 0, 0);
break;
}
List passaggi = route.passaggi;
if(passaggi.size() == 0) {
vh.rowRouteTimetable.setText(R.string.no_passages);
} else {
vh.rowRouteTimetable.setText(route.getPassaggiToString());
}
return convertView;
}
}
diff --git a/src/it/reyboz/bustorino/backend/ArrivalsFetcher.java b/src/it/reyboz/bustorino/backend/ArrivalsFetcher.java
index 9844d47..89d63c1 100644
--- a/src/it/reyboz/bustorino/backend/ArrivalsFetcher.java
+++ b/src/it/reyboz/bustorino/backend/ArrivalsFetcher.java
@@ -1,58 +1,56 @@
/*
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;
// "arrivals" è più usato di "transit" o simili, e chi sono io per mettermi a dibattere con gli inglesi?
-import java.util.ArrayList;
-import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
public interface ArrivalsFetcher extends Fetcher {
// /**
// * Reads arrival times from a (hopefully) real-time source, e.g. the GTT website.
// * Don't call this in UI thread!
// *
// * @param stopID stop ID, in normalized form.
// * @param routeID route ID, in normalized form.
// * @param res result code (will be set by this method)
// * @return arrival times
// * @see it.reyboz.bustorino.backend.Fetcher.result
// * @see FiveTNormalizer
// */
// Palina ReadArrivalTimesRoute(String stopID, String routeID, AtomicReference res);
/**
* Reads arrival times from a (hopefully) real-time source, e.g. the GTT website.
* Don't call this in UI thread!
*
* @param stopID stop ID, in normalized form.
* @param res result code (will be set by this method)
* @return arrival times
- * @see it.reyboz.bustorino.backend.Fetcher.result
+ * @see Result
* @see FiveTNormalizer
*/
- Palina ReadArrivalTimesAll(String stopID, AtomicReference res);
+ Palina ReadArrivalTimesAll(String stopID, AtomicReference res);
/**
* Get the determined source for the Fetcher
* @return the source of the arrival times
*/
Passaggio.Source getSourceForFetcher();
}
diff --git a/src/it/reyboz/bustorino/backend/Fetcher.java b/src/it/reyboz/bustorino/backend/Fetcher.java
index 3477457..8f1ef20 100644
--- a/src/it/reyboz/bustorino/backend/Fetcher.java
+++ b/src/it/reyboz/bustorino/backend/Fetcher.java
@@ -1,36 +1,36 @@
/*
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;
public interface Fetcher {
/**
* Status codes.
*
* OK: got a response, parsed correctly, obtained some data
* CLIENT_OFFLINE: can't connect to the internet
* SERVER_ERROR: the server replied anything other than HTTP 200, basically
* for 404 special constant (see @FiveTAPIFetcher)
* PARSER_ERROR: the server replied something that can't be parsed, probably it's not the data we're looking for (e.g. "PHP: Fatal Error")
* EMPTY_RESULT_SET: the response is valid and indicates there are no stops\routes\"passaggi"\results for your query
* QUERY_TOO_SHORT: input more characters and retry.
*/
- enum result {
+ enum Result {
OK, CLIENT_OFFLINE, SERVER_ERROR, SETUP_ERROR,PARSER_ERROR, EMPTY_RESULT_SET, QUERY_TOO_SHORT,SERVER_ERROR_404
}
}
diff --git a/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java b/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java
index a51f48c..b744072 100644
--- a/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java
+++ b/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java
@@ -1,396 +1,411 @@
/*
BusTO - Backend components
Copyright (C) 2018 Fabio Mazza
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.Nullable;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
public class FiveTAPIFetcher implements ArrivalsFetcher{
private static final String DEBUG_NAME = "FiveTAPIFetcher";
private final Map defaultHeaders = getDefaultHeaders();
final static LinkedList apiDays = new LinkedList<>(Arrays.asList("dom","lun","mar","mer","gio","ven","sab"));
@Override
- public Palina ReadArrivalTimesAll(String stopID, AtomicReference res) {
+ public Palina ReadArrivalTimesAll(String stopID, AtomicReference res) {
//set the date for the request as now
Palina p = new Palina(stopID);
//request parameters
String response = performAPIRequest(QueryType.ARRIVALS,stopID,res);
if(response==null) {
- if(res.get()==result.SERVER_ERROR_404) {
- Log.w(DEBUG_NAME,"Got 404, either the server failed, or the stop was not found, or the hack is not working anymore");
- res.set(result.EMPTY_RESULT_SET);
+ if(res.get()== Result.SERVER_ERROR_404) {
+ Log.w(DEBUG_NAME,"Got 404, either the server failed, or the stop was not found, or the address is wrong");
+ res.set(Result.EMPTY_RESULT_SET);
}
return p;
}
- try {
- List routes = parseArrivalsServerResponse(response, res);
- for(Route r: routes){
+ List routes = parseArrivalsServerResponse(response, res);
+ if(res.get()==Result.OK) {
+ for (Route r : routes) {
p.addRoute(r);
}
- } catch (JSONException ex){
- res.set(result.PARSER_ERROR);
- Log.w(DEBUG_NAME, "Couldn't get the JSON repr of:\n"+response);
- return null;
+ p.sortRoutes();
}
- res.set(result.OK);
- p.sortRoutes();
return p;
}
@Override
public Passaggio.Source getSourceForFetcher() {
return Passaggio.Source.FiveTAPI;
}
- List parseArrivalsServerResponse(String JSONresponse, AtomicReference res) throws JSONException{
+ List parseArrivalsServerResponse(String JSONresponse, AtomicReference res){
ArrayList routes = new ArrayList<>(3);
/*
Slight problem:
"longName": ==> DESCRIPTION
"name": "13N",
"departures": [
{
"arrivalTimeInt": 1272,
"time": "21:12",
"rt": false
}]
"lineType": "URBANO" ==> URBANO can be either bus or tram or METRO
*/
JSONArray arr;
try{
arr = new JSONArray(JSONresponse);
String type;
- Route.Type routetype;
+ Route.Type routetype = Route.Type.UNKNOWN;
for(int i =0; i parseDirectionsFromResponse(String response) throws IllegalArgumentException,JSONException{
if(response == null || response.length()==0) throw new IllegalArgumentException("Response string is null or void");
ArrayList routes = new ArrayList<>(10);
JSONArray lines =new JSONArray(response);
for(int i=0; i 1) {
String secondo = exploded[exploded.length-2];
if (secondo.contains("festivo")) {
festivo = Route.FestiveInfo.FESTIVO;
} else if (secondo.contains("feriale")) {
festivo = Route.FestiveInfo.FERIALE;
} else if(secondo.contains("lun. - ven")) {
serviceDays = Route.reduced_week;
} else if(secondo.contains("sab - fest.")){
serviceDays = Route.weekend;
festivo = Route.FestiveInfo.FESTIVO;
} else {
/*
Log.d(DEBUG_NAME,"Parsing details of line "+lineName+" branchid "+branchid+":\n\t"+
"Couldn't find a the service days\n"+
"Description: "+secondo+","+description
);
*/
}
if(exploded.length>2){
switch (exploded[exploded.length-3].trim()) {
case "bus":
t = Route.Type.BUS;
break;
case "tram":
//never happened, but if it could happen you can get it
t = Route.Type.TRAM;
break;
default:
//nothing
}
}
} else //only one piece
if(description.contains("festivo")){
festivo = Route.FestiveInfo.FESTIVO;
} else if(description.contains("feriale")){
festivo = Route.FestiveInfo.FERIALE;
}
if(t == Route.Type.UNKNOWN &&(lineName.trim().equals("10")|| lineName.trim().equals("15"))) t= Route.Type.TRAM;
- if(direction.contains("-")){
+ //check for the presence of parenthesis
+ String preParenthesis, postParenthesis;
+ boolean hasParenth = false;
+ if (description.contains("(")){
+ hasParenth =true;
+ preParenthesis = description.split("\\(")[0];
+ postParenthesis = description.split("\\(")[1];
+ } else {
+ preParenthesis = description;
+ postParenthesis = "";
+ }
+ if(preParenthesis.contains("-")){
//Sometimes the actual filtered direction still remains the full line (including both extremes)
- direction = direction.split("-")[1];
+ preParenthesis = preParenthesis.split("-")[1];
}
- Route r = new Route(lineName.trim(),direction.trim(),t,new ArrayList<>());
+ final String directionFinal = hasParenth? preParenthesis.trim() + " (" + postParenthesis : preParenthesis;
+ Route r = new Route(lineName.trim(),directionFinal.trim(),t,new ArrayList<>());
if(serviceDays.length>0) r.serviceDays = serviceDays;
r.festivo = festivo;
r.branchid = branchid;
r.description = description.trim();
- r.setStopsList(Arrays.asList(stops.split(",")));
+ //check if we have the stop list
+ if (branchJSON.has("branchDetail")) {
+ final String stops = branchJSON.getJSONObject("branchDetail").getString("stops");
+ r.setStopsList(Arrays.asList(stops.split(",")));
+ }
routes.add(r);
}
return routes;
}
- public List getDirectionsForStop(String stopID, AtomicReference res) {
+ public List getDirectionsForStop(String stopID, AtomicReference res) {
String response = performAPIRequest(QueryType.DETAILS,stopID,res);
List routes;
try{
routes = parseDirectionsFromResponse(response);
- res.set(result.OK);
+ res.set(Result.OK);
} catch (JSONException | IllegalArgumentException e) {
e.printStackTrace();
- res.set(result.PARSER_ERROR);
+ res.set(Result.PARSER_ERROR);
routes = null;
}
return routes;
}
- public ArrayList getAllStopsFromGTT(AtomicReference res){
+ public ArrayList getAllStopsFromGTT(AtomicReference res){
String response = performAPIRequest(QueryType.STOPS_ALL,null,res);
if(response==null) return null;
ArrayList stopslist;
try{
JSONObject responseJSON = new JSONObject(response);
JSONArray stops = responseJSON.getJSONArray("stops");
stopslist = new ArrayList<>(stops.length());
for (int i=0;i getAllLinesFromGTT(AtomicReference res){
+ public ArrayList getAllLinesFromGTT(AtomicReference res){
String resp = performAPIRequest(QueryType.LINES,null,res);
if(resp==null) {
return null;
}
ArrayList routes = null;
try {
JSONArray lines = new JSONArray(resp);
routes = new ArrayList<>(lines.length());
for(int i = 0; i getDefaultHeaders(){
HashMap param = new HashMap<>();
param.put("Host","www.5t.torino.it");
param.put("Connection","Keep-Alive");
param.put("Accept-Encoding", "gzip");
return param;
}
/**
* Create and perform the network request. This method adds parameters and returns the result
* @param t type of request to be performed
* @param stopID optional parameter, stop ID which you need for passages and branches
* @param res result container
* @return a String which contains the result of the query, to be parsed
*/
@Nullable
- public static String performAPIRequest(QueryType t,@Nullable String stopID, AtomicReference res){
+ public static String performAPIRequest(QueryType t,@Nullable String stopID, AtomicReference res){
URL u;
Map param;
try {
String address = getURLForOperation(t,stopID);
//Log.d(DEBUG_NAME,"The address to query is: "+address);
param = getDefaultHeaders();
u = new URL(address);
} catch (UnsupportedEncodingException |MalformedURLException e) {
e.printStackTrace();
- res.set(result.PARSER_ERROR);
+ res.set(Result.PARSER_ERROR);
return null;
}
String response = networkTools.queryURL(u,res,param);
return response;
}
/**
* Get the right url for the operation you are doing, to be fed into the queryURL method
* @param t type of operation
* @param stopID stop on which you are working on
* @return the Url to go to
* @throws UnsupportedEncodingException if it cannot be converted to utf-8
*/
public static String getURLForOperation(QueryType t,@Nullable String stopID) throws UnsupportedEncodingException {
final StringBuilder sb = new StringBuilder();
sb.append("http://www.5t.torino.it/ws2.1/rest/");
if(t!=QueryType.LINES) sb.append("stops/");
switch (t){
case ARRIVALS:
sb.append(URLEncoder.encode(stopID,"utf-8"));
sb.append("/departures");
break;
case DETAILS:
sb.append(URLEncoder.encode(stopID,"utf-8"));
sb.append("/branches/details");
break;
case STOPS_ALL:
sb.append("all");
break;
case STOPS_VERSION:
sb.append("version");
break;
case LINES:
sb.append("lines/all");
break;
}
return sb.toString();
}
public enum QueryType {
ARRIVALS, DETAILS,STOPS_ALL, STOPS_VERSION,LINES
}
}
diff --git a/src/it/reyboz/bustorino/backend/FiveTAPIVolleyRequest.java b/src/it/reyboz/bustorino/backend/FiveTAPIVolleyRequest.java
index 46391da..b667be8 100644
--- a/src/it/reyboz/bustorino/backend/FiveTAPIVolleyRequest.java
+++ b/src/it/reyboz/bustorino/backend/FiveTAPIVolleyRequest.java
@@ -1,130 +1,130 @@
/*
BusTO - Backend components
Copyright (C) 2019 Fabio Mazza
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.Nullable;
import android.util.Log;
import com.android.volley.*;
import com.android.volley.toolbox.HttpHeaderParser;
import org.json.JSONException;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
/**
* Class to handle request with the Volley Library
*/
public class FiveTAPIVolleyRequest extends Request {
private static final String LOG_TAG = "BusTO-FiveTAPIVolleyReq";
private ResponseListener listener;
final private String url,stopID;
- final private AtomicReference resultRef;
+ final private AtomicReference resultRef;
final private FiveTAPIFetcher fetcher;
final private FiveTAPIFetcher.QueryType type;
private FiveTAPIVolleyRequest(int method, String url, String stopID, FiveTAPIFetcher.QueryType kind,
ResponseListener listener,
@Nullable Response.ErrorListener errorListener) {
super(method, url, errorListener);
this.url = url;
this.resultRef = new AtomicReference<>();
this.fetcher = new FiveTAPIFetcher();
this.listener = listener;
this.stopID = stopID;
this.type = kind;
}
@Nullable
public static FiveTAPIVolleyRequest getNewRequest(FiveTAPIFetcher.QueryType type, String stopID,
ResponseListener listener, @Nullable Response.ErrorListener errorListener){
String url;
try {
url = FiveTAPIFetcher.getURLForOperation(type,stopID);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
Log.e(LOG_TAG,"Cannot get an URL for the operation");
return null;
}
return new FiveTAPIVolleyRequest(Method.GET,url,stopID,type,listener,errorListener);
}
@Override
protected Response parseNetworkResponse(NetworkResponse response) {
if(response.statusCode != 200)
return Response.error(new VolleyError("Response Error Code "+response.statusCode));
final String stringResponse = new String(response.data);
List routeList;
try{
switch (type){
case ARRIVALS:
routeList = fetcher.parseArrivalsServerResponse(stringResponse,resultRef);
break;
case DETAILS:
routeList = fetcher.parseDirectionsFromResponse(stringResponse);
break;
default:
//empty
return Response.error(new VolleyError("Invalid query type"));
}
} catch (JSONException e) {
- resultRef.set(Fetcher.result.PARSER_ERROR);
+ resultRef.set(Fetcher.Result.PARSER_ERROR);
//e.printStackTrace();
Log.w("FivetVolleyParser","JSON Exception in parsing response of: "+url);
return Response.error(new ParseError(response));
}
- if(resultRef.get()== Fetcher.result.PARSER_ERROR){
+ if(resultRef.get()== Fetcher.Result.PARSER_ERROR){
return Response.error(new ParseError(response));
}
final Palina p = new Palina(stopID);
p.setRoutes(routeList);
p.sortRoutes();
return Response.success(p, HttpHeaderParser.parseCacheHeaders(response));
}
@Override
protected void deliverResponse(Palina p) {
listener.onResponse(p,type);
}
@Override
public Map getHeaders() {
return FiveTAPIFetcher.getDefaultHeaders();
}
//from https://stackoverflow.com/questions/21867929/android-how-handle-message-error-from-the-server-using-volley
@Override
protected VolleyError parseNetworkError(VolleyError volleyError){
if(volleyError.networkResponse != null && volleyError.networkResponse.data != null){
volleyError = new NetworkError(volleyError.networkResponse);
}
return volleyError;
}
public interface ResponseListener{
void onResponse(Palina result, FiveTAPIFetcher.QueryType type);
}
//public interface ErrorListener extends Response.ErrorListener{}
}
diff --git a/src/it/reyboz/bustorino/backend/FiveTScraperFetcher.java b/src/it/reyboz/bustorino/backend/FiveTScraperFetcher.java
index 5c4152f..4d482e3 100644
--- a/src/it/reyboz/bustorino/backend/FiveTScraperFetcher.java
+++ b/src/it/reyboz/bustorino/backend/FiveTScraperFetcher.java
@@ -1,211 +1,211 @@
/*
BusTO - Arrival times for Turin public transports.
Copyright (C) 2014 Valerio Bozzolan
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.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
//import android.util.Log;
/**
* Contains large chunks of code taken from the old GTTSiteSucker, AsyncWget and AsyncWgetBusStopFromBusStopID classes.
*
* «BusTO, because sucking happens»
*
* @author Valerio Bozzolan
*/
public class FiveTScraperFetcher implements ArrivalsFetcher {
/**
* Execute regexes.
*
* @param needle Regex
* @param haystack Entire string
* @return Matched string
*/
private static String grep(String needle, String haystack) {
String matched = null;
Matcher matcher = Pattern.compile(
needle).matcher(haystack);
if (matcher.find()) {
matched = matcher.group(1);
}
return matched;
}
@Override
- public Palina ReadArrivalTimesAll(final String stopID, final AtomicReference res) {
+ public Palina ReadArrivalTimesAll(final String stopID, final AtomicReference res) {
Palina p = new Palina(stopID);
int routeIndex;
String responseInDOMFormatBecause5THaveAbsolutelyNoIdeaWhatJSONWas = null;
try {
responseInDOMFormatBecause5THaveAbsolutelyNoIdeaWhatJSONWas = networkTools.getDOM(new URL("http://www.5t.torino.it/5t/trasporto/arrival-times-byline.jsp?action=getTransitsByLine&shortName=" + URLEncoder.encode(stopID, "utf-8")), res);
} catch (Exception e) {
- res.set(result.PARSER_ERROR);
+ res.set(Result.PARSER_ERROR);
}
if(responseInDOMFormatBecause5THaveAbsolutelyNoIdeaWhatJSONWas == null) {
// result already set in getDOM()
return p;
}
Document doc = Jsoup.parse(responseInDOMFormatBecause5THaveAbsolutelyNoIdeaWhatJSONWas);
// Tried in rete Edisu (it does Man In The Middle... asd)
Element span = doc.select("span").first();
if(span == null) {
- res.set(result.SERVER_ERROR);
+ res.set(Result.SERVER_ERROR);
return p;
}
String busStopID = grep("^(.+) ", span.html());
if (busStopID == null) {
//Log.e("BusStop", "Empty busStopID from " + span.html());
- res.set(result.EMPTY_RESULT_SET);
+ res.set(Result.EMPTY_RESULT_SET);
return p;
}
// this also appears when no stops are found, but that case has already been handled above
Element error = doc.select("p.errore").first();
if (error != null) {
- res.set(result.SERVER_ERROR);
+ res.set(Result.SERVER_ERROR);
return p;
}
String busStopName = grep("^.+ (.+)", span.html()); // The first "dot" is the single strange space character in the middle of "39{HERE→} {←HERE}PORTA NUOVA"
if (busStopName == null) {
//Log.e("BusStop", "Empty busStopName from " + span.html());
- res.set(result.SERVER_ERROR);
+ res.set(Result.SERVER_ERROR);
return p;
}
p.setStopName(busStopName.trim());
// Every table row is a busLine
Elements trs = doc.select("table tr");
for (Element tr : trs) {
Element line = tr.select("td.line a").first();
if (!line.hasText()) {
- res.set(result.SERVER_ERROR);
+ res.set(Result.SERVER_ERROR);
return p;
}
String busLineName = line.text();
// this is yet another ID, that has no known use so we can safely ignore it
// Integer busLineID = string2Integer(
// grep(
// "([0-9]+)$",
// line.attr("href")
// )
// );
if (busLineName == null) {
- res.set(result.SERVER_ERROR);
+ res.set(Result.SERVER_ERROR);
return p;
}
// this fetcher doesn't support railways and probably they've removed METRO too, but anyway...
if(busLineName.equals("METRO")) {
routeIndex = p.addRoute(busLineName, "", Route.Type.METRO);
} else {
if(busLineName.length() >= 4) {
boolean isExtraurbano = true;
for(int ch = 0; ch < busLineName.length(); ch++) {
if(!Character.isDigit(busLineName.charAt(ch))) {
isExtraurbano = false;
break;
}
}
if(isExtraurbano) {
routeIndex = p.addRoute(busLineName, "", Route.Type.LONG_DISTANCE_BUS);
} else {
routeIndex = p.addRoute(busLineName, "", Route.Type.BUS);
}
} else {
routeIndex = p.addRoute(busLineName, "", Route.Type.BUS);
}
}
// Every busLine have passages
Elements tds = tr.select("td:not(.line)");
for (Element td : tds) {
//boolean isInRealTime = td.select("i").size() > 0;
//td.select("i").remove(); // Stripping "*"
String time = td.text().trim();
if (time.equals("")) {
// Yes... Sometimes there is an EMPTY td ._.
continue;
}
p.addPassaggio(time, Passaggio.Source.FiveTScraper, routeIndex);
}
}
p.sortRoutes();
- res.set(result.OK);
+ res.set(Result.OK);
return p;
}
@Override
public Passaggio.Source getSourceForFetcher() {
return Passaggio.Source.FiveTScraper;
}
// preserved for future generations:
// /*
// * I've sent many emails to the public email info@5t.torino.it to write down something like:
// * «YOUR SITE EXPLODE IF I USE **YOUR** BUS LINE IDs STARTING WITH ZERO!!!!!»
// * So, waiting for a response, I must purge the busStopID from "0"s .__.
// * IN YOUR FACE 5T/GTT. IN YOUR FACE.
// *
// * @param busStopID
// * @return parseInt(busStopID)
// * @antifeatured yep
// * @notabug yep
// * @wontfix yep
// */
// protected final String getFilteredBusStopID(String busStopID) {
// /*
// * OK leds me ezplain why 'm dong this shot of shittt. OK zo swhy?
// * Bhumm thads because the GTT/5T site-"developer" ids obviusli drunk.
// */
// String enableGTTDeveloperSimulator = "on"; // DRUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUNK
// final char ZZZZZZZEEEEROOOOOO = '0'; // DRUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUNK
// char[] cinquettiBarraGtt = busStopID.toCharArray(); // DRUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUNK
// int merda = 0; // DRUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUNK
// while (merda < cinquettiBarraGtt.length && cinquettiBarraGtt[merda] == ZZZZZZZEEEEROOOOOO) {
// // COMPLETELELELLELEEELY DRUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUNK
// Log.i("AsyncWgetBusStop", "scimmie ubriache assunte per tirar su il sito 5T/GTT"); // DR
// merda++; // DRUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUNK
// } // DRUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUNK
// String trenoDiMerda = ""; // DRUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUNK
// for (; merda < cinquettiBarraGtt.length; merda++) { // DRUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUNK
// trenoDiMerda += cinquettiBarraGtt[merda]; // DRUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUNK
// } // DRUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUNK
// enableGTTDeveloperSimulator = "off"; // DRUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUNK
//
// return trenoDiMerda;
// }
}
diff --git a/src/it/reyboz/bustorino/backend/FiveTStopsFetcher.java b/src/it/reyboz/bustorino/backend/FiveTStopsFetcher.java
index 6c6dca9..e5bc001 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) {
+ 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) {
- res.set(result.QUERY_TOO_SHORT);
+ 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(Fetcher.result.PARSER_ERROR);
+ 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);
+ res.set(Result.EMPTY_RESULT_SET);
} else {
- res.set(result.OK);
+ res.set(Result.OK);
}
Collections.sort(busStops);
// TODO: remove duplicates? (see GTTStopsFetcher)
return busStops;
}
}
diff --git a/src/it/reyboz/bustorino/backend/GTTJSONFetcher.java b/src/it/reyboz/bustorino/backend/GTTJSONFetcher.java
index ca14f48..112eca6 100644
--- a/src/it/reyboz/bustorino/backend/GTTJSONFetcher.java
+++ b/src/it/reyboz/bustorino/backend/GTTJSONFetcher.java
@@ -1,132 +1,132 @@
/*
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 android.util.Log;
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.HashMap;
import java.util.concurrent.atomic.AtomicReference;
public class GTTJSONFetcher implements ArrivalsFetcher {
private final String DEBUG_TAG = "GTTJSONFetcher-BusTO";
@Override @NonNull
- public Palina ReadArrivalTimesAll(String stopID, AtomicReference res) {
+ public Palina ReadArrivalTimesAll(String stopID, AtomicReference res) {
URL url;
Palina p = new Palina(stopID);
String routename;
String bacino;
String content;
JSONArray json;
int howManyRoutes, howManyPassaggi, i, j, pos; // il misto inglese-italiano è un po' ridicolo ma tanto vale...
JSONObject thisroute;
JSONArray passaggi;
try {
url = new URL("https://www.gtt.to.it/cms/index.php?option=com_gtt&task=palina.getTransitiOld&palina=" + URLEncoder.encode(stopID, "utf-8") + "&bacino=U&realtime=true&get_param=value");
} catch (Exception e) {
- res.set(result.PARSER_ERROR);
+ res.set(Result.PARSER_ERROR);
return p;
}
HashMap headers = new HashMap<>();
//headers.put("Referer","https://www.gtt.to.it/cms/percorari/urbano?view=percorsi&bacino=U&linea=15&Regol=GE");
headers.put("Host", "www.gtt.to.it");
content = networkTools.queryURL(url, res, headers);
if(content == null) {
Log.w("GTTJSONFetcher", "NULL CONTENT");
return p;
}
try {
json = new JSONArray(content);
} catch(JSONException e) {
Log.w(DEBUG_TAG, "Error parsing JSON: \n"+content);
Log.w(DEBUG_TAG, e);
- res.set(result.PARSER_ERROR);
+ res.set(Result.PARSER_ERROR);
return p;
}
try {
// returns [{"PassaggiRT":[],"Passaggi":[]}] for non existing stops!
json.getJSONObject(0).getString("Linea"); // if we can get this, then there's something useful in the array.
} catch(JSONException e) {
Log.w(DEBUG_TAG, "No existing lines");
- res.set(result.EMPTY_RESULT_SET);
+ res.set(Result.EMPTY_RESULT_SET);
return p;
}
howManyRoutes = json.length();
if(howManyRoutes == 0) {
- res.set(result.EMPTY_RESULT_SET);
+ res.set(Result.EMPTY_RESULT_SET);
return p;
}
try {
for(i = 0; i < howManyRoutes; i++) {
thisroute = json.getJSONObject(i);
routename = thisroute.getString("Linea");
try {
bacino = thisroute.getString("Bacino");
} catch (JSONException ignored) { // if "Bacino" gets removed...
bacino = "U";
}
pos = p.addRoute(routename, thisroute.getString("Direzione"), FiveTNormalizer.decodeType(routename, bacino));
passaggi = thisroute.getJSONArray("PassaggiRT");
howManyPassaggi = passaggi.length();
for(j = 0; j < howManyPassaggi; j++) {
String mPassaggio = passaggi.getString(j);
if (mPassaggio.contains("__")){
mPassaggio = mPassaggio.replace("_", "");
}
p.addPassaggio(mPassaggio.concat("*"), Passaggio.Source.GTTJSON, pos);
}
passaggi = thisroute.getJSONArray("PassaggiPR"); // now the non-real-time ones
howManyPassaggi = passaggi.length();
for(j = 0; j < howManyPassaggi; j++) {
p.addPassaggio(passaggi.getString(j), Passaggio.Source.GTTJSON, pos);
}
}
} catch (JSONException e) {
- res.set(result.PARSER_ERROR);
+ res.set(Result.PARSER_ERROR);
return p;
}
p.sortRoutes();
- res.set(result.OK);
+ res.set(Result.OK);
return p;
}
@Override
public Passaggio.Source getSourceForFetcher() {
return Passaggio.Source.GTTJSON;
}
}
diff --git a/src/it/reyboz/bustorino/backend/GTTStopsFetcher.java b/src/it/reyboz/bustorino/backend/GTTStopsFetcher.java
index 1bde9dd..89836af 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) {
+ 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) {
- res.set(result.QUERY_TOO_SHORT);
+ res.set(Result.QUERY_TOO_SHORT);
return s;
}
try {
url = new URL("http://www.gtt.to.it/cms/components/com_gtt/views/palinejson/view.html.php?term=" + URLEncoder.encode(name, "utf-8"));
} catch (Exception e) {
- res.set(result.PARSER_ERROR);
+ 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);
+ res.set(Result.EMPTY_RESULT_SET);
} else {
- res.set(result.PARSER_ERROR);
+ res.set(Result.PARSER_ERROR);
}
return s;
}
howManyStops = json.length();
if(howManyStops == 0) {
- res.set(result.EMPTY_RESULT_SET);
+ 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);
+ 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);
+ 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);
+ 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);
+ res.set(Result.OK);
return s2;
}
}
diff --git a/src/it/reyboz/bustorino/backend/StopsFinderByName.java b/src/it/reyboz/bustorino/backend/StopsFinderByName.java
index b30a9ed..f1d9a27 100644
--- a/src/it/reyboz/bustorino/backend/StopsFinderByName.java
+++ b/src/it/reyboz/bustorino/backend/StopsFinderByName.java
@@ -1,33 +1,33 @@
/*
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 java.util.List;
import java.util.concurrent.atomic.AtomicReference;
public interface StopsFinderByName extends Fetcher {
/**
* Finds stops by name. Don't call this in UI thread!
*
* @param name the string to search for
* @return list of stops, in normalized form.
* @see FiveTNormalizer
*/
- List FindByName(String name, AtomicReference res);
+ List FindByName(String name, AtomicReference res);
}
diff --git a/src/it/reyboz/bustorino/backend/StopsFinderByRoute.java b/src/it/reyboz/bustorino/backend/StopsFinderByRoute.java
index d11dd25..5d99fdb 100644
--- a/src/it/reyboz/bustorino/backend/StopsFinderByRoute.java
+++ b/src/it/reyboz/bustorino/backend/StopsFinderByRoute.java
@@ -1,33 +1,33 @@
/*
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 java.util.List;
import java.util.concurrent.atomic.AtomicReference;
public interface StopsFinderByRoute extends Fetcher {
/**
* Finds every stop in a route. Don't call this in UI thread!
*
* @param routeID route ID, in normalized form.
* @return list of stops, in normalized form.
* @see FiveTNormalizer
*/
- List FindByRoute(String routeID, StopsDBInterface db, AtomicReference res);
+ List FindByRoute(String routeID, StopsDBInterface db, AtomicReference res);
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/backend/networkTools.java b/src/it/reyboz/bustorino/backend/networkTools.java
index d0285aa..4856239 100644
--- a/src/it/reyboz/bustorino/backend/networkTools.java
+++ b/src/it/reyboz/bustorino/backend/networkTools.java
@@ -1,180 +1,180 @@
/*
BusTO - Arrival times for Turin public transports.
Copyright (C) 2014 Valerio Bozzolan
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.Nullable;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.atomic.AtomicReference;
public abstract class networkTools {
- static String getDOM(final URL url, final AtomicReference res) {
+ static String getDOM(final URL url, final AtomicReference res) {
//Log.d("asyncwget", "Catching URL in background: " + uri[0]);
HttpURLConnection urlConnection;
StringBuilder result = null;
try {
urlConnection = (HttpURLConnection) url.openConnection();
} catch(IOException e) {
- res.set(Fetcher.result.SERVER_ERROR);
+ res.set(Fetcher.Result.SERVER_ERROR);
return null;
}
try {
InputStream in = new BufferedInputStream(
urlConnection.getInputStream());
BufferedReader reader = new BufferedReader(
new InputStreamReader(in));
result = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
result.append(line);
}
} catch (Exception e) {
//Log.e("asyncwget", e.getMessage());
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
if (result == null) {
- res.set(Fetcher.result.SERVER_ERROR);
+ res.set(Fetcher.Result.SERVER_ERROR);
return null;
}
- res.set(Fetcher.result.PARSER_ERROR); // will be set to "OK" later, this is a safety net in case StringBuilder returns null, the website returns an HTTP 204 or something like that.
+ res.set(Fetcher.Result.PARSER_ERROR); // will be set to "OK" later, this is a safety net in case StringBuilder returns null, the website returns an HTTP 204 or something like that.
return result.toString();
}
@Nullable
- static String queryURL(URL url, AtomicReference res){
+ static String queryURL(URL url, AtomicReference res){
return queryURL(url,res,null);
}
@Nullable
- static String queryURL(URL url, AtomicReference res, Map headers) {
+ static String queryURL(URL url, AtomicReference res, Map headers) {
HttpURLConnection urlConnection;
InputStream in;
String s;
try {
urlConnection = (HttpURLConnection) url.openConnection();
} catch(IOException e) {
//e.printStackTrace();
- res.set(Fetcher.result.SERVER_ERROR); // even when offline, urlConnection works fine. WHY.
+ res.set(Fetcher.Result.SERVER_ERROR); // even when offline, urlConnection works fine. WHY.
return null;
}
// TODO: make this configurable?
urlConnection.setConnectTimeout(3000);
urlConnection.setReadTimeout(10000);
if(headers!= null){
for(String key : headers.keySet()){
urlConnection.setRequestProperty(key,headers.get(key));
}
}
- res.set(Fetcher.result.SERVER_ERROR); // will be set to OK later
+ res.set(Fetcher.Result.SERVER_ERROR); // will be set to OK later
try {
in = urlConnection.getInputStream();
} catch (Exception e) {
try {
if(urlConnection.getResponseCode()==404)
- res.set(Fetcher.result.SERVER_ERROR_404);
+ res.set(Fetcher.Result.SERVER_ERROR_404);
} catch (IOException e2) {
e2.printStackTrace();
}
return null;
}
//s = streamToString(in);
try {
final long startTime = System.currentTimeMillis();
s = parseStreamToString(in);
final long endtime = System.currentTimeMillis();
Log.d("NetworkTools-queryURL","reading response took "+(endtime-startTime)+" millisec");
} catch (IOException e) {
e.printStackTrace();
return null;
}
try {
in.close();
} catch(IOException ignored) {
//ignored.printStackTrace();
}
try {
urlConnection.disconnect();
} catch(Exception ignored) {
//ignored.printStackTrace();
}
if(s.length() == 0) {
Log.w("NET TOOLS", "string is empty");
return null;
} else {
//Log.d("NET TOOLS", s);
return s;
}
}
// https://stackoverflow.com/a/5445161
static String streamToString(InputStream is) {
Scanner s = new Scanner(is, "UTF-8").useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
/**
* New method, maybe faster, to read inputStream
* also see https://stackoverflow.com/a/5445161
* @param is what to read
* @return the String Read
* @throws IOException from the InputStreamReader
*/
static String parseStreamToString(InputStream is) throws IOException{
final int bufferSize = 1024;
final char[] buffer = new char[bufferSize];
final StringBuilder out = new StringBuilder();
InputStreamReader in = new InputStreamReader(is, "UTF-8");
int rsz= in.read(buffer, 0, buffer.length);
while( rsz >0) {
out.append(buffer, 0, rsz);
rsz = in.read(buffer, 0, buffer.length);
}
return out.toString();
}
static int failsafeParseInt(String str) {
try {
return Integer.parseInt(str);
} catch(NumberFormatException e) {
return 0;
}
}
}
diff --git a/src/it/reyboz/bustorino/backend/utils.java b/src/it/reyboz/bustorino/backend/utils.java
index 16e2fa4..3c0f697 100644
--- a/src/it/reyboz/bustorino/backend/utils.java
+++ b/src/it/reyboz/bustorino/backend/utils.java
@@ -1,130 +1,150 @@
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 java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.Arrays;
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 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) {
+ String[] arr = givenString.split(" ");
+ StringBuffer sb = new StringBuffer();
+ //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)))
+ .append(arr[i].substring(1)).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));
context.startActivity(browserIntent1);
}
+
/**
* 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.";
}
+ */
}
diff --git a/src/it/reyboz/bustorino/data/DBUpdateWorker.java b/src/it/reyboz/bustorino/data/DBUpdateWorker.java
index 618eeb2..3aaa514 100644
--- a/src/it/reyboz/bustorino/data/DBUpdateWorker.java
+++ b/src/it/reyboz/bustorino/data/DBUpdateWorker.java
@@ -1,138 +1,138 @@
package it.reyboz.bustorino.data;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.work.*;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.Fetcher;
import it.reyboz.bustorino.backend.Notifications;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import static android.content.Context.MODE_PRIVATE;
public class DBUpdateWorker extends Worker{
public static final String ERROR_CODE_KEY ="Error_Code";
public static final String ERROR_REASON_KEY = "ERROR_REASON";
public static final int ERROR_FETCHING_VERSION = 4;
public static final int ERROR_DOWNLOADING_STOPS = 5;
public static final int ERROR_DOWNLOADING_LINES = 6;
public static final String SUCCESS_REASON_KEY = "SUCCESS_REASON";
public static final int SUCCESS_NO_ACTION_NEEDED = 9;
public static final int SUCCESS_UPDATE_DONE = 1;
private final int notifi_ID=62341;
public static final String FORCED_UPDATE = "FORCED-UPDATE";
public static final String DEBUG_TAG = "Busto-UpdateWorker";
public DBUpdateWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@SuppressLint("RestrictedApi")
@NonNull
@Override
public Result doWork() {
//register Notification channel
final Context con = getApplicationContext();
Notifications.createDefaultNotificationChannel(con);
final SharedPreferences shPr = con.getSharedPreferences(con.getString(R.string.mainSharedPreferences),MODE_PRIVATE);
final int current_DB_version = shPr.getInt(DatabaseUpdate.DB_VERSION_KEY,-10);
final int new_DB_version = DatabaseUpdate.getNewVersion();
final boolean isUpdateCompulsory = getInputData().getBoolean(FORCED_UPDATE,false);
final int notificationID = showNotification();
Log.d(DEBUG_TAG, "Have previous version: "+current_DB_version +" and new version "+new_DB_version);
Log.d(DEBUG_TAG, "Update compulsory: "+isUpdateCompulsory);
if (new_DB_version < 0){
//there has been an error
final Data out = new Data.Builder().putInt(ERROR_REASON_KEY, ERROR_FETCHING_VERSION)
.putInt(ERROR_CODE_KEY,new_DB_version).build();
cancelNotification(notificationID);
- return Result.failure(out);
+ return ListenableWorker.Result.failure(out);
}
//we got a good version
if (current_DB_version >= new_DB_version && !isUpdateCompulsory) {
//don't need to update
cancelNotification(notificationID);
- return Result.success(new Data.Builder().
+ return ListenableWorker.Result.success(new Data.Builder().
putInt(SUCCESS_REASON_KEY, SUCCESS_NO_ACTION_NEEDED).build());
}
//start the real update
- AtomicReference resultAtomicReference = new AtomicReference<>();
+ AtomicReference resultAtomicReference = new AtomicReference<>();
DatabaseUpdate.setDBUpdatingFlag(con, shPr,true);
final DatabaseUpdate.Result resultUpdate = DatabaseUpdate.performDBUpdate(con,resultAtomicReference);
DatabaseUpdate.setDBUpdatingFlag(con, shPr,false);
if (resultUpdate != DatabaseUpdate.Result.DONE){
- Fetcher.result result = resultAtomicReference.get();
+ Fetcher.Result result = resultAtomicReference.get();
final Data.Builder dataBuilder = new Data.Builder();
switch (resultUpdate){
case ERROR_STOPS_DOWNLOAD:
dataBuilder.put(ERROR_REASON_KEY, ERROR_DOWNLOADING_STOPS);
break;
case ERROR_LINES_DOWNLOAD:
dataBuilder.put(ERROR_REASON_KEY, ERROR_DOWNLOADING_LINES);
break;
}
cancelNotification(notificationID);
- return Result.failure(dataBuilder.build());
+ return ListenableWorker.Result.failure(dataBuilder.build());
}
Log.d(DEBUG_TAG, "Update finished successfully!");
//update the version in the shared preference
final SharedPreferences.Editor editor = shPr.edit();
editor.putInt(DatabaseUpdate.DB_VERSION_KEY, new_DB_version);
editor.apply();
cancelNotification(notificationID);
- return Result.success(new Data.Builder().putInt(SUCCESS_REASON_KEY, SUCCESS_UPDATE_DONE).build());
+ return ListenableWorker.Result.success(new Data.Builder().putInt(SUCCESS_REASON_KEY, SUCCESS_UPDATE_DONE).build());
}
public static Constraints getWorkConstraints(){
return new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresCharging(false).build();
}
public static WorkRequest newFirstTimeWorkRequest(){
return new OneTimeWorkRequest.Builder(DBUpdateWorker.class)
.setBackoffCriteria(BackoffPolicy.LINEAR, 15, TimeUnit.SECONDS)
//.setInputData(new Data.Builder().putBoolean())
.build();
}
private int showNotification(){
final NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), Notifications.DEFAULT_CHANNEL_ID)
.setContentTitle("Libre BusTO - Updating Database")
.setProgress(0,0,true)
.setPriority(NotificationCompat.PRIORITY_LOW);
builder.setSmallIcon(R.drawable.ic_bus_orange);
final NotificationManagerCompat notifcManager = NotificationManagerCompat.from(getApplicationContext());
final int notification_ID = 32198;
notifcManager.notify(notification_ID,builder.build());
return notification_ID;
}
private void cancelNotification(int notificationID){
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext());
notificationManager.cancel(notificationID);
}
}
diff --git a/src/it/reyboz/bustorino/data/DatabaseUpdate.java b/src/it/reyboz/bustorino/data/DatabaseUpdate.java
index be15d95..6e1aaad 100644
--- a/src/it/reyboz/bustorino/data/DatabaseUpdate.java
+++ b/src/it/reyboz/bustorino/data/DatabaseUpdate.java
@@ -1,158 +1,158 @@
package it.reyboz.bustorino.data;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.Fetcher;
import it.reyboz.bustorino.backend.FiveTAPIFetcher;
import it.reyboz.bustorino.backend.Route;
import it.reyboz.bustorino.backend.Stop;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicReference;
import static android.content.Context.MODE_PRIVATE;
public class DatabaseUpdate {
public static final String DEBUG_TAG = "BusTO-DBUpdate";
public static final int VERSION_UNAVAILABLE = -2;
public static final int JSON_PARSING_ERROR = -4;
public static final String DB_VERSION_KEY = "NextGenDB.GTTVersion";
enum Result {
DONE, ERROR_STOPS_DOWNLOAD, ERROR_LINES_DOWNLOAD
}
/**
* Request the server the version of the database
* @return the version of the DB, or an error code
*/
public static int getNewVersion(){
- AtomicReference gres = new AtomicReference<>();
+ AtomicReference gres = new AtomicReference<>();
String networkRequest = FiveTAPIFetcher.performAPIRequest(FiveTAPIFetcher.QueryType.STOPS_VERSION,null,gres);
if(networkRequest == null){
return VERSION_UNAVAILABLE;
}
try {
JSONObject resp = new JSONObject(networkRequest);
return resp.getInt("id");
} catch (JSONException e) {
e.printStackTrace();
Log.e(DEBUG_TAG,"Error: wrong JSON response\nResponse:\t"+networkRequest);
return JSON_PARSING_ERROR;
}
}
/**
* Run the DB Update
* @param con a context
* @param gres a result reference
* @return result of the update
*/
- public static Result performDBUpdate(Context con, AtomicReference gres) {
+ public static Result performDBUpdate(Context con, AtomicReference gres) {
final FiveTAPIFetcher f = new FiveTAPIFetcher();
final ArrayList stops = f.getAllStopsFromGTT(gres);
//final ArrayList cpOp = new ArrayList<>();
- if (gres.get() != Fetcher.result.OK) {
+ if (gres.get() != Fetcher.Result.OK) {
Log.w(DEBUG_TAG, "Something went wrong downloading");
- return Result.ERROR_STOPS_DOWNLOAD;
+ return DatabaseUpdate.Result.ERROR_STOPS_DOWNLOAD;
}
// return false; //If the commit to the SharedPreferences didn't succeed, simply stop updating the database
final NextGenDB dbHelp = new NextGenDB(con.getApplicationContext());
final SQLiteDatabase db = dbHelp.getWritableDatabase();
//Empty the needed tables
db.beginTransaction();
//db.execSQL("DELETE FROM "+StopsTable.TABLE_NAME);
//db.delete(LinesTable.TABLE_NAME,null,null);
//put new data
long startTime = System.currentTimeMillis();
Log.d(DEBUG_TAG, "Inserting " + stops.size() + " stops");
for (final Stop s : stops) {
final ContentValues cv = new ContentValues();
cv.put(NextGenDB.Contract.StopsTable.COL_ID, s.ID);
cv.put(NextGenDB.Contract.StopsTable.COL_NAME, s.getStopDefaultName());
if (s.location != null)
cv.put(NextGenDB.Contract.StopsTable.COL_LOCATION, s.location);
cv.put(NextGenDB.Contract.StopsTable.COL_LAT, s.getLatitude());
cv.put(NextGenDB.Contract.StopsTable.COL_LONG, s.getLongitude());
if (s.getAbsurdGTTPlaceName() != null) cv.put(NextGenDB.Contract.StopsTable.COL_PLACE, s.getAbsurdGTTPlaceName());
cv.put(NextGenDB.Contract.StopsTable.COL_LINES_STOPPING, s.routesThatStopHereToString());
if (s.type != null) cv.put(NextGenDB.Contract.StopsTable.COL_TYPE, s.type.getCode());
//Log.d(DEBUG_TAG,cv.toString());
//cpOp.add(ContentProviderOperation.newInsert(uritobeused).withValues(cv).build());
//valuesArr[i] = cv;
db.replace(NextGenDB.Contract.StopsTable.TABLE_NAME, null, cv);
}
db.setTransactionSuccessful();
db.endTransaction();
long endTime = System.currentTimeMillis();
Log.d(DEBUG_TAG, "Inserting stops took: " + ((double) (endTime - startTime) / 1000) + " s");
final ArrayList routes = f.getAllLinesFromGTT(gres);
if (routes == null) {
Log.w(DEBUG_TAG, "Something went wrong downloading the lines");
dbHelp.close();
- return Result.ERROR_LINES_DOWNLOAD;
+ return DatabaseUpdate.Result.ERROR_LINES_DOWNLOAD;
}
db.beginTransaction();
startTime = System.currentTimeMillis();
for (Route r : routes) {
final ContentValues cv = new ContentValues();
cv.put(NextGenDB.Contract.LinesTable.COLUMN_NAME, r.getName());
switch (r.type) {
case BUS:
cv.put(NextGenDB.Contract.LinesTable.COLUMN_TYPE, "URBANO");
break;
case RAILWAY:
cv.put(NextGenDB.Contract.LinesTable.COLUMN_TYPE, "FERROVIA");
break;
case LONG_DISTANCE_BUS:
cv.put(NextGenDB.Contract.LinesTable.COLUMN_TYPE, "EXTRA");
break;
}
cv.put(NextGenDB.Contract.LinesTable.COLUMN_DESCRIPTION, r.description);
//db.insert(LinesTable.TABLE_NAME,null,cv);
int rows = db.update(NextGenDB.Contract.LinesTable.TABLE_NAME, cv, NextGenDB.Contract.LinesTable.COLUMN_NAME + " = ?", new String[]{r.getName()});
if (rows < 1) { //we haven't changed anything
db.insert(NextGenDB.Contract.LinesTable.TABLE_NAME, null, cv);
}
}
db.setTransactionSuccessful();
db.endTransaction();
endTime = System.currentTimeMillis();
Log.d(DEBUG_TAG, "Inserting lines took: " + ((double) (endTime - startTime) / 1000) + " s");
dbHelp.close();
- return Result.DONE;
+ return DatabaseUpdate.Result.DONE;
}
public static boolean setDBUpdatingFlag(Context con, boolean value){
final SharedPreferences shPr = con.getSharedPreferences(con.getString(R.string.mainSharedPreferences),MODE_PRIVATE);
return setDBUpdatingFlag(con, shPr, value);
}
static boolean setDBUpdatingFlag(Context con, SharedPreferences shPr,boolean value){
final SharedPreferences.Editor editor = shPr.edit();
editor.putBoolean(con.getString(R.string.databaseUpdatingPref),value);
return editor.commit();
}
}
diff --git a/src/it/reyboz/bustorino/data/DatabaseUpdateService.java b/src/it/reyboz/bustorino/data/DatabaseUpdateService.java
index aba376f..75cda4a 100644
--- a/src/it/reyboz/bustorino/data/DatabaseUpdateService.java
+++ b/src/it/reyboz/bustorino/data/DatabaseUpdateService.java
@@ -1,278 +1,278 @@
/*
BusTO (middleware)
Copyright (C) 2018 Fabio Mazza
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.data;
import android.app.IntentService;
import android.content.*;
import androidx.annotation.Nullable;
import android.util.Log;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.Fetcher;
import it.reyboz.bustorino.backend.FiveTAPIFetcher;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.concurrent.atomic.AtomicReference;
/**
* An {@link IntentService} subclass for handling asynchronous task requests in
* a service on a separate handler thread.
*/
public class DatabaseUpdateService extends IntentService {
// IntentService can perform, e.g. ACTION_FETCH_NEW_ITEMS
private static final String ACTION_UPDATE = "it.reyboz.bustorino.middleware.action.UPDATE_DB";
private static final String DB_VERSION = "NextGenDB.GTTVersion";
private static final String DEBUG_TAG = "DatabaseService_BusTO";
// TODO: Rename parameters
private static final String TRIAL = "it.reyboz.bustorino.middleware.extra.TRIAL";
private static final String COMPULSORY = "compulsory_update";
private static final int MAX_TRIALS = 5;
private static final int VERSION_UNAIVALABLE = -2;
public DatabaseUpdateService() {
super("DatabaseUpdateService");
}
private boolean isRunning;
private int updateTrial;
/**
* Starts this service to perform action Foo with the given parameters. If
* the service is already performing a task this action will be queued.
*
* @see IntentService
*/
public static void startDBUpdate(Context con, int trial, @Nullable Boolean mustUpdate){
Intent intent = new Intent(con, DatabaseUpdateService.class);
intent.setAction(ACTION_UPDATE);
intent.putExtra(TRIAL,trial);
if(mustUpdate!=null){
intent.putExtra(COMPULSORY,mustUpdate);
}
con.startService(intent);
}
public static void startDBUpdate(Context con) {
startDBUpdate(con, 0, false);
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (ACTION_UPDATE.equals(action)) {
Log.d(DEBUG_TAG,"Started action update");
SharedPreferences shPr = getSharedPreferences(getString(R.string.mainSharedPreferences),MODE_PRIVATE);
int versionDB = shPr.getInt(DB_VERSION,-1);
final int trial = intent.getIntExtra(TRIAL,-1);
final SharedPreferences.Editor editor = shPr.edit();
updateTrial = trial;
UpdateRequestParams params = new UpdateRequestParams(intent);
int newVersion = getNewVersion(params);
if(newVersion==VERSION_UNAIVALABLE){
//NOTHING LEFT TO DO
return;
}
Log.d(DEBUG_TAG,"newDBVersion: "+newVersion+" oldVersion: "+versionDB);
if(params.mustUpdate || versionDB==-1 || newVersion>versionDB){
Log.d(DEBUG_TAG,"Downloading the bus stops info");
- final AtomicReference gres = new AtomicReference<>();
+ final AtomicReference gres = new AtomicReference<>();
if(!performDBUpdate(gres))
restartDBUpdateifPossible(params,gres);
else {
editor.putInt(DB_VERSION,newVersion);
// BY COMMENTING THIS, THE APP WILL CONTINUOUSLY UPDATE THE DATABASE
editor.apply();
}
} else {
Log.d(DEBUG_TAG,"No update needed");
}
Log.d(DEBUG_TAG,"Finished update");
setDBUpdatingFlag(shPr,false);
}
}
}
private boolean setDBUpdatingFlag(SharedPreferences shPr,boolean value){
final SharedPreferences.Editor editor = shPr.edit();
editor.putBoolean(getString(R.string.databaseUpdatingPref),value);
return editor.commit();
}
private boolean setDBUpdatingFlag(boolean value){
final SharedPreferences shPr = getSharedPreferences(getString(R.string.mainSharedPreferences),MODE_PRIVATE);
return setDBUpdatingFlag(shPr,value);
}
- private boolean performDBUpdate(AtomicReference gres){
+ private boolean performDBUpdate(AtomicReference gres){
if(!setDBUpdatingFlag(true))
return false;
/*
final FiveTAPIFetcher f = new FiveTAPIFetcher();
final ArrayList stops = f.getAllStopsFromGTT(gres);
//final ArrayList cpOp = new ArrayList<>();
if(gres.get()!= Fetcher.result.OK){
Log.w(DEBUG_TAG,"Something went wrong downloading");
return false;
}
if(!setDBUpdatingFlag(true))
return false; //If the commit to the SharedPreferences didn't succeed, simply stop updating the database
final NextGenDB dbHelp = new NextGenDB(getApplicationContext());
final SQLiteDatabase db = dbHelp.getWritableDatabase();
//Empty the needed tables
db.beginTransaction();
//db.execSQL("DELETE FROM "+StopsTable.TABLE_NAME);
//db.delete(LinesTable.TABLE_NAME,null,null);
//put new data
long startTime = System.currentTimeMillis();
Log.d(DEBUG_TAG,"Inserting "+stops.size()+" stops");
for (final Stop s : stops) {
final ContentValues cv = new ContentValues();
cv.put(StopsTable.COL_ID, s.ID);
cv.put(StopsTable.COL_NAME, s.getStopDefaultName());
if (s.location != null)
cv.put(StopsTable.COL_LOCATION, s.location);
cv.put(StopsTable.COL_LAT, s.getLatitude());
cv.put(StopsTable.COL_LONG, s.getLongitude());
if (s.getAbsurdGTTPlaceName() != null) cv.put(StopsTable.COL_PLACE, s.getAbsurdGTTPlaceName());
cv.put(StopsTable.COL_LINES_STOPPING, s.routesThatStopHereToString());
if (s.type != null) cv.put(StopsTable.COL_TYPE, s.type.getCode());
//Log.d(DEBUG_TAG,cv.toString());
//cpOp.add(ContentProviderOperation.newInsert(uritobeused).withValues(cv).build());
//valuesArr[i] = cv;
db.replace(StopsTable.TABLE_NAME,null,cv);
}
db.setTransactionSuccessful();
db.endTransaction();
long endTime = System.currentTimeMillis();
Log.d(DEBUG_TAG,"Inserting stops took: "+((double) (endTime-startTime)/1000)+" s");
final ArrayList routes = f.getAllLinesFromGTT(gres);
if(routes==null){
Log.w(DEBUG_TAG,"Something went wrong downloading the lines");
dbHelp.close();
return false;
}
db.beginTransaction();
startTime = System.currentTimeMillis();
for (Route r: routes){
final ContentValues cv = new ContentValues();
cv.put(LinesTable.COLUMN_NAME,r.getName());
switch (r.type){
case BUS:
cv.put(LinesTable.COLUMN_TYPE,"URBANO");
break;
case RAILWAY:
cv.put(LinesTable.COLUMN_TYPE,"FERROVIA");
break;
case LONG_DISTANCE_BUS:
cv.put(LinesTable.COLUMN_TYPE,"EXTRA");
break;
}
cv.put(LinesTable.COLUMN_DESCRIPTION,r.description);
//db.insert(LinesTable.TABLE_NAME,null,cv);
int rows = db.update(LinesTable.TABLE_NAME,cv,LinesTable.COLUMN_NAME+" = ?",new String[]{r.getName()});
if(rows<1){ //we haven't changed anything
db.insert(LinesTable.TABLE_NAME,null,cv);
}
}
db.setTransactionSuccessful();
db.endTransaction();
endTime = System.currentTimeMillis();
Log.d(DEBUG_TAG,"Inserting lines took: "+((double) (endTime-startTime)/1000)+" s");
dbHelp.close();
return true;
*/
return DatabaseUpdate.performDBUpdate(getApplication(),gres) == DatabaseUpdate.Result.DONE;
}
private int getNewVersion(UpdateRequestParams params){
- AtomicReference gres = new AtomicReference<>();
+ AtomicReference gres = new AtomicReference<>();
String networkRequest = FiveTAPIFetcher.performAPIRequest(FiveTAPIFetcher.QueryType.STOPS_VERSION,null,gres);
if(networkRequest == null){
restartDBUpdateifPossible(params,gres);
return VERSION_UNAIVALABLE;
}
boolean needed;
try {
JSONObject resp = new JSONObject(networkRequest);
return resp.getInt("id");
} catch (JSONException e) {
e.printStackTrace();
Log.e(DEBUG_TAG,"Error: wrong JSON response\nResponse:\t"+networkRequest);
return -4;
}
}
- private void restartDBUpdateifPossible(UpdateRequestParams pars, AtomicReference res){
- if (pars.trial res){
+ if (pars.trial.
*/
package it.reyboz.bustorino.fragments;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.adapters.PalinaAdapter;
import it.reyboz.bustorino.backend.ArrivalsFetcher;
import it.reyboz.bustorino.backend.DBStatusManager;
import it.reyboz.bustorino.backend.Fetcher;
import it.reyboz.bustorino.backend.FiveTAPIFetcher;
import it.reyboz.bustorino.backend.FiveTNormalizer;
import it.reyboz.bustorino.backend.FiveTScraperFetcher;
import it.reyboz.bustorino.backend.GTTJSONFetcher;
import it.reyboz.bustorino.backend.Palina;
import it.reyboz.bustorino.backend.Passaggio;
import it.reyboz.bustorino.backend.Route;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.data.AppDataProvider;
import it.reyboz.bustorino.data.NextGenDB;
import it.reyboz.bustorino.data.UserDB;
import it.reyboz.bustorino.middleware.AsyncStopFavoriteAction;
public class ArrivalsFragment extends ResultListFragment implements LoaderManager.LoaderCallbacks {
private final static String KEY_STOP_ID = "stopid";
private final static String KEY_STOP_NAME = "stopname";
private final static String DEBUG_TAG_ALL = "BUSTOArrivalsFragment";
private String DEBUG_TAG = DEBUG_TAG_ALL;
private final static int loaderFavId = 2;
private final static int loaderStopId = 1;
private final static ArrivalsFetcher[] defaultFetchers = new ArrivalsFetcher[]{new FiveTAPIFetcher(), new GTTJSONFetcher(), new FiveTScraperFetcher()};
static final String STOP_TITLE = "messageExtra";
private @Nullable String stopID,stopName;
private DBStatusManager prefs;
private DBStatusManager.OnDBUpdateStatusChangeListener listener;
private boolean justCreated = false;
private Palina lastUpdatedPalina = null;
private boolean needUpdateOnAttach = false;
private boolean fetchersChangeRequestPending = false;
private boolean stopIsInFavorites = false;
//Views
protected ImageButton addToFavorites;
protected TextView timesSourceTextView;
private List fetchers = new ArrayList<>(Arrays.asList(defaultFetchers));
private boolean reloadOnResume = true;
public static ArrivalsFragment newInstance(String stopID){
return newInstance(stopID, null);
}
public static ArrivalsFragment newInstance(@NonNull String stopID, @Nullable String stopName){
ArrivalsFragment fragment = new ArrivalsFragment();
Bundle args = new Bundle();
args.putString(KEY_STOP_ID,stopID);
//parameter for ResultListFragmentrequestArrivalsForStopID
args.putSerializable(LIST_TYPE,FragmentKind.ARRIVALS);
if (stopName != null){
args.putString(KEY_STOP_NAME,stopName);
}
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
stopID = getArguments().getString(KEY_STOP_ID);
DEBUG_TAG = DEBUG_TAG_ALL+" "+stopID;
//this might really be null
stopName = getArguments().getString(KEY_STOP_NAME);
final ArrivalsFragment arrivalsFragment = this;
listener = new DBStatusManager.OnDBUpdateStatusChangeListener() {
@Override
public void onDBStatusChanged(boolean updating) {
if(!updating){
getLoaderManager().restartLoader(loaderFavId,getArguments(),arrivalsFragment);
} else {
final LoaderManager lm = getLoaderManager();
lm.destroyLoader(loaderFavId);
lm.destroyLoader(loaderStopId);
}
}
@Override
public boolean defaultStatusValue() {
return true;
}
};
prefs = new DBStatusManager(getContext().getApplicationContext(),listener);
justCreated = true;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_arrivals, container, false);
messageTextView = (TextView) root.findViewById(R.id.messageTextView);
addToFavorites = (ImageButton) root.findViewById(R.id.addToFavorites);
resultsListView = (ListView) root.findViewById(R.id.resultsListView);
timesSourceTextView = (TextView) root.findViewById(R.id.timesSourceTextView);
timesSourceTextView.setOnLongClickListener(view -> {
if(!fetchersChangeRequestPending){
rotateFetchers();
//Show we are changing provider
timesSourceTextView.setText(R.string.arrival_source_changing);
mListener.requestArrivalsForStopID(stopID);
fetchersChangeRequestPending = true;
return true;
}
return false;
});
timesSourceTextView.setOnClickListener(view -> {
Toast.makeText(getContext(), R.string.change_arrivals_source_message, Toast.LENGTH_SHORT)
.show();
});
//Button
addToFavorites.setClickable(true);
addToFavorites.setOnClickListener(v -> {
// add/remove the stop in the favorites
toggleLastStopToFavorites();
});
resultsListView.setOnItemClickListener((parent, view, position, id) -> {
String routeName;
Route r = (Route) parent.getItemAtPosition(position);
routeName = FiveTNormalizer.routeInternalToDisplay(r.getNameForDisplay());
if (routeName == null) {
routeName = r.getNameForDisplay();
}
if (r.destinazione == null || r.destinazione.length() == 0) {
Toast.makeText(getContext(),
getString(R.string.route_towards_unknown, routeName), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getContext(),
getString(R.string.route_towards_destination, routeName, r.destinazione), Toast.LENGTH_SHORT).show();
}
});
String displayName = getArguments().getString(STOP_TITLE);
if(displayName!=null)
setTextViewMessage(String.format(
getString(R.string.passages), displayName));
String probablemessage = getArguments().getString(MESSAGE_TEXT_VIEW);
if (probablemessage != null) {
//Log.d("BusTO fragment " + this.getTag(), "We have a possible message here in the savedInstaceState: " + probablemessage);
messageTextView.setText(probablemessage);
messageTextView.setVisibility(View.VISIBLE);
}
return root;
}
@Override
public void onResume() {
super.onResume();
LoaderManager loaderManager = getLoaderManager();
Log.d(DEBUG_TAG, "OnResume, justCreated "+justCreated);
+ /*if(needUpdateOnAttach){
+ updateFragmentData(null);
+ needUpdateOnAttach=false;
+ }*/
if(stopID!=null){
//refresh the arrivals
if(!justCreated){
if (reloadOnResume)
mListener.requestArrivalsForStopID(stopID);
}
else justCreated = false;
//start the loader
if(prefs.isDBUpdating(true)){
prefs.registerListener();
} else {
Log.d(DEBUG_TAG, "Restarting loader for stop");
loaderManager.restartLoader(loaderFavId, getArguments(), this);
}
updateMessage();
}
}
@Override
public void onStart() {
super.onStart();
if (needUpdateOnAttach){
updateFragmentData(null);
+ needUpdateOnAttach = false;
}
}
+
@Override
public void onPause() {
if(listener!=null)
prefs.unregisterListener();
super.onPause();
LoaderManager loaderManager = getLoaderManager();
Log.d(DEBUG_TAG, "onPause, have running loaders: "+loaderManager.hasRunningLoaders());
loaderManager.destroyLoader(loaderFavId);
}
@Nullable
public String getStopID() {
return stopID;
}
public boolean reloadsOnResume() {
return reloadOnResume;
}
public void setReloadOnResume(boolean reloadOnResume) {
this.reloadOnResume = reloadOnResume;
}
/**
* Give the fetchers
* @return the list of the fetchers
*/
public ArrayList getCurrentFetchers(){
ArrayList v = new ArrayList();
for (ArrivalsFetcher fetcher: fetchers){
v.add(fetcher);
}
return v;
}
public Fetcher[] getCurrentFetchersAsArray(){
Fetcher[] arr = new Fetcher[fetchers.size()];
fetchers.toArray(arr);
return arr;
}
private void rotateFetchers(){
Collections.rotate(fetchers, -1);
}
/**
* Update the UI with the new data
* @param p the full Palina
*/
public void updateFragmentData(@Nullable Palina p){
if (p!=null)
lastUpdatedPalina = p;
if (!isAdded()){
//defer update at next show
if (p==null)
Log.w(DEBUG_TAG, "Asked to update the data, but we're not attached and the data is null");
else needUpdateOnAttach = true;
} else {
final PalinaAdapter adapter = new PalinaAdapter(getContext(), lastUpdatedPalina);
showArrivalsSources(lastUpdatedPalina);
super.resetListAdapter(adapter);
}
}
/**
* Set the message of the arrival times source
* @param p Palina with the arrival times
*/
protected void showArrivalsSources(Palina p){
final Passaggio.Source source = p.getPassaggiSourceIfAny();
if (source == null){
Log.e(DEBUG_TAG, "NULL SOURCE");
return;
}
String source_txt;
switch (source){
case GTTJSON:
source_txt = getString(R.string.gttjsonfetcher);
break;
case FiveTAPI:
source_txt = getString(R.string.fivetapifetcher);
break;
case FiveTScraper:
source_txt = getString(R.string.fivetscraper);
break;
case UNDETERMINED:
//Don't show the view
timesSourceTextView.setVisibility(View.GONE);
return;
default:
throw new IllegalStateException("Unexpected value: " + source);
}
int count = 0;
while (source != fetchers.get(0).getSourceForFetcher() && count < 100){
//we need to update the fetcher that is requested
rotateFetchers();
count++;
}
if (count>10)
Log.w(DEBUG_TAG, "Tried to update the source fetcher but it didn't work");
final String base_message = getString(R.string.times_source_fmt, source_txt);
timesSourceTextView.setVisibility(View.VISIBLE);
timesSourceTextView.setText(base_message);
fetchersChangeRequestPending = false;
}
@Override
public void setNewListAdapter(ListAdapter adapter) {
throw new UnsupportedOperationException();
}
/**
* Update the message in the fragment
*
* It may eventually change the "Add to Favorite" icon
*/
private void updateMessage(){
String message = null;
if (stopName != null && stopID != null && stopName.length() > 0) {
message = (stopID.concat(" - ").concat(stopName));
} else if(stopID!=null) {
message = stopID;
} else {
Log.e("ArrivalsFragm"+getTag(),"NO ID FOR THIS FRAGMENT - something went horribly wrong");
}
if(message!=null) {
setTextViewMessage(getString(R.string.passages,message));
}
// whatever is the case, update the star icon
//updateStarIconFromLastBusStop();
}
@NonNull
@Override
public Loader onCreateLoader(int id, Bundle args) {
if(args.getString(KEY_STOP_ID)==null) return null;
final String stopID = args.getString(KEY_STOP_ID);
final Uri.Builder builder = AppDataProvider.getUriBuilderToComplete();
CursorLoader cl;
switch (id){
case loaderFavId:
builder.appendPath("favorites").appendPath(stopID);
cl = new CursorLoader(getContext(),builder.build(),UserDB.getFavoritesColumnNamesAsArray,null,null,null);
break;
case loaderStopId:
builder.appendPath("stop").appendPath(stopID);
cl = new CursorLoader(getContext(),builder.build(),new String[]{NextGenDB.Contract.StopsTable.COL_NAME},
null,null,null);
break;
default:
return null;
}
cl.setUpdateThrottle(500);
return cl;
}
@Override
public void onLoadFinished(Loader loader, Cursor data) {
switch (loader.getId()){
case loaderFavId:
final int colUserName = data.getColumnIndex(UserDB.getFavoritesColumnNamesAsArray[1]);
if(data.getCount()>0){
// IT'S IN FAVORITES
data.moveToFirst();
final String probableName = data.getString(colUserName);
stopIsInFavorites = true;
stopName = probableName;
//update the message in the textview
updateMessage();
} else {
stopIsInFavorites =false;
}
updateStarIcon();
if(stopName == null){
//stop is not inside the favorites and wasn't provided
Log.d("ArrivalsFragment"+getTag(),"Stop wasn't in the favorites and has no name, looking in the DB");
getLoaderManager().restartLoader(loaderStopId,getArguments(),this);
}
break;
case loaderStopId:
if(data.getCount()>0){
data.moveToFirst();
stopName = data.getString(data.getColumnIndex(
NextGenDB.Contract.StopsTable.COL_NAME
));
updateMessage();
} else {
Log.w("ArrivalsFragment"+getTag(),"Stop is not inside the database... CLOISTER BELL");
}
}
}
@Override
public void onLoaderReset(Loader loader) {
//NOTHING TO DO
}
public void toggleLastStopToFavorites() {
Stop stop = lastUpdatedPalina;
if (stop != null) {
// toggle the status in background
new AsyncStopFavoriteAction(getContext().getApplicationContext(), AsyncStopFavoriteAction.Action.TOGGLE,
v->updateStarIconFromLastBusStop(v)).execute(stop);
} else {
// this case have no sense, but just immediately update the favorite icon
updateStarIconFromLastBusStop(true);
}
}
/**
* Update the star "Add to favorite" icon
*/
public void updateStarIconFromLastBusStop(Boolean toggleDone) {
if (stopIsInFavorites)
stopIsInFavorites = !toggleDone;
else stopIsInFavorites = toggleDone;
updateStarIcon();
// check if there is a last Stop
/*
if (stopID == null) {
addToFavorites.setVisibility(View.INVISIBLE);
} else {
// filled or outline?
if (isStopInFavorites(stopID)) {
addToFavorites.setImageResource(R.drawable.ic_star_filled);
} else {
addToFavorites.setImageResource(R.drawable.ic_star_outline);
}
addToFavorites.setVisibility(View.VISIBLE);
}
*/
}
/**
* Update the star icon according to `stopIsInFavorites`
*/
public void updateStarIcon() {
// no favorites no party!
// check if there is a last Stop
if (stopID == null) {
addToFavorites.setVisibility(View.INVISIBLE);
} else {
// filled or outline?
if (stopIsInFavorites) {
addToFavorites.setImageResource(R.drawable.ic_star_filled);
} else {
addToFavorites.setImageResource(R.drawable.ic_star_outline);
}
addToFavorites.setVisibility(View.VISIBLE);
}
}
}
diff --git a/src/it/reyboz/bustorino/fragments/FragmentHelper.java b/src/it/reyboz/bustorino/fragments/FragmentHelper.java
index 4ff4e53..3f265a4 100644
--- a/src/it/reyboz/bustorino/fragments/FragmentHelper.java
+++ b/src/it/reyboz/bustorino/fragments/FragmentHelper.java
@@ -1,267 +1,268 @@
/*
BusTO (fragments)
Copyright (C) 2018 Fabio Mazza
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.fragments;
import android.content.Context;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import android.util.Log;
import android.widget.Toast;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.Fetcher;
import it.reyboz.bustorino.backend.Palina;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.backend.utils;
import it.reyboz.bustorino.middleware.*;
import java.lang.ref.WeakReference;
import java.util.List;
/**
* Helper class to manage the fragments and their needs
*/
public class FragmentHelper {
//GeneralActivity act;
private final FragmentListenerMain listenerMain;
private final WeakReference managerWeakRef;
private Stop lastSuccessfullySearchedBusStop;
//support for multiple frames
private final int secondaryFrameLayout;
private final int primaryFrameLayout;
private final Context context;
public static final int NO_FRAME = -3;
private static final String DEBUG_TAG = "BusTO FragmHelper";
private WeakReference lastTaskRef;
private boolean shouldHaltAllActivities=false;
public FragmentHelper(FragmentListenerMain listener, FragmentManager framan, Context context, int mainFrame) {
this(listener,framan, context,mainFrame,NO_FRAME);
}
public FragmentHelper(FragmentListenerMain listener, FragmentManager fraMan, Context context, int primaryFrameLayout, int secondaryFrameLayout) {
this.listenerMain = listener;
this.managerWeakRef = new WeakReference<>(fraMan);
this.primaryFrameLayout = primaryFrameLayout;
this.secondaryFrameLayout = secondaryFrameLayout;
this.context = context.getApplicationContext();
}
/**
* Get the last successfully searched bus stop or NULL
*
* @return the stop
*/
public Stop getLastSuccessfullySearchedBusStop() {
return lastSuccessfullySearchedBusStop;
}
public void setLastSuccessfullySearchedBusStop(Stop stop) {
this.lastSuccessfullySearchedBusStop = stop;
}
public void setLastTaskRef(WeakReference lastTaskRef) {
this.lastTaskRef = lastTaskRef;
}
/**
* Called when you need to create a fragment for a specified Palina
* @param p the Stop that needs to be displayed
*/
public void createOrUpdateStopFragment(Palina p, boolean addToBackStack){
boolean sameFragment;
ArrivalsFragment arrivalsFragment;
if(managerWeakRef.get()==null || shouldHaltAllActivities) {
//SOMETHING WENT VERY WRONG
Log.e(DEBUG_TAG, "We are asked for a new stop but we can't show anything");
return;
}
FragmentManager fm = managerWeakRef.get();
if(fm.findFragmentById(primaryFrameLayout) instanceof ArrivalsFragment) {
arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(primaryFrameLayout);
//Log.d(DEBUG_TAG, "Arrivals are for fragment with same stop?");
assert arrivalsFragment != null;
sameFragment = arrivalsFragment.isFragmentForTheSameStop(p);
} else {
sameFragment = false;
Log.d(DEBUG_TAG, "We aren't showing an ArrivalsFragment");
}
setLastSuccessfullySearchedBusStop(p);
if(!sameFragment) {
//set the String to be displayed on the fragment
String displayName = p.getStopDisplayName();
String displayStuff;
if (displayName != null && displayName.length() > 0) {
arrivalsFragment = ArrivalsFragment.newInstance(p.ID,displayName);
} else {
arrivalsFragment = ArrivalsFragment.newInstance(p.ID);
}
String probableTag = ResultListFragment.getFragmentTag(p);
attachFragmentToContainer(fm,arrivalsFragment,new AttachParameters(probableTag, true, addToBackStack));
} else {
Log.d("BusTO", "Same bus stop, accessing existing fragment");
arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(primaryFrameLayout);
}
// DO NOT CALL `setListAdapter` ever on arrivals fragment
arrivalsFragment.updateFragmentData(p);
// enable fragment auto refresh
arrivalsFragment.setReloadOnResume(true);
listenerMain.hideKeyboard();
toggleSpinner(false);
}
/**
* Called when you need to display the results of a search of stops
* @param resultList the List of stops found
* @param query String queried
*/
public void createStopListFragment(List resultList, String query, boolean addToBackStack){
listenerMain.hideKeyboard();
StopListFragment listfragment = StopListFragment.newInstance(query);
if(managerWeakRef.get()==null || shouldHaltAllActivities) {
//SOMETHING WENT VERY WRONG
Log.e(DEBUG_TAG, "We are asked for a new stop but we can't show anything");
return;
}
attachFragmentToContainer(managerWeakRef.get(),listfragment,
new AttachParameters("search_"+query, false,addToBackStack));
listfragment.setStopList(resultList);
toggleSpinner(false);
}
/**
* Wrapper for toggleSpinner in Activity
* @param on new status of spinner system
*/
public void toggleSpinner(boolean on){
listenerMain.toggleSpinner(on);
}
/**
* Attach a new fragment to a cointainer
* @param fm the FragmentManager
* @param fragment the Fragment
* @param parameters attach parameters
*/
protected void attachFragmentToContainer(FragmentManager fm,Fragment fragment, AttachParameters parameters){
FragmentTransaction ft = fm.beginTransaction();
int frameID;
if(parameters.attachToSecondaryFrame && secondaryFrameLayout!=NO_FRAME)
// ft.replace(secondaryFrameLayout,fragment,tag);
frameID = secondaryFrameLayout;
else frameID = primaryFrameLayout;
switch (parameters.transaction){
case REPLACE:
ft.replace(frameID,fragment,parameters.tag);
}
if (parameters.addToBackStack)
ft.addToBackStack("state_"+parameters.tag);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
- ft.commit();
+ if(!fm.isDestroyed())
+ ft.commit();
//fm.executePendingTransactions();
}
public void setBlockAllActivities(boolean shouldI) {
this.shouldHaltAllActivities = shouldI;
}
public void stopLastRequestIfNeeded(){
if(lastTaskRef == null) return;
AsyncDataDownload task = lastTaskRef.get();
if(task!=null){
task.cancel(true);
}
}
/**
* Wrapper to show the errors/status that happened
* @param res result from Fetcher
*/
- public void showErrorMessage(Fetcher.result res){
+ public void showErrorMessage(Fetcher.Result res){
//TODO: implement a common set of errors for all fragments
switch (res){
case OK:
break;
case CLIENT_OFFLINE:
showToastMessage(R.string.network_error, true);
break;
case SERVER_ERROR:
if (utils.isConnected(context)) {
showToastMessage(R.string.parsing_error, true);
} else {
showToastMessage(R.string.network_error, true);
}
case PARSER_ERROR:
default:
showShortToast(R.string.internal_error);
break;
case QUERY_TOO_SHORT:
showShortToast(R.string.query_too_short);
break;
case EMPTY_RESULT_SET:
showShortToast(R.string.no_bus_stop_have_this_name);
break;
}
}
public void showToastMessage(int messageID, boolean short_lenght) {
final int length = short_lenght ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG;
if (context != null)
Toast.makeText(context, messageID, length).show();
}
private void showShortToast(int messageID){
showToastMessage(messageID, true);
}
enum Transaction{
REPLACE,
}
static final class AttachParameters {
String tag;
boolean attachToSecondaryFrame;
Transaction transaction;
boolean addToBackStack;
public AttachParameters(String tag, boolean attachToSecondaryFrame, Transaction transaction, boolean addToBackStack) {
this.tag = tag;
this.attachToSecondaryFrame = attachToSecondaryFrame;
this.transaction = transaction;
this.addToBackStack = addToBackStack;
}
public AttachParameters(String tag, boolean attachToSecondaryFrame, boolean addToBackStack) {
this.tag = tag;
this.attachToSecondaryFrame = attachToSecondaryFrame;
this.addToBackStack = addToBackStack;
this.transaction = Transaction.REPLACE;
}
}
}
diff --git a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java
index 02c4258..a055cff 100644
--- a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java
+++ b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java
@@ -1,699 +1,700 @@
+
package it.reyboz.bustorino.fragments;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Criteria;
import android.location.Location;
import android.os.Build;
import android.os.Bundle;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.zxing.integration.android.IntentIntegrator;
import java.util.Map;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.ArrivalsFetcher;
import it.reyboz.bustorino.backend.FiveTAPIFetcher;
import it.reyboz.bustorino.backend.FiveTScraperFetcher;
import it.reyboz.bustorino.backend.FiveTStopsFetcher;
import it.reyboz.bustorino.backend.GTTJSONFetcher;
import it.reyboz.bustorino.backend.GTTStopsFetcher;
import it.reyboz.bustorino.backend.Palina;
import it.reyboz.bustorino.backend.StopsFinderByName;
import it.reyboz.bustorino.middleware.AppLocationManager;
import it.reyboz.bustorino.middleware.AsyncDataDownload;
import it.reyboz.bustorino.util.LocationCriteria;
import it.reyboz.bustorino.util.Permissions;
import static it.reyboz.bustorino.util.Permissions.LOCATION_PERMISSIONS;
import static it.reyboz.bustorino.util.Permissions.LOCATION_PERMISSION_GIVEN;
/**
* A simple {@link Fragment} subclass.
* Use the {@link MainScreenFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class MainScreenFragment extends BaseFragment implements FragmentListenerMain{
private static final String OPTION_SHOW_LEGEND = "show_legend";
private static final String SAVED_FRAGMENT="saved_fragment";
private static final String DEBUG_TAG = "BusTO - MainFragment";
public final static String FRAGMENT_TAG = "MainScreenFragment";
/// UI ELEMENTS //
private ImageButton addToFavorites;
private FragmentHelper fragmentHelper;
private SwipeRefreshLayout swipeRefreshLayout;
private EditText busStopSearchByIDEditText;
private EditText busStopSearchByNameEditText;
private ProgressBar progressBar;
private TextView howDoesItWorkTextView;
private Button hideHintButton;
private MenuItem actionHelpMenuItem;
private FloatingActionButton floatingActionButton;
private boolean setupOnAttached = true;
private boolean suppressArrivalsReload = false;
//private Snackbar snackbar;
/*
* Search mode
*/
private static final int SEARCH_BY_NAME = 0;
private static final int SEARCH_BY_ID = 1;
private static final int SEARCH_BY_ROUTE = 2; // TODO: implement this -- https://gitpull.it/T12
private int searchMode;
//private ImageButton addToFavorites;
private final ArrivalsFetcher[] arrivalsFetchers = new ArrivalsFetcher[]{new FiveTAPIFetcher(), new GTTJSONFetcher(), new FiveTScraperFetcher()};
//// HIDDEN BUT IMPORTANT ELEMENTS ////
FragmentManager fragMan;
Handler mainHandler;
private final Runnable refreshStop = new Runnable() {
public void run() {
if (fragMan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) {
ArrivalsFragment fragment = (ArrivalsFragment) fragMan.findFragmentById(R.id.resultFrame);
if (fragment == null){
//we create a new fragment, which is WRONG
new AsyncDataDownload(fragmentHelper, arrivalsFetchers,getContext()).execute();
} else{
String stopName = fragment.getStopID();
new AsyncDataDownload(fragmentHelper, fragment.getCurrentFetchersAsArray(), getContext()).execute(stopName);
}
} else //we create a new fragment, which is WRONG
new AsyncDataDownload(fragmentHelper, arrivalsFetchers, getContext()).execute();
}
};
/// LOCATION STUFF ///
boolean pendingNearbyStopsRequest = false;
boolean locationPermissionGranted, locationPermissionAsked = false;
AppLocationManager locationManager;
private final LocationCriteria cr = new LocationCriteria(2000, 10000);
//Location
private AppLocationManager.LocationRequester requester = new AppLocationManager.LocationRequester() {
@Override
public void onLocationChanged(Location loc) {
}
@Override
public void onLocationStatusChanged(int status) {
if(status == AppLocationManager.LOCATION_GPS_AVAILABLE && !isNearbyFragmentShown()){
//request Stops
pendingNearbyStopsRequest = false;
mainHandler.post(new NearbyStopsRequester(getContext(), cr));
}
}
@Override
public long getLastUpdateTimeMillis() {
return 50;
}
@Override
public LocationCriteria getLocationCriteria() {
return cr;
}
@Override
public void onLocationProviderAvailable() {
//Log.w(DEBUG_TAG, "pendingNearbyStopRequest: "+pendingNearbyStopsRequest);
if(!isNearbyFragmentShown()){
pendingNearbyStopsRequest = false;
mainHandler.post(new NearbyStopsRequester(getContext(), cr));
}
}
@Override
public void onLocationDisabled() {
}
};
private final ActivityResultLauncher requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback