diff --git a/app/src/main/java/it/reyboz/bustorino/ActivityIntro.kt b/app/src/main/java/it/reyboz/bustorino/ActivityIntro.kt --- a/app/src/main/java/it/reyboz/bustorino/ActivityIntro.kt +++ b/app/src/main/java/it/reyboz/bustorino/ActivityIntro.kt @@ -14,6 +14,7 @@ import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator +import it.reyboz.bustorino.data.DBUpdateCheckWorker import it.reyboz.bustorino.data.PreferencesHolder import it.reyboz.bustorino.fragments.IntroFragment import it.reyboz.bustorino.middleware.GeneralActivity @@ -31,7 +32,8 @@ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_intro) - + //check default settings and apply them + checkApplyDefaultSettingsValues() viewPager = findViewById(R.id.viewPager) btnBackward = findViewById(R.id.btnPrevious) btnForward = findViewById(R.id.btnNext) @@ -104,6 +106,9 @@ findViewById(R.id.theConstraintLayout), this.applyBottomAndBordersInsetsListener ) + // Start the DB Update now + DBUpdateCheckWorker.schedulePeriodicCheck(this, true) + } diff --git a/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java b/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java --- a/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java +++ b/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java @@ -29,6 +29,7 @@ import android.view.*; import android.widget.Toast; +import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBarDrawerToggle; @@ -41,19 +42,15 @@ import androidx.fragment.app.FragmentTransaction; import androidx.preference.PreferenceManager; import androidx.work.WorkInfo; -import androidx.work.WorkManager; import com.google.android.material.navigation.NavigationView; import com.google.android.material.snackbar.Snackbar; import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; import it.reyboz.bustorino.backend.Stop; -import it.reyboz.bustorino.backend.utils; +import it.reyboz.bustorino.data.DBUpdateCheckWorker; import it.reyboz.bustorino.data.DBUpdateWorker; -import it.reyboz.bustorino.data.DatabaseUpdate; import it.reyboz.bustorino.data.PreferencesHolder; import it.reyboz.bustorino.data.gtfs.GtfsDatabase; import it.reyboz.bustorino.fragments.*; @@ -74,6 +71,13 @@ private boolean showingMainFragmentFromOther = false; private boolean onCreateComplete = false; + private final OnBackPressedCallback callback = new OnBackPressedCallback(false) { + @Override + public void handleOnBackPressed() { + activityCustomBackPressed(); + } + }; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -84,6 +88,10 @@ getWindow().setNavigationBarContrastEnforced(false); } */ + + //onBackPressed solution required from Android 16 + callback.setEnabled(true); + this.getOnBackPressedDispatcher().addCallback( callback); boolean showingArrivalsFromIntent = false; final Toolbar mToolbar = findViewById(R.id.default_toolbar); @@ -184,31 +192,15 @@ showingArrivalsFromIntent = true; } //database check - 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; - DatabaseUpdate.requestDBUpdateWithWork(this, true, true); - } - PreferencesHolder.setGtfsDBVersion(theShPr, db_version); - } - //Try (hopefully) database update + // THIS CHECK IS DUPLICATED, TODO: REMOVE + final boolean dataUpdateRequested = checkIfNeedSpecialUpgradeDB(); if(!dataUpdateRequested) - DatabaseUpdate.requestDBUpdateWithWork(this, false, false); - + // DatabaseUpdate.requestDBUpdateWithWork(this, false, false); + DBUpdateCheckWorker.Companion.schedulePeriodicCheck(this,false); /* Watch for database update */ - final WorkManager workManager = WorkManager.getInstance(this); - workManager.getWorkInfosForUniqueWorkLiveData(DBUpdateWorker.DEBUG_TAG) + DBUpdateWorker.getWorkInfoLiveData(this) .observe(this, workInfoList -> { // If there are no matching work info, do nothing if (workInfoList == null || workInfoList.isEmpty()) { @@ -223,7 +215,6 @@ break; } } - if (showProgress) { createDefaultSnackbar(); } else { @@ -258,7 +249,7 @@ onCreateComplete = true; //last but not least, set the good default values - setDefaultSettingsValuesWhenMissing(); + checkApplyDefaultSettingsValues(); // handle the device "insets" ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.rootRelativeLayout), (v, windowInsets) -> { Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); @@ -308,7 +299,10 @@ } + + //check if first run activity (IntroActivity) has been started once or not + final SharedPreferences theShPr = getMainSharedPreferences(); boolean hasIntroRun = theShPr.getBoolean(PreferencesHolder.PREF_INTRO_ACTIVITY_RUN,false); if(!hasIntroRun){ startIntroductionActivity(); @@ -321,6 +315,27 @@ } + 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 * @param navigationView the navigation view on which to set the callbacks @@ -450,9 +465,16 @@ } - @Override + /*@Override public void onBackPressed() { - boolean foundFragment = false; + if (!activityCustomBackPressed()) + super.onBackPressed(); + } + + */ + + private boolean activityCustomBackPressed(){ + boolean resolved = true; Fragment shownFrag = getSupportFragmentManager().findFragmentById(R.id.mainActContentFrame); if (mDrawer.isDrawerOpen(GravityCompat.START)) mDrawer.closeDrawer(GravityCompat.START); @@ -472,12 +494,15 @@ getSupportFragmentManager().popBackStack(); Log.d(DEBUG_TAG, "Popping main frame backstack for fragments"); } - else - super.onBackPressed(); + else{ + resolved = false; + } + return resolved; } /** * Create and show the SnackBar with the message + * The fragment shown points to which view to attach the snackbar */ private void createDefaultSnackbar() { @@ -821,45 +846,4 @@ } } - /** - * Adjust setting to match the default ones - */ - private void setDefaultSettingsValuesWhenMissing(){ - SharedPreferences mainSharedPref = PreferenceManager.getDefaultSharedPreferences(this); - SharedPreferences.Editor editor = mainSharedPref.edit(); - //Main fragment to show - String screen = mainSharedPref.getString(SettingsFragment.PREF_KEY_STARTUP_SCREEN, ""); - boolean edit = false; - if (screen.isEmpty()){ - editor.putString(SettingsFragment.PREF_KEY_STARTUP_SCREEN, "arrivals"); - edit=true; - } - //Fetchers - final Set setSelected = mainSharedPref.getStringSet(SettingsFragment.KEY_ARRIVALS_FETCHERS_USE, new HashSet<>()); - if (setSelected.isEmpty()){ - String[] defaultVals = getResources().getStringArray(R.array.arrivals_sources_values_default); - editor.putStringSet(SettingsFragment.KEY_ARRIVALS_FETCHERS_USE, utils.convertArrayToSet(defaultVals)); - edit=true; - } - //Live bus positions - final String keySourcePositions=getString(R.string.pref_positions_source); - final String positionsSource = mainSharedPref.getString(keySourcePositions, ""); - if(positionsSource.isEmpty()){ - String[] defaultVals = getResources().getStringArray(R.array.positions_source_values); - editor.putString(keySourcePositions, defaultVals[0]); - edit=true; - } - //Map style - final String mapStylePref = mainSharedPref.getString(SettingsFragment.LIBREMAP_STYLE_PREF_KEY, ""); - if(mapStylePref.isEmpty()){ - final String[] defaultVals = getResources().getStringArray(R.array.map_style_pref_values); - editor.putString(SettingsFragment.LIBREMAP_STYLE_PREF_KEY, defaultVals[0]); - edit=true; - } - if (edit){ - editor.commit(); - } - - - } } diff --git a/app/src/main/java/it/reyboz/bustorino/backend/Notifications.java b/app/src/main/java/it/reyboz/bustorino/backend/Notifications.java --- a/app/src/main/java/it/reyboz/bustorino/backend/Notifications.java +++ b/app/src/main/java/it/reyboz/bustorino/backend/Notifications.java @@ -93,7 +93,7 @@ manager.cancel(notificationID); } - public static void createDBNotificationChannel(Context context){ + public static void createDBNotificationChannelIfNeeded(Context context){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( Notifications.DB_UPDATE_CHANNELS_ID, diff --git a/app/src/main/java/it/reyboz/bustorino/data/DBUpdateCheckWorker.kt b/app/src/main/java/it/reyboz/bustorino/data/DBUpdateCheckWorker.kt new file mode 100644 --- /dev/null +++ b/app/src/main/java/it/reyboz/bustorino/data/DBUpdateCheckWorker.kt @@ -0,0 +1,78 @@ +/* + BusTO - Data components + Copyright (C) 2021-2026 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.content.Context +import android.util.Log +import androidx.work.* +import java.util.concurrent.TimeUnit + +/** + * Lightweight periodic worker that checks local state and enqueues [DBUpdateWorker] + * only when an update is actually needed, without making any network calls itself. + */ +class DBUpdateCheckWorker(context: Context, workerParams: WorkerParameters) + : CoroutineWorker(context, workerParams) { + + override suspend fun doWork(): Result { + val con = applicationContext + val sharedPrefs = PreferencesHolder.getMainSharedPreferences(con) + + val currentDBVersion = sharedPrefs.getInt(PreferencesHolder.DB_GTT_VERSION_KEY, -10) + val lastDBUpdateTime = sharedPrefs.getLong(PreferencesHolder.DB_LAST_UPDATE_KEY, 0L) + val currentTime = System.currentTimeMillis() / 1000 + + val neverUpdated = currentDBVersion < 0 || lastDBUpdateTime <= 0 + val timeElapsed = currentTime > lastDBUpdateTime + UPDATE_MIN_DELAY + + if (neverUpdated || timeElapsed) { + Log.d(DEBUG_TAG, "Scheduling DBUpdateWorker") + DBUpdateWorker.requestDBUpdateUniqueWork(con, forced = true) + } else { + Log.d(DEBUG_TAG, "No update needed") + } + + return Result.success() + } + + companion object { + const val DEBUG_TAG = "BusTO-DBUpdateScheduler" + const val WORK_NAME = "DBUpdateChecker" + + private const val UPDATE_MIN_DELAY = (3 * 24 * 3600L) // + + fun schedulePeriodicCheck(context: Context, restart: Boolean = false) { + val workRequest = PeriodicWorkRequest.Builder( + DBUpdateCheckWorker::class.java, 1, TimeUnit.DAYS + ) + .setConstraints( + Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + ) + .build() + + val policy = if (restart) ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE + else ExistingPeriodicWorkPolicy.KEEP + + WorkManager.getInstance(context) + .enqueueUniquePeriodicWork(WORK_NAME, policy, workRequest) + } + + } +} diff --git a/app/src/main/java/it/reyboz/bustorino/data/DBUpdateWorker.java b/app/src/main/java/it/reyboz/bustorino/data/DBUpdateWorker.java deleted file mode 100644 --- a/app/src/main/java/it/reyboz/bustorino/data/DBUpdateWorker.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - BusTO - Data components - Copyright (C) 2021 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.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; - -//TODO: Move to code to Kotlin -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 int ERROR_CODE_DB_CLOSED=-2; - - 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 static int NOTIFIC_ID =32198; - - public static final String FORCED_UPDATE = "FORCED-UPDATE"; - - public static final String DEBUG_TAG = "Busto-UpdateWorker"; - - private static final long UPDATE_MIN_DELAY= 9*24*3600; //9 days - - - 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); - //Use the new notification channels - Notifications.createNotificationChannel(con,con.getString(R.string.database_notification_channel), - con.getString(R.string.database_notification_channel_desc), NotificationManagerCompat.IMPORTANCE_LOW, - Notifications.DB_UPDATE_CHANNELS_ID - ); - final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext()); - final int notification_ID = 32198; - final SharedPreferences shPr = con.getSharedPreferences(con.getString(R.string.mainSharedPreferences),MODE_PRIVATE); - final int current_DB_version = shPr.getInt(PreferencesHolder.DB_GTT_VERSION_KEY,-10); - - final int new_DB_version = DatabaseUpdate.getNewVersion(); - - final boolean isUpdateCompulsory = getInputData().getBoolean(FORCED_UPDATE,false); - - final long lastDBUpdateTime = shPr.getLong(PreferencesHolder.DB_LAST_UPDATE_KEY, 0); - long currentTime = System.currentTimeMillis()/1000; - - //showNotification(notificationManager, notification_ID); - final NotificationCompat.Builder builder = new NotificationCompat.Builder(con, - Notifications.DB_UPDATE_CHANNELS_ID) - .setContentTitle(con.getString(R.string.database_update_msg_notif)) - .setProgress(0,0,true) - .setPriority(NotificationCompat.PRIORITY_LOW); - builder.setSmallIcon(R.drawable.ic_bus_stilized); - - - notificationManager.notify(notification_ID,builder.build()); - - Log.d(DEBUG_TAG, "Have previous version: "+current_DB_version +" and new version "+new_DB_version); - Log.d(DEBUG_TAG, "Update compulsory: "+isUpdateCompulsory); - /* - SKIP CHECK (Reason: The Old API might fail at any moment) - 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 ListenableWorker.Result.failure(out); - } - */ - - //we got a good version - if (!(current_DB_version < new_DB_version || currentTime > lastDBUpdateTime + UPDATE_MIN_DELAY ) - && !isUpdateCompulsory) { - //don't need to update - cancelNotification(notification_ID); - return ListenableWorker.Result.success(new Data.Builder(). - putInt(SUCCESS_REASON_KEY, SUCCESS_NO_ACTION_NEEDED).build()); - } - //start the real update - 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(); - 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; - case DB_CLOSED: - dataBuilder.put(ERROR_REASON_KEY, ERROR_CODE_DB_CLOSED); - break; - } - cancelNotification(notification_ID); - 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(PreferencesHolder.DB_GTT_VERSION_KEY, new_DB_version); - currentTime = System.currentTimeMillis()/1000; - editor.putLong(PreferencesHolder.DB_LAST_UPDATE_KEY, currentTime); - editor.apply(); - cancelNotification(notification_ID); - - 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(@NonNull final NotificationManagerCompat notificManager, final int notification_ID, - final String channel_ID){ - final NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), channel_ID) - .setContentTitle("Libre BusTO - Updating Database") - .setProgress(0,0,true) - .setPriority(NotificationCompat.PRIORITY_LOW); - builder.setSmallIcon(R.drawable.ic_bus_orange); - - - notificManager.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/app/src/main/java/it/reyboz/bustorino/data/DBUpdateWorker.kt b/app/src/main/java/it/reyboz/bustorino/data/DBUpdateWorker.kt new file mode 100644 --- /dev/null +++ b/app/src/main/java/it/reyboz/bustorino/data/DBUpdateWorker.kt @@ -0,0 +1,205 @@ +/* + BusTO - Data components + Copyright (C) 2021-2026 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.annotation.SuppressLint +import android.content.Context +import android.content.pm.ServiceInfo +import android.os.Build +import android.util.Log +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.lifecycle.LiveData +import androidx.work.* +import androidx.work.WorkManager.Companion.getInstance +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 + +/** + * Worker class that runs the DB update, without checking if it is needed or not + */ +class DBUpdateWorker(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) { + + @SuppressLint("RestrictedApi") + override suspend fun doWork(): Result { + val con = applicationContext + val sharedPrefs = con.getSharedPreferences(con.getString(R.string.mainSharedPreferences), Context.MODE_PRIVATE) + val newDBVersion = DatabaseUpdate.getNewVersion() + + /*val currentDBVersion = sharedPrefs.getInt(PreferencesHolder.DB_GTT_VERSION_KEY, -10) + + val isUpdateCompulsory = inputData.getBoolean(FORCED_UPDATE, false) + + val lastDBUpdateTime = sharedPrefs.getLong(PreferencesHolder.DB_LAST_UPDATE_KEY, 0) + var currentTime = System.currentTimeMillis() / 1000 + + // ---- RECREATE NOTIFICATION HERE IF YOU WANT TO SHOW IT TO THE USER ---- + // ---- create notification channel first + Log.d(DEBUG_TAG, "Have previous version: $currentDBVersion and new version $newDBVersion") + Log.d(DEBUG_TAG, "Update compulsory: $isUpdateCompulsory") + + + //we got a good version + if (!(currentDBVersion < newDBVersion || currentTime > lastDBUpdateTime + UPDATE_MIN_DELAY) + && !isUpdateCompulsory + ) { + //don't need to update + //cancelNotification(NOTIFICATION_ID) + return Result.success( + Data.Builder().putInt + (SUCCESS_REASON_KEY, SUCCESS_NO_ACTION_NEEDED).build() + ) + } + + */ + //start the real update + val resultAtomicReference = AtomicReference() + + DatabaseUpdate.setDBUpdatingFlag(con, sharedPrefs, true) + val resultUpdate = DatabaseUpdate.performDBUpdate(con, resultAtomicReference) + DatabaseUpdate.setDBUpdatingFlag(con, sharedPrefs, false) + + if (resultUpdate != DatabaseUpdate.Result.DONE) { + //Fetcher.Result result = resultAtomicReference.get(); + val dataBuilder = Data.Builder() + when (resultUpdate) { + DatabaseUpdate.Result.ERROR_STOPS_DOWNLOAD -> dataBuilder.put(ERROR_REASON_KEY, ERROR_DOWNLOADING_STOPS) + DatabaseUpdate.Result.ERROR_LINES_DOWNLOAD -> dataBuilder.put(ERROR_REASON_KEY, ERROR_DOWNLOADING_LINES) + DatabaseUpdate.Result.DB_CLOSED -> dataBuilder.put(ERROR_REASON_KEY, ERROR_CODE_DB_CLOSED) + DatabaseUpdate.Result.DONE -> {} + } + //cancelNotification(NOTIFICATION_ID) + return Result.failure(dataBuilder.build()) + } + Log.d(DEBUG_TAG, "Update finished successfully!") + //update the version in the shared preference + val editor = sharedPrefs.edit() + editor.putInt(PreferencesHolder.DB_GTT_VERSION_KEY, newDBVersion) + val currentTime = System.currentTimeMillis() / 1000 + editor.putLong(PreferencesHolder.DB_LAST_UPDATE_KEY, currentTime) + editor.apply() + //cancelNotification(NOTIFICATION_ID) + + return Result.success(Data.Builder().putInt(SUCCESS_REASON_KEY, SUCCESS_UPDATE_DONE).build()) + } + + override suspend fun getForegroundInfo(): ForegroundInfo { + //val notificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val context = applicationContext + Notifications.createDBNotificationChannelIfNeeded(context) + + val builder = NotificationCompat.Builder( + context, + Notifications.DB_UPDATE_CHANNELS_ID + ) + .setContentTitle(context.getString(R.string.database_update_msg_notif)) + .setProgress(0, 0, true) + .setPriority(NotificationCompat.PRIORITY_LOW) + builder.setSmallIcon(R.drawable.ic_bus_stilized) + + /*val typeInt = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC + } else 0 + + */ + + return ForegroundInfo(NOTIFICATION_ID, builder.build()) + } + + /* + private int showNotification(@NonNull final NotificationManagerCompat notificManager, final int notification_ID, + final String channel_ID){ + final NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), channel_ID) + .setContentTitle("Libre BusTO - Updating Database") + .setProgress(0,0,true) + .setPriority(NotificationCompat.PRIORITY_LOW); + builder.setSmallIcon(R.drawable.ic_bus_orange); + + + notificManager.notify(notification_ID,builder.build()); + + return notification_ID; + } + */ + private fun cancelNotification(notificationID: Int) { + val notificationManager = NotificationManagerCompat.from(getApplicationContext()) + + notificationManager.cancel(notificationID) + } + + companion object { + const val ERROR_CODE_KEY: String = "Error_Code" + const val ERROR_REASON_KEY: String = "ERROR_REASON" + const val ERROR_FETCHING_VERSION: Int = 4 + const val ERROR_DOWNLOADING_STOPS: Int = 5 + const val ERROR_DOWNLOADING_LINES: Int = 6 + val ERROR_CODE_DB_CLOSED: Int = -2 + + const val SUCCESS_REASON_KEY: String = "SUCCESS_REASON" + const val SUCCESS_NO_ACTION_NEEDED: Int = 9 + const val SUCCESS_UPDATE_DONE: Int = 1 + + const val FORCED_UPDATE: String = "FORCED-UPDATE" + + private const val DEBUG_TAG: String = "BusTO-UpdateWorker" + const val STATUS_UPDATE: String = "STATUS_UPDATE" + const val WORK_NAME = "BusTO-UpdateWorker" + private const val NOTIFICATION_ID = 32198 + + + private const val UPDATE_MIN_DELAY = (9 * 24 * 3600 //9 days + ).toLong() + + + val workConstraints: Constraints + get() = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED) + .setRequiresCharging(false).build() + + /** + * Run the database update immediately + */ + @JvmStatic + fun requestDBUpdateUniqueWork(con: Context, forced: Boolean) { + + val workManager = getInstance(con) + val reqData = Data.Builder() + .putBoolean(FORCED_UPDATE, forced).build() + + val wr = OneTimeWorkRequest.Builder(DBUpdateWorker::class.java) + .setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.MINUTES) + .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) + .setConstraints( + Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED) + .build() + ) + .setInputData(reqData) + .build() + + workManager.enqueueUniqueWork(WORK_NAME, ExistingWorkPolicy.REPLACE, wr) + } + + @JvmStatic + fun getWorkInfoLiveData(context: Context): LiveData> { + val workManager = WorkManager.getInstance(context) + return workManager.getWorkInfosForUniqueWorkLiveData(WORK_NAME) + } + } +} diff --git a/app/src/main/java/it/reyboz/bustorino/data/DatabaseUpdate.java b/app/src/main/java/it/reyboz/bustorino/data/DatabaseUpdate.java --- a/app/src/main/java/it/reyboz/bustorino/data/DatabaseUpdate.java +++ b/app/src/main/java/it/reyboz/bustorino/data/DatabaseUpdate.java @@ -45,7 +45,6 @@ import org.json.JSONObject; import java.util.*; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import static android.content.Context.MODE_PRIVATE; @@ -60,7 +59,7 @@ - enum Result { + public enum Result { DONE, ERROR_STOPS_DOWNLOAD, ERROR_LINES_DOWNLOAD, DB_CLOSED } @@ -287,18 +286,17 @@ return editor.commit(); } - /** + /* * Request update using workmanager framework * @param con the context to use * @param forced if you want to force the request to go now - */ public static void requestDBUpdateWithWork(Context con,boolean restart, boolean forced){ final SharedPreferences theShPr = PreferencesHolder.getMainSharedPreferences(con); final WorkManager workManager = WorkManager.getInstance(con); final Data reqData = new Data.Builder() .putBoolean(DBUpdateWorker.FORCED_UPDATE, forced).build(); - PeriodicWorkRequest wr = new PeriodicWorkRequest.Builder(DBUpdateWorker.class, 7, TimeUnit.DAYS) + PeriodicWorkRequest wr = new PeriodicWorkRequest.Builder(DBUpdateWorker.class, 2, TimeUnit.DAYS) .setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.MINUTES) .setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED) .build()) @@ -307,11 +305,12 @@ final int version = theShPr.getInt(PreferencesHolder.DB_GTT_VERSION_KEY, -10); final long lastDBUpdateTime = theShPr.getLong(PreferencesHolder.DB_LAST_UPDATE_KEY, -10); if ((version >= 0 || lastDBUpdateTime >=0) && !restart) - workManager.enqueueUniquePeriodicWork(DBUpdateWorker.DEBUG_TAG, + workManager.enqueueUniquePeriodicWork(DBUpdateWorker.WORK_NAME, ExistingPeriodicWorkPolicy.KEEP, wr); - else workManager.enqueueUniquePeriodicWork(DBUpdateWorker.DEBUG_TAG, + else workManager.enqueueUniquePeriodicWork(DBUpdateWorker.WORK_NAME, ExistingPeriodicWorkPolicy.REPLACE, wr); } + */ /* public static boolean isDBUpdating(){ return false; @@ -321,8 +320,7 @@ public static void watchUpdateWorkStatus(Context context, @NonNull LifecycleOwner lifecycleOwner, @NonNull Observer> observer) { - WorkManager workManager = WorkManager.getInstance(context); - workManager.getWorkInfosForUniqueWorkLiveData(DBUpdateWorker.DEBUG_TAG).observe( + DBUpdateWorker.getWorkInfoLiveData(context).observe( lifecycleOwner, observer ); } diff --git a/app/src/main/java/it/reyboz/bustorino/data/GtfsMaintenanceWorker.kt b/app/src/main/java/it/reyboz/bustorino/data/GtfsMaintenanceWorker.kt --- a/app/src/main/java/it/reyboz/bustorino/data/GtfsMaintenanceWorker.kt +++ b/app/src/main/java/it/reyboz/bustorino/data/GtfsMaintenanceWorker.kt @@ -1,10 +1,6 @@ package it.reyboz.bustorino.data -import android.app.NotificationChannel -import android.app.NotificationManager import android.content.Context -import android.os.Build -import android.widget.Toast import androidx.core.app.NotificationCompat import androidx.work.* import it.reyboz.bustorino.R @@ -30,17 +26,9 @@ return Result.success() } override suspend fun getForegroundInfo(): ForegroundInfo { - val notificationManager = - applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val context = applicationContext - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channel = NotificationChannel( - Notifications.DB_UPDATE_CHANNELS_ID, - context.getString(R.string.database_notification_channel), - NotificationManager.IMPORTANCE_MIN - ) - notificationManager.createNotificationChannel(channel) - } + Notifications.createDBNotificationChannelIfNeeded(context) val notification = NotificationCompat.Builder(context, Notifications.DB_UPDATE_CHANNELS_ID) //.setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), Constants.PENDING_INTENT_FLAG_IMMUTABLE)) diff --git a/app/src/main/java/it/reyboz/bustorino/data/MatoPatternsDownloadWorker.kt b/app/src/main/java/it/reyboz/bustorino/data/MatoPatternsDownloadWorker.kt --- a/app/src/main/java/it/reyboz/bustorino/data/MatoPatternsDownloadWorker.kt +++ b/app/src/main/java/it/reyboz/bustorino/data/MatoPatternsDownloadWorker.kt @@ -58,7 +58,7 @@ val notificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val context = applicationContext - Notifications.createDBNotificationChannel(context) + Notifications.createDBNotificationChannelIfNeeded(context) return ForegroundInfo(NOTIFICATION_ID, Notifications.makeMatoDownloadNotification(context)) } diff --git a/app/src/main/java/it/reyboz/bustorino/data/MatoTripsDownloadWorker.kt b/app/src/main/java/it/reyboz/bustorino/data/MatoTripsDownloadWorker.kt --- a/app/src/main/java/it/reyboz/bustorino/data/MatoTripsDownloadWorker.kt +++ b/app/src/main/java/it/reyboz/bustorino/data/MatoTripsDownloadWorker.kt @@ -21,7 +21,6 @@ import android.content.Context import android.util.Log import androidx.work.* -import com.android.volley.toolbox.ClearCacheRequest import it.reyboz.bustorino.backend.Notifications import it.reyboz.bustorino.data.gtfs.GtfsTrip import java.util.concurrent.CountDownLatch @@ -119,7 +118,7 @@ val notificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val context = applicationContext - Notifications.createDBNotificationChannel(context) + Notifications.createDBNotificationChannelIfNeeded(context) return ForegroundInfo(NOTIFICATION_ID, Notifications.makeMatoDownloadNotification(context)) } diff --git a/app/src/main/java/it/reyboz/bustorino/data/NextGenDB.java b/app/src/main/java/it/reyboz/bustorino/data/NextGenDB.java --- a/app/src/main/java/it/reyboz/bustorino/data/NextGenDB.java +++ b/app/src/main/java/it/reyboz/bustorino/data/NextGenDB.java @@ -139,7 +139,8 @@ db.execSQL(SQL_CREATE_BRANCH_TABLE); db.execSQL(SQL_CREATE_CONNECTIONS_TABLE); - DatabaseUpdate.requestDBUpdateWithWork(appContext, true, true); + //DatabaseUpdate.requestDBUpdateWithWork(appContext, true, true); + DBUpdateWorker.requestDBUpdateUniqueWork(appContext, true); } if(oldVersion < 3 && newVersion == 3){ Log.d("BusTO-Database", "Running upgrades for version 3"); diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt --- a/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt +++ b/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt @@ -16,7 +16,6 @@ import androidx.lifecycle.Lifecycle import androidx.recyclerview.widget.RecyclerView import androidx.work.WorkInfo -import androidx.work.WorkManager import com.google.android.flexbox.FlexDirection import com.google.android.flexbox.FlexboxLayoutManager import com.google.android.flexbox.JustifyContent @@ -200,7 +199,7 @@ arrows[k]?.setOnClickListener { openLinesAndCloseOthersIfNeeded(k) } } // watch for the db update - WorkManager.getInstance(requireContext()).getWorkInfosForUniqueWorkLiveData(DBUpdateWorker.DEBUG_TAG).observe(viewLifecycleOwner){ + DBUpdateWorker.getWorkInfoLiveData(requireContext()).observe(viewLifecycleOwner){ workInfoList -> if (workInfoList == null || workInfoList.isEmpty()) { return@observe @@ -438,8 +437,4 @@ return rotate } } - - override fun showSnackbarOnDBUpdate(): Boolean { - return false - } } \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/SettingsFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/SettingsFragment.java --- a/app/src/main/java/it/reyboz/bustorino/fragments/SettingsFragment.java +++ b/app/src/main/java/it/reyboz/bustorino/fragments/SettingsFragment.java @@ -36,7 +36,7 @@ import androidx.work.WorkManager; import it.reyboz.bustorino.ActivityBackup; import it.reyboz.bustorino.R; -import it.reyboz.bustorino.data.DatabaseUpdate; +import it.reyboz.bustorino.data.DBUpdateWorker; import it.reyboz.bustorino.data.GtfsMaintenanceWorker; import org.jetbrains.annotations.NotNull; @@ -94,9 +94,9 @@ if (dbUpdateNow!=null) dbUpdateNow.setOnPreferenceClickListener( preference -> { - //trigger update + //force update if(getContext()!=null) { - DatabaseUpdate.requestDBUpdateWithWork(getContext().getApplicationContext(), true, true); + DBUpdateWorker.requestDBUpdateUniqueWork(requireContext(), true); Toast.makeText(getContext(),R.string.requesting_db_update,Toast.LENGTH_SHORT).show(); return true; } diff --git a/app/src/main/java/it/reyboz/bustorino/middleware/GeneralActivity.java b/app/src/main/java/it/reyboz/bustorino/middleware/GeneralActivity.java --- a/app/src/main/java/it/reyboz/bustorino/middleware/GeneralActivity.java +++ b/app/src/main/java/it/reyboz/bustorino/middleware/GeneralActivity.java @@ -29,6 +29,7 @@ import androidx.core.view.OnApplyWindowInsetsListener; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; +import androidx.preference.PreferenceManager; import com.google.android.material.snackbar.Snackbar; import androidx.annotation.Nullable; @@ -42,10 +43,13 @@ import android.widget.Toast; import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; import it.reyboz.bustorino.R; import it.reyboz.bustorino.backend.utils; import it.reyboz.bustorino.data.PreferencesHolder; +import it.reyboz.bustorino.fragments.SettingsFragment; /** * Activity class that contains all the generally useful methods @@ -227,4 +231,44 @@ return WindowInsetsCompat.CONSUMED; }; + /** + * Adjust setting to match the default ones + */ + protected void checkApplyDefaultSettingsValues(){ + SharedPreferences mainSharedPref = PreferenceManager.getDefaultSharedPreferences(this); + SharedPreferences.Editor editor = mainSharedPref.edit(); + //Main fragment to show + String screen = mainSharedPref.getString(SettingsFragment.PREF_KEY_STARTUP_SCREEN, ""); + boolean edit = false; + if (screen.isEmpty()){ + editor.putString(SettingsFragment.PREF_KEY_STARTUP_SCREEN, "arrivals"); + edit=true; + } + //Fetchers + final Set setSelected = mainSharedPref.getStringSet(SettingsFragment.KEY_ARRIVALS_FETCHERS_USE, new HashSet<>()); + if (setSelected.isEmpty()){ + String[] defaultVals = getResources().getStringArray(R.array.arrivals_sources_values_default); + editor.putStringSet(SettingsFragment.KEY_ARRIVALS_FETCHERS_USE, utils.convertArrayToSet(defaultVals)); + edit=true; + } + //Live bus positions + final String keySourcePositions=getString(R.string.pref_positions_source); + final String positionsSource = mainSharedPref.getString(keySourcePositions, ""); + if(positionsSource.isEmpty()){ + String[] defaultVals = getResources().getStringArray(R.array.positions_source_values); + editor.putString(keySourcePositions, defaultVals[0]); + edit=true; + } + //Map style + final String mapStylePref = mainSharedPref.getString(SettingsFragment.LIBREMAP_STYLE_PREF_KEY, ""); + if(mapStylePref.isEmpty()){ + final String[] defaultVals = getResources().getStringArray(R.array.map_style_pref_values); + editor.putString(SettingsFragment.LIBREMAP_STYLE_PREF_KEY, defaultVals[0]); + edit=true; + } + if (edit){ + editor.commit(); + } + + } }