diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 7769bd0..24ba670 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,104 +1,105 @@ + diff --git a/src/it/reyboz/bustorino/ActivityMain.java b/src/it/reyboz/bustorino/ActivityMain.java index 8aeb737..c6b3840 100644 --- a/src/it/reyboz/bustorino/ActivityMain.java +++ b/src/it/reyboz/bustorino/ActivityMain.java @@ -1,583 +1,594 @@ /* 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; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.location.LocationManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.support.annotation.NonNull; import android.support.v4.app.FragmentManager; import android.support.v4.app.NavUtils; import android.support.v4.widget.SwipeRefreshLayout; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.*; import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; //import com.melnykov.fab.FloatingActionButton; import android.support.design.widget.FloatingActionButton; import it.reyboz.bustorino.backend.ArrivalsFetcher; 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.StopsFinderByName; import it.reyboz.bustorino.fragments.FragmentHelper; import it.reyboz.bustorino.fragments.ResultListFragment; import it.reyboz.bustorino.middleware.*; public class ActivityMain extends GeneralActivity implements ResultListFragment.ResultFragmentListener { /* * Layout elements */ private EditText busStopSearchByIDEditText; private EditText busStopSearchByNameEditText; private ProgressBar progressBar; private TextView howDoesItWorkTextView; private Button hideHintButton; private MenuItem actionHelpMenuItem; private SwipeRefreshLayout swipeRefreshLayout; private FloatingActionButton floatingActionButton; private FragmentManager framan; /* * 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 (bug #1512948) private int searchMode; /* * Options */ private final String OPTION_SHOW_LEGEND = "show_legend"; private final String LOCATION_PERMISSION_GIVEN = "loc_permission"; /* // useful for testing: public class MockFetcher implements ArrivalsFetcher { @Override public Palina ReadArrivalTimesAll(String routeID, AtomicReference res) { SystemClock.sleep(5000); res.set(result.SERVER_ERROR); return new Palina(); } } private ArrivalsFetcher[] ArrivalFetchers = {new MockFetcher(), new MockFetcher(), new MockFetcher(), new MockFetcher(), new MockFetcher()};*/ private RecursionHelper ArrivalFetchersRecursionHelper = new RecursionHelper<>(new ArrivalsFetcher[] {new GTTJSONFetcher(), new FiveTScraperFetcher()}); private RecursionHelper StopsFindersByNameRecursionHelper = new RecursionHelper<>(new StopsFinderByName[] {new GTTStopsFetcher(), new FiveTStopsFetcher()}); private StopsDB stopsDB; private UserDB userDB; private FragmentHelper fh; private GPSLocationAdapter locationHandler; ///////////////////////////////// EVENT HANDLERS /////////////////////////////////////////////// /* * @see swipeRefreshLayout */ private Handler handler = new Handler(); private final Runnable refreshing = new Runnable() { public void run() { new AsyncDataDownload(AsyncDataDownload.RequestType.ARRIVALS,fh).execute(); } }; //// MAIN METHOD /// @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); framan = getSupportFragmentManager(); this.stopsDB = new StopsDB(getApplicationContext()); this.userDB = new UserDB(getApplicationContext()); setContentView(R.layout.activity_main); busStopSearchByIDEditText = (EditText) findViewById(R.id.busStopSearchByIDEditText); busStopSearchByNameEditText = (EditText) findViewById(R.id.busStopSearchByNameEditText); progressBar = (ProgressBar) findViewById(R.id.progressBar); howDoesItWorkTextView = (TextView) findViewById(R.id.howDoesItWorkTextView); hideHintButton = (Button) findViewById(R.id.hideHintButton); swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.listRefreshLayout); floatingActionButton = (FloatingActionButton) findViewById(R.id.floatingActionButton); framan.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() { @Override public void onBackStackChanged() { Log.d("MainActivity, BusTO", "BACK STACK CHANGED"); } }); busStopSearchByIDEditText.setSelectAllOnFocus(true); busStopSearchByIDEditText .setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { // IME_ACTION_SEARCH alphabetical option if (actionId == EditorInfo.IME_ACTION_SEARCH) { onSearchClick(v); return true; } return false; } }); busStopSearchByNameEditText .setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { // IME_ACTION_SEARCH alphabetical option if (actionId == EditorInfo.IME_ACTION_SEARCH) { onSearchClick(v); return true; } return false; } }); // Called when the layout is pulled down swipeRefreshLayout .setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { handler.post(refreshing); } }); /** * @author Marco Gagino!!! */ //swipeRefreshLayout.setColorSchemeColors(R.color.blue_500, R.color.orange_500); // setColorScheme is deprecated, setColorSchemeColors isn't swipeRefreshLayout.setColorSchemeResources(R.color.blue_500,R.color.orange_500); fh = new FragmentHelper(this,R.id.listRefreshLayout,R.id.resultFrame,FragmentHelper.NO_FRAME); setSearchModeBusStopID(); //---------------------------- START INTENT CHECK QUEUE ------------------------------------ // Intercept calls from URL intent boolean tryedFromIntent = false; String busStopID = null; String busStopDisplayName = null; Uri data = getIntent().getData(); if (data != null) { busStopID = getBusStopIDFromUri(data); tryedFromIntent = true; } // Intercept calls from other activities if (!tryedFromIntent) { Bundle b = getIntent().getExtras(); if (b != null) { busStopID = b.getString("bus-stop-ID"); busStopDisplayName = b.getString("bus-stop-display-name"); /** * I'm not very sure if you are coming from an Intent. * Some launchers work in strange ways. */ tryedFromIntent = busStopID != null; } } //---------------------------- END INTENT CHECK QUEUE -------------------------------------- if (busStopID == null) { // Show keyboard if can't start from intent showKeyboard(); // You haven't obtained anything... from an intent? if (tryedFromIntent) { // This shows a luser warning ArrivalFetchersRecursionHelper.reset(); Toast.makeText(getApplicationContext(), R.string.insert_bus_stop_number_error, Toast.LENGTH_SHORT).show(); } } else { // If you are here an intent has worked successfully setBusStopSearchByIDEditText(busStopID); /* //THIS PART SHOULDN'T BE NECESSARY SINCE THE LAST SUCCESSFULLY SEARCHED BUS // STOP IS ADDED AUTOMATICALLY Stop nextStop = new Stop(busStopID); // forcing it as user name even though it could be standard name, it doesn't really matter nextStop.setStopUserName(busStopDisplayName); //set stop as last succe fh.setLastSuccessfullySearchedBusStop(nextStop); */ createFragmentForStop(busStopID); } //Try (hopefully) database update //TODO: Start the service in foreground, check last time it ran before DatabaseUpdateService.startDBUpdate(getApplicationContext()); - assertLocationPermissions(); + if(getOption(LOCATION_PERMISSION_GIVEN,false)==false){ + assertLocationPermissions(); + } locationHandler = new GPSLocationAdapter(getApplicationContext()); - + LocationManager locationManager = (LocationManager) + getSystemService(Context.LOCATION_SERVICE); + try { + locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000, 10, locationHandler); + } catch (SecurityException ec){ + //ignored + Log.w("Busto","Position request failed even though user accepted permission: "+ec.getMessage()); + } Log.d("MainActivity", "Created"); } /** * Reload bus stop timetable when it's fulled resumed from background. */ @Override protected void onPostResume() { super.onPostResume(); Log.d("ActivityMain", "onPostResume fired. Last successfully bus stop ID: " + fh.getLastSuccessfullySearchedBusStop()); if (searchMode == SEARCH_BY_ID && fh.getLastSuccessfullySearchedBusStop() != null) { setBusStopSearchByIDEditText(fh.getLastSuccessfullySearchedBusStop().ID); //new asyncWgetBusStopFromBusStopID(lastSuccessfullySearchedBusStop.ID, ArrivalFetchersRecursionHelper, lastSuccessfullySearchedBusStop); new AsyncDataDownload(AsyncDataDownload.RequestType.ARRIVALS,fh).execute(); } else { //we have new activity or we don't have a new searched stop. //Let's search stops nearby } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); actionHelpMenuItem = menu.findItem(R.id.action_help); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. switch (item.getItemId()) { case android.R.id.home: // Respond to the action bar's Up/Home button NavUtils.navigateUpFromSameTask(this); return true; case R.id.action_help: showHints(); return true; case R.id.action_favorites: startActivity(new Intent(ActivityMain.this, ActivityFavorites.class)); return true; case R.id.action_about: startActivity(new Intent(ActivityMain.this, ActivityAbout.class)); return true; case R.id.action_news: openIceweasel("http://blog.reyboz.it/tag/busto/"); return true; case R.id.action_bugs: openIceweasel("https://bugs.launchpad.net/bus-torino"); return true; case R.id.action_source: openIceweasel("https://code.launchpad.net/bus-torino"); return true; case R.id.action_licence: openIceweasel("http://www.gnu.org/licenses/gpl-3.0.html"); return true; case R.id.action_author: openIceweasel("http://boz.reyboz.it?lovebusto"); return true; } return super.onOptionsItemSelected(item); } /** * OK this is pure shit * * @param v View clicked */ public void onSearchClick(View v) { if (searchMode == SEARCH_BY_ID) { String busStopID = busStopSearchByIDEditText.getText().toString(); //OLD ASYNCTASK //new asyncWgetBusStopFromBusStopID(busStopID, ArrivalFetchersRecursionHelper, lastSuccessfullySearchedBusStop); if(busStopID == null || busStopID.length() <= 0) { showMessage(R.string.insert_bus_stop_number_error); toggleSpinner(false); } else new AsyncDataDownload(AsyncDataDownload.RequestType.ARRIVALS,fh).execute(busStopID); } else { // searchMode == SEARCH_BY_NAME String query = busStopSearchByNameEditText.getText().toString(); //new asyncWgetBusStopSuggestions(query, stopsDB, StopsFindersByNameRecursionHelper); new AsyncDataDownload(AsyncDataDownload.RequestType.STOPS,fh).execute(query); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode){ case PERMISSION_REQUEST_POSITION: if(grantResults.length>0 && grantResults[0]==PackageManager.PERMISSION_GRANTED){ setOption(LOCATION_PERMISSION_GIVEN,true); + locationHandler.startRequestingPosition(); } else { //permission denied setOption(LOCATION_PERMISSION_GIVEN,false); } //add other cases for permissions } } @Override public void createFragmentForStop(String ID) { //new asyncWgetBusStopFromBusStopID(ID, ArrivalFetchersRecursionHelper,lastSuccessfullySearchedBusStop); if(ID == null || ID.length() <= 0) { // we're still in UI thread, no need to mess with Progress showMessage(R.string.insert_bus_stop_number_error); toggleSpinner(false); } else new AsyncDataDownload(AsyncDataDownload.RequestType.ARRIVALS,fh).execute(ID); } /** * QR scan button clicked * * @param v View QRButton clicked */ public void onQRButtonClick(View v) { IntentIntegrator integrator = new IntentIntegrator(this); integrator.initiateScan(); } /** * Receive the Barcode Scanner Intent * */ public void onActivityResult(int requestCode, int resultCode, Intent intent) { IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); Uri uri; try { uri = Uri.parse(scanResult != null ? scanResult.getContents() : null); // this apparently prevents NullPointerException. Somehow. } catch (NullPointerException e) { Toast.makeText(getApplicationContext(), R.string.no_qrcode, Toast.LENGTH_SHORT).show(); return; } String busStopID = getBusStopIDFromUri(uri); busStopSearchByIDEditText.setText(busStopID); createFragmentForStop(busStopID); } public void onHideHint(View v) { hideHints(); setOption(OPTION_SHOW_LEGEND, false); } public void onToggleKeyboardLayout(View v) { if (searchMode == SEARCH_BY_NAME) { setSearchModeBusStopID(); if (busStopSearchByIDEditText.requestFocus()) { showKeyboard(); } } else { // searchMode == SEARCH_BY_ID setSearchModeBusStopName(); if (busStopSearchByNameEditText.requestFocus()) { showKeyboard(); } } } ///////////////////////////////// OTHER STUFF ////////////////////////////////////////////////// @Override public void addLastStopToFavorites() { if(fh.getLastSuccessfullySearchedBusStop() != null) { new AsyncAddToFavorites(this).execute(fh.getLastSuccessfullySearchedBusStop()); } } ////////////////////////////////////// GUI HELPERS ///////////////////////////////////////////// @Override public void showKeyboard() { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); View view = searchMode == SEARCH_BY_ID ? busStopSearchByIDEditText : busStopSearchByNameEditText; imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); } @Override public void showMessage(int messageID) { Toast.makeText(getApplicationContext(), messageID, Toast.LENGTH_SHORT).show(); } private void setSearchModeBusStopID() { searchMode = SEARCH_BY_ID; busStopSearchByNameEditText.setVisibility(View.GONE); busStopSearchByNameEditText.setText(""); busStopSearchByIDEditText.setVisibility(View.VISIBLE); floatingActionButton.setImageResource(R.drawable.alphabetical); } private void setSearchModeBusStopName() { searchMode = SEARCH_BY_NAME; busStopSearchByIDEditText.setVisibility(View.GONE); busStopSearchByIDEditText.setText(""); busStopSearchByNameEditText.setVisibility(View.VISIBLE); floatingActionButton.setImageResource(R.drawable.numeric); } /** * Having that cursor at the left of the edit text makes me cancer. * @param busStopID bus stop ID */ private void setBusStopSearchByIDEditText(String busStopID) { busStopSearchByIDEditText.setText(busStopID); busStopSearchByIDEditText.setSelection(busStopID.length()); } private void showHints() { howDoesItWorkTextView.setVisibility(View.VISIBLE); hideHintButton.setVisibility(View.VISIBLE); actionHelpMenuItem.setVisible(false); } private void hideHints() { howDoesItWorkTextView.setVisibility(View.GONE); hideHintButton.setVisibility(View.GONE); actionHelpMenuItem.setVisible(true); } //TODO: toggle spinner from mainActivity @Override public void toggleSpinner(boolean enable) { if (enable) { //already set by the RefreshListener when needed //swipeRefreshLayout.setRefreshing(true); progressBar.setVisibility(View.VISIBLE); } else { swipeRefreshLayout.setRefreshing(false); progressBar.setVisibility(View.GONE); } } private void prepareGUIForBusLines() { swipeRefreshLayout.setEnabled(true); swipeRefreshLayout.setVisibility(View.VISIBLE); actionHelpMenuItem.setVisible(true); } private void prepareGUIForBusStops() { swipeRefreshLayout.setEnabled(false); swipeRefreshLayout.setVisibility(View.VISIBLE); actionHelpMenuItem.setVisible(false); } /** * This provides a temporary fix to make the transition * to a single asynctask go smoother * @param fragmentType the type of fragment created */ @Override public void readyGUIfor(String fragmentType) { hideKeyboard(); if(fragmentType==null) Log.e("ActivityMain","Problem with fragmentType"); else switch (fragmentType){ case ResultListFragment.TYPE_LINES: prepareGUIForBusLines(); if (getOption(OPTION_SHOW_LEGEND, true)) { showHints(); } break; case ResultListFragment.TYPE_STOPS: prepareGUIForBusStops(); break; default: Log.e("BusTO Activity","Called readyGUI with unsupported type of Fragment"); return; } // Shows hints } /** * Open an URL in the default browser. * * @param url URL */ public void openIceweasel(String url) { Intent browserIntent1 = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); startActivity(browserIntent1); } ///////////////////// 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; } } \ No newline at end of file diff --git a/src/it/reyboz/bustorino/middleware/DatabaseUpdateService.java b/src/it/reyboz/bustorino/middleware/DatabaseUpdateService.java index 9653bb6..92a78ce 100644 --- a/src/it/reyboz/bustorino/middleware/DatabaseUpdateService.java +++ b/src/it/reyboz/bustorino/middleware/DatabaseUpdateService.java @@ -1,225 +1,228 @@ /* 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.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 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){ Log.d(DEBUG_TAG,"Downloading the bus stops info"); final AtomicReference gres = new AtomicReference<>(); - getContentResolver().delete(Uri.parse("content://"+AppDataProvider.AUTHORITY+"/stops"),null,null); 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 { SharedPreferences.Editor ed = shPr.edit(); ed.putInt(DB_VERSION,newVersion); // BY COMMENTING THIS, THE APP WILL CONTINUOUSLY UPDATE THE DATABASE ed.apply(); } } else { Log.d(DEBUG_TAG,"No update needed"); } Log.d(DEBUG_TAG,"Finished update"); } } } public 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); + //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.insert(StopsTable.TABLE_NAME, null, 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"); 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); + //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"); 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