commit 972ccbf4a2e097fccbc7e570236c7fc62789b214 Author: Fabio Mazza Date: Tue May 5 16:38:43 2026 +0200 Fix crash where DB becomes blocked, add test for Palina Parcelable Summary: - Review code for Databases, using the same Singleton pattern and never closing them manually. This fixes a crash due to database lockup. - Make check for DB version inside the DB update checker worker - Refactor NearbyStopsFragment to use ViewModel, fixing case with database update running - Show correct message in Nearby if there is not a position found yet switching to Arrivals - Add test for Parcelable implementation of Palina Test Plan: On previous version, try deleting all application data and then reopening it. This should trigger the DB lockup. Check that in this diff the problem is gone. Reviewers: #libre_busto_hackers, valerio.bozzolan Reviewed By: #libre_busto_hackers, valerio.bozzolan Subscribers: valerio.bozzolan Project Tags: #libre_busto Differential Revision: https://gitpull.it/D235 diff --git a/app/src/androidTest/java/it/reyboz/bustorino/data/gtfs/ParcelableTest.java b/app/src/androidTest/java/it/reyboz/bustorino/data/gtfs/ParcelableTest.java new file mode 100644 index 0000000..ba2bcda --- /dev/null +++ b/app/src/androidTest/java/it/reyboz/bustorino/data/gtfs/ParcelableTest.java @@ -0,0 +1,64 @@ +package it.reyboz.bustorino.data.gtfs; + +import android.os.Parcel; +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 org.junit.Test; +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.List; + +public class ParcelableTest { + + @Test + public void testPalinaParcelableTransfer() { + + Palina p = new Palina("32", "TestPalina", "myname", "Via madre di dio", 9.211,-8.92, "gtt:none"); + ArrayList pass1,pass2; + pass1 = new ArrayList<>(); + pass2 = new ArrayList<>(); + for (int i = 0; i < 4; i++) { + pass1.add(Passaggio.newInstance(20, 9+5*i,true, Passaggio.Source.MatoAPI, 0)); + pass1.add(Passaggio.newInstance(20, 9+7*i,true, Passaggio.Source.MatoAPI, 0)); + } + Route r; + r= new Route("Mas","destinazione",pass1,Route.Type.BUS,"maiam"); + p.addRoute(r); + r = new Route("18","Max",pass2,Route.Type.TRAM,"descr"); + p.addRoute(r); + + Parcel parcel = Parcel.obtain(); + p.writeToParcel(parcel, p.describeContents()); + + parcel.setDataPosition(0); + + Palina p2 = Palina.CREATOR.createFromParcel(parcel); + + parcel.recycle(); + + assertEquals(p2.ID, p.ID); + assertEquals(p2.gtfsID, p.gtfsID); + assertEquals(p.location, p2.location); + assertEquals("Via madre di dio", p.location); + assertEquals(p.type, p2.type); + assertEquals(p.getLatitude(),p2.getLatitude()); + assertEquals(p.getLongitude(),p2.getLongitude()); + assertEquals(p.getTotalNumberOfPassages(),p2.getTotalNumberOfPassages()); + List rl1, rl2; + rl1 = p.queryAllRoutes(); + rl2 = p.queryAllRoutes(); + assertEquals(rl1.size(), rl2.size()); + Route x,y; + for (int i = 0; i < rl1.size(); i++) { + x = rl1.get(i); + y = rl2.get(i); + assertEquals(x.destinazione,y.destinazione); + assertEquals(x.type,y.type); + assertEquals(x.description,y.description); + assertEquals(x.getPassaggiToString(),y.getPassaggiToString()); + } + } +} diff --git a/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java b/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java index 2f6aee4..1c90d03 100644 --- a/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java +++ b/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java @@ -196,11 +196,9 @@ public class ActivityPrincipal extends GeneralActivity implements FragmentListen showingArrivalsFromIntent = true; } //database check - // THIS CHECK IS DUPLICATED, TODO: REMOVE - final boolean dataUpdateRequested = checkIfNeedSpecialUpgradeDB(); - if(!dataUpdateRequested) + // DatabaseUpdate.requestDBUpdateWithWork(this, false, false); - DBUpdateCheckWorker.Companion.schedulePeriodicCheck(this,false); + DBUpdateCheckWorker.Companion.schedulePeriodicCheck(this,false); /* Watch for database update */ @@ -325,26 +323,7 @@ public class ActivityPrincipal extends GeneralActivity implements FragmentListen } - private boolean checkIfNeedSpecialUpgradeDB(){ - final GtfsDatabase gtfsDB = GtfsDatabase.Companion.getGtfsDatabase(this); - - final int db_version = gtfsDB.getOpenHelper().getReadableDatabase().getVersion(); - boolean dataUpdateRequested = false; - final SharedPreferences theShPr = getMainSharedPreferences(); - final int old_version = PreferencesHolder.getGtfsDBVersion(theShPr); - Log.d(DEBUG_TAG, "GTFS Database: old version is "+old_version+ ", new version is "+db_version); - if (old_version < db_version){ - //decide update conditions in the future - if(old_version < 2 && db_version >= 2) { - dataUpdateRequested = true; - DBUpdateWorker.requestDBUpdateUniqueWork(this, true); - } - PreferencesHolder.setGtfsDBVersion(theShPr, db_version); - } - - return dataUpdateRequested; - } /** * Setup drawer actions diff --git a/app/src/main/java/it/reyboz/bustorino/backend/Palina.java b/app/src/main/java/it/reyboz/bustorino/backend/Palina.java index 66466a3..4031fb5 100644 --- a/app/src/main/java/it/reyboz/bustorino/backend/Palina.java +++ b/app/src/main/java/it/reyboz/bustorino/backend/Palina.java @@ -26,11 +26,9 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import java.io.Serializable; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; -import java.util.Comparator; import java.util.List; import it.reyboz.bustorino.util.LinesNameSorter; @@ -500,7 +498,7 @@ public class Palina extends Stop implements Parcelable { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeTypedList(routes); dest.writeByte((byte) (routesModified ? 1 : 0)); diff --git a/app/src/main/java/it/reyboz/bustorino/data/AppDataProvider.java b/app/src/main/java/it/reyboz/bustorino/data/AppDataProvider.java index dde6b78..a813606 100644 --- a/app/src/main/java/it/reyboz/bustorino/data/AppDataProvider.java +++ b/app/src/main/java/it/reyboz/bustorino/data/AppDataProvider.java @@ -21,9 +21,11 @@ import android.content.*; import android.database.Cursor; import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteDatabaseLockedException; import android.net.Uri; import android.util.Log; +import androidx.annotation.Nullable; import it.reyboz.bustorino.BuildConfig; import it.reyboz.bustorino.backend.DBStatusManager; import it.reyboz.bustorino.backend.Stop; @@ -140,12 +142,7 @@ public class AppDataProvider extends ContentProvider { 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(); @@ -193,7 +190,7 @@ public class AppDataProvider extends ContentProvider { public boolean onCreate() { con = getContext(); appDBHelper = NextGenDB.getInstance(getContext()); - userDBHelper = new UserDB(getContext()); + userDBHelper = UserDB.getInstance(getContext()); if(con!=null) { preferences = new DBStatusManager(con,null); } else { @@ -204,6 +201,7 @@ public class AppDataProvider extends ContentProvider { } @Override + @Nullable public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) throws UnsupportedOperationException,IllegalArgumentException { //IMPORTANT @@ -211,7 +209,15 @@ public class AppDataProvider extends ContentProvider { if(preferences.isDBUpdating(true)) //throw new UnsupportedOperationException("DB is updating"); return null; - SQLiteDatabase db = appDBHelper.getReadableDatabase(); + SQLiteDatabase db; + try{ + //try to get a readable database + db = appDBHelper.getReadableDatabase(); + } catch (SQLiteDatabaseLockedException ex){ + Log.e(DEBUG_TAG,"Database is locked",ex); + return null; + } + List parts = uri.getPathSegments(); switch (sUriMatcher.match(uri)){ case LOCATION_SEARCH: diff --git a/app/src/main/java/it/reyboz/bustorino/data/DBUpdateCheckWorker.kt b/app/src/main/java/it/reyboz/bustorino/data/DBUpdateCheckWorker.kt index 8b49f1b..d75129c 100644 --- a/app/src/main/java/it/reyboz/bustorino/data/DBUpdateCheckWorker.kt +++ b/app/src/main/java/it/reyboz/bustorino/data/DBUpdateCheckWorker.kt @@ -18,8 +18,10 @@ package it.reyboz.bustorino.data import android.content.Context +import android.content.SharedPreferences import android.util.Log import androidx.work.* +import it.reyboz.bustorino.data.gtfs.GtfsDatabase import java.util.concurrent.TimeUnit /** @@ -40,16 +42,27 @@ class DBUpdateCheckWorker(context: Context, workerParams: WorkerParameters) val neverUpdated = currentDBVersion < 0 || lastDBUpdateTime <= 0 val timeElapsed = currentTime > lastDBUpdateTime + UPDATE_MIN_DELAY - if (neverUpdated || timeElapsed) { + val isSpecialCase = checkIfNeedSpecialUpgradeDB(con) + + + if (neverUpdated || timeElapsed || isSpecialCase) { Log.d(DEBUG_TAG, "Scheduling DBUpdateWorker") DBUpdateWorker.requestDBUpdateUniqueWork(con, forced = true) - } else { + if(isSpecialCase){ + val gtfsDBVer = getGtfsDBVersion(con) + Log.d(DEBUG_TAG, "Set new gtfs database version $gtfsDBVer") + setGtfsDBVersionPreference(con, gtfsDBVer) + } + } + else { Log.d(DEBUG_TAG, "No update needed") } return Result.success() } + + companion object { const val DEBUG_TAG = "BusTO-DBUpdateScheduler" const val WORK_NAME = "DBUpdateChecker" @@ -74,5 +87,41 @@ class DBUpdateCheckWorker(context: Context, workerParams: WorkerParameters) .enqueueUniquePeriodicWork(WORK_NAME, policy, workRequest) } + @JvmStatic + private fun getGtfsDBVersion(context: Context): Int { + val gtfsDB = GtfsDatabase.Companion.getGtfsDatabase(context) + + val db_version = gtfsDB.openHelper.readableDatabase.version + return db_version + } + + @JvmStatic + private fun checkIfNeedSpecialUpgradeDB(context: Context): Boolean { + val db_version = getGtfsDBVersion(context) + var dataUpdateNeeded = false + val theShPr: SharedPreferences = PreferencesHolder.getMainSharedPreferences(context); + //applicationContext.getMainSharedPreferences() + + val old_version = PreferencesHolder.getGtfsDBVersion(theShPr) + Log.d( + DEBUG_TAG, + "GTFS Database: old version is $old_version, new version is $db_version" + ) + if (old_version < db_version) { + //decide update conditions in the future + if (old_version < 2 && db_version >= 2) { + dataUpdateNeeded = true + //DBUpdateWorker.requestDBUpdateUniqueWork(this, true) + } + //PreferencesHolder.setGtfsDBVersion(theShPr, db_version) + } + + return dataUpdateNeeded + } + @JvmStatic + private fun setGtfsDBVersionPreference(context: Context, version: Int) { + val theShPr: SharedPreferences = PreferencesHolder.getMainSharedPreferences(context); + PreferencesHolder.setGtfsDBVersion(theShPr, version) + } } } diff --git a/app/src/main/java/it/reyboz/bustorino/data/DatabaseUpdate.java b/app/src/main/java/it/reyboz/bustorino/data/DatabaseUpdate.java index d002457..92824e0 100644 --- a/app/src/main/java/it/reyboz/bustorino/data/DatabaseUpdate.java +++ b/app/src/main/java/it/reyboz/bustorino/data/DatabaseUpdate.java @@ -270,8 +270,9 @@ public class DatabaseUpdate { long endTime = System.currentTimeMillis(); Log.d(DEBUG_TAG, "Inserting stops took: " + ((double) (endTime - startTime) / 1000) + " s"); Log.d(DEBUG_TAG, "\t"+patternsStopsHits+" routes string were built from the patterns"); - db.close(); - dbHelp.close(); + // These should NOT be closed: the database is a singleton, the connections are recycled. + //db.close(); + //dbHelp.close(); return DatabaseUpdate.Result.DONE; } diff --git a/app/src/main/java/it/reyboz/bustorino/data/NextGenDB.java b/app/src/main/java/it/reyboz/bustorino/data/NextGenDB.java index 147e4d6..8e288b7 100644 --- a/app/src/main/java/it/reyboz/bustorino/data/NextGenDB.java +++ b/app/src/main/java/it/reyboz/bustorino/data/NextGenDB.java @@ -22,10 +22,7 @@ import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.SQLException; -import android.database.sqlite.SQLiteConstraintException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.*; import android.net.Uri; import android.provider.BaseColumns; import android.util.Log; @@ -105,7 +102,7 @@ public class NextGenDB extends SQLiteOpenHelper{ } public static NextGenDB getInstance(Context context) { if (INSTANCE == null){ - INSTANCE = new NextGenDB(context); + INSTANCE = new NextGenDB(context.getApplicationContext()); } return INSTANCE; } @@ -179,7 +176,7 @@ public class NextGenDB extends SQLiteOpenHelper{ * double lngFrom = bb.getLonWestE6() / 1E6; * double lngTo = bb.getLonEastE6() / 1E6; */ - public synchronized ArrayList queryAllInsideMapView(double minLat, double maxLat, double minLng, double maxLng) { + public synchronized ArrayList queryAllInsideMapView(double minLat, double maxLat, double minLng, double maxLng) throws SQLiteDatabaseLockedException { ArrayList stops = new ArrayList<>(); SQLiteDatabase db = this.getReadableDatabase(); @@ -415,7 +412,7 @@ public class NextGenDB extends SQLiteOpenHelper{ endtime = System.currentTimeMillis(); Log.d("DataDownload", "Inserted connections found, took " + (endtime - starttime) + " ms, inserted " + rows + " rows"); } - nextGenDB.close(); + //nextGenDB.close(); return true; } /* diff --git a/app/src/main/java/it/reyboz/bustorino/data/OldDataRepository.kt b/app/src/main/java/it/reyboz/bustorino/data/OldDataRepository.kt index fb1f894..19d2b35 100644 --- a/app/src/main/java/it/reyboz/bustorino/data/OldDataRepository.kt +++ b/app/src/main/java/it/reyboz/bustorino/data/OldDataRepository.kt @@ -18,6 +18,7 @@ package it.reyboz.bustorino.data import android.content.Context +import androidx.sqlite.SQLiteException import it.reyboz.bustorino.backend.Result import it.reyboz.bustorino.backend.Stop import it.reyboz.bustorino.backend.utils @@ -51,16 +52,21 @@ class OldDataRepository(private val executor: Executor, latitTo: Double, longitFrom: Double, longitTo: Double, - callback: Callback> + callback: Callback> ){ //Log.d(DEBUG_TAG, "Async Stop Fetcher started working"); executor.execute { - val stops = nextGenDB.queryAllInsideMapView( - latitFrom, latitTo, - longitFrom, longitTo - ) + var result = ArrayList() + try { + result = nextGenDB.queryAllInsideMapView( + latitFrom, latitTo, + longitFrom, longitTo + ) + } catch (e: SQLiteException){ + callback.onComplete(Result.failure(e)) + } - callback.onComplete(Result.success(stops)) + callback.onComplete(Result.success(result)) } } diff --git a/app/src/main/java/it/reyboz/bustorino/data/UserDB.java b/app/src/main/java/it/reyboz/bustorino/data/UserDB.java index b8faf60..8a02ddf 100644 --- a/app/src/main/java/it/reyboz/bustorino/data/UserDB.java +++ b/app/src/main/java/it/reyboz/bustorino/data/UserDB.java @@ -20,6 +20,7 @@ package it.reyboz.bustorino.data; import android.content.ContentValues; import android.database.Cursor; +import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; @@ -53,10 +54,18 @@ public class UserDB extends SQLiteOpenHelper { private static final Uri FAVORITES_URI = AppDataProvider.getUriBuilderToComplete().appendPath( AppDataProvider.FAVORITES).build(); + private static UserDB mInstance; - public UserDB(Context context) { + public static synchronized UserDB getInstance(Context context) { + if (mInstance == null) { + mInstance = new UserDB(context.getApplicationContext()); + } + return mInstance; + } + + private UserDB(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); - this.c = context; + this.c = context.getApplicationContext(); } @Override @@ -169,15 +178,13 @@ public class UserDB extends SQLiteOpenHelper { boolean found = false; try { - Cursor c = db.query(TABLE_NAME, usernameColumnNameAsArray, "ID = ?", new String[] {stopId}, null, null, null); - - if(c.moveToNext()) { - found = true; - } - - c.close(); - } catch(SQLiteException ignored) { + // better way to check the existence + long count = DatabaseUtils.queryNumEntries(db, TABLE_NAME, "ID = ?", + new String[]{stopId}); + return count > 0; + } catch(SQLiteException e) { // don't care + Log.w("BusTO-UserDB", "isStopInFavorites failed for " + stopId, e); } return found; @@ -389,8 +396,8 @@ public class UserDB extends SQLiteOpenHelper { } db.setTransactionSuccessful(); db.endTransaction(); - - db.close(); + // These should NOT be closed: the database is a singleton, the connections are recycled. + //db.close(); return updated; } diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/BackupImportFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/BackupImportFragment.kt index 01dcc9d..ed3573f 100644 --- a/app/src/main/java/it/reyboz/bustorino/fragments/BackupImportFragment.kt +++ b/app/src/main/java/it/reyboz/bustorino/fragments/BackupImportFragment.kt @@ -121,7 +121,7 @@ class BackupImportFragment : Fragment() { contentResolver.openOutputStream(uri)?.use { OutputStreamWriter(it).use {wr-> val csvWriter = CsvWriter.builder().build(wr) - val userDB = UserDB(context) + val userDB = UserDB.getInstance(context) userDB.writeFavoritesToCsv(csvWriter) csvWriter.close() Toast.makeText(context, R.string.saved_data, Toast.LENGTH_SHORT).show() @@ -173,7 +173,7 @@ class BackupImportFragment : Fragment() { zipOutputStream.putNextEntry(ZipEntry(FAVORITES_NAME)) val outWriter = OutputStreamWriter(zipOutputStream) val csvWriter = CsvWriter.builder().build(outWriter) - val userDB = UserDB(context) + val userDB = UserDB.getInstance(context) userDB.writeFavoritesToCsv(csvWriter) outWriter.flush() zipOutputStream.closeEntry() @@ -198,11 +198,12 @@ class BackupImportFragment : Fragment() { val reader = InputStreamReader(zipstream) val csvReader = CsvReader.builder().ofCsvRecord(reader) - val userDB = UserDB(context) + val userDB = UserDB.getInstance(context) val updated = userDB.insertRowsFromCSV(csvReader) - userDB.close() + //userDB.close() //csvReader.close() + Log.d(DEBUG_TAG, "Inserted $updated rows into UserDB - Favorites") } APP_PREF_NAME -> if(loadPreferences){ val jsonString = readFileToString(zipstream) @@ -258,10 +259,10 @@ class BackupImportFragment : Fragment() { InputStreamReader(it).use { stream -> val csvReader = CsvReader.builder().ofCsvRecord(stream) - val userDB = UserDB(context) + val userDB = UserDB.getInstance(context) val updated = userDB.insertRowsFromCSV(csvReader) Toast.makeText(context, "Read $updated favorites", Toast.LENGTH_SHORT).show() - userDB.close() + //userDB.close() csvReader.close() } } diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/FragmentHelper.java b/app/src/main/java/it/reyboz/bustorino/fragments/FragmentHelper.java index 3a0c77c..5c3b498 100644 --- a/app/src/main/java/it/reyboz/bustorino/fragments/FragmentHelper.java +++ b/app/src/main/java/it/reyboz/bustorino/fragments/FragmentHelper.java @@ -253,7 +253,7 @@ public class FragmentHelper { 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(); + Toast.makeText(context, messageID, length).show(); } private void showShortToast(int messageID){ showToastMessage(messageID, true); diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java index 96392f0..6259bad 100644 --- a/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java +++ b/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java @@ -25,11 +25,9 @@ import android.location.Location; import android.location.LocationManager; import android.os.Bundle; -import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.location.LocationListenerCompat; -import androidx.core.location.LocationManagerCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; @@ -51,10 +49,8 @@ import it.reyboz.bustorino.R; import it.reyboz.bustorino.adapters.ArrivalsStopAdapter; import it.reyboz.bustorino.backend.*; import it.reyboz.bustorino.data.DatabaseUpdate; -import it.reyboz.bustorino.middleware.AppLocationManager; import it.reyboz.bustorino.adapters.SquareStopAdapter; import it.reyboz.bustorino.middleware.AutoFitGridLayoutManager; -import it.reyboz.bustorino.util.LocationCriteria; import it.reyboz.bustorino.util.Permissions; import it.reyboz.bustorino.util.StopSorterByDistance; import it.reyboz.bustorino.viewmodels.NearbyStopsViewModel; @@ -97,9 +93,8 @@ public class NearbyStopsFragment extends Fragment { private AutoFitGridLayoutManager gridLayoutManager; private GPSPoint lastPosition = null; private ProgressBar circlingProgressBar,flatProgressBar; - private int distance = 10; - protected SharedPreferences globalSharedPref; - private SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener; + //protected SharedPreferences globalSharedPref; + //private SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener; private TextView messageTextView,titleTextView, loadingTextView; private CommonScrollListener scrollListener; private AppCompatButton switchButton; @@ -116,8 +111,6 @@ public class NearbyStopsFragment extends Fragment { private NearbyArrivalsDownloader arrivalsManager = null; private ArrivalsStopAdapter arrivalsStopAdapter = null; - private boolean dbUpdateRunning = false; - private ArrayList currentNearbyStops = new ArrayList<>(); private NearbyArrivalsDownloader nearbyArrivalsDownloader; @@ -181,8 +174,8 @@ public class NearbyStopsFragment extends Fragment { locManager = (LocationManager) requireContext().getSystemService(Context.LOCATION_SERVICE); fragmentLocationListener = new FragmentLocationListener(); if (getContext()!=null) { - globalSharedPref = getContext().getSharedPreferences(getString(R.string.mainSharedPreferences), Context.MODE_PRIVATE); - globalSharedPref.registerOnSharedPreferenceChangeListener(preferenceChangeListener); + //globalSharedPref = getContext().getSharedPreferences(getString(R.string.mainSharedPreferences), Context.MODE_PRIVATE); + //globalSharedPref.registerOnSharedPreferenceChangeListener(preferenceChangeListener); } nearbyArrivalsDownloader = new NearbyArrivalsDownloader(getContext().getApplicationContext(), arrivalsListener); @@ -209,40 +202,49 @@ public class NearbyStopsFragment extends Fragment { scrollListener = new CommonScrollListener(mListener,false); switchButton.setOnClickListener(v -> switchFragmentType()); - Log.d(DEBUG_TAG, "onCreateView"); + if(BuildConfig.DEBUG) + Log.d(DEBUG_TAG, "onCreateView"); final Context appContext =requireContext().getApplicationContext(); DatabaseUpdate.watchUpdateWorkStatus(getContext(), this, new Observer>() { @SuppressLint("MissingPermission") @Override public void onChanged(List workInfos) { - if(workInfos.isEmpty()) return; + if(workInfos.isEmpty()) { + viewModel.setDBUpdateRunning(false); + return; + } WorkInfo wi = workInfos.get(0); if (wi.getState() == WorkInfo.State.RUNNING && fragmentLocationListener.isRegistered) { locManager.removeUpdates(fragmentLocationListener); fragmentLocationListener.isRegistered = true; - dbUpdateRunning = true; + viewModel.setDBUpdateRunning(true); } else{ //start the request if(!fragmentLocationListener.isRegistered){ requestLocationUpdates(); } - dbUpdateRunning = false; + viewModel.setDBUpdateRunning(false); + //actually restart request } } }); //observe the livedata viewModel.getStopsAtDistance().observe(getViewLifecycleOwner(), stops -> { - if (!dbUpdateRunning && (stops.size() < MIN_NUM_STOPS && distance <= MAX_DISTANCE)) { - distance = distance + 40; - viewModel.requestStopsAtDistance(distance, true); + Log.d(DEBUG_TAG, "Received "+stops.size()+" stops nearby"); + Integer distance = viewModel.getDistanceMtLiveData().getValue(); + if(distance == null){ + distance = 40; + } + if ((stops.size() < MIN_NUM_STOPS && distance <= MAX_DISTANCE)) { + viewModel.setDistance(distance + 40); + //viewModel.requestStopsAtDistance(distance, true); //Log.d(DEBUG_TAG, "Doubling distance now!"); - return; + return; // THIS WORKS AS AN `else` } if(!stops.isEmpty()) { - Log.d(DEBUG_TAG, "Showing "+stops.size()+" stops nearby"); currentNearbyStops =stops; showStopsInViews(currentNearbyStops, lastPosition); } @@ -290,7 +292,8 @@ public class NearbyStopsFragment extends Fragment { } private void setShowingStatus(@NonNull LocationShowingStatus newStatus){ if(newStatus == showingStatus){ - Log.d(DEBUG_TAG, "Asked to set new displaying status but it's the same"); + if(BuildConfig.DEBUG) + Log.d(DEBUG_TAG, "Asked to set new displaying status but it's the same"); return; } switch (newStatus){ @@ -352,14 +355,6 @@ public class NearbyStopsFragment extends Fragment { @Override public void onResume() { super.onResume(); - try{ - if(!dbUpdateRunning && !fragmentLocationListener.isRegistered) { - requestLocationUpdates(); - } - } catch (SecurityException ex){ - //ignored - //try another location provider - } //fix view if we were showing the stops or the arrivals prepareForFragmentType(); switch(fragment_type){ @@ -488,7 +483,10 @@ public class NearbyStopsFragment extends Fragment { fragmentLocationListener.lastUpdateTime = -1; //locManager.removeLocationRequestFor(fragmentLocationListener); //locManager.addLocationRequestFor(fragmentLocationListener); - showStopsInViews(currentNearbyStops, lastPosition); + if(lastPosition!=null) { + // we have at least one fix on the position + showStopsInViews(currentNearbyStops, lastPosition); + } } /** @@ -598,25 +596,18 @@ public class NearbyStopsFragment extends Fragment { public boolean isRegistered = false; @Override - public void onLocationChanged(Location location) { - //set adapter - - if(location==null){ - Log.e(DEBUG_TAG, "Location is null, cannot request stops"); - return; - } else if(viewModel==null){ + public void onLocationChanged(@NonNull Location location) { + if(viewModel==null){ return; } - if(location.getAccuracy()<200 && !dbUpdateRunning) { - if(viewModel.getDistanceMtLiveData().getValue()==null){ - //never run request - distance = 40; - } + if(location.getAccuracy()<200) { + lastPosition = new GPSPoint(location.getLatitude(), location.getLongitude()); - viewModel.requestStopsAtDistance(location.getLatitude(), location.getLongitude(), distance, true); + //viewModel.requestStopsAtDistance(location.getLatitude(), location.getLongitude(), distance, true); + viewModel.setLastLocation(location); } lastUpdateTime = System.currentTimeMillis(); - Log.d("BusTO:NearPositListen","can start request for stops: "+ !dbUpdateRunning); + //Log.d("BusTO:NearPositListen","can start request for stops: "+ !dbUpdateRunning); } @Override @@ -636,50 +627,8 @@ public class NearbyStopsFragment extends Fragment { } @Override - public void onStatusChanged(@NonNull @NotNull String provider, int status, @Nullable @org.jetbrains.annotations.Nullable Bundle extras) { + public void onStatusChanged(@NonNull String provider, int status, @Nullable Bundle extras) { LocationListenerCompat.super.onStatusChanged(provider, status, extras); } - /* - @Override - public void onLocationStatusChanged(int status) { - switch(status){ - case AppLocationManager.LOCATION_GPS_AVAILABLE: - messageTextView.setVisibility(View.GONE); - - break; - case AppLocationManager.LOCATION_UNAVAILABLE: - messageTextView.setText(R.string.enableGpsText); - messageTextView.setVisibility(View.VISIBLE); - break; - default: - Log.e(DEBUG_TAG,"Location status not recognized"); - } - } - - @Override - public @NotNull LocationCriteria getLocationCriteria() { - - return new LocationCriteria(200,TIME_INTERVAL_REQUESTS); - } - - @Override - public long getLastUpdateTimeMillis() { - return lastUpdateTime; - } - void resetUpdateTime(){ - lastUpdateTime = -1; - } - - @Override - public void onLocationProviderAvailable() { - - } - - @Override - public void onLocationDisabled() { - - } - - */ } } diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/ResultListFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/ResultListFragment.java index 5c79087..66bf32f 100644 --- a/app/src/main/java/it/reyboz/bustorino/fragments/ResultListFragment.java +++ b/app/src/main/java/it/reyboz/bustorino/fragments/ResultListFragment.java @@ -112,7 +112,7 @@ public class ResultListFragment extends Fragment{ // no stop no party if(busStopId != null) { - SQLiteDatabase userDB = new UserDB(getContext()).getReadableDatabase(); + SQLiteDatabase userDB = UserDB.getInstance(getContext()).getReadableDatabase(); found = UserDB.isStopInFavorites(userDB, busStopId); } diff --git a/app/src/main/java/it/reyboz/bustorino/middleware/AsyncArrivalsSearcher.java b/app/src/main/java/it/reyboz/bustorino/middleware/AsyncArrivalsSearcher.java index 623d2a1..023651c 100644 --- a/app/src/main/java/it/reyboz/bustorino/middleware/AsyncArrivalsSearcher.java +++ b/app/src/main/java/it/reyboz/bustorino/middleware/AsyncArrivalsSearcher.java @@ -333,7 +333,8 @@ public class AsyncArrivalsSearcher extends AsyncTask { if(stop != null) { // get a writable database - UserDB userDatabase = new UserDB(context); + UserDB userDatabase = UserDB.getInstance(context); SQLiteDatabase db = userDatabase.getWritableDatabase(); // eventually toggle the status @@ -99,8 +99,8 @@ public class AsyncStopFavoriteAction extends AsyncTask { result = UserDB.deleteStop(stop, db); } - // please sir, close the door - db.close(); + // These should NOT be closed: the database is a singleton, the connections are recycled. + //db.close(); } return result; diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/NearbyStopsViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/NearbyStopsViewModel.kt index 0da572d..eb35c2e 100644 --- a/app/src/main/java/it/reyboz/bustorino/viewmodels/NearbyStopsViewModel.kt +++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/NearbyStopsViewModel.kt @@ -4,7 +4,9 @@ import android.app.Application import android.location.Location import android.util.Log import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData +import it.reyboz.bustorino.BuildConfig import it.reyboz.bustorino.backend.GPSPoint import it.reyboz.bustorino.backend.Stop import it.reyboz.bustorino.data.OldDataRepository @@ -21,16 +23,30 @@ class NearbyStopsViewModel(application: Application): AndroidViewModel(applicati val distanceMtLiveData = MutableLiveData(40) - val stopsAtDistance = MutableLiveData>() + val stopsAtDistance = MediatorLiveData>() + + private val dbUpdateRunning = MutableLiveData(false) private val callback = OldDataRepository.Callback> { res -> if(res.isSuccess){ stopsAtDistance.postValue(res.result) - Log.d(DEBUG_TAG, "Setting value of stops in bounding box") + if(BuildConfig.DEBUG) + Log.d(DEBUG_TAG, "Setting value of stops in bounding box") } } + fun setLastLocation(location: Location) { + locationLiveData.value = GPSPoint(location.latitude, location.longitude) + } + fun setDistance(distance: Int) { + distanceMtLiveData.value = distance + } + fun setDBUpdateRunning(running: Boolean) { + dbUpdateRunning.value = (running) + } + + /** * Request stop in location [latitude], [longitude], at distance [distanceMeters] * If [saveValues] is true, store the position and the distance used @@ -55,18 +71,66 @@ class NearbyStopsViewModel(application: Application): AndroidViewModel(applicati locationLiveData.value!!.longitude, distanceMeters, callback) } + fun requestStopsCheckDBRunning(position: GPSPoint, distanceMt: Int){ + if(dbUpdateRunning.value==null || !(dbUpdateRunning.value!!)){ + oldRepo.requestStopsWithinDistance(position.latitude, position.longitude, distanceMt, callback) + } else{ + Log.d(DEBUG_TAG, "Database update is running, cannot do it") + } + } + - fun setLocation(location: Location){ + fun postLocation(location: Location){ locationLiveData.postValue(GPSPoint(location.latitude, location.longitude)) } - fun setLocation(location: GPSPoint){ + fun postLocation(location: GPSPoint){ locationLiveData.postValue(location) } - fun setLastDistance(distanceMeters: Int){ + fun postLastDistance(distanceMeters: Int){ distanceMtLiveData.postValue(distanceMeters) } + init { + stopsAtDistance.addSource(locationLiveData){ point-> + if(BuildConfig.DEBUG) Log.d(DEBUG_TAG, "New location: $point") + val distance = distanceMtLiveData.value ?: 40 + //oldRepo.requestStopsWithinDistance(point.latitude, point.longitude, distance, callback) + requestStopsCheckDBRunning(point, distance) + } + + stopsAtDistance.addSource(distanceMtLiveData){ dist-> + if(BuildConfig.DEBUG) Log.d(DEBUG_TAG, "New distance: $dist") + if(locationLiveData.value != null){ + val point: GPSPoint = locationLiveData.value!! + //oldRepo.requestStopsWithinDistance(point.latitude, point.longitude, dist, callback) + requestStopsCheckDBRunning(point, dist) + } else{ + Log.d(DEBUG_TAG, "Modified distance but locationLiveData value is null") + } + } + stopsAtDistance.addSource(dbUpdateRunning){ running -> + if(BuildConfig.DEBUG) Log.d(DEBUG_TAG, "DB update running: $running") + if(!running) { + reRequestStops() + } + } + } + + + private fun reRequestStops(){ + var req = false + locationLiveData.value?.let{ point -> + distanceMtLiveData.value ?.let { dist-> + req = true + oldRepo.requestStopsWithinDistance(point.latitude, point.longitude, dist, callback) + } + + } + if(!req){ + Log.w(DEBUG_TAG, "Requested to rerun stops, but position or distance (or both) are null") + } + } companion object{