diff --git a/src/it/reyboz/bustorino/backend/GlobalStatusPreferences.java b/src/it/reyboz/bustorino/backend/GlobalStatusPreferences.java new file mode 100644 index 0000000..a29dd59 --- /dev/null +++ b/src/it/reyboz/bustorino/backend/GlobalStatusPreferences.java @@ -0,0 +1,76 @@ +/* + 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 android.content.Context; +import android.content.SharedPreferences; + +public class GlobalStatusPreferences { + private static final String PREFERENCES_NAME = "it.reyboz.bustorino.statusPreferences"; + private static final String DB_UPDATING = "DB_updating"; + + private Context thecon; + private SharedPreferences preferences; + private SharedPreferences.OnSharedPreferenceChangeListener sharedPrefListener; + + public GlobalStatusPreferences(Context thecon) { + this.thecon = thecon; + this.preferences = thecon.getSharedPreferences(PREFERENCES_NAME,Context.MODE_PRIVATE); + } + + public boolean isDBUpdating(){ + if (preferences == null) preferences = thecon.getSharedPreferences(PREFERENCES_NAME,Context.MODE_PRIVATE); + return preferences.getBoolean(DB_UPDATING,false); + } + + public void registerListener(OnDBStatusChangedListener listener){ + if(sharedPrefListener!=null) unregisterListener(); + sharedPrefListener = new FinishedUpdateListener(listener); + preferences.registerOnSharedPreferenceChangeListener(sharedPrefListener); + } + + public void unregisterListener(){ + preferences.unregisterOnSharedPreferenceChangeListener(sharedPrefListener); + } + public void setDbUpdating(boolean value){ + final SharedPreferences.Editor editor = preferences.edit(); + editor.putBoolean(DB_UPDATING,value); + editor.apply(); + } + /** + * Probably useless + */ + public class FinishedUpdateListener implements SharedPreferences.OnSharedPreferenceChangeListener{ + private OnDBStatusChangedListener thingToDo; + + public FinishedUpdateListener(OnDBStatusChangedListener thingToDo) { + this.thingToDo = thingToDo; + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if(key.equals(DB_UPDATING)){ + thingToDo.onDBUpdateStatusChanged(sharedPreferences.getBoolean(DB_UPDATING,true)); + } + } + } + public interface OnDBStatusChangedListener { + void onDBUpdateStatusChanged(boolean isUpdating); + } + +} diff --git a/src/it/reyboz/bustorino/middleware/AppDataProvider.java b/src/it/reyboz/bustorino/middleware/AppDataProvider.java index 0389117..39dbfc5 100644 --- a/src/it/reyboz/bustorino/middleware/AppDataProvider.java +++ b/src/it/reyboz/bustorino/middleware/AppDataProvider.java @@ -1,251 +1,266 @@ /* 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.middleware; -import android.content.ContentProvider; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.UriMatcher; +import android.content.*; import android.database.Cursor; import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.util.Log; +import it.reyboz.bustorino.R; +import it.reyboz.bustorino.backend.GlobalStatusPreferences; import it.reyboz.bustorino.middleware.NextGenDB.Contract.*; import java.util.List; public class AppDataProvider extends ContentProvider { public static final String AUTHORITY = "it.reyboz.bustorino.provider"; private static final int STOP_OP = 1; private static final int LINE_OP = 2; private static final int BRANCH_OP = 3; private static final int FAVORITES_OP =4; private static final int MANY_STOPS = 5; private static final int ADD_UPDATE_BRANCHES = 6; private static final int LINE_INSERT_OP = 7; private static final int CONNECTIONS = 8; private static final int LOCATION_SEARCH = 9; private static final String DEBUG_TAG="AppDataProvider"; - + private Context con; private NextGenDB appDBHelper; private UserDB udbhelper; private SQLiteDatabase db; - + private GlobalStatusPreferences preferences; public AppDataProvider() { } private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); static { /* * The calls to addURI() go here, for all of the content URI patterns that the provider * should recognize. */ sUriMatcher.addURI(AUTHORITY, "stop/#", STOP_OP); sUriMatcher.addURI(AUTHORITY,"stops",MANY_STOPS); sUriMatcher.addURI(AUTHORITY,"stops/location/*/*/*",LOCATION_SEARCH); /* * Sets the code for a single row to 2. In this case, the "#" wildcard is * used. "content://com.example.app.provider/table3/3" matches, but * "content://com.example.app.provider/table3 doesn't. */ sUriMatcher.addURI(AUTHORITY, "line/#", LINE_OP); sUriMatcher.addURI(AUTHORITY,"branch/#",BRANCH_OP); sUriMatcher.addURI(AUTHORITY,"line/insert",LINE_INSERT_OP); sUriMatcher.addURI(AUTHORITY,"branches",ADD_UPDATE_BRANCHES); sUriMatcher.addURI(AUTHORITY,"connections",CONNECTIONS); sUriMatcher.addURI(AUTHORITY,"favorites/#",FAVORITES_OP); } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // Implement this to handle requests to delete one or more rows. db = appDBHelper.getWritableDatabase(); int rows; switch (sUriMatcher.match(uri)){ case MANY_STOPS: rows = db.delete(NextGenDB.Contract.StopsTable.TABLE_NAME,null,null); break; default: throw new UnsupportedOperationException("Not yet implemented"); } return rows; } @Override public String getType(Uri uri) { // TODO: Implement this to handle requests for the MIME type of the data // at the given URI. int match = sUriMatcher.match(uri); String baseTypedir = "vnd.android.cursor.dir/"; String baseTypeitem = "vnd.android.cursor.item/"; switch (match){ case LOCATION_SEARCH: return baseTypedir+"stop"; case LINE_OP: return baseTypeitem+"line"; case CONNECTIONS: return baseTypedir+"stops"; } return baseTypedir+"/item"; } @Override public Uri insert(Uri uri, ContentValues values) throws IllegalArgumentException{ + //AVOID OPENING A DB CONNECTION, WILL THROW VERY NASTY ERRORS + if(preferences.isDBUpdating()) + return null; db = appDBHelper.getWritableDatabase(); - Uri finalUri = null; + Uri finalUri; long last_rowid = -1; switch (sUriMatcher.match(uri)){ case ADD_UPDATE_BRANCHES: Log.d("InsBranchWithProvider","new Insert request"); String line_name = values.getAsString(NextGenDB.Contract.LinesTable.COLUMN_NAME); if(line_name==null) throw new IllegalArgumentException("No line name given"); long lineid = -1; Cursor c = db.query(LinesTable.TABLE_NAME, new String[]{LinesTable._ID,LinesTable.COLUMN_NAME,LinesTable.COLUMN_DESCRIPTION},NextGenDB.Contract.LinesTable.COLUMN_NAME +" =?", new String[]{line_name},null,null,null); Log.d("InsBranchWithProvider","finding line in the database: "+c.getCount()+" matches"); if(c.getCount() == 0){ //There are no lines, insert? //NOPE /* c.close(); ContentValues cv = new ContentValues(); cv.put(LinesTable.COLUMN_NAME,line_name); lineid = db.insert(LinesTable.TABLE_NAME,null,cv); */ break; }else { c.moveToFirst(); /* while(c.moveToNext()){ Log.d("InsBranchWithProvider","line: "+c.getString(c.getColumnIndex(LinesTable.COLUMN_NAME))+"\n" +c.getString(c.getColumnIndex(LinesTable.COLUMN_DESCRIPTION))); }*/ lineid = c.getInt(c.getColumnIndex(NextGenDB.Contract.LinesTable._ID)); c.close(); } values.remove(NextGenDB.Contract.LinesTable.COLUMN_NAME); values.put(BranchesTable.COL_LINE,lineid); last_rowid = db.insertWithOnConflict(NextGenDB.Contract.BranchesTable.TABLE_NAME,null,values,SQLiteDatabase.CONFLICT_REPLACE); break; case MANY_STOPS: //Log.d("AppDataProvider_busTO","New stop insert request"); try{ last_rowid = db.insertOrThrow(NextGenDB.Contract.StopsTable.TABLE_NAME,null,values); } catch (SQLiteConstraintException e){ Log.w("AppDataProvider_busTO","Insert failed because of constraint"); last_rowid = -1; e.printStackTrace(); } break; case CONNECTIONS: try{ last_rowid = db.insertOrThrow(NextGenDB.Contract.ConnectionsTable.TABLE_NAME,null,values); } catch (SQLiteConstraintException e){ Log.w("AppDataProvider_busTO","Insert failed because of constraint"); last_rowid = -1; e.printStackTrace(); } break; default: throw new IllegalArgumentException("Invalid parameters"); } finalUri = ContentUris.withAppendedId(uri,last_rowid); return finalUri; } @Override public boolean onCreate() { + con = getContext(); appDBHelper = new NextGenDB(getContext()); udbhelper = new UserDB(getContext()); + if(con!=null) { + preferences = new GlobalStatusPreferences(con); + } else { + preferences = null; + Log.e(DEBUG_TAG,"Cannot get shared preferences"); + } return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) throws UnsupportedOperationException,IllegalArgumentException{ + //IMPORTANT + if(preferences.isDBUpdating()) return null; SQLiteDatabase db = appDBHelper.getReadableDatabase(); List parts = uri.getPathSegments(); switch (sUriMatcher.match(uri)){ case LOCATION_SEARCH: //authority/stops/location/"Lat"/"Lon"/"distance" //distance in metres (integer) if(parts.size()>=4 && "location".equals(parts.get(1))){ Double latitude = Double.parseDouble(parts.get(2)); Double longitude = Double.parseDouble(parts.get(3)); //converting distance to a float to not lose precision Float distance = parts.size()>=5 ? Float.parseFloat(parts.get(4))/1000 : 0.1f; if(parts.size()>=5) Log.d("LocationSearch"," given distance to search is "+parts.get(4)+" m"); Double distasAngle = (distance/6371)*180/Math.PI; //small angles approximation, still valid for about 500 metres String whereClause = StopsTable.COL_LAT+ "< "+(latitude+distasAngle)+" AND " +StopsTable.COL_LAT +" > "+(latitude-distasAngle)+" AND "+ StopsTable.COL_LONG+" < "+(longitude+distasAngle)+" AND "+StopsTable.COL_LONG+" > "+(longitude-distasAngle); //Log.d("Provider-LOCSearch","Querying stops by position, query args: \n"+whereClause); return db.query(StopsTable.TABLE_NAME,projection,whereClause,null,null,null,null); } else { Log.w(DEBUG_TAG,"Not enough parameters"); if(parts.size()>=5) for(String s:parts) Log.d(DEBUG_TAG,"\t element "+parts.indexOf(s)+" is: "+s); return null; } case FAVORITES_OP: final String stopFavSelection = UserDB.getFavoritesColumnNamesAsArray[0]+" = ?"; db = udbhelper.getReadableDatabase(); Log.d(DEBUG_TAG,"Asked information on Favorites about stop with id "+uri.getLastPathSegment()); return db.query(UserDB.TABLE_NAME,projection,stopFavSelection,new String[]{uri.getLastPathSegment()},null,null,sortOrder); case STOP_OP: //Let's try this plain and simple final String[] selectionValues = {uri.getLastPathSegment()}; final String stopSelection = StopsTable.COL_ID+" = ?"; Log.d(DEBUG_TAG,"Asked information about stop with id "+selectionValues[0]); return db.query(StopsTable.TABLE_NAME,projection,stopSelection,selectionValues,null,null,sortOrder); default: Log.d("DataProvider","got request "+uri.getPath()+" which doesn't match anything"); } throw new UnsupportedOperationException("Not yet implemented"); } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO: Implement this to handle requests to update one or more rows. throw new UnsupportedOperationException("Not yet implemented"); } // public static Uri getBaseUriGivenOp(int operationType); public static Uri.Builder getUriBuilderToComplete(){ final Uri.Builder b = new Uri.Builder(); b.scheme("content").authority(AUTHORITY); return b; } + @Override + public void onLowMemory() { + super.onLowMemory(); + } } diff --git a/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java b/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java index 459d41f..dc3a845 100644 --- a/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java +++ b/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java @@ -1,347 +1,354 @@ /* 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.middleware; import android.content.ContentResolver; import android.content.ContentValues; +import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.os.AsyncTask; import android.support.v7.app.AppCompatActivity; import android.util.Log; import it.reyboz.bustorino.R; import it.reyboz.bustorino.backend.*; import it.reyboz.bustorino.fragments.FragmentHelper; import it.reyboz.bustorino.middleware.NextGenDB.Contract.*; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import java.util.Calendar; /** * This should be used to download data, but not to display it */ public class AsyncDataDownload extends AsyncTask{ private static final String TAG = "BusTO-DataDownload"; private boolean failedAll = false; private AtomicReference res; private RequestType t; private String query; WeakReference helperRef; private ArrayList otherActivities = new ArrayList<>(); public AsyncDataDownload(RequestType type,FragmentHelper fh) { t = type; helperRef = new WeakReference<>(fh); res = new AtomicReference<>(); } @Override protected Object doInBackground(String... params) { RecursionHelper r; boolean success=false; Object result; switch (t){ case ARRIVALS: r = new RecursionHelper<>(new ArrivalsFetcher[] {new FiveTAPIFetcher(),new GTTJSONFetcher(), new FiveTScraperFetcher()}); break; case STOPS: r = new RecursionHelper<>(new StopsFinderByName[] {new GTTStopsFetcher(), new FiveTStopsFetcher()}); break; default: //TODO put error message return null; } FragmentHelper fh = helperRef.get(); //If the FragmentHelper is null, that means the activity doesn't exist anymore if (fh == null){ return null; } //Log.d(TAG,"refresh layout reference is: "+fh.isRefreshLayoutReferenceTrue()); while(r.valid()) { if(this.isCancelled()) { return null; } //get the data from the fetcher switch (t){ case ARRIVALS: ArrivalsFetcher f = (ArrivalsFetcher) r.getAndMoveForward(); Log.d(TAG,"Using the ArrivalsFetcher: "+f.getClass()); Stop lastSearchedBusStop = fh.getLastSuccessfullySearchedBusStop(); Palina p; String stopID; if(params.length>0) stopID=params[0]; //(it's a Palina) else if(lastSearchedBusStop!=null) stopID = lastSearchedBusStop.ID; //(it's a Palina) else { publishProgress(Fetcher.result.QUERY_TOO_SHORT); return null; } //Skip the FiveTAPIFetcher for the Metro Stops because it shows incomprehensible arrival times if(f instanceof FiveTAPIFetcher && Integer.parseInt(stopID)>= 8200) continue; p= f.ReadArrivalTimesAll(stopID,res); publishProgress(res.get()); if(f instanceof FiveTAPIFetcher){ AtomicReference gres = new AtomicReference<>(); List branches = ((FiveTAPIFetcher) f).getDirectionsForStop(stopID,gres); if(gres.get() == Fetcher.result.OK){ p.addInfoFromRoutes(branches); Thread t = new Thread(new BranchInserter(branches,fh,stopID)); t.start(); otherActivities.add(t); } //put updated values into Database } if(lastSearchedBusStop != null && res.get()== Fetcher.result.OK) { // check that we don't have the same stop if(lastSearchedBusStop.ID.equals(p.ID)) { // searched and it's the same String sn = lastSearchedBusStop.getStopDisplayName(); if(sn != null) { // "merge" Stop over Palina and we're good to go p.mergeNameFrom(lastSearchedBusStop); } } } result = p; //TODO: find a way to avoid overloading the user with toasts break; case STOPS: StopsFinderByName finder = (StopsFinderByName) r.getAndMoveForward(); List resultList= finder.FindByName(params[0], this.res); //it's a List Log.d(TAG,"Using the StopFinderByName: "+finder.getClass()); query =params[0]; result = resultList; //dummy result break; default: result = null; } //find if it went well if(res.get()== Fetcher.result.OK) { //wait for other threads to finish for(Thread t: otherActivities){ try { t.join(); } catch (InterruptedException e) { //do nothing } } return result; } } //at this point, we are sure that the result has been negative failedAll=true; return null; } @Override protected void onProgressUpdate(Fetcher.result... values) { FragmentHelper fh = helperRef.get(); if (fh!=null) for (Fetcher.result r : values){ //TODO: make Toast fh.showErrorMessage(r); } else { Log.w(TAG,"We had to show some progress but activity was destroyed"); } } @Override protected void onPostExecute(Object o) { FragmentHelper fh = helperRef.get(); if(failedAll || o == null || fh == null){ //everything went bad if(fh!=null) fh.toggleSpinner(false); cancel(true); //TODO: send message here return; } switch (t){ case ARRIVALS: Palina palina = (Palina) o; fh.createOrUpdateStopFragment(palina); break; case STOPS: //this should never be a problem List stopList = (List) o; if(query!=null) { fh.createFragmentFor(stopList,query); } else Log.e(TAG,"QUERY NULL, COULD NOT CREATE FRAGMENT"); break; case DBUPDATE: break; } } @Override protected void onCancelled() { FragmentHelper fh = helperRef.get(); if (fh!=null) fh.toggleSpinner(false); } @Override protected void onPreExecute() { FragmentHelper fh = helperRef.get(); if (fh!=null) fh.toggleSpinner(true); } public enum RequestType { ARRIVALS,STOPS,DBUPDATE } /** * Run this in a background thread.
* Sets a stop name for this.palina, guaranteed not to be null! **/ //TODO:Implement this /* private void getNameOrGetRekt(Palina p) { String nameMaybe; SQLiteDatabase udb = uDB.getReadableDatabase(); // does it already have a name (for fetchers that support it, or already got from favorites)? nameMaybe = p.getStopDisplayName(); if(nameMaybe != null && nameMaybe.length() > 0) { return; } // ok, let's search favorites. String usernameMaybe = UserDB.getStopUserName(udb, this.p.ID); if(usernameMaybe != null && usernameMaybe.length() > 0) { p.setStopUserName(usernameMaybe); return; } // let's try StopsDB, then. db.openIfNeeded(); nameMaybe = db.getNameFromID(this.p.ID); db.closeIfNeeded(); if(nameMaybe != null && nameMaybe.length() > 0) { p.setStopName(nameMaybe); return; } // no name to be found anywhere, don't bother searching it next time p.setStopName(""); }*/ public class BranchInserter implements Runnable{ private List routesToInsert; private String stopID; private FragmentHelper fragmentHelper; public BranchInserter(List routesToInsert,FragmentHelper fh,String stopID) { this.routesToInsert = routesToInsert; this.stopID = stopID; this.fragmentHelper = fh; } @Override public void run() { ContentValues[] values = new ContentValues[routesToInsert.size()]; ArrayList connectionsVals = new ArrayList<>(routesToInsert.size()*4); long starttime,endtime; for (Route r:routesToInsert){ //if it has received an interrupt, stop if(Thread.interrupted()) return; //otherwise, build contentValues final ContentValues cv = new ContentValues(); cv.put(BranchesTable.COL_BRANCHID,r.branchid); cv.put(LinesTable.COLUMN_NAME,r.name); cv.put(BranchesTable.COL_DIRECTION,r.destinazione); cv.put(BranchesTable.COL_DESCRIPTION,r.description); for (int day :r.serviceDays) { switch (day){ case Calendar.MONDAY: cv.put(BranchesTable.COL_LUN,1); break; case Calendar.TUESDAY: cv.put(BranchesTable.COL_MAR,1); break; case Calendar.WEDNESDAY: cv.put(BranchesTable.COL_MER,1); break; case Calendar.THURSDAY: cv.put(BranchesTable.COL_GIO,1); break; case Calendar.FRIDAY: cv.put(BranchesTable.COL_VEN,1); break; case Calendar.SATURDAY: cv.put(BranchesTable.COL_SAB,1); break; case Calendar.SUNDAY: cv.put(BranchesTable.COL_DOM,1); break; } } if(r.type!=null) cv.put(BranchesTable.COL_TYPE, r.type.getCode()); cv.put(BranchesTable.COL_FESTIVO, r.festivo.getCode()); values[routesToInsert.indexOf(r)] = cv; for(int i=0; i. */ package it.reyboz.bustorino.middleware; import android.app.IntentService; import android.content.*; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; 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 it.reyboz.bustorino.middleware.NextGenDB.Contract.*; /** * 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 int MAX_TRIALS = 5; 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 context) { startDBUpdate(context,0); } public static void startDBUpdate(Context con, int trial){ Intent intent = new Intent(con, DatabaseUpdateService.class); intent.setAction(ACTION_UPDATE); intent.putExtra(TRIAL,trial); con.startService(intent); } @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); updateTrial = trial; int newVersion = getNewVersion(trial); Log.d(DEBUG_TAG,"newDBVersion: "+newVersion+" oldVersion: "+versionDB); if(versionDB==-1 || newVersion>versionDB){ final SharedPreferences.Editor editor = shPr.edit(); editor.putBoolean(getString(R.string.databaseUpdatingPref),true); editor.apply(); Log.d(DEBUG_TAG,"Downloading the bus stops info"); final AtomicReference gres = new AtomicReference<>(); if(!performDBUpdate(gres)) restartDBUpdateifPossible(trial,gres); /*switch (gres.get()){ case SERVER_ERROR: restartDBUpdateifPossible(trial); break; case PARSER_ERROR: break; case EMPTY_RESULT_SET: break; case QUERY_TOO_SHORT: break; case SERVER_ERROR_404: break; }*/ 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"); SharedPreferences.Editor editor = shPr.edit(); editor.putBoolean(getString(R.string.databaseUpdatingPref),false); editor.apply(); } } } private boolean performDBUpdate(AtomicReference gres){ 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; } 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.name); 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.name}); 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; } private int getNewVersion(int trial){ AtomicReference gres = new AtomicReference<>(); String networkRequest = FiveTAPIFetcher.performAPIRequest(FiveTAPIFetcher.QueryType.STOPS_VERSION,null,gres); if(networkRequest == null){ restartDBUpdateifPossible(trial,gres); return -2; } 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 -2; } } private void restartDBUpdateifPossible(int currentTrial,AtomicReference res){ if (currentTrial