diff --git a/res/drawable/ic_baseline_settings_24.xml b/res/drawable/ic_baseline_settings_24.xml
new file mode 100644
index 0000000..b240b83
--- /dev/null
+++ b/res/drawable/ic_baseline_settings_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/res/layout/activity_about.xml b/res/layout/activity_about.xml
index ff57657..5c8bf4f 100644
--- a/res/layout/activity_about.xml
+++ b/res/layout/activity_about.xml
@@ -1,16 +1,22 @@
+
+
+
\ No newline at end of file
diff --git a/res/layout/new_main_activity.xml b/res/layout/activity_principal.xml
similarity index 100%
rename from res/layout/new_main_activity.xml
rename to res/layout/activity_principal.xml
diff --git a/res/layout/fragment_main_screen.xml b/res/layout/fragment_main_screen.xml
index e7ca44e..746319c 100644
--- a/res/layout/fragment_main_screen.xml
+++ b/res/layout/fragment_main_screen.xml
@@ -1,163 +1,164 @@
+ tools:context=".fragments.MainScreenFragment"
+ android:paddingTop="10dip"
+ >
+ />
+ android:id="@+id/busStopSearchByNameEditText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="5dp"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="5dip"
+ android:layout_toEndOf="@+id/QRButton"
+ android:layout_toLeftOf="@+id/searchButton"
+ android:layout_toRightOf="@+id/QRButton"
+ android:layout_toStartOf="@+id/searchButton"
+
+ android:ems="10"
+ android:hint="@string/insert_bus_stop_name"
+ android:imeOptions="actionSearch"
+ android:singleLine="true"
+ android:visibility="gone">
+ />
\ No newline at end of file
diff --git a/res/menu/drawer_main.xml b/res/menu/drawer_main.xml
index 0b6840e..c055a84 100644
--- a/res/menu/drawer_main.xml
+++ b/res/menu/drawer_main.xml
@@ -1,19 +1,24 @@
\ No newline at end of file
diff --git a/res/menu/extra_menu_items.xml b/res/menu/extra_menu_items.xml
new file mode 100644
index 0000000..f0d6fc0
--- /dev/null
+++ b/res/menu/extra_menu_items.xml
@@ -0,0 +1,24 @@
+
+
\ No newline at end of file
diff --git a/res/values/theme.xml b/res/values/theme.xml
index da27fe5..29e8921 100644
--- a/res/values/theme.xml
+++ b/res/values/theme.xml
@@ -1,24 +1,24 @@
-
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/ActivityAbout.java b/src/it/reyboz/bustorino/ActivityAbout.java
index 783a792..d0babcf 100644
--- a/src/it/reyboz/bustorino/ActivityAbout.java
+++ b/src/it/reyboz/bustorino/ActivityAbout.java
@@ -1,59 +1,59 @@
/*
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 androidx.appcompat.widget.Toolbar;
import androidx.core.app.NavUtils;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import android.text.Html;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.os.Bundle;
import android.view.MenuItem;
import android.widget.TextView;
public class ActivityAbout extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
Spanned htmlText = Html.fromHtml(getResources().getString(
R.string.about_history));
TextView aboutTextView = (TextView) findViewById(R.id.aboutTextView);
assert aboutTextView != null;
aboutTextView.setText(htmlText);
aboutTextView.setMovementMethod(LinkMovementMethod.getInstance());
- // Back button
- ActionBar ab = getSupportActionBar();
- assert ab != null;
- ab.setDisplayHomeAsUpEnabled(true);
+ Toolbar mToolbar = findViewById(R.id.default_toolbar);
+ setSupportActionBar(mToolbar);
+ if (getSupportActionBar()!=null)
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- // Respond to the action bar's Up/Home button
- case android.R.id.home:
- NavUtils.navigateUpFromSameTask(this);
- return true;
+ // Respond to the action bar's Up/Home button
+ if (item.getItemId() == android.R.id.home) {//NavUtils.navigateUpFromSameTask(this);
+ onBackPressed();
+ return true;
}
return super.onOptionsItemSelected(item);
}
}
diff --git a/src/it/reyboz/bustorino/ActivityMain.java b/src/it/reyboz/bustorino/ActivityMain.java
index bb1e395..568e345 100644
--- a/src/it/reyboz/bustorino/ActivityMain.java
+++ b/src/it/reyboz/bustorino/ActivityMain.java
@@ -1,895 +1,840 @@
/*
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.Manifest;
+import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.sqlite.SQLiteDatabase;
import android.location.*;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.preference.PreferenceManager;
import androidx.work.BackoffPolicy;
import androidx.work.Constraints;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;
import com.google.android.material.snackbar.Snackbar;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.core.app.NavUtils;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.util.Log;
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.google.android.material.floatingactionbutton.FloatingActionButton;
import it.reyboz.bustorino.backend.*;
import it.reyboz.bustorino.data.DBUpdateWorker;
import it.reyboz.bustorino.data.DatabaseUpdate;
import it.reyboz.bustorino.data.UserDB;
import it.reyboz.bustorino.fragments.*;
import it.reyboz.bustorino.middleware.*;
+import it.reyboz.bustorino.util.Permissions;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
+import static it.reyboz.bustorino.backend.utils.getBusStopIDFromUri;
+
public class ActivityMain extends GeneralActivity implements FragmentListenerMain {
/*
* Layout elements
*/
private EditText busStopSearchByIDEditText;
private EditText busStopSearchByNameEditText;
private ProgressBar progressBar;
private TextView howDoesItWorkTextView;
private Button hideHintButton;
private MenuItem actionHelpMenuItem, experimentsMenuItem;
private SwipeRefreshLayout swipeRefreshLayout;
private FloatingActionButton floatingActionButton;
private FragmentManager framan;
private Snackbar snackbar;
/*
* Search mode
*/
private static final int SEARCH_BY_NAME = 0;
private static final int SEARCH_BY_ID = 1;
private static final int SEARCH_BY_ROUTE = 2; // TODO: implement this -- https://gitpull.it/T12
private int searchMode;
private ImageButton addToFavorites;
/*
* Options
*/
private final String OPTION_SHOW_LEGEND = "show_legend";
private static final String DEBUG_TAG = "BusTO - MainActivity";
/* // 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 ArrivalsFetcher[] arrivalsFetchers = new ArrivalsFetcher[]{new FiveTAPIFetcher(), new GTTJSONFetcher(), new FiveTScraperFetcher()};
private StopsFinderByName[] stopsFinderByNames = new StopsFinderByName[]{new GTTStopsFetcher(), new FiveTStopsFetcher()};
/*
* Position
*/
//Fine location criteria
private final Criteria cr = new Criteria();
private boolean pendingNearbyStopsRequest = false;
private LocationManager locmgr;
private FragmentHelper fh;
///////////////////////////////// EVENT HANDLERS ///////////////////////////////////////////////
/*
* @see swipeRefreshLayout
*/
private final Handler theHandler = new Handler();
- private final Runnable refreshing = new Runnable() {
+ private final Runnable refreshStop = new Runnable() {
public void run() {
if (framan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) {
ArrivalsFragment fragment = (ArrivalsFragment) framan.findFragmentById(R.id.resultFrame);
- String stopName = fragment.getStopID();
+ if (fragment == null){
+ new AsyncDataDownload(fh, arrivalsFetchers, getApplicationContext()).execute();
+ } else{
+ String stopName = fragment.getStopID();
- new AsyncDataDownload(fh, fragment.getCurrentFetchersAsArray()).execute(stopName);
+ new AsyncDataDownload(fh, fragment.getCurrentFetchersAsArray(), getApplicationContext()).execute(stopName);
+ }
} else //we create a new fragment, which is WRONG
- new AsyncDataDownload(fh, arrivalsFetchers).execute();
+ new AsyncDataDownload(fh, arrivalsFetchers, getApplicationContext()).execute();
}
};
//// MAIN METHOD ///
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
framan = getSupportFragmentManager();
final SharedPreferences theShPr = getMainSharedPreferences();
/*
* UI
*/
setContentView(R.layout.activity_main);
Toolbar defToolbar = findViewById(R.id.that_toolbar);
setSupportActionBar(defToolbar);
busStopSearchByIDEditText = findViewById(R.id.busStopSearchByIDEditText);
busStopSearchByNameEditText = findViewById(R.id.busStopSearchByNameEditText);
progressBar = findViewById(R.id.progressBar);
howDoesItWorkTextView = findViewById(R.id.howDoesItWorkTextView);
hideHintButton = findViewById(R.id.hideHintButton);
swipeRefreshLayout = findViewById(R.id.listRefreshLayout);
floatingActionButton = findViewById(R.id.floatingActionButton);
framan.addOnBackStackChangedListener(() -> Log.d("MainActivity, BusTO", "BACK STACK CHANGED"));
busStopSearchByIDEditText.setSelectAllOnFocus(true);
busStopSearchByIDEditText
.setOnEditorActionListener((v, actionId, event) -> {
// IME_ACTION_SEARCH alphabetical option
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
onSearchClick(v);
return true;
}
return false;
});
busStopSearchByNameEditText
.setOnEditorActionListener((v, actionId, 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(() -> theHandler.post(refreshing));
+ .setOnRefreshListener(() -> theHandler.post(refreshStop));
/**
* @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);
+ fh = new FragmentHelper(this, framan, getApplicationContext(),R.id.resultFrame);
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
// JUST DON'T
// showKeyboard();
// You haven't obtained anything... from an intent?
if (tryedFromIntent) {
// This shows a luser warning
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);
*/
requestArrivalsForStopID(busStopID);
}
//Try (hopefully) database update
- //TODO: Check if service shows the notification
- //Old code for the db update
- //DatabaseUpdateService.startDBUpdate(getApplicationContext());
PeriodicWorkRequest wr = new PeriodicWorkRequest.Builder(DBUpdateWorker.class, 1, TimeUnit.DAYS)
.setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES)
.setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.build();
final WorkManager workManager = WorkManager.getInstance(this);
final int version = theShPr.getInt(DatabaseUpdate.DB_VERSION_KEY, -10);
if (version >= 0)
workManager.enqueueUniquePeriodicWork(DBUpdateWorker.DEBUG_TAG,
ExistingPeriodicWorkPolicy.KEEP, wr);
else workManager.enqueueUniquePeriodicWork(DBUpdateWorker.DEBUG_TAG,
ExistingPeriodicWorkPolicy.REPLACE, wr);
/*
Set database update
*/
workManager.getWorkInfosForUniqueWorkLiveData(DBUpdateWorker.DEBUG_TAG)
.observe(this, workInfoList -> {
// If there are no matching work info, do nothing
if (workInfoList == null || workInfoList.isEmpty()) {
return;
}
Log.d(DEBUG_TAG, "WorkerInfo: "+workInfoList);
boolean showProgress = false;
for (WorkInfo workInfo : workInfoList) {
if (workInfo.getState() == WorkInfo.State.RUNNING) {
showProgress = true;
}
}
if (showProgress) {
createDefaultSnackbar();
} else {
if(snackbar!=null) {
snackbar.dismiss();
snackbar = null;
}
}
});
//locationHandler = new GPSLocationAdapter(getApplicationContext());
//--------- NEARBY STOPS--------//
//SETUP LOCATION
locmgr = (LocationManager) getSystemService(LOCATION_SERVICE);
cr.setAccuracy(Criteria.ACCURACY_FINE);
cr.setAltitudeRequired(false);
cr.setBearingRequired(false);
cr.setCostAllowed(true);
cr.setPowerRequirement(Criteria.NO_REQUIREMENT);
//We want the nearby bus stops!
- theHandler.post(new NearbyStopsRequester());
+ theHandler.post(new NearbyStopsRequester(this));
//If there are no providers available, then, wait for them
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 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
* LocationManager locManager = (LocationManager) getSystemService(LOCATION_SERVICE);
* Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.resultFrame);
*
*
* }
* //show the FAB since it remains hidden
* floatingActionButton.show();
*
* }
**/
@Override
protected void onPause() {
super.onPause();
fh.stopLastRequestIfNeeded();
fh.setBlockAllActivities(true);
locmgr.removeUpdates(locListener);
}
@Override
protected void onResume() {
super.onResume();
fh.setBlockAllActivities(false);
//TODO: check if current LiveData-bound observer works
if (pendingNearbyStopsRequest)
- theHandler.post(new NearbyStopsRequester());
+ theHandler.post(new NearbyStopsRequester(this));
ActionBar bar = getSupportActionBar();
if(bar!=null) bar.show();
else Log.w(DEBUG_TAG, "ACTION BAR IS NULL");
//check if we can display the experiments or not
SharedPreferences shPr = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
boolean exper_On = shPr.getBoolean(getString(R.string.pref_key_experimental), false);
//Log.w(DEBUG_TAG, "Preference experimental is "+exper_On);
//MenuItem experimentsItem =
if (experimentsMenuItem != null)
experimentsMenuItem.setVisible(exper_On);
}
@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);
experimentsMenuItem = menu.findItem(R.id.action_experiments);
SharedPreferences shPr = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
boolean exper_On = shPr.getBoolean(getString(R.string.pref_key_experimental), false);
experimentsMenuItem.setVisible(exper_On);
return true;
}
/**
* Callback fired when a MenuItem is selected
*
* @param item
* @return
*/
@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.
Resources res = getResources();
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_map:
//ensure storage permission is granted
final String permission = Manifest.permission.WRITE_EXTERNAL_STORAGE;
int result = askForPermissionIfNeeded(permission, STORAGE_PERMISSION_REQ);
switch (result) {
case PERMISSION_OK:
startActivity(new Intent(ActivityMain.this, ActivityMap.class));
break;
case PERMISSION_ASKING:
permissionDoneRunnables.put(permission,
() -> startActivity(new Intent(ActivityMain.this, ActivityMap.class)));
break;
case PERMISSION_NEG_CANNOT_ASK:
String storage_perm = res.getString(R.string.storage_permission);
String text = res.getString(R.string.too_many_permission_asks, storage_perm);
Toast.makeText(getApplicationContext(),text, Toast.LENGTH_LONG).show();
}
return true;
case R.id.action_about:
startActivity(new Intent(ActivityMain.this, ActivityAbout.class));
return true;
case R.id.action_hack:
openIceweasel(res.getString(R.string.hack_url));
return true;
case R.id.action_source:
openIceweasel("https://gitpull.it/source/libre-busto/");
return true;
case R.id.action_licence:
openIceweasel("https://www.gnu.org/licenses/gpl-3.0.html");
return true;
case R.id.action_donate:
openIceweasel("https://www.liberapay.com/Libre_BusTO/");
return true;
case R.id.action_settings:
Log.d("MAINBusTO", "Pressed button preferences");
startActivity(new Intent(ActivityMain.this, ActivitySettings.class));
return true;
case R.id.action_experiments:
startActivity(new Intent(this, ActivityPrincipal.class));
}
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();
requestArrivalsForStopID(busStopID);
} else { // searchMode == SEARCH_BY_NAME
String query = busStopSearchByNameEditText.getText().toString();
//new asyncWgetBusStopSuggestions(query, stopsDB, StopsFindersByNameRecursionHelper);
- new AsyncDataDownload(fh, stopsFinderByNames).execute(query);
+ new AsyncDataDownload(fh, stopsFinderByNames, getApplicationContext()).execute(query);
}
}
/**
* PERMISSION STUFF
**/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case PERMISSION_REQUEST_POSITION:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
setOption(LOCATION_PERMISSION_GIVEN, true);
//if we sent a request for a new NearbyStopsFragment
if (pendingNearbyStopsRequest) {
pendingNearbyStopsRequest = false;
- theHandler.post(new NearbyStopsRequester());
+ theHandler.post(new NearbyStopsRequester(this));
}
} else {
//permission denied
setOption(LOCATION_PERMISSION_GIVEN, false);
}
//add other cases for permissions
break;
case STORAGE_PERMISSION_REQ:
final String storageKey = Manifest.permission.WRITE_EXTERNAL_STORAGE;
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(DEBUG_TAG, "Permissions check: " + Arrays.toString(permissions));
if (permissionDoneRunnables.containsKey(storageKey)) {
Runnable toRun = permissionDoneRunnables.get(storageKey);
if (toRun != null)
toRun.run();
permissionDoneRunnables.remove(storageKey);
}
} else {
//permission denied
showToastMessage(R.string.permission_storage_maps_msg, false);
/*final int canGetPermission = askForPermissionIfNeeded(Manifest.permission.ACCESS_FINE_LOCATION, STORAGE_PERMISSION_REQ);
switch (canGetPermission) {
case PERMISSION_ASKING:
break;
case PERMISSION_NEG_CANNOT_ASK:
permissionDoneRunnables.remove(storageKey);
showToastMessage(R.string.closing_act_crash_msg, false);
}*/
}
}
}
@Override
public void requestArrivalsForStopID(String ID) {
if (ID == null || ID.length() <= 0) {
// we're still in UI thread, no need to mess with Progress
showToastMessage(R.string.insert_bus_stop_number_error, true);
toggleSpinner(false);
} else if (framan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) {
ArrivalsFragment fragment = (ArrivalsFragment) framan.findFragmentById(R.id.resultFrame);
if (fragment !=null && fragment.getStopID() != null && fragment.getStopID().equals(ID)){
// Run with previous fetchers
//fragment.getCurrentFetchers().toArray()
- new AsyncDataDownload(fh,fragment.getCurrentFetchersAsArray()).execute(ID);
+ new AsyncDataDownload(fh,fragment.getCurrentFetchersAsArray(), this).execute(ID);
} else{
- new AsyncDataDownload(fh, arrivalsFetchers).execute(ID);
+ new AsyncDataDownload(fh, arrivalsFetchers, this).execute(ID);
}
}
else {
- new AsyncDataDownload(fh,arrivalsFetchers).execute(ID);
+ new AsyncDataDownload(fh, arrivalsFetchers, this).execute(ID);
Log.d("MainActiv", "Started search for arrivals of stop " + 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) {
super.onActivityResult(requestCode, resultCode, 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);
requestArrivalsForStopID(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();
}
}
}
private void createDefaultSnackbar() {
if (snackbar == null) {
snackbar = Snackbar.make(findViewById(R.id.searchButton), R.string.database_update_message, Snackbar.LENGTH_INDEFINITE);
}
snackbar.show();
}
///////////////////////////////// POSITION STUFF//////////////////////////////////////////////
private void resolveStopRequest(String provider) {
Log.d(DEBUG_TAG, "Provider " + provider + " got enabled");
if (locmgr != null && pendingNearbyStopsRequest && locmgr.getProvider(provider).meetsCriteria(cr)) {
pendingNearbyStopsRequest = false;
- theHandler.post(new NearbyStopsRequester());
+ theHandler.post(new NearbyStopsRequester(this));
}
}
final LocationListener locListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
Log.d(DEBUG_TAG, "Location changed");
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
Log.d(DEBUG_TAG, "Location provider status: " + status);
if (status == LocationProvider.AVAILABLE) {
resolveStopRequest(provider);
}
}
@Override
public void onProviderEnabled(String provider) {
resolveStopRequest(provider);
}
@Override
public void onProviderDisabled(String provider) {
}
};
/**
* Run location requests separately and asynchronously
*/
class NearbyStopsRequester implements Runnable {
+
+ Activity runningAct;
+
+ public NearbyStopsRequester(Activity runningAct) {
+ this.runningAct = runningAct;
+ }
+
@Override
public void run() {
final boolean canRunPosition = Build.VERSION.SDK_INT < Build.VERSION_CODES.M || getOption(LOCATION_PERMISSION_GIVEN, false);
final boolean noPermission = ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED;
//if we don't have the permission, we have to ask for it, if we haven't
// asked too many times before
if (noPermission) {
if (!canRunPosition) {
pendingNearbyStopsRequest = true;
- assertLocationPermissions();
+ Permissions.assertLocationPermissions(getApplicationContext(),runningAct);
Log.w(DEBUG_TAG, "Cannot get position: Asking permission, noPositionFromSys: " + noPermission);
return;
} else {
Toast.makeText(getApplicationContext(), "Asked for permission position too many times", Toast.LENGTH_LONG).show();
}
} else setOption(LOCATION_PERMISSION_GIVEN, true);
LocationManager locManager = (LocationManager) getSystemService(LOCATION_SERVICE);
if (locManager == null) {
Log.e(DEBUG_TAG, "location manager is nihil, cannot create NearbyStopsFragment");
return;
}
if (anyLocationProviderMatchesCriteria(locManager, cr, true)
&& fh.getLastSuccessfullySearchedBusStop() == null
&& !framan.isDestroyed()) {
//Go ahead with the request
Log.d("mainActivity", "Recreating stop fragment");
swipeRefreshLayout.setVisibility(View.VISIBLE);
NearbyStopsFragment fragment = NearbyStopsFragment.newInstance(NearbyStopsFragment.TYPE_STOPS);
Fragment oldFrag = framan.findFragmentById(R.id.resultFrame);
FragmentTransaction ft = framan.beginTransaction();
if (oldFrag != null)
ft.remove(oldFrag);
ft.add(R.id.resultFrame, fragment, "nearbyStop_correct");
ft.commit();
framan.executePendingTransactions();
pendingNearbyStopsRequest = false;
} else if (!anyLocationProviderMatchesCriteria(locManager, cr, true)) {
//Wait for the providers
Log.d(DEBUG_TAG, "Queuing position request");
pendingNearbyStopsRequest = true;
locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 10, 0.1f, locListener);
}
}
}
private boolean anyLocationProviderMatchesCriteria(LocationManager mng, Criteria cr, boolean enabled) {
List providers = mng.getProviders(cr, enabled);
Log.d(DEBUG_TAG, "Getting enabled location providers: ");
for (String s : providers) {
Log.d(DEBUG_TAG, "Provider " + s);
}
return providers.size() > 0;
}
///////////////////////////////// OTHER STUFF //////////////////////////////////////////////////
- /**
- * Check if the last Bus Stop is in the favorites
- *
- * @return
- */
- public boolean isStopInFavorites(String busStopId) {
- boolean found = false;
-
- // no stop no party
- if (busStopId != null) {
- SQLiteDatabase userDB = new UserDB(getApplicationContext()).getReadableDatabase();
- found = UserDB.isStopInFavorites(userDB, busStopId);
- }
-
- return found;
- }
-
- /**
- * Add the last Stop to favorites
- */
@Override
public void showFloatingActionButton(boolean yes) {
if (yes) floatingActionButton.show();
else floatingActionButton.hide();
}
@Override
public void enableRefreshLayout(boolean yes) {
swipeRefreshLayout.setEnabled(yes);
}
////////////////////////////////////// GUI HELPERS /////////////////////////////////////////////
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);
}
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(FragmentKind fragmentType) {
hideKeyboard();
//if we are getting results, already, stop waiting for nearbyStops
if (pendingNearbyStopsRequest && (fragmentType == FragmentKind.ARRIVALS || fragmentType == FragmentKind.STOPS)) {
locmgr.removeUpdates(locListener);
pendingNearbyStopsRequest = false;
}
if (fragmentType == null) Log.e("ActivityMain", "Problem with fragmentType");
else
switch (fragmentType) {
case ARRIVALS:
prepareGUIForBusLines();
if (getOption(OPTION_SHOW_LEGEND, true)) {
showHints();
}
break;
case 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;
+ private void openIceweasel(String url){
+ utils.openIceweasel(url, this);
}
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/ActivityMap.java b/src/it/reyboz/bustorino/ActivityMap.java
index a5ea91c..0a79fcd 100644
--- a/src/it/reyboz/bustorino/ActivityMap.java
+++ b/src/it/reyboz/bustorino/ActivityMap.java
@@ -1,429 +1,429 @@
/*
BusTO Activities
Copyright (C) 2020 Andrea Ugo e 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;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.app.ActivityCompat;
import androidx.preference.PreferenceManager;
import it.reyboz.bustorino.middleware.GeneralActivity;
import it.reyboz.bustorino.data.NextGenDB;
import org.osmdroid.api.IMapController;
import org.osmdroid.config.Configuration;
import org.osmdroid.events.DelayedMapListener;
import org.osmdroid.events.MapListener;
import org.osmdroid.events.ScrollEvent;
import org.osmdroid.events.ZoomEvent;
import org.osmdroid.tileprovider.tilesource.TileSourceFactory;
import org.osmdroid.util.BoundingBox;
import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.FolderOverlay;
import org.osmdroid.views.overlay.Marker;
import org.osmdroid.views.overlay.Overlay;
import org.osmdroid.views.overlay.infowindow.InfoWindow;
import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider;
import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay;
import java.util.*;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.map.CustomInfoWindow;
public class ActivityMap extends GeneralActivity {
private static final String TAG = "Busto-MapActivity";
private static final String MAP_CURRENT_ZOOM_KEY = "map-current-zoom";
private static final String MAP_CENTER_LAT_KEY = "map-center-lat";
private static final String MAP_CENTER_LON_KEY = "map-center-lon";
public static final String BUNDLE_LATIT = "lat";
public static final String BUNDLE_LONGIT = "lon";
public static final String BUNDLE_NAME = "name";
public static final String BUNDLE_ID = "ID";
private static final double DEFAULT_CENTER_LAT = 45.0708;
private static final double DEFAULT_CENTER_LON = 7.6858;
private static final double POSITION_FOUND_ZOOM = 18.3;
private HashSet shownStops = null;
private MapView map = null;
public Context ctx;
private MyLocationNewOverlay mLocationOverlay = null;
private FolderOverlay stopsFolderOverlay = null;
protected ImageButton btCenterMap;
protected ImageButton btFollowMe;
- @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
+ //@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//handle permissions first, before map is created. not depicted here
//load/initialize the osmdroid configuration
ctx = getApplicationContext();
Configuration.getInstance().load(ctx, PreferenceManager.getDefaultSharedPreferences(ctx));
//setting this before the layout is inflated is a good idea
//it 'should' ensure that the map has a writable location for the map cache, even without permissions
//if no tiles are displayed, you can try overriding the cache path using Configuration.getInstance().setCachePath
//see also StorageUtils
//note, the load method also sets the HTTP User Agent to your application's package name, abusing osm's tile servers will get you banned based on this string
//inflate and create the map
setContentView(R.layout.activity_map);
map = (MapView) findViewById(R.id.map);
map.setTileSource(TileSourceFactory.MAPNIK);
//map.setTilesScaledToDpi(true);
map.setFlingEnabled(true);
// add ability to zoom with 2 fingers
map.setMultiTouchControls(true);
btCenterMap = (ImageButton) findViewById(R.id.ic_center_map);
btFollowMe = (ImageButton) findViewById(R.id.ic_follow_me);
//setup FolderOverlay
stopsFolderOverlay = new FolderOverlay();
// take the parameters if it's called from other Activities
Bundle b = getIntent().getExtras();
startMap(b, savedInstanceState);
// on drag and zoom reload the markers
map.addMapListener(new DelayedMapListener(new MapListener() {
@Override
public boolean onScroll(ScrollEvent paramScrollEvent) {
loadMarkers();
return true;
}
@Override
public boolean onZoom(ZoomEvent event) {
loadMarkers();
return true;
}
}));
btCenterMap.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i(TAG, "centerMap clicked ");
final GeoPoint myPosition = mLocationOverlay.getMyLocation();
map.getController().animateTo(myPosition);
}
});
btFollowMe.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i(TAG, "btFollowMe clicked ");
if (!mLocationOverlay.isFollowLocationEnabled()) {
mLocationOverlay.enableFollowLocation();
btFollowMe.setImageResource(R.drawable.ic_follow_me_on);
} else {
mLocationOverlay.disableFollowLocation();
btFollowMe.setImageResource(R.drawable.ic_follow_me);
}
}
});
}
public void startMap(Bundle incoming, Bundle savedInstanceState) {
//parse incoming bundle
GeoPoint marker = null;
String name = null;
String ID = null;
if (incoming != null) {
double lat = incoming.getDouble(BUNDLE_LATIT);
double lon = incoming.getDouble(BUNDLE_LONGIT);
marker = new GeoPoint(lat, lon);
name = incoming.getString(BUNDLE_NAME);
ID = incoming.getString(BUNDLE_ID);
}
shownStops = new HashSet<>();
// move the map on the marker position or on a default view point: Turin, Piazza Castello
// and set the start zoom
IMapController mapController = map.getController();
GeoPoint startPoint = null;
boolean havePositionPermission = true;
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
askForPermissionIfNeeded(Manifest.permission.ACCESS_FINE_LOCATION, PERMISSION_REQUEST_POSITION);
havePositionPermission = false;
}
if (marker != null) {
startPoint = marker;
mapController.setZoom(POSITION_FOUND_ZOOM);
} else if (savedInstanceState != null || !havePositionPermission) {
mapController.setZoom(savedInstanceState.getDouble(MAP_CURRENT_ZOOM_KEY));
mapController.setCenter(new GeoPoint(savedInstanceState.getDouble(MAP_CENTER_LAT_KEY),
savedInstanceState.getDouble(MAP_CENTER_LON_KEY)));
} else {
boolean found = false;
LocationManager locationManager =
(LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
if (locationManager != null) {
Location userLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (userLocation != null) {
mapController.setZoom(POSITION_FOUND_ZOOM);
startPoint = new GeoPoint(userLocation);
found = true;
}
}
if(!found){
startPoint = new GeoPoint(DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON);
mapController.setZoom(16.0);
}
}
// set the minimum zoom level
map.setMinZoomLevel(15.0);
//add contingency check (shouldn't happen..., but)
if (startPoint != null) {
mapController.setCenter(startPoint);
}
// Location Overlay
// from OpenBikeSharing (THANK GOD)
GpsMyLocationProvider imlp = new GpsMyLocationProvider(this.getBaseContext());
imlp.setLocationUpdateMinDistance(5);
imlp.setLocationUpdateMinTime(2000);
this.mLocationOverlay = new MyLocationNewOverlay(imlp,map);
mLocationOverlay.enableMyLocation();
mLocationOverlay.enableFollowLocation();
btFollowMe.setImageResource(R.drawable.ic_follow_me_on);
mLocationOverlay.setOptionsMenuEnabled(true);
/*
mLocationOverlay.runOnFirstFix(() -> {
mapController.setCenter(mLocationOverlay.getMyLocation());
mapController.animateTo(mLocationOverlay.getMyLocation());
});
*/
map.getOverlays().add(this.mLocationOverlay);
//add stops overlay
map.getOverlays().add(this.stopsFolderOverlay);
loadMarkers();
if (marker != null) {
// make a marker with the info window open for the searched marker
makeMarker(startPoint, name , ID, true);
}
}
public Marker makeMarker(GeoPoint geoPoint, String stopName, String ID, boolean isStartMarker) {
// add a marker
Marker marker = new Marker(map);
// set custom info window as info window
CustomInfoWindow popup = new CustomInfoWindow(map, ID, stopName);
marker.setInfoWindow(popup);
// make the marker clickable
marker.setOnMarkerClickListener((thisMarker, mapView) -> {
if (thisMarker.isInfoWindowOpen()) {
// on second click
// create an intent with these extras
Intent intent = new Intent(ActivityMap.this, ActivityMain.class);
Bundle b = new Bundle();
b.putString("bus-stop-ID", ID);
b.putString("bus-stop-display-name", stopName);
intent.putExtras(b);
intent.setFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
// start ActivityMain with the previous intent
startActivity(intent);
} else {
// on first click
// hide all opened info window
InfoWindow.closeAllInfoWindowsOn(map);
// show this particular info window
thisMarker.showInfoWindow();
// move the map to its position
map.getController().animateTo(thisMarker.getPosition());
}
return true;
});
// set its position
marker.setPosition(geoPoint);
marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM);
// add to it an icon
marker.setIcon(getResources().getDrawable(R.drawable.bus_marker));
// add to it a title
marker.setTitle(stopName);
// set the description as the ID
marker.setSnippet(ID);
// show popup info window of the searched marker
if (isStartMarker) {
marker.showInfoWindow();
}
return marker;
}
public void loadMarkers() {
// get rid of the previous markers
//map.getOverlays().clear();
//stopsFolderOverlay = new FolderOverlay();
List stopsOverlays = stopsFolderOverlay.getItems();
/*if (stopsOverlays != null){
stopsOverlays.clear();
}*/
// get the top, bottom, left and right screen's coordinate
BoundingBox bb = map.getBoundingBox();
double latFrom = bb.getLatSouth();
double latTo = bb.getLatNorth();
double lngFrom = bb.getLonWest();
double lngTo = bb.getLonEast();
// get the stops located in those coordinates
/*
StopsDB stopsDB = new StopsDB(ctx);
stopsDB.openIfNeeded();
Stop[] stops = stopsDB.queryAllInsideMapView(latFrom, latTo, lngFrom, lngTo);
stopsDB.closeIfNeeded();
*/
NextGenDB dbHelper = new NextGenDB(ctx);
Stop[] stops = dbHelper.queryAllInsideMapView(latFrom, latTo, lngFrom, lngTo);
// add new markers of those stops
for (Stop stop : stops) {
if (shownStops.contains(stop.ID)){
continue;
}
try{
stop.getLatitude();
stop.getLongitude();
} catch (NullPointerException e) {
Log.e(TAG,"Stop "+stop.ID+ " gives null coordinates");
e.printStackTrace();
continue;
}
shownStops.add(stop.ID);
GeoPoint marker = new GeoPoint(stop.getLatitude(), stop.getLongitude());
Marker stopMarker = makeMarker(marker, stop.getStopDefaultName(), stop.ID, false);
stopsFolderOverlay.add(stopMarker);
}
}
protected boolean detachMapFromPosition(){
if (mLocationOverlay.isFollowLocationEnabled()) {
mLocationOverlay.disableFollowLocation();
btFollowMe.setImageResource(R.drawable.ic_follow_me);
return true;
} return false;
}
public void onResume(){
super.onResume();
//this will refresh the osmdroid configuration on resuming.
//if you make changes to the configuration, use
//SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
//Configuration.getInstance().load(this, PreferenceManager.getDefaultSharedPreferences(this));
map.onResume(); //needed for compass, my location overlays, v6.0.0 and up
mLocationOverlay.enableMyLocation();
}
public void onPause(){
super.onPause();
//this will refresh the osmdroid configuration on resuming.
//if you make changes to the configuration, use
//SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
//Configuration.getInstance().save(this, prefs);
map.onPause(); //needed for compass, my location overlays, v6.0.0 and up
mLocationOverlay.disableMyLocation();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putDouble(MAP_CURRENT_ZOOM_KEY, map.getZoomLevelDouble());
outState.putDouble(MAP_CENTER_LAT_KEY, map.getMapCenter().getLatitude());
outState.putDouble(MAP_CENTER_LON_KEY, map.getMapCenter().getLongitude());
}
/**
* PERMISSION STUFF
**/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case PERMISSION_REQUEST_POSITION:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
setOption(LOCATION_PERMISSION_GIVEN, true);
//if we sent a request for a new NearbyStopsFragment
} else {
//permission denied
setOption(LOCATION_PERMISSION_GIVEN, false);
}
break;
//add other cases for permissions
}
}
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/ActivityPrincipal.java b/src/it/reyboz/bustorino/ActivityPrincipal.java
index 6f40bb7..a162c92 100644
--- a/src/it/reyboz/bustorino/ActivityPrincipal.java
+++ b/src/it/reyboz/bustorino/ActivityPrincipal.java
@@ -1,126 +1,373 @@
package it.reyboz.bustorino;
+import android.content.Intent;
+import android.content.SharedPreferences;
import android.content.res.Configuration;
-import android.content.res.Resources;
+import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
+import android.view.Menu;
import android.view.MenuItem;
+import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.widget.Toolbar;
-import androidx.core.app.NavUtils;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+import androidx.work.BackoffPolicy;
+import androidx.work.Constraints;
+import androidx.work.ExistingPeriodicWorkPolicy;
+import androidx.work.NetworkType;
+import androidx.work.PeriodicWorkRequest;
+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.concurrent.TimeUnit;
+
+import it.reyboz.bustorino.data.DBUpdateWorker;
+import it.reyboz.bustorino.data.DatabaseUpdate;
+import it.reyboz.bustorino.fragments.FragmentKind;
+import it.reyboz.bustorino.fragments.FragmentListenerMain;
+import it.reyboz.bustorino.fragments.MainScreenFragment;
import it.reyboz.bustorino.middleware.GeneralActivity;
-public class ActivityPrincipal extends GeneralActivity {
+import static it.reyboz.bustorino.backend.utils.getBusStopIDFromUri;
+import static it.reyboz.bustorino.backend.utils.openIceweasel;
+
+public class ActivityPrincipal extends GeneralActivity implements FragmentListenerMain {
private DrawerLayout mDrawer;
private NavigationView mNavView;
private ActionBarDrawerToggle drawerToggle;
private final static String DEBUG_TAG="BusTO Act Principal";
+ private Snackbar snackbar;
+
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.new_main_activity);
+ setContentView(R.layout.activity_principal);
+ final SharedPreferences theShPr = getMainSharedPreferences();
+
Toolbar mToolbar = findViewById(R.id.default_toolbar);
setSupportActionBar(mToolbar);
if (getSupportActionBar()!=null)
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
else Log.w(DEBUG_TAG, "NO ACTION BAR");
+ mToolbar.setOnMenuItemClickListener(new ToolbarItemClickListener());
+
mDrawer = findViewById(R.id.drawer_layout);
drawerToggle = setupDrawerToggle(mToolbar);
// Setup toggle to display hamburger icon with nice animation
drawerToggle.setDrawerIndicatorEnabled(true);
drawerToggle.syncState();
mDrawer.addDrawerListener(drawerToggle);
mNavView = findViewById(R.id.nvView);
setupDrawerContent(mNavView);
+ /// LEGACY CODE
+ //---------------------------- START INTENT CHECK QUEUE ------------------------------------
+
+ // Intercept calls from URL intent
+ boolean tryedFromIntent = false;
+
+ String busStopID = 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");
+
+ /**
+ * 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
+ // JUST DON'T
+ // showKeyboard();
+
+ // You haven't obtained anything... from an intent?
+ if (tryedFromIntent) {
+
+ // This shows a luser warning
+ 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);
+
+ requestArrivalsForStopID(busStopID);
+ }
+ //Try (hopefully) database update
+
+
+ PeriodicWorkRequest wr = new PeriodicWorkRequest.Builder(DBUpdateWorker.class, 1, TimeUnit.DAYS)
+ .setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES)
+ .setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED)
+ .build())
+ .build();
+ final WorkManager workManager = WorkManager.getInstance(this);
+
+ final int version = theShPr.getInt(DatabaseUpdate.DB_VERSION_KEY, -10);
+ if (version >= 0)
+ workManager.enqueueUniquePeriodicWork(DBUpdateWorker.DEBUG_TAG,
+ ExistingPeriodicWorkPolicy.KEEP, wr);
+ else workManager.enqueueUniquePeriodicWork(DBUpdateWorker.DEBUG_TAG,
+ ExistingPeriodicWorkPolicy.REPLACE, wr);
+ /*
+ Set database update
+ */
+ workManager.getWorkInfosForUniqueWorkLiveData(DBUpdateWorker.DEBUG_TAG)
+ .observe(this, workInfoList -> {
+ // If there are no matching work info, do nothing
+ if (workInfoList == null || workInfoList.isEmpty()) {
+ return;
+ }
+ Log.d(DEBUG_TAG, "WorkerInfo: "+workInfoList);
+
+ boolean showProgress = false;
+ for (WorkInfo workInfo : workInfoList) {
+ if (workInfo.getState() == WorkInfo.State.RUNNING) {
+ showProgress = true;
+ }
+ }
+
+ if (showProgress) {
+ createDefaultSnackbar();
+ } else {
+ if(snackbar!=null) {
+ snackbar.dismiss();
+ snackbar = null;
+ }
+ }
+
+ });
+ // show the main fragment
+ showMainFragment();
+
+
}
private ActionBarDrawerToggle setupDrawerToggle(Toolbar toolbar) {
// NOTE: Make sure you pass in a valid toolbar reference. ActionBarDrawToggle() does not require it
// and will not render the hamburger icon without it.
return new ActionBarDrawerToggle(this, mDrawer, toolbar, R.string.drawer_open, R.string.drawer_close);
}
private void setupDrawerContent(NavigationView navigationView) {
navigationView.setNavigationItemSelectedListener(
menuItem -> {
-
+ if (menuItem.getItemId() == R.id.drawer_action_settings) {
+ Log.d("MAINBusTO", "Pressed button preferences");
+ closeDrawerIfOpen();
+ startActivity(new Intent(ActivityPrincipal.this, ActivitySettings.class));
+ return true;
+ }
//selectDrawerItem(menuItem);
Log.d(DEBUG_TAG, "pressed item "+menuItem.toString());
return true;
});
}
+ private void closeDrawerIfOpen(){
+ if (mDrawer.isDrawerOpen(GravityCompat.START))
+ mDrawer.closeDrawer(GravityCompat.START);
+ }
+
// `onPostCreate` called when activity start-up is complete after `onStart()`
// NOTE 1: Make sure to override the method with only a single `Bundle` argument
// Note 2: Make sure you implement the correct `onPostCreate(Bundle savedInstanceState)` method.
// There are 2 signatures and only `onPostCreate(Bundle state)` shows the hamburger icon.
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Sync the toggle state after onRestoreInstanceState has occurred.
drawerToggle.syncState();
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Pass any configuration change to the drawer toggles
drawerToggle.onConfigurationChanged(newConfig);
}
+
@Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.extra_menu_items, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+ @Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
int[] cases = {R.id.nav_arrivals, R.id.nav_favorites_item};
+ Log.d(DEBUG_TAG, "Item pressed");
+
switch (item.getItemId()){
case android.R.id.home:
mDrawer.openDrawer(GravityCompat.START);
return true;
-
- case R.id.nav_arrivals:
- //do something
- break;
default:
}
if (drawerToggle.onOptionsItemSelected(item)) {
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onBackPressed() {
+ boolean foundFragment = false;
+ Fragment shownFrag = getSupportFragmentManager().findFragmentById(R.id.mainActContentFrame);
if (mDrawer.isDrawerOpen(GravityCompat.START))
mDrawer.closeDrawer(GravityCompat.START);
+ else if(shownFrag != null && shownFrag.isVisible() && shownFrag.getChildFragmentManager().getBackStackEntryCount() > 0){
+ shownFrag.getChildFragmentManager().popBackStack();
+ }
+ else if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
+ getSupportFragmentManager().popBackStack();
+ }
else
super.onBackPressed();
}
+
+ private void createDefaultSnackbar() {
+ if (snackbar == null) {
+ snackbar = Snackbar.make(findViewById(R.id.searchButton), R.string.database_update_message, Snackbar.LENGTH_INDEFINITE);
+ }
+ snackbar.show();
+ }
+
+ private MainScreenFragment showMainFragment(){
+ FragmentManager fraMan = getSupportFragmentManager();
+
+ MainScreenFragment fragment = MainScreenFragment.newInstance();
+
+ FragmentTransaction transaction = fraMan.beginTransaction();
+ transaction.replace(R.id.mainActContentFrame, fragment, MainScreenFragment.FRAGMENT_TAG);
+ transaction.commit();
+ return fragment;
+ }
+ @Nullable
+ private MainScreenFragment getMainFragmentIfVisible(){
+ FragmentManager fraMan = getSupportFragmentManager();
+ Fragment fragment = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG);
+ if (fragment!= null && fragment.isVisible()) return (MainScreenFragment) fragment;
+ else return null;
+ }
+
+ @Override
+ public void showFloatingActionButton(boolean yes) {
+ //TODO
+ }
+
+ @Override
+ public void readyGUIfor(FragmentKind fragmentType) {
+ MainScreenFragment probableFragment = getMainFragmentIfVisible();
+ if (probableFragment!=null){
+ probableFragment.readyGUIfor(fragmentType);
+ }
+ }
+
+ @Override
+ public void requestArrivalsForStopID(String ID) {
+ FragmentManager fraMan = getSupportFragmentManager();
+ Fragment fragment = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG);
+ MainScreenFragment mainScreenFragment = null;
+ if (fragment==null | !(fragment instanceof MainScreenFragment)){
+ mainScreenFragment = showMainFragment();
+ }
+ else if(!fragment.isVisible()){
+
+ fraMan.beginTransaction().replace(R.id.mainActContentFrame, fragment)
+ .addToBackStack(null)
+ .commit();
+ mainScreenFragment = (MainScreenFragment) fragment;
+ Log.d(DEBUG_TAG, "Found the main fragment");
+ } else{
+ mainScreenFragment = (MainScreenFragment) fragment;
+ }
+
+ mainScreenFragment.requestArrivalsForStopID(ID);
+ }
+
+ @Override
+ public void toggleSpinner(boolean state) {
+ MainScreenFragment probableFragment = getMainFragmentIfVisible();
+ if (probableFragment!=null){
+ probableFragment.toggleSpinner(state);
+ }
+ }
+
+ @Override
+ public void enableRefreshLayout(boolean yes) {
+ MainScreenFragment probableFragment = getMainFragmentIfVisible();
+ if (probableFragment!=null){
+ probableFragment.enableRefreshLayout(yes);
+ }
+ }
+
+ class ToolbarItemClickListener implements Toolbar.OnMenuItemClickListener{
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_about:
+ startActivity(new Intent(ActivityPrincipal.this, ActivityAbout.class));
+ return true;
+ case R.id.action_hack:
+ openIceweasel(getString(R.string.hack_url), getApplicationContext());
+ return true;
+ case R.id.action_source:
+ openIceweasel("https://gitpull.it/source/libre-busto/", getApplicationContext());
+ return true;
+ case R.id.action_licence:
+ openIceweasel("https://www.gnu.org/licenses/gpl-3.0.html", getApplicationContext());
+ return true;
+ default:
+ }
+ return false;
+ }
+ }
}
diff --git a/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java b/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java
index 13d9f4e..a51f48c 100644
--- a/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java
+++ b/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java
@@ -1,395 +1,396 @@
/*
BusTO - Backend components
Copyright (C) 2018 Fabio Mazza
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.backend;
import androidx.annotation.Nullable;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
public class FiveTAPIFetcher implements ArrivalsFetcher{
private static final String DEBUG_NAME = "FiveTAPIFetcher";
private final Map defaultHeaders = getDefaultHeaders();
final static LinkedList apiDays = new LinkedList<>(Arrays.asList("dom","lun","mar","mer","gio","ven","sab"));
@Override
public Palina ReadArrivalTimesAll(String stopID, AtomicReference res) {
//set the date for the request as now
Palina p = new Palina(stopID);
//request parameters
String response = performAPIRequest(QueryType.ARRIVALS,stopID,res);
if(response==null) {
if(res.get()==result.SERVER_ERROR_404) {
Log.w(DEBUG_NAME,"Got 404, either the server failed, or the stop was not found, or the hack is not working anymore");
res.set(result.EMPTY_RESULT_SET);
}
return p;
}
try {
List routes = parseArrivalsServerResponse(response, res);
for(Route r: routes){
p.addRoute(r);
}
} catch (JSONException ex){
res.set(result.PARSER_ERROR);
+ Log.w(DEBUG_NAME, "Couldn't get the JSON repr of:\n"+response);
return null;
}
res.set(result.OK);
p.sortRoutes();
return p;
}
@Override
public Passaggio.Source getSourceForFetcher() {
return Passaggio.Source.FiveTAPI;
}
List parseArrivalsServerResponse(String JSONresponse, AtomicReference res) throws JSONException{
ArrayList routes = new ArrayList<>(3);
/*
Slight problem:
"longName": ==> DESCRIPTION
"name": "13N",
"departures": [
{
"arrivalTimeInt": 1272,
"time": "21:12",
"rt": false
}]
"lineType": "URBANO" ==> URBANO can be either bus or tram or METRO
*/
JSONArray arr;
try{
arr = new JSONArray(JSONresponse);
String type;
Route.Type routetype;
for(int i =0; i parseDirectionsFromResponse(String response) throws IllegalArgumentException,JSONException{
if(response == null || response.length()==0) throw new IllegalArgumentException("Response string is null or void");
ArrayList routes = new ArrayList<>(10);
JSONArray lines =new JSONArray(response);
for(int i=0; i 1) {
String secondo = exploded[exploded.length-2];
if (secondo.contains("festivo")) {
festivo = Route.FestiveInfo.FESTIVO;
} else if (secondo.contains("feriale")) {
festivo = Route.FestiveInfo.FERIALE;
} else if(secondo.contains("lun. - ven")) {
serviceDays = Route.reduced_week;
} else if(secondo.contains("sab - fest.")){
serviceDays = Route.weekend;
festivo = Route.FestiveInfo.FESTIVO;
} else {
/*
Log.d(DEBUG_NAME,"Parsing details of line "+lineName+" branchid "+branchid+":\n\t"+
"Couldn't find a the service days\n"+
"Description: "+secondo+","+description
);
*/
}
if(exploded.length>2){
switch (exploded[exploded.length-3].trim()) {
case "bus":
t = Route.Type.BUS;
break;
case "tram":
//never happened, but if it could happen you can get it
t = Route.Type.TRAM;
break;
default:
//nothing
}
}
} else //only one piece
if(description.contains("festivo")){
festivo = Route.FestiveInfo.FESTIVO;
} else if(description.contains("feriale")){
festivo = Route.FestiveInfo.FERIALE;
}
if(t == Route.Type.UNKNOWN &&(lineName.trim().equals("10")|| lineName.trim().equals("15"))) t= Route.Type.TRAM;
if(direction.contains("-")){
//Sometimes the actual filtered direction still remains the full line (including both extremes)
direction = direction.split("-")[1];
}
Route r = new Route(lineName.trim(),direction.trim(),t,new ArrayList<>());
if(serviceDays.length>0) r.serviceDays = serviceDays;
r.festivo = festivo;
r.branchid = branchid;
r.description = description.trim();
r.setStopsList(Arrays.asList(stops.split(",")));
routes.add(r);
}
return routes;
}
public List getDirectionsForStop(String stopID, AtomicReference res) {
String response = performAPIRequest(QueryType.DETAILS,stopID,res);
List routes;
try{
routes = parseDirectionsFromResponse(response);
res.set(result.OK);
} catch (JSONException | IllegalArgumentException e) {
e.printStackTrace();
res.set(result.PARSER_ERROR);
routes = null;
}
return routes;
}
public ArrayList getAllStopsFromGTT(AtomicReference res){
String response = performAPIRequest(QueryType.STOPS_ALL,null,res);
if(response==null) return null;
ArrayList stopslist;
try{
JSONObject responseJSON = new JSONObject(response);
JSONArray stops = responseJSON.getJSONArray("stops");
stopslist = new ArrayList<>(stops.length());
for (int i=0;i getAllLinesFromGTT(AtomicReference res){
String resp = performAPIRequest(QueryType.LINES,null,res);
if(resp==null) {
return null;
}
ArrayList routes = null;
try {
JSONArray lines = new JSONArray(resp);
routes = new ArrayList<>(lines.length());
for(int i = 0; i getDefaultHeaders(){
HashMap param = new HashMap<>();
param.put("Host","www.5t.torino.it");
param.put("Connection","Keep-Alive");
param.put("Accept-Encoding", "gzip");
return param;
}
/**
* Create and perform the network request. This method adds parameters and returns the result
* @param t type of request to be performed
* @param stopID optional parameter, stop ID which you need for passages and branches
* @param res result container
* @return a String which contains the result of the query, to be parsed
*/
@Nullable
public static String performAPIRequest(QueryType t,@Nullable String stopID, AtomicReference res){
URL u;
Map param;
try {
String address = getURLForOperation(t,stopID);
//Log.d(DEBUG_NAME,"The address to query is: "+address);
param = getDefaultHeaders();
u = new URL(address);
} catch (UnsupportedEncodingException |MalformedURLException e) {
e.printStackTrace();
res.set(result.PARSER_ERROR);
return null;
}
String response = networkTools.queryURL(u,res,param);
return response;
}
/**
* Get the right url for the operation you are doing, to be fed into the queryURL method
* @param t type of operation
* @param stopID stop on which you are working on
* @return the Url to go to
* @throws UnsupportedEncodingException if it cannot be converted to utf-8
*/
public static String getURLForOperation(QueryType t,@Nullable String stopID) throws UnsupportedEncodingException {
final StringBuilder sb = new StringBuilder();
sb.append("http://www.5t.torino.it/ws2.1/rest/");
if(t!=QueryType.LINES) sb.append("stops/");
switch (t){
case ARRIVALS:
sb.append(URLEncoder.encode(stopID,"utf-8"));
sb.append("/departures");
break;
case DETAILS:
sb.append(URLEncoder.encode(stopID,"utf-8"));
sb.append("/branches/details");
break;
case STOPS_ALL:
sb.append("all");
break;
case STOPS_VERSION:
sb.append("version");
break;
case LINES:
sb.append("lines/all");
break;
}
return sb.toString();
}
public enum QueryType {
ARRIVALS, DETAILS,STOPS_ALL, STOPS_VERSION,LINES
}
}
diff --git a/src/it/reyboz/bustorino/backend/Palina.java b/src/it/reyboz/bustorino/backend/Palina.java
index 4e3c065..966d3d6 100644
--- a/src/it/reyboz/bustorino/backend/Palina.java
+++ b/src/it/reyboz/bustorino/backend/Palina.java
@@ -1,349 +1,354 @@
/*
BusTO (backend components)
Copyright (C) 2016 Ludovico Pavesi
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.backend;
import android.util.Log;
+import androidx.annotation.NonNull;
+
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
/**
* Timetable for multiple routes.
*
* Apparently "palina" and a bunch of other terms can't really be translated into English.
* Not in a way that makes sense and keeps the code readable, at least.
*/
public class Palina extends Stop {
private ArrayList routes = new ArrayList<>();
private boolean routesModified = false;
private Passaggio.Source allSource = null;
public Palina(String stopID) {
super(stopID);
}
public Palina(Stop s){
super(s.ID,s.getStopDefaultName(),s.getStopUserName(),s.location,s.type,
s.getRoutesThatStopHere(),s.getLatitude(),s.getLongitude());
}
/**
* Adds a timetable entry to a route.
*
* @param TimeGTT time in GTT format (e.g. "11:22*")
* @param arrayIndex position in the array for this route (returned by addRoute)
*/
public void addPassaggio(String TimeGTT, Passaggio.Source src,int arrayIndex) {
this.routes.get(arrayIndex).addPassaggio(TimeGTT,src);
routesModified = true;
}
/**
* Count routes with missing directions
* @return number
*/
public int countRoutesWithMissingDirections(){
int i = 0;
for (Route r : routes){
if(r.destinazione==null||r.destinazione.equals(""))
i++;
}
return i;
}
/**
* Adds a route to the timetable.
*
* @param routeID name
* @param type bus, underground, railway, ...
* @param destinazione end of line\terminus (underground stations have the same ID for both directions)
* @return array index for this route
*/
public int addRoute(String routeID, String destinazione, Route.Type type) {
this.routes.add(new Route(routeID, destinazione, type, new ArrayList<>(6)));
routesModified = true;
return this.routes.size() - 1; // last inserted element and pray that direct access to ArrayList elements really is direct
}
public int addRoute(Route r){
this.routes.add(r);
routesModified = true;
return this.routes.size()-1;
}
public void setRoutes(List routeList){
routes = new ArrayList<>(routeList);
}
// /**
// * Clears a route timetable (or creates an empty route) and returns its index
// *
// * @param routeID name
// * @param destinazione end of line\terminus
// * @return array index for this route
// */
// public int updateRoute(String routeID, String destinazione) {
// int s = this.routes.size();
// RouteInternal r;
//
// for(int i = 0; i < s; i++) {
// r = routes.get(i);
// if(r.name.compareTo(routeID) == 0 && r.destinazione.compareTo(destinazione) == 0) {
// // capire se è possibile che ci siano stessa linea e stessa destinazione su 2 righe diverse del sito e qui una sovrascrive l'altra (probabilmente no)
// r.updateFlag();
// r.deletePassaggio();
// return i;
// }
// }
//
// return this.addRoute(routeID, destinazione);
// }
//
// /**
// * Deletes routes marked as "not updated" (= disappeared from the GTT website\API\whatever).
// * Sets all remaining routes to "not updated" because that's how this contraption works.
// */
// public void finishUpdatingRoutes() {
// RouteInternal r;
//
// for(Iterator itr = this.routes.iterator(); itr.hasNext(); ) {
// r = itr.next();
// if(r.unupdateFlag()) {
// itr.remove();
// }
// }
// }
// /**
// * Gets the current timetable for a route. Returns null if the route doesn't exist.
// * This is slower than queryRouteByIndex.
// *
// * @return timetable (passaggi)
// */
// public List queryRoute(String routeID) {
// for(Route r : this.routes) {
// if(routeID.equals(r.name)) {
// return r.getPassaggi();
// }
// }
//
// return null;
// }
//
// /**
// * Gets the current timetable for this route, from its index in the array.
// *
// * @return timetable (passaggi)
// */
// public List queryRouteByIndex(int index) {
// return this.routes.get(index).getPassaggi();
// }
protected void checkPassaggi(){
Passaggio.Source mSource = null;
for (Route r: routes){
for(Passaggio pass: r.passaggi){
if (mSource == null) {
mSource = pass.source;
} else if (mSource != pass.source){
Log.w("BusTO-CheckPassaggi",
"Cannot determine the source, have got "+mSource +" so far, the next one is "+pass.source );
mSource = Passaggio.Source.UNDETERMINED;
break;
}
}
if(mSource == Passaggio.Source.UNDETERMINED)
break;
}
+ // if the Source is still null, set undetermined
+ if (mSource == null) mSource = Passaggio.Source.UNDETERMINED;
//finished with the check, setting flags
routesModified = false;
allSource = mSource;
}
+ @NonNull
public Passaggio.Source getPassaggiSourceIfAny(){
if(allSource==null || routesModified){
checkPassaggi();
}
assert allSource != null;
return allSource;
}
/**
* Gets every route and its timetable.
*
* @return routes and timetables.
*/
public List queryAllRoutes() {
return this.routes;
}
public void sortRoutes() {
Collections.sort(this.routes);
}
/**
* Add info about the routes already found from another source
* @param additionalRoutes ArrayList of routes to get the info from
* @return the number of routes modified
*/
public int addInfoFromRoutes(List additionalRoutes){
if(routes == null || routes.size()==0) {
this.routes = new ArrayList<>(additionalRoutes);
return routes.size();
}
int count=0;
final Calendar c = Calendar.getInstance();
final int todaysInt = c.get(Calendar.DAY_OF_WEEK);
for(Route r:routes) {
int j = 0;
boolean correct = false;
Route selected = null;
//TODO: rewrite this as a simple loop
//MADNESS begins here
while (!correct) {
//find the correct route to merge to
// scan routes and find the first which has the same name
while (j < additionalRoutes.size() && !r.getName().equals(additionalRoutes.get(j).getName())) {
j++;
}
if (j == additionalRoutes.size()) break; //no match has been found
//should have found the first occurrence of the line
selected = additionalRoutes.get(j);
//move forward
j++;
if (selected.serviceDays != null && selected.serviceDays.length > 0) {
//check if it is in service
for (int d : selected.serviceDays) {
if (d == todaysInt) {
correct = true;
break;
}
}
} else if (r.festivo != null) {
switch (r.festivo) {
case FERIALE:
//Domenica = 1 --> Saturday=7
if (todaysInt <= 7 && todaysInt > 1) correct = true;
break;
case FESTIVO:
if (todaysInt == 1) correct = true; //TODO: implement way to recognize all holidays
break;
case UNKNOWN:
correct = true;
}
} else {
//case a: there is no info because the line is always active
//case b: there is no info because the information is missing
correct = true;
}
}
if (!correct || selected == null) {
Log.w("Palina_mergeRoutes","Cannot match the route with name "+r.getName());
continue; //we didn't find any match
}
//found the correct correspondance
//MERGE INFO
if(r.mergeRouteWithAnother(selected)) count++;
}
return count;
}
// /**
// * Route with terminus (destinazione) and timetables (passaggi), internal implementation.
// *
// * Contains mostly the same data as the Route public class, but methods are quite different and extending Route doesn't really work, here.
// */
// private final class RouteInternal {
// public final String name;
// public final String destinazione;
// private boolean updated;
// private List passaggi;
//
// /**
// * Creates a new route and marks it as "updated", since it's new.
// *
// * @param routeID name
// * @param destinazione end of line\terminus
// */
// public RouteInternal(String routeID, String destinazione) {
// this.name = routeID;
// this.destinazione = destinazione;
// this.passaggi = new LinkedList<>();
// this.updated = true;
// }
//
// /**
// * Adds a time (passaggio) to the timetable for this route
// *
// * @param TimeGTT time in GTT format (e.g. "11:22*")
// */
// public void addPassaggio(String TimeGTT) {
// this.passaggi.add(new Passaggio(TimeGTT));
// }
//
// /**
// * Deletes al times (passaggi) from the timetable.
// */
// public void deletePassaggio() {
// this.passaggi = new LinkedList<>();
// this.updated = true;
// }
//
// /**
// * Sets the "updated" flag to false.
// *
// * @return previous state
// */
// public boolean unupdateFlag() {
// if(this.updated) {
// this.updated = false;
// return true;
// } else {
// return false;
// }
// }
//
// /**
// * Sets the "updated" flag to true.
// *
// * @return previous state
// */
// public boolean updateFlag() {
// if(this.updated) {
// return true;
// } else {
// this.updated = true;
// return false;
// }
// }
//
// /**
// * Exactly what it says on the tin.
// *
// * @return times from the timetable
// */
// public List getPassaggi() {
// return this.passaggi;
// }
// }
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/backend/utils.java b/src/it/reyboz/bustorino/backend/utils.java
index a77bd7e..4a2a28f 100644
--- a/src/it/reyboz/bustorino/backend/utils.java
+++ b/src/it/reyboz/bustorino/backend/utils.java
@@ -1,31 +1,99 @@
package it.reyboz.bustorino.backend;
import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.Uri;
+import android.util.Log;
import android.view.View;
public abstract class utils {
private static final double EarthRadius = 6371e3;
public static Double measuredistanceBetween(double lat1,double long1,double lat2,double long2){
final double phi1 = Math.toRadians(lat1);
final double phi2 = Math.toRadians(lat2);
final double deltaPhi = Math.toRadians(lat2-lat1);
final double deltaTheta = Math.toRadians(long2-long1);
final double a = Math.sin(deltaPhi/2)*Math.sin(deltaPhi/2)+
Math.cos(phi1)*Math.cos(phi2)*Math.sin(deltaTheta/2)*Math.sin(deltaTheta/2);
final double c = 2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a));
return Math.abs(EarthRadius*c);
}
public static int convertDipToPixels(Context con,float dips)
{
return (int) (dips * con.getResources().getDisplayMetrics().density + 0.5f);
}
public static int calculateNumColumnsFromSize(View containerView, int pixelsize){
int width = containerView.getWidth();
float ncols = ((float)width)/pixelsize;
return (int) Math.floor(ncols);
}
+
+ /**
+ * Check if there is an internet connection
+ * @param con context object to get the system service
+ * @return true if we are
+ */
+ public static boolean isConnected(Context con) {
+ ConnectivityManager connMgr = (ConnectivityManager) con.getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
+ return networkInfo != null && networkInfo.isConnected();
+ }
+
+
+ ///////////////////// INTENT HELPER ////////////////////////////////////////////////////////////
+
+ /**
+ * Try to extract the bus stop ID from a URi
+ *
+ * @param uri The URL
+ * @return bus stop ID or null
+ */
+ public static String getBusStopIDFromUri(Uri uri) {
+ String busStopID;
+
+ // everithing catches fire when passing null to a switch.
+ String host = uri.getHost();
+ if (host == null) {
+ Log.e("ActivityMain", "Not an URL: " + uri);
+ return null;
+ }
+
+ switch (host) {
+ case "m.gtt.to.it":
+ // http://m.gtt.to.it/m/it/arrivi.jsp?n=1254
+ busStopID = uri.getQueryParameter("n");
+ if (busStopID == null) {
+ Log.e("ActivityMain", "Expected ?n from: " + uri);
+ }
+ break;
+ case "www.gtt.to.it":
+ case "gtt.to.it":
+ // http://www.gtt.to.it/cms/percorari/arrivi?palina=1254
+ busStopID = uri.getQueryParameter("palina");
+ if (busStopID == null) {
+ Log.e("ActivityMain", "Expected ?palina from: " + uri);
+ }
+ break;
+ default:
+ Log.e("ActivityMain", "Unexpected intent URL: " + uri);
+ busStopID = null;
+ }
+ return busStopID;
+ }
+
+ /**
+ * Open an URL in the default browser.
+ *
+ * @param url URL
+ */
+ public static void openIceweasel(String url, Context context) {
+ Intent browserIntent1 = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+ context.startActivity(browserIntent1);
+ }
}
diff --git a/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java b/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java
index f1f8ac3..00f856c 100644
--- a/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java
+++ b/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java
@@ -1,484 +1,487 @@
/*
BusTO - Fragments components
Copyright (C) 2018 Fabio Mazza
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.fragments;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.adapters.PalinaAdapter;
import it.reyboz.bustorino.backend.ArrivalsFetcher;
import it.reyboz.bustorino.backend.DBStatusManager;
import it.reyboz.bustorino.backend.Fetcher;
import it.reyboz.bustorino.backend.FiveTAPIFetcher;
import it.reyboz.bustorino.backend.FiveTNormalizer;
import it.reyboz.bustorino.backend.FiveTScraperFetcher;
import it.reyboz.bustorino.backend.GTTJSONFetcher;
import it.reyboz.bustorino.backend.Palina;
import it.reyboz.bustorino.backend.Passaggio;
import it.reyboz.bustorino.backend.Route;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.data.AppDataProvider;
import it.reyboz.bustorino.data.NextGenDB;
import it.reyboz.bustorino.data.UserDB;
import it.reyboz.bustorino.middleware.AsyncStopFavoriteAction;
public class ArrivalsFragment extends ResultListFragment implements LoaderManager.LoaderCallbacks {
private final static String KEY_STOP_ID = "stopid";
private final static String KEY_STOP_NAME = "stopname";
private final static String DEBUG_TAG = "BUSTOArrivalsFragment";
private final static int loaderFavId = 2;
private final static int loaderStopId = 1;
private final static ArrivalsFetcher[] defaultFetchers = new ArrivalsFetcher[]{new FiveTAPIFetcher(), new GTTJSONFetcher(), new FiveTScraperFetcher()};
static final String STOP_TITLE = "messageExtra";
private @Nullable String stopID,stopName;
private DBStatusManager prefs;
private DBStatusManager.OnDBUpdateStatusChangeListener listener;
private boolean justCreated = false;
private Palina lastUpdatedPalina = null;
private boolean needUpdateOnAttach = false;
private boolean fetchersChangeRequestPending = false;
private boolean stopIsInFavorites = false;
//Views
protected ImageButton addToFavorites;
protected TextView timesSourceTextView;
private List fetchers = new ArrayList<>(Arrays.asList(defaultFetchers));
public static ArrivalsFragment newInstance(String stopID){
return newInstance(stopID, null);
}
public static ArrivalsFragment newInstance(@NonNull String stopID, @Nullable String stopName){
ArrivalsFragment fragment = new ArrivalsFragment();
Bundle args = new Bundle();
args.putString(KEY_STOP_ID,stopID);
//parameter for ResultListFragment
args.putSerializable(LIST_TYPE,FragmentKind.ARRIVALS);
if (stopName != null){
args.putString(KEY_STOP_NAME,stopName);
}
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
stopID = getArguments().getString(KEY_STOP_ID);
//this might really be null
stopName = getArguments().getString(KEY_STOP_NAME);
final ArrivalsFragment arrivalsFragment = this;
listener = new DBStatusManager.OnDBUpdateStatusChangeListener() {
@Override
public void onDBStatusChanged(boolean updating) {
if(!updating){
getLoaderManager().restartLoader(loaderFavId,getArguments(),arrivalsFragment);
} else {
final LoaderManager lm = getLoaderManager();
lm.destroyLoader(loaderFavId);
lm.destroyLoader(loaderStopId);
}
}
@Override
public boolean defaultStatusValue() {
return true;
}
};
prefs = new DBStatusManager(getContext().getApplicationContext(),listener);
justCreated = true;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_arrivals, container, false);
messageTextView = (TextView) root.findViewById(R.id.messageTextView);
addToFavorites = (ImageButton) root.findViewById(R.id.addToFavorites);
resultsListView = (ListView) root.findViewById(R.id.resultsListView);
timesSourceTextView = (TextView) root.findViewById(R.id.timesSourceTextView);
timesSourceTextView.setOnLongClickListener(view -> {
if(!fetchersChangeRequestPending){
rotateFetchers();
//Show we are changing provider
timesSourceTextView.setText(R.string.arrival_source_changing);
mListener.requestArrivalsForStopID(stopID);
fetchersChangeRequestPending = true;
return true;
}
return false;
});
timesSourceTextView.setOnClickListener(view -> {
Toast.makeText(getContext(), R.string.change_arrivals_source_message, Toast.LENGTH_SHORT)
.show();
});
//Button
addToFavorites.setClickable(true);
addToFavorites.setOnClickListener(v -> {
// add/remove the stop in the favorites
toggleLastStopToFavorites();
});
resultsListView.setOnItemClickListener((parent, view, position, id) -> {
String routeName;
Route r = (Route) parent.getItemAtPosition(position);
routeName = FiveTNormalizer.routeInternalToDisplay(r.getNameForDisplay());
if (routeName == null) {
routeName = r.getNameForDisplay();
}
if (r.destinazione == null || r.destinazione.length() == 0) {
Toast.makeText(getContext(),
getString(R.string.route_towards_unknown, routeName), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getContext(),
getString(R.string.route_towards_destination, routeName, r.destinazione), Toast.LENGTH_SHORT).show();
}
});
String displayName = getArguments().getString(STOP_TITLE);
setTextViewMessage(String.format(
getString(R.string.passages), displayName));
String probablemessage = getArguments().getString(MESSAGE_TEXT_VIEW);
if (probablemessage != null) {
//Log.d("BusTO fragment " + this.getTag(), "We have a possible message here in the savedInstaceState: " + probablemessage);
messageTextView.setText(probablemessage);
messageTextView.setVisibility(View.VISIBLE);
}
return root;
}
@Override
public void onResume() {
super.onResume();
LoaderManager loaderManager = getLoaderManager();
if(stopID!=null){
//refresh the arrivals
if(!justCreated)
mListener.requestArrivalsForStopID(stopID);
else justCreated = false;
//start the loader
if(prefs.isDBUpdating(true)){
prefs.registerListener();
} else {
loaderManager.restartLoader(loaderFavId, getArguments(), this);
}
updateMessage();
}
}
@Override
public void onStart() {
super.onStart();
if (needUpdateOnAttach){
updateFragmentData(null);
}
}
@Nullable
public String getStopID() {
return stopID;
}
/**
* Give the fetchers
* @return the list of the fetchers
*/
public ArrayList getCurrentFetchers(){
ArrayList v = new ArrayList();
for (ArrivalsFetcher fetcher: fetchers){
v.add(fetcher);
}
return v;
}
public Fetcher[] getCurrentFetchersAsArray(){
Fetcher[] arr = new Fetcher[fetchers.size()];
fetchers.toArray(arr);
return arr;
}
private void rotateFetchers(){
Collections.rotate(fetchers, -1);
}
/**
* Update the UI with the new data
* @param p the full Palina
*/
public void updateFragmentData(@Nullable Palina p){
if (p!=null)
lastUpdatedPalina = p;
if (!isAdded()){
//defer update at next show
if (p==null)
Log.w(DEBUG_TAG, "Asked to update the data, but we're not attached and the data is null");
else needUpdateOnAttach = true;
} else {
final PalinaAdapter adapter = new PalinaAdapter(getContext(), lastUpdatedPalina);
showArrivalsSources(lastUpdatedPalina);
super.resetListAdapter(adapter);
}
}
/**
* Set the message of the arrival times source
* @param p Palina with the arrival times
*/
protected void showArrivalsSources(Palina p){
final Passaggio.Source source = p.getPassaggiSourceIfAny();
-
+ if (source == null){
+ Log.e(DEBUG_TAG, "NULL SOURCE");
+ return;
+ }
String source_txt;
switch (source){
case GTTJSON:
source_txt = getString(R.string.gttjsonfetcher);
break;
case FiveTAPI:
source_txt = getString(R.string.fivetapifetcher);
break;
case FiveTScraper:
source_txt = getString(R.string.fivetscraper);
break;
case UNDETERMINED:
//Don't show the view
timesSourceTextView.setVisibility(View.GONE);
return;
default:
throw new IllegalStateException("Unexpected value: " + source);
}
int count = 0;
while (source != fetchers.get(0).getSourceForFetcher() && count < 100){
//we need to update the fetcher that is requested
rotateFetchers();
count++;
}
if (count>10)
Log.w(DEBUG_TAG, "Tried to update the source fetcher but it didn't work");
final String base_message = getString(R.string.times_source_fmt, source_txt);
timesSourceTextView.setVisibility(View.VISIBLE);
timesSourceTextView.setText(base_message);
fetchersChangeRequestPending = false;
}
@Override
public void setNewListAdapter(ListAdapter adapter) {
throw new UnsupportedOperationException();
}
/**
* Update the message in the fragment
*
* It may eventually change the "Add to Favorite" icon
*/
private void updateMessage(){
String message = null;
if (stopName != null && stopID != null && stopName.length() > 0) {
message = (stopID.concat(" - ").concat(stopName));
} else if(stopID!=null) {
message = stopID;
} else {
Log.e("ArrivalsFragm"+getTag(),"NO ID FOR THIS FRAGMENT - something went horribly wrong");
}
if(message!=null) {
setTextViewMessage(getString(R.string.passages,message));
}
// whatever is the case, update the star icon
//updateStarIconFromLastBusStop();
}
@NonNull
@Override
public Loader onCreateLoader(int id, Bundle args) {
if(args.getString(KEY_STOP_ID)==null) return null;
final String stopID = args.getString(KEY_STOP_ID);
final Uri.Builder builder = AppDataProvider.getUriBuilderToComplete();
CursorLoader cl;
switch (id){
case loaderFavId:
builder.appendPath("favorites").appendPath(stopID);
cl = new CursorLoader(getContext(),builder.build(),UserDB.getFavoritesColumnNamesAsArray,null,null,null);
break;
case loaderStopId:
builder.appendPath("stop").appendPath(stopID);
cl = new CursorLoader(getContext(),builder.build(),new String[]{NextGenDB.Contract.StopsTable.COL_NAME},
null,null,null);
break;
default:
return null;
}
cl.setUpdateThrottle(500);
return cl;
}
@Override
public void onLoadFinished(Loader loader, Cursor data) {
switch (loader.getId()){
case loaderFavId:
final int colUserName = data.getColumnIndex(UserDB.getFavoritesColumnNamesAsArray[1]);
if(data.getCount()>0){
// IT'S IN FAVORITES
data.moveToFirst();
final String probableName = data.getString(colUserName);
stopIsInFavorites = true;
if(probableName!=null && !probableName.isEmpty()){
stopName = probableName;
//update the message in the textview
updateMessage();
}
} else {
stopIsInFavorites =false;
}
updateStarIcon();
if(stopName == null){
//stop is not inside the favorites and wasn't provided
Log.d("ArrivalsFragment"+getTag(),"Stop wasn't in the favorites and has no name, looking in the DB");
getLoaderManager().restartLoader(loaderStopId,getArguments(),this);
}
break;
case loaderStopId:
if(data.getCount()>0){
data.moveToFirst();
stopName = data.getString(data.getColumnIndex(
NextGenDB.Contract.StopsTable.COL_NAME
));
updateMessage();
} else {
Log.w("ArrivalsFragment"+getTag(),"Stop is not inside the database... CLOISTER BELL");
}
}
}
@Override
public void onPause() {
if(listener!=null)
prefs.unregisterListener();
super.onPause();
}
@Override
public void onLoaderReset(Loader loader) {
//NOTHING TO DO
}
public void toggleLastStopToFavorites() {
Stop stop = lastUpdatedPalina;
if (stop != null) {
// toggle the status in background
new AsyncStopFavoriteAction(getContext().getApplicationContext(), AsyncStopFavoriteAction.Action.TOGGLE,
v->updateStarIconFromLastBusStop(v)).execute(stop);
} else {
// this case have no sense, but just immediately update the favorite icon
updateStarIconFromLastBusStop(true);
}
}
/**
* Update the star "Add to favorite" icon
*/
public void updateStarIconFromLastBusStop(Boolean toggleDone) {
if (stopIsInFavorites)
stopIsInFavorites = !toggleDone;
else stopIsInFavorites = toggleDone;
updateStarIcon();
// check if there is a last Stop
/*
if (stopID == null) {
addToFavorites.setVisibility(View.INVISIBLE);
} else {
// filled or outline?
if (isStopInFavorites(stopID)) {
addToFavorites.setImageResource(R.drawable.ic_star_filled);
} else {
addToFavorites.setImageResource(R.drawable.ic_star_outline);
}
addToFavorites.setVisibility(View.VISIBLE);
}
*/
}
/**
* Update the star icon according to `stopIsInFavorites`
*/
public void updateStarIcon() {
// no favorites no party!
// check if there is a last Stop
if (stopID == null) {
addToFavorites.setVisibility(View.INVISIBLE);
} else {
// filled or outline?
if (stopIsInFavorites) {
addToFavorites.setImageResource(R.drawable.ic_star_filled);
} else {
addToFavorites.setImageResource(R.drawable.ic_star_outline);
}
addToFavorites.setVisibility(View.VISIBLE);
}
}
}
diff --git a/src/it/reyboz/bustorino/fragments/CommonFragmentListener.java b/src/it/reyboz/bustorino/fragments/CommonFragmentListener.java
index 439b22f..fa7cc43 100644
--- a/src/it/reyboz/bustorino/fragments/CommonFragmentListener.java
+++ b/src/it/reyboz/bustorino/fragments/CommonFragmentListener.java
@@ -1,24 +1,29 @@
package it.reyboz.bustorino.fragments;
public interface CommonFragmentListener {
/**
* Tell the activity that we need to disable/enable its floatingActionButton
* @param yes or no
*/
void showFloatingActionButton(boolean yes);
/**
* Sends the message to the activity to adapt the GUI
* to the fragment that has been attached
* @param fragmentType the type of fragment attached
*/
void readyGUIfor(FragmentKind fragmentType);
/**
* Houston, we need another fragment!
*
* @param ID the Stop ID
*/
void requestArrivalsForStopID(String ID);
+
+ /**
+ * Method to call when we want to hide the keyboard
+ */
+ void hideKeyboard();
}
diff --git a/src/it/reyboz/bustorino/fragments/FragmentHelper.java b/src/it/reyboz/bustorino/fragments/FragmentHelper.java
index 9836f48..66063ec 100644
--- a/src/it/reyboz/bustorino/fragments/FragmentHelper.java
+++ b/src/it/reyboz/bustorino/fragments/FragmentHelper.java
@@ -1,237 +1,232 @@
/*
BusTO (fragments)
Copyright (C) 2018 Fabio Mazza
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.fragments;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.sqlite.SQLiteException;
+import android.content.Context;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
-import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.util.Log;
+import android.widget.Toast;
+
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.Fetcher;
import it.reyboz.bustorino.backend.Palina;
import it.reyboz.bustorino.backend.Stop;
+import it.reyboz.bustorino.backend.utils;
import it.reyboz.bustorino.data.NextGenDB;
import it.reyboz.bustorino.middleware.*;
import java.lang.ref.WeakReference;
import java.util.List;
/**
* Helper class to manage the fragments and their needs
*/
public class FragmentHelper {
- GeneralActivity act;
+ //GeneralActivity act;
+ private final FragmentListenerMain listenerMain;
+ private final WeakReference managerWeakRef;
private Stop lastSuccessfullySearchedBusStop;
//support for multiple frames
private final int secondaryFrameLayout;
- private final int swipeRefID;
private final int primaryFrameLayout;
+ private final Context context;
public static final int NO_FRAME = -3;
+ private static final String DEBUG_TAG = "BusTO FragmHelper";
private WeakReference lastTaskRef;
- private NextGenDB newDBHelper;
private boolean shouldHaltAllActivities=false;
- public FragmentHelper(GeneralActivity act, int swipeRefID, int mainFrame) {
- this(act,swipeRefID,mainFrame,NO_FRAME);
+ public FragmentHelper(FragmentListenerMain listener, FragmentManager framan, Context context, int mainFrame) {
+ this(listener,framan, context,mainFrame,NO_FRAME);
}
- public FragmentHelper(GeneralActivity act, int swipeRefID, int primaryFrameLayout, int secondaryFrameLayout) {
- this.act = act;
- this.swipeRefID = swipeRefID;
+ public FragmentHelper(FragmentListenerMain listener, FragmentManager fraMan, Context context, int primaryFrameLayout, int secondaryFrameLayout) {
+ this.listenerMain = listener;
+ this.managerWeakRef = new WeakReference<>(fraMan);
this.primaryFrameLayout = primaryFrameLayout;
this.secondaryFrameLayout = secondaryFrameLayout;
- newDBHelper = new NextGenDB(act.getApplicationContext());
+ this.context = context.getApplicationContext();
}
/**
* Get the last successfully searched bus stop or NULL
*
* @return the stop
*/
public Stop getLastSuccessfullySearchedBusStop() {
return lastSuccessfullySearchedBusStop;
}
public void setLastSuccessfullySearchedBusStop(Stop stop) {
this.lastSuccessfullySearchedBusStop = stop;
}
public void setLastTaskRef(WeakReference lastTaskRef) {
this.lastTaskRef = lastTaskRef;
}
/**
* Called when you need to create a fragment for a specified Palina
* @param p the Stop that needs to be displayed
*/
public void createOrUpdateStopFragment(Palina p){
boolean sameFragment;
ArrivalsFragment arrivalsFragment;
- if(act==null || shouldHaltAllActivities) {
+ if(managerWeakRef.get()==null || shouldHaltAllActivities) {
//SOMETHING WENT VERY WRONG
+ Log.e(DEBUG_TAG, "We are asked for a new stop but we can't show anything");
return;
}
- FragmentManager fm = act.getSupportFragmentManager();
+ FragmentManager fm = managerWeakRef.get();
if(fm.findFragmentById(primaryFrameLayout) instanceof ArrivalsFragment) {
arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(primaryFrameLayout);
+ //Log.d(DEBUG_TAG, "Arrivals are for fragment with same stop?");
sameFragment = arrivalsFragment.isFragmentForTheSameStop(p);
- } else
+ } else {
sameFragment = false;
+ Log.d(DEBUG_TAG, "We aren't showing an ArrivalsFragment");
+ }
setLastSuccessfullySearchedBusStop(p);
if(!sameFragment) {
//set the String to be displayed on the fragment
String displayName = p.getStopDisplayName();
String displayStuff;
if (displayName != null && displayName.length() > 0) {
arrivalsFragment = ArrivalsFragment.newInstance(p.ID,displayName);
} else {
arrivalsFragment = ArrivalsFragment.newInstance(p.ID);
}
attachFragmentToContainer(fm,arrivalsFragment,true,ResultListFragment.getFragmentTag(p));
} else {
Log.d("BusTO", "Same bus stop, accessing existing fragment");
arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(primaryFrameLayout);
}
// DO NOT CALL `setListAdapter` ever on arrivals fragment
arrivalsFragment.updateFragmentData(p);
- act.hideKeyboard();
+ listenerMain.hideKeyboard();
toggleSpinner(false);
}
/**
* Called when you need to display the results of a search of stops
* @param resultList the List of stops found
* @param query String queried
*/
public void createFragmentFor(List resultList,String query){
- act.hideKeyboard();
+ listenerMain.hideKeyboard();
StopListFragment listfragment = StopListFragment.newInstance(query);
- attachFragmentToContainer(act.getSupportFragmentManager(),listfragment,false,"search_"+query);
+ if(managerWeakRef.get()==null || shouldHaltAllActivities) {
+ //SOMETHING WENT VERY WRONG
+ Log.e(DEBUG_TAG, "We are asked for a new stop but we can't show anything");
+ return;
+ }
+ attachFragmentToContainer(managerWeakRef.get(),listfragment,false,"search_"+query);
listfragment.setStopList(resultList);
toggleSpinner(false);
}
/**
* Wrapper for toggleSpinner in Activity
* @param on new status of spinner system
*/
public void toggleSpinner(boolean on){
- if (act instanceof FragmentListenerMain)
- ((FragmentListenerMain) act).toggleSpinner(on);
- else {
- SwipeRefreshLayout srl = (SwipeRefreshLayout) act.findViewById(swipeRefID);
- srl.setRefreshing(false);
- }
+ listenerMain.toggleSpinner(on);
}
/**
* Attach a new fragment to a cointainer
* @param fm the FragmentManager
* @param fragment the Fragment
* @param sendToSecondaryFrame needs to be displayed in secondary frame or not
* @param tag tag for the fragment
*/
public void attachFragmentToContainer(FragmentManager fm,Fragment fragment, boolean sendToSecondaryFrame, String tag){
FragmentTransaction ft = fm.beginTransaction();
if(sendToSecondaryFrame && secondaryFrameLayout!=NO_FRAME)
ft.replace(secondaryFrameLayout,fragment,tag);
else ft.replace(primaryFrameLayout,fragment,tag);
ft.addToBackStack("state_"+tag);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
ft.commit();
//fm.executePendingTransactions();
}
- synchronized public int insertBatchDataInNextGenDB(ContentValues[] valuesArr,String tableName){
- if(newDBHelper !=null)
- try {
- return newDBHelper.insertBatchContent(valuesArr, tableName);
- } catch (SQLiteException exc){
- Log.w("DB Batch inserting: ","ERROR Inserting the data batch: ",exc.fillInStackTrace());
- return -2;
- }
- else return -1;
- }
-
- synchronized public ContentResolver getContentResolver(){
- return act.getContentResolver();
- }
-
public void setBlockAllActivities(boolean shouldI) {
this.shouldHaltAllActivities = shouldI;
}
public void stopLastRequestIfNeeded(){
if(lastTaskRef == null) return;
AsyncDataDownload task = lastTaskRef.get();
if(task!=null){
task.cancel(true);
}
}
/**
* Wrapper to show the errors/status that happened
* @param res result from Fetcher
*/
public void showErrorMessage(Fetcher.result res){
//TODO: implement a common set of errors for all fragments
switch (res){
case OK:
break;
case CLIENT_OFFLINE:
- act.showToastMessage(R.string.network_error, true);
+ showToastMessage(R.string.network_error, true);
break;
case SERVER_ERROR:
- if (act.isConnected()) {
- act.showToastMessage(R.string.parsing_error, true);
+ if (utils.isConnected(context)) {
+ showToastMessage(R.string.parsing_error, true);
} else {
- act.showToastMessage(R.string.network_error, true);
+ showToastMessage(R.string.network_error, true);
}
case PARSER_ERROR:
default:
showShortToast(R.string.internal_error);
break;
case QUERY_TOO_SHORT:
showShortToast(R.string.query_too_short);
break;
case EMPTY_RESULT_SET:
showShortToast(R.string.no_bus_stop_have_this_name);
break;
}
}
- public void showShortToast(int message){
- if (act!=null)
- act.showToastMessage(message,true);
+ public void showToastMessage(int messageID, boolean short_lenght) {
+ final int length = short_lenght ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG;
+ if (context != null)
+ Toast.makeText(context, messageID, length).show();
+ }
+ private void showShortToast(int messageID){
+ showToastMessage(messageID, true);
}
}
diff --git a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java
index ee168a3..789d76a 100644
--- a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java
+++ b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java
@@ -1,308 +1,546 @@
package it.reyboz.bustorino.fragments;
+import android.Manifest;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.location.LocationProvider;
+import android.os.Build;
import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.widget.AppCompatImageButton;
+import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.TextView;
+import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.zxing.integration.android.IntentIntegrator;
+
+import it.reyboz.bustorino.ActivityMain;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.ArrivalsFetcher;
import it.reyboz.bustorino.backend.FiveTAPIFetcher;
import it.reyboz.bustorino.backend.FiveTScraperFetcher;
import it.reyboz.bustorino.backend.FiveTStopsFetcher;
import it.reyboz.bustorino.backend.GTTJSONFetcher;
import it.reyboz.bustorino.backend.GTTStopsFetcher;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.backend.StopsFinderByName;
import it.reyboz.bustorino.data.UserDB;
import it.reyboz.bustorino.middleware.AsyncDataDownload;
+import it.reyboz.bustorino.util.Permissions;
+
+import static android.content.Context.LOCATION_SERVICE;
+import static it.reyboz.bustorino.util.Permissions.LOCATION_PERMISSION_GIVEN;
/**
* A simple {@link Fragment} subclass.
* Use the {@link MainScreenFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class MainScreenFragment extends BaseFragment implements FragmentListenerMain{
private final String OPTION_SHOW_LEGEND = "show_legend";
private static final String DEBUG_TAG = "BusTO - MainFragment";
- private CommonFragmentListener mListener;
+ public final static String FRAGMENT_TAG = "MainScreenFragment";
+
/// UI ELEMENTS //
private ImageButton addToFavorites;
private FragmentHelper fragmentHelper;
private SwipeRefreshLayout swipeRefreshLayout;
private EditText busStopSearchByIDEditText;
private EditText busStopSearchByNameEditText;
private ProgressBar progressBar;
private TextView howDoesItWorkTextView;
private Button hideHintButton;
private MenuItem actionHelpMenuItem;
private FloatingActionButton floatingActionButton;
+
+ private boolean setupOnAttached = true;
//private Snackbar snackbar;
/*
* Search mode
*/
private static final int SEARCH_BY_NAME = 0;
private static final int SEARCH_BY_ID = 1;
private static final int SEARCH_BY_ROUTE = 2; // TODO: implement this -- https://gitpull.it/T12
private int searchMode;
//private ImageButton addToFavorites;
private final ArrivalsFetcher[] arrivalsFetchers = new ArrivalsFetcher[]{new FiveTAPIFetcher(), new GTTJSONFetcher(), new FiveTScraperFetcher()};
+ //// HIDDEN BUT IMPORTANT ELEMENTS ////
+ FragmentManager fragMan;
+ Handler mainHandler;
+ private final Runnable refreshStop = new Runnable() {
+ public void run() {
+ if (fragMan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) {
+ ArrivalsFragment fragment = (ArrivalsFragment) fragMan.findFragmentById(R.id.resultFrame);
+ if (fragment == null){
+ //we create a new fragment, which is WRONG
+ new AsyncDataDownload(fragmentHelper, arrivalsFetchers,getContext()).execute();
+ } else{
+ String stopName = fragment.getStopID();
+
+ new AsyncDataDownload(fragmentHelper, fragment.getCurrentFetchersAsArray(), getContext()).execute(stopName);
+ }
+ } else //we create a new fragment, which is WRONG
+ new AsyncDataDownload(fragmentHelper, arrivalsFetchers, getContext()).execute();
+ }
+ };
+
+
+ /// LOCATION STUFF ///
+ boolean pendingNearbyStopsRequest = false;
+ LocationManager locmgr;
+
+ private final Criteria cr = new Criteria();
+
+ //// ACTIVITY ATTACHED (LISTENER ///
+ private CommonFragmentListener mListener;
+
public MainScreenFragment() {
// Required empty public constructor
}
public static MainScreenFragment newInstance() {
MainScreenFragment fragment = new MainScreenFragment();
Bundle args = new Bundle();
//args.putString(ARG_PARAM1, param1);
//args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
//do nothing
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View root = inflater.inflate(R.layout.fragment_main_screen, container, false);
addToFavorites = (ImageButton) root.findViewById(R.id.addToFavorites);
busStopSearchByIDEditText = root.findViewById(R.id.busStopSearchByIDEditText);
busStopSearchByNameEditText = root.findViewById(R.id.busStopSearchByNameEditText);
progressBar = root.findViewById(R.id.progressBar);
howDoesItWorkTextView = root.findViewById(R.id.howDoesItWorkTextView);
hideHintButton = root.findViewById(R.id.hideHintButton);
swipeRefreshLayout = root.findViewById(R.id.listRefreshLayout);
floatingActionButton = root.findViewById(R.id.floatingActionButton);
+ busStopSearchByIDEditText.setSelectAllOnFocus(true);
+ busStopSearchByIDEditText
+ .setOnEditorActionListener((v, actionId, event) -> {
+ // IME_ACTION_SEARCH alphabetical option
+ if (actionId == EditorInfo.IME_ACTION_SEARCH) {
+ onSearchClick(v);
+ return true;
+ }
+ return false;
+ });
+ busStopSearchByNameEditText
+ .setOnEditorActionListener((v, actionId, event) -> {
+ // IME_ACTION_SEARCH alphabetical option
+ if (actionId == EditorInfo.IME_ACTION_SEARCH) {
+ onSearchClick(v);
+ return true;
+ }
+ return false;
+ });
+
+ swipeRefreshLayout
+ .setOnRefreshListener(() -> mainHandler.post(refreshStop));
+ swipeRefreshLayout.setColorSchemeResources(R.color.blue_500, R.color.orange_500);
+
+ floatingActionButton.setOnClickListener((this::onToggleKeyboardLayout));
+ hideHintButton.setOnClickListener(this::onHideHint);
+
+ AppCompatImageButton qrButton = root.findViewById(R.id.QRButton);
+ qrButton.setOnClickListener(this::onQRButtonClick);
+
+ AppCompatImageButton searchButton = root.findViewById(R.id.searchButton);
+ searchButton.setOnClickListener(this::onSearchClick);
+
+ // Fragment stuff
+ fragMan = getChildFragmentManager();
+ fragMan.addOnBackStackChangedListener(() -> Log.d("BusTO Main Fragment", "BACK STACK CHANGED"));
+
+ fragmentHelper = new FragmentHelper(this, getChildFragmentManager(), getContext(), R.id.resultFrame);
+ setSearchModeBusStopID();
+
+
+ cr.setAccuracy(Criteria.ACCURACY_FINE);
+ cr.setAltitudeRequired(false);
+ cr.setBearingRequired(false);
+ cr.setCostAllowed(true);
+ cr.setPowerRequirement(Criteria.NO_REQUIREMENT);
+
+ locmgr = (LocationManager) getContext().getSystemService(LOCATION_SERVICE);
return root;
}
+ @Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+ mainHandler = new Handler();
+ if (context instanceof CommonFragmentListener) {
+ mListener = (CommonFragmentListener) context;
+ } else {
+ throw new RuntimeException(context.toString()
+ + " must implement CommonFragmentListener");
+ }
+ if (setupOnAttached){
+ //We want the nearby bus stops!
+ mainHandler.post(new NearbyStopsRequester(getContext(),cr, locListener));
+ //If there are no providers available, then, wait for them
+
+ setupOnAttached = false;
+ }
+
+ }
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mListener = null;
+ }
+ @Override
+ public void onResume() {
+
+ final Context con = getContext();
+ if (con != null)
+ locmgr = (LocationManager) getContext().getSystemService(LOCATION_SERVICE);
+ else {
+ Log.w(DEBUG_TAG, "Context is null at onResume");
+ }
+ super.onResume();
+ }
+ @Override
+ public void onPause() {
+ //mainHandler = null;
+ locmgr = null;
+ super.onPause();
+ }
/*
GUI METHODS
*/
/**
* QR scan button clicked
*
* @param v View QRButton clicked
*/
public void onQRButtonClick(View v) {
IntentIntegrator integrator = new IntentIntegrator(getActivity());
integrator.initiateScan();
}
public void onHideHint(View v) {
hideHints();
setOption(OPTION_SHOW_LEGEND, false);
}
/**
* OK this is pure shit
*
* @param v View clicked
*/
public void onSearchClick(View v) {
final StopsFinderByName[] stopsFinderByNames = new StopsFinderByName[]{new GTTStopsFetcher(), new FiveTStopsFetcher()};
if (searchMode == SEARCH_BY_ID) {
String busStopID = busStopSearchByIDEditText.getText().toString();
requestArrivalsForStopID(busStopID);
} else { // searchMode == SEARCH_BY_NAME
String query = busStopSearchByNameEditText.getText().toString();
//new asyncWgetBusStopSuggestions(query, stopsDB, StopsFindersByNameRecursionHelper);
- new AsyncDataDownload(fragmentHelper, stopsFinderByNames).execute(query);
+ new AsyncDataDownload(fragmentHelper, stopsFinderByNames, getContext()).execute(query);
}
}
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();
}
}
}
@Override
public void enableRefreshLayout(boolean yes) {
swipeRefreshLayout.setEnabled(yes);
}
////////////////////////////////////// GUI HELPERS /////////////////////////////////////////////
public void showKeyboard() {
InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
View view = searchMode == SEARCH_BY_ID ? busStopSearchByIDEditText : busStopSearchByNameEditText;
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
}
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);
+ //actionHelpMenuItem.setVisible(false);
}
private void hideHints() {
howDoesItWorkTextView.setVisibility(View.GONE);
hideHintButton.setVisibility(View.GONE);
- actionHelpMenuItem.setVisible(true);
+ //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);
+ //actionHelpMenuItem.setVisible(true);
}
private void prepareGUIForBusStops() {
swipeRefreshLayout.setEnabled(false);
swipeRefreshLayout.setVisibility(View.VISIBLE);
- actionHelpMenuItem.setVisible(false);
+ //actionHelpMenuItem.setVisible(false);
}
@Override
public void showFloatingActionButton(boolean yes) {
mListener.showFloatingActionButton(yes);
}
/**
* 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(FragmentKind fragmentType) {
- throw new UnsupportedOperationException();
- /*
+
hideKeyboard();
- //TODO: fix this
+
//if we are getting results, already, stop waiting for nearbyStops
if (pendingNearbyStopsRequest && (fragmentType == FragmentKind.ARRIVALS || fragmentType == FragmentKind.STOPS)) {
locmgr.removeUpdates(locListener);
pendingNearbyStopsRequest = false;
}
if (fragmentType == null) Log.e("ActivityMain", "Problem with fragmentType");
else
switch (fragmentType) {
case ARRIVALS:
prepareGUIForBusLines();
if (getOption(OPTION_SHOW_LEGEND, true)) {
showHints();
}
break;
case STOPS:
prepareGUIForBusStops();
break;
default:
Log.e("BusTO Activity", "Called readyGUI with unsupported type of Fragment");
return;
}
// Shows hints
- */
+
}
@Override
public void requestArrivalsForStopID(String ID) {
final FragmentManager framan = getChildFragmentManager();
if (ID == null || ID.length() <= 0) {
// we're still in UI thread, no need to mess with Progress
showToastMessage(R.string.insert_bus_stop_number_error, true);
toggleSpinner(false);
} else if (framan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) {
ArrivalsFragment fragment = (ArrivalsFragment) framan.findFragmentById(R.id.resultFrame);
if (fragment != null && fragment.getStopID() != null && fragment.getStopID().equals(ID)){
// Run with previous fetchers
//fragment.getCurrentFetchers().toArray()
- new AsyncDataDownload(fragmentHelper,fragment.getCurrentFetchersAsArray()).execute(ID);
+ new AsyncDataDownload(fragmentHelper,fragment.getCurrentFetchersAsArray(), getContext()).execute(ID);
} else{
- new AsyncDataDownload(fragmentHelper, arrivalsFetchers).execute(ID);
+ new AsyncDataDownload(fragmentHelper, arrivalsFetchers, getContext()).execute(ID);
}
}
else {
- new AsyncDataDownload(fragmentHelper,arrivalsFetchers).execute(ID);
+ new AsyncDataDownload(fragmentHelper,arrivalsFetchers, getContext()).execute(ID);
Log.d("MainActiv", "Started search for arrivals of stop " + ID);
}
}
+ /////////// LOCATION METHODS //////////
+ final LocationListener locListener = new LocationListener() {
+ @Override
+ public void onLocationChanged(Location location) {
+ Log.d(DEBUG_TAG, "Location changed");
+ }
+
+ @Override
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+ Log.d(DEBUG_TAG, "Location provider status: " + status);
+ if (status == LocationProvider.AVAILABLE) {
+ resolveStopRequest(provider);
+ }
+ }
+
+ @Override
+ public void onProviderEnabled(String provider) {
+ resolveStopRequest(provider);
+ }
+
+ @Override
+ public void onProviderDisabled(String provider) {
+ }
+ };
+
+ private void resolveStopRequest(String provider) {
+ Log.d(DEBUG_TAG, "Provider " + provider + " got enabled");
+ if (locmgr != null && mainHandler != null && pendingNearbyStopsRequest && locmgr.getProvider(provider).meetsCriteria(cr)) {
+ pendingNearbyStopsRequest = false;
+ mainHandler.post(new NearbyStopsRequester(getContext(), cr, locListener));
+ }
+ }
+
+ /**
+ * Run location requests separately and asynchronously
+ */
+ class NearbyStopsRequester implements Runnable {
+ Context appContext;
+ Criteria cr;
+ LocationListener listener;
+
+ public NearbyStopsRequester(Context appContext, Criteria criteria, LocationListener listener) {
+ this.appContext = appContext.getApplicationContext();
+ this.cr = criteria;
+ this.listener = listener;
+ }
+
+ @Override
+ public void run() {
+ final boolean canRunPosition = Build.VERSION.SDK_INT < Build.VERSION_CODES.M || getOption(LOCATION_PERMISSION_GIVEN, false);
+ final boolean noPermission = ActivityCompat.checkSelfPermission(appContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
+ ActivityCompat.checkSelfPermission(appContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED;
+
+ //if we don't have the permission, we have to ask for it, if we haven't
+ // asked too many times before
+ if (noPermission) {
+ if (!canRunPosition) {
+ pendingNearbyStopsRequest = true;
+ Permissions.assertLocationPermissions(appContext,getActivity());
+ Log.w(DEBUG_TAG, "Cannot get position: Asking permission, noPositionFromSys: " + noPermission);
+ return;
+ } else {
+ Toast.makeText(appContext, "Asked for permission position too many times", Toast.LENGTH_LONG).show();
+ }
+ } else setOption(LOCATION_PERMISSION_GIVEN, true);
+
+ LocationManager locManager = (LocationManager) appContext.getSystemService(LOCATION_SERVICE);
+ if (locManager == null) {
+ Log.e(DEBUG_TAG, "location manager is nihil, cannot create NearbyStopsFragment");
+ return;
+ }
+ if (Permissions.anyLocationProviderMatchesCriteria(locManager, cr, true)
+ && fragmentHelper.getLastSuccessfullySearchedBusStop() == null
+ && !fragMan.isDestroyed()) {
+ //Go ahead with the request
+ Log.d("mainActivity", "Recreating stop fragment");
+ swipeRefreshLayout.setVisibility(View.VISIBLE);
+ NearbyStopsFragment fragment = NearbyStopsFragment.newInstance(NearbyStopsFragment.TYPE_STOPS);
+ Fragment oldFrag = fragMan.findFragmentById(R.id.resultFrame);
+ FragmentTransaction ft = fragMan.beginTransaction();
+ if (oldFrag != null)
+ ft.remove(oldFrag);
+ ft.add(R.id.resultFrame, fragment, "nearbyStop_correct");
+ ft.commit();
+ //fragMan.executePendingTransactions();
+ pendingNearbyStopsRequest = false;
+ } else if (!Permissions.anyLocationProviderMatchesCriteria(locManager, cr, true)) {
+ //Wait for the providers
+ Log.d(DEBUG_TAG, "Queuing position request");
+ pendingNearbyStopsRequest = true;
+
+ locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 10, 0.1f, listener);
+ }
+
+ }
+ }
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java b/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
index 78d044a..68981b0 100644
--- a/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
+++ b/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
@@ -1,651 +1,652 @@
/*
BusTO - Fragments components
Copyright (C) 2018 Fabio Mazza
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.fragments;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import androidx.core.util.Pair;
import androidx.preference.PreferenceManager;
import androidx.appcompat.widget.AppCompatButton;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.volley.*;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.adapters.ArrivalsStopAdapter;
import it.reyboz.bustorino.backend.*;
import it.reyboz.bustorino.backend.FiveTAPIFetcher.QueryType;
import it.reyboz.bustorino.middleware.AppLocationManager;
import it.reyboz.bustorino.data.AppDataProvider;
import it.reyboz.bustorino.data.NextGenDB.Contract.*;
import it.reyboz.bustorino.adapters.SquareStopAdapter;
import it.reyboz.bustorino.util.LocationCriteria;
import it.reyboz.bustorino.util.StopSorterByDistance;
import java.util.*;
public class NearbyStopsFragment extends Fragment implements LoaderManager.LoaderCallbacks {
private FragmentListenerMain mListener;
private FragmentLocationListener fragmentLocationListener;
private final String[] PROJECTION = {StopsTable.COL_ID,StopsTable.COL_LAT,StopsTable.COL_LONG,
StopsTable.COL_NAME,StopsTable.COL_TYPE,StopsTable.COL_LINES_STOPPING};
private final static String DEBUG_TAG = "NearbyStopsFragment";
private final static String FRAGMENT_TYPE_KEY = "FragmentType";
public final static int TYPE_STOPS = 19, TYPE_ARRIVALS = 20;
private int fragment_type;
//data Bundle
private final String BUNDLE_LOCATION = "location";
private final int LOADER_ID = 0;
private RecyclerView gridRecyclerView;
private SquareStopAdapter dataAdapter;
private AutoFitGridLayoutManager gridLayoutManager;
boolean canStartDBQuery = true;
private Location lastReceivedLocation = null;
private ProgressBar circlingProgressBar,flatProgressBar;
private int distance;
protected SharedPreferences globalSharedPref;
private SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener;
private TextView messageTextView,titleTextView;
private CommonScrollListener scrollListener;
private AppCompatButton switchButton;
private boolean firstLocForStops = true,firstLocForArrivals = true;
public static final int COLUMN_WIDTH_DP = 250;
private Integer MAX_DISTANCE = -3;
private int MIN_NUM_STOPS = -1;
private int TIME_INTERVAL_REQUESTS = -1;
private AppLocationManager locManager;
//These are useful for the case of nearby arrivals
private ArrivalsManager arrivalsManager = null;
private ArrivalsStopAdapter arrivalsStopAdapter = null;
public NearbyStopsFragment() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
* @return A new instance of fragment NearbyStopsFragment.
*/
public static NearbyStopsFragment newInstance(int fragmentType) {
if(fragmentType != TYPE_STOPS && fragmentType != TYPE_ARRIVALS )
throw new IllegalArgumentException("WRONG KIND OF FRAGMENT USED");
NearbyStopsFragment fragment = new NearbyStopsFragment();
final Bundle args = new Bundle(1);
args.putInt(FRAGMENT_TYPE_KEY,fragmentType);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
setFragmentType(getArguments().getInt(FRAGMENT_TYPE_KEY));
}
locManager = AppLocationManager.getInstance(getContext());
fragmentLocationListener = new FragmentLocationListener(this);
globalSharedPref = getContext().getSharedPreferences(getString(R.string.mainSharedPreferences),Context.MODE_PRIVATE);
globalSharedPref.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View root = inflater.inflate(R.layout.fragment_nearby_stops, container, false);
- gridRecyclerView = (RecyclerView) root.findViewById(R.id.stopGridRecyclerView);
+ gridRecyclerView = root.findViewById(R.id.stopGridRecyclerView);
gridLayoutManager = new AutoFitGridLayoutManager(getContext().getApplicationContext(), utils.convertDipToPixels(getContext(),COLUMN_WIDTH_DP));
gridRecyclerView.setLayoutManager(gridLayoutManager);
gridRecyclerView.setHasFixedSize(false);
- circlingProgressBar = (ProgressBar) root.findViewById(R.id.loadingBar);
- flatProgressBar = (ProgressBar) root.findViewById(R.id.horizontalProgressBar);
- messageTextView = (TextView) root.findViewById(R.id.messageTextView);
- titleTextView = (TextView) root.findViewById(R.id.titleTextView);
- switchButton = (AppCompatButton) root.findViewById(R.id.switchButton);
+ circlingProgressBar = root.findViewById(R.id.loadingBar);
+ flatProgressBar = root.findViewById(R.id.horizontalProgressBar);
+ messageTextView = root.findViewById(R.id.messageTextView);
+ titleTextView = root.findViewById(R.id.titleTextView);
+ switchButton = root.findViewById(R.id.switchButton);
preferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Log.d(DEBUG_TAG,"Key "+key+" was changed");
if(key.equals(getString(R.string.databaseUpdatingPref))){
if(!sharedPreferences.getBoolean(getString(R.string.databaseUpdatingPref),true)){
canStartDBQuery = true;
Log.d(DEBUG_TAG,"The database has finished updating, can start update now");
}
}
}
};
scrollListener = new CommonScrollListener(mListener,false);
switchButton.setOnClickListener(v -> {
switchFragmentType();
});
return root;
}
protected ArrayList createStopListFromCursor(Cursor data){
ArrayList stopList = new ArrayList<>();
final int col_id = data.getColumnIndex(StopsTable.COL_ID);
final int latInd = data.getColumnIndex(StopsTable.COL_LAT);
final int lonInd = data.getColumnIndex(StopsTable.COL_LONG);
final int nameindex = data.getColumnIndex(StopsTable.COL_NAME);
final int typeIndex = data.getColumnIndex(StopsTable.COL_TYPE);
final int linesIndex = data.getColumnIndex(StopsTable.COL_LINES_STOPPING);
data.moveToFirst();
for(int i=0; i onCreateLoader(int id, Bundle args) {
//BUILD URI
lastReceivedLocation = args.getParcelable(BUNDLE_LOCATION);
Uri.Builder builder = new Uri.Builder();
builder.scheme("content").authority(AppDataProvider.AUTHORITY)
.appendPath("stops").appendPath("location")
.appendPath(String.valueOf(lastReceivedLocation.getLatitude()))
.appendPath(String.valueOf(lastReceivedLocation.getLongitude()))
.appendPath(String.valueOf(distance)); //distance
CursorLoader cl = new CursorLoader(getContext(),builder.build(),PROJECTION,null,null,null);
cl.setUpdateThrottle(2000);
return cl;
}
@Override
public void onLoadFinished(@NonNull Loader loader, Cursor data) {
if (0 > MAX_DISTANCE) throw new AssertionError();
//Cursor might be null
if(data==null){
Log.e(DEBUG_TAG,"Null cursor, something really wrong happened");
return;
}
if(!isDBUpdating() && (data.getCount() stopList = createStopListFromCursor(data);
if(data.getCount()>0) {
//quick trial to hopefully always get the stops in the correct order
Collections.sort(stopList,new StopSorterByDistance(lastReceivedLocation));
switch (fragment_type){
case TYPE_STOPS:
showStopsInRecycler(stopList);
break;
case TYPE_ARRIVALS:
arrivalsManager = new ArrivalsManager(stopList);
flatProgressBar.setVisibility(View.VISIBLE);
flatProgressBar.setProgress(0);
flatProgressBar.setIndeterminate(false);
//for the moment, be satisfied with only one location
//AppLocationManager.getInstance(getContext()).removeLocationRequestFor(fragmentLocationListener);
break;
default:
}
} else {
setNoStopsLayout();
}
}
@Override
public void onLoaderReset(Loader loader) {
}
/**
* To enable targeting from the Button
*/
public void switchFragmentType(View v){
switchFragmentType();
}
/**
* Call when you need to switch the type of fragment
*/
private void switchFragmentType(){
if(fragment_type==TYPE_ARRIVALS){
setFragmentType(TYPE_STOPS);
switchButton.setText(getString(R.string.show_arrivals));
titleTextView.setText(getString(R.string.nearby_stops_message));
if(arrivalsManager!=null)
arrivalsManager.cancelAllRequests();
if(dataAdapter!=null)
gridRecyclerView.setAdapter(dataAdapter);
} else if (fragment_type==TYPE_STOPS){
setFragmentType(TYPE_ARRIVALS);
titleTextView.setText(getString(R.string.nearby_arrivals_message));
switchButton.setText(getString(R.string.show_stops));
if(arrivalsStopAdapter!=null)
gridRecyclerView.setAdapter(arrivalsStopAdapter);
}
fragmentLocationListener.lastUpdateTime = -1;
locManager.removeLocationRequestFor(fragmentLocationListener);
locManager.addLocationRequestFor(fragmentLocationListener);
}
//useful methods
protected boolean isDBUpdating(){
return globalSharedPref.getBoolean(getString(R.string.databaseUpdatingPref),false);
}
/////// GUI METHODS ////////
private void showStopsInRecycler(List stops){
if(firstLocForStops) {
dataAdapter = new SquareStopAdapter(stops, mListener, lastReceivedLocation);
gridRecyclerView.setAdapter(dataAdapter);
firstLocForStops = false;
}else {
dataAdapter.setStops(stops);
dataAdapter.setUserPosition(lastReceivedLocation);
}
dataAdapter.notifyDataSetChanged();
//showRecyclerHidingLoadMessage();
if (gridRecyclerView.getVisibility() != View.VISIBLE) {
circlingProgressBar.setVisibility(View.GONE);
gridRecyclerView.setVisibility(View.VISIBLE);
}
messageTextView.setVisibility(View.GONE);
}
private void showArrivalsInRecycler(List palinas){
Collections.sort(palinas,new StopSorterByDistance(lastReceivedLocation));
final ArrayList> routesPairList = new ArrayList<>(10);
//int maxNum = Math.min(MAX_STOPS, stopList.size());
for(Palina p: palinas){
//if there are no routes available, skip stop
if(p.queryAllRoutes().size() == 0) continue;
for(Route r: p.queryAllRoutes()){
//if there are no routes, should not do anything
routesPairList.add(new Pair<>(p,r));
}
}
if(firstLocForArrivals){
arrivalsStopAdapter = new ArrivalsStopAdapter(routesPairList,mListener,getContext(),lastReceivedLocation);
gridRecyclerView.setAdapter(arrivalsStopAdapter);
firstLocForArrivals = false;
} else {
arrivalsStopAdapter.setRoutesPairListAndPosition(routesPairList,lastReceivedLocation);
}
//arrivalsStopAdapter.notifyDataSetChanged();
showRecyclerHidingLoadMessage();
}
private void setNoStopsLayout(){
messageTextView.setVisibility(View.VISIBLE);
messageTextView.setText(R.string.no_stops_nearby);
circlingProgressBar.setVisibility(View.GONE);
}
/**
* Does exactly what is says on the tin
*/
private void showRecyclerHidingLoadMessage(){
if (gridRecyclerView.getVisibility() != View.VISIBLE) {
circlingProgressBar.setVisibility(View.GONE);
gridRecyclerView.setVisibility(View.VISIBLE);
}
messageTextView.setVisibility(View.GONE);
}
class ArrivalsManager implements FiveTAPIVolleyRequest.ResponseListener, Response.ErrorListener{
final HashMap mStops;
final Map> routesToAdd = new HashMap<>();
final static String REQUEST_TAG = "NearbyArrivals";
private final QueryType[] types = {QueryType.ARRIVALS,QueryType.DETAILS};
final NetworkVolleyManager volleyManager;
private final int MAX_ARRIVAL_STOPS =35;
int activeRequestCount = 0,reqErrorCount = 0, reqSuccessCount=0;
ArrivalsManager(List stops){
mStops = new HashMap<>();
volleyManager = NetworkVolleyManager.getInstance(getContext());
for(Stop s: stops.subList(0,Math.min(stops.size(), MAX_ARRIVAL_STOPS))){
mStops.put(s.ID,new Palina(s));
for(QueryType t: types) {
final FiveTAPIVolleyRequest req = FiveTAPIVolleyRequest.getNewRequest(t, s.ID, this, this);
if (req != null) {
req.setTag(REQUEST_TAG);
volleyManager.addToRequestQueue(req);
activeRequestCount++;
}
}
}
flatProgressBar.setMax(activeRequestCount);
}
@Override
public void onErrorResponse(VolleyError error) {
if(error instanceof ParseError){
//TODO
Log.w(DEBUG_TAG,"Parsing error for stop request");
} else if (error instanceof NetworkError){
String s;
if(error.networkResponse!=null)
s = new String(error.networkResponse.data);
else s="";
Log.w(DEBUG_TAG,"Network error: "+s);
}else {
Log.w(DEBUG_TAG,"Volley Error: "+error.getMessage());
}
if(error.networkResponse!=null){
Log.w(DEBUG_TAG, "Error status code: "+error.networkResponse.statusCode);
}
//counters
activeRequestCount--;
reqErrorCount++;
flatProgressBar.setProgress(reqErrorCount+reqSuccessCount);
}
@Override
public void onResponse(Palina result, QueryType type) {
//counter for requests
activeRequestCount--;
reqSuccessCount++;
final Palina palinaInMap = mStops.get(result.ID);
//palina cannot be null here
//sorry for the brutal crash when it happens
if(palinaInMap == null) throw new IllegalStateException("Cannot get the palina from the map");
//necessary to split the Arrivals and Details cases
switch (type){
case ARRIVALS:
palinaInMap.addInfoFromRoutes(result.queryAllRoutes());
final List possibleRoutes = routesToAdd.get(result.ID);
if(possibleRoutes!=null) {
palinaInMap.addInfoFromRoutes(possibleRoutes);
routesToAdd.remove(result.ID);
}
break;
case DETAILS:
if(palinaInMap.queryAllRoutes().size()>0){
//merge the branches
palinaInMap.addInfoFromRoutes(result.queryAllRoutes());
} else {
routesToAdd.put(result.ID,result.queryAllRoutes());
}
break;
default:
throw new IllegalArgumentException("Wrong QueryType in onResponse");
}
final ArrayList outList = new ArrayList<>();
for(Palina p: mStops.values()){
final List routes = p.queryAllRoutes();
if(routes!=null && routes.size()>0) outList.add(p);
}
showArrivalsInRecycler(outList);
flatProgressBar.setProgress(reqErrorCount+reqSuccessCount);
if(activeRequestCount==0) {
flatProgressBar.setIndeterminate(true);
flatProgressBar.setVisibility(View.GONE);
}
}
void cancelAllRequests(){
volleyManager.getRequestQueue().cancelAll(REQUEST_TAG);
flatProgressBar.setVisibility(View.GONE);
}
}
/**
* Local locationListener, to use for the GPS
*/
class FragmentLocationListener implements AppLocationManager.LocationRequester{
LoaderManager.LoaderCallbacks callbacks;
private int oldLocStatus = -2;
private LocationCriteria cr;
private long lastUpdateTime = -1;
public FragmentLocationListener(LoaderManager.LoaderCallbacks callbacks) {
this.callbacks = callbacks;
}
@Override
public void onLocationChanged(Location location) {
//set adapter
float accuracy = location.getAccuracy();
if(accuracy<60 && canStartDBQuery) {
distance = 20;
final Bundle msgBundle = new Bundle();
msgBundle.putParcelable(BUNDLE_LOCATION,location);
getLoaderManager().restartLoader(LOADER_ID,msgBundle,callbacks);
}
lastUpdateTime = System.currentTimeMillis();
Log.d("BusTO:NearPositListen","can start loader "+ canStartDBQuery);
}
@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 LocationCriteria getLocationCriteria() {
return new LocationCriteria(60,TIME_INTERVAL_REQUESTS);
}
@Override
public long getLastUpdateTimeMillis() {
return lastUpdateTime;
}
void resetUpdateTime(){
lastUpdateTime = -1;
}
}
/**
* Simple trick to get an automatic number of columns (from https://www.journaldev.com/13792/android-gridlayoutmanager-example)
*
*/
class AutoFitGridLayoutManager extends GridLayoutManager {
private int columnWidth;
private boolean columnWidthChanged = true;
public AutoFitGridLayoutManager(Context context, int columnWidth) {
super(context, 1);
setColumnWidth(columnWidth);
}
public void setColumnWidth(int newColumnWidth) {
if (newColumnWidth > 0 && newColumnWidth != columnWidth) {
columnWidth = newColumnWidth;
columnWidthChanged = true;
}
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (columnWidthChanged && columnWidth > 0) {
int totalSpace;
if (getOrientation() == VERTICAL) {
totalSpace = getWidth() - getPaddingRight() - getPaddingLeft();
} else {
totalSpace = getHeight() - getPaddingTop() - getPaddingBottom();
}
int spanCount = Math.max(1, totalSpace / columnWidth);
setSpanCount(spanCount);
columnWidthChanged = false;
}
super.onLayoutChildren(recycler, state);
}
}
}
diff --git a/src/it/reyboz/bustorino/middleware/AppLocationManager.java b/src/it/reyboz/bustorino/middleware/AppLocationManager.java
index a9c42d3..059b588 100644
--- a/src/it/reyboz/bustorino/middleware/AppLocationManager.java
+++ b/src/it/reyboz/bustorino/middleware/AppLocationManager.java
@@ -1,219 +1,223 @@
/*
BusTO (middleware)
Copyright (C) 2019 Fabio Mazza
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.middleware;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import it.reyboz.bustorino.util.LocationCriteria;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.ListIterator;
/**
* Singleton class used to access location. Possibly extended with other location sources.
*/
public class AppLocationManager implements LocationListener {
public static final int LOCATION_GPS_AVAILABLE = 22;
public static final int LOCATION_UNAVAILABLE = -22;
private Context con;
private LocationManager locMan;
public static final String DEBUG_TAG = "BUSTO LocAdapter";
private final String BUNDLE_LOCATION = "location";
private static AppLocationManager instance;
private int oldGPSLocStatus = LOCATION_UNAVAILABLE;
private int minimum_time_milli = -1;
private ArrayList> requestersRef = new ArrayList<>();
private AppLocationManager(Context con) {
this.con = con.getApplicationContext();
locMan = (LocationManager) con.getSystemService(Context.LOCATION_SERVICE);
}
public static AppLocationManager getInstance(Context con) {
if(instance==null) instance = new AppLocationManager(con.getApplicationContext());
return instance;
}
private void requestGPSPositionUpdates(){
final int timeinterval = (minimum_time_milli>0 && minimum_time_milli> iter = requestersRef.listIterator();
while(iter.hasNext()){
final LocationRequester cReq = iter.next().get();
if(cReq==null) iter.remove();
else {
minimum_time_milli = Math.min(cReq.getLocationCriteria().getTimeInterval(),minimum_time_milli);
}
}
Log.d(DEBUG_TAG,"Updated requesters, got "+requestersRef.size()+" listeners to update every "+minimum_time_milli+" ms at least");
}
public void addLocationRequestFor(LocationRequester req){
boolean present = false;
minimum_time_milli = Integer.MAX_VALUE;
ListIterator> iter = requestersRef.listIterator();
while(iter.hasNext()){
final LocationRequester cReq = iter.next().get();
if(cReq==null) iter.remove();
else if(cReq.equals(req)){
present = true;
minimum_time_milli = Math.min(cReq.getLocationCriteria().getTimeInterval(),minimum_time_milli);
}
}
if(!present) {
WeakReference newref = new WeakReference<>(req);
requestersRef.add(newref);
minimum_time_milli = Math.min(req.getLocationCriteria().getTimeInterval(),minimum_time_milli);
Log.d(DEBUG_TAG,"Added new stop requester, instance of "+req.getClass().getSimpleName());
}
if(requestersRef.size()>0){
+ Log.d(DEBUG_TAG,"Requesting position updates");
requestGPSPositionUpdates();
}
}
public void removeLocationRequestFor(LocationRequester req){
minimum_time_milli = Integer.MAX_VALUE;
ListIterator> iter = requestersRef.listIterator();
while(iter.hasNext()){
final LocationRequester cReq = iter.next().get();
if(cReq==null || cReq.equals(req)) iter.remove();
else {
minimum_time_milli = Math.min(cReq.getLocationCriteria().getTimeInterval(),minimum_time_milli);
}
}
if(requestersRef.size()<=0){
locMan.removeUpdates(this);
} else {
requestGPSPositionUpdates();
}
}
private void sendLocationStatusToAll(int status){
ListIterator> iter = requestersRef.listIterator();
while(iter.hasNext()){
final LocationRequester cReq = iter.next().get();
if(cReq==null) iter.remove();
else cReq.onLocationStatusChanged(status);
}
}
@Override
public void onLocationChanged(Location location) {
- Log.d("GPSLocationListener","found location:\nlat: "+location.getLatitude()+" lon: "+location.getLongitude()+"\naccuracy: "+location.getAccuracy());
+ Log.d(DEBUG_TAG,"found location:\nlat: "+location.getLatitude()+" lon: "+location.getLongitude()+"\naccuracy: "+location.getAccuracy());
ListIterator> iter = requestersRef.listIterator();
int new_min_interval = Integer.MAX_VALUE;
while(iter.hasNext()){
final LocationRequester requester = iter.next().get();
if(requester==null) iter.remove();
else{
final long timeNow = System.currentTimeMillis();
final LocationCriteria criteria = requester.getLocationCriteria();
if(location.getAccuracy()criteria.getTimeInterval()){
requester.onLocationChanged(location);
Log.d("AppLocationManager","Updating position for instance of requester "+requester.getClass().getSimpleName());
}
//update minimum time interval
new_min_interval = Math.min(requester.getLocationCriteria().getTimeInterval(),new_min_interval);
}
}
minimum_time_milli = new_min_interval;
if(requestersRef.size()==0){
//stop requesting the position
locMan.removeUpdates(this);
}
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
//IF ANOTHER LOCATION SOURCE IS READY, USE IT
//OTHERWISE, SIGNAL THAT WE HAVE NO LOCATION
if(oldGPSLocStatus !=status){
if(status == LocationProvider.OUT_OF_SERVICE || status == LocationProvider.TEMPORARILY_UNAVAILABLE) {
sendLocationStatusToAll(LOCATION_UNAVAILABLE);
}else if(status == LocationProvider.AVAILABLE){
sendLocationStatusToAll(LOCATION_GPS_AVAILABLE);
}
oldGPSLocStatus = status;
}
+ Log.d(DEBUG_TAG, "Provider: "+provider+" status: "+status);
}
@Override
public void onProviderEnabled(String provider) {
requestGPSPositionUpdates();
+ Log.d(DEBUG_TAG, "Provider: "+provider+" enabled");
}
@Override
public void onProviderDisabled(String provider) {
locMan.removeUpdates(this);
+ Log.d(DEBUG_TAG, "Provider: "+provider+" disabled");
}
/**
* Interface to be implemented to get the location request
*/
public interface LocationRequester{
/**
* Do something with the newly obtained location
* @param loc the obtained location
*/
void onLocationChanged(Location loc);
/**
* Inform the requester that the GPS status has changed
* @param status new status
*/
void onLocationStatusChanged(int status);
/**
* Give the last time of update the requester has
* Set it to -1 in order to receive each new location
* @return the time for update in milliseconds since epoch
*/
long getLastUpdateTimeMillis();
/**
* Get the specifications for the location
* @return fully parsed LocationCriteria
*/
LocationCriteria getLocationCriteria();
}
}
diff --git a/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java b/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java
index c0fe5c0..16203c2 100644
--- a/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java
+++ b/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java
@@ -1,321 +1,327 @@
/*
BusTO (middleware)
Copyright (C) 2018 Fabio Mazza
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.middleware;
import android.content.ContentResolver;
import android.content.ContentValues;
+import android.content.Context;
import android.database.SQLException;
import android.net.Uri;
import android.os.AsyncTask;
import androidx.annotation.NonNull;
import android.util.Log;
import it.reyboz.bustorino.backend.*;
import it.reyboz.bustorino.data.AppDataProvider;
+import it.reyboz.bustorino.data.NextGenDB;
import it.reyboz.bustorino.fragments.FragmentHelper;
import it.reyboz.bustorino.data.NextGenDB.Contract.*;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.Calendar;
/**
* This should be used to download data, but not to display it
*/
public class AsyncDataDownload extends AsyncTask{
private static final String TAG = "BusTO-DataDownload";
private boolean failedAll = false;
private final AtomicReference res;
private final RequestType t;
private String query;
WeakReference helperRef;
private final ArrayList otherActivities = new ArrayList<>();
private final Fetcher[] theFetchers;
+ private Context context;
- public AsyncDataDownload(FragmentHelper fh, @NonNull Fetcher[] fetchers) {
+ public AsyncDataDownload(FragmentHelper fh, @NonNull Fetcher[] fetchers, Context context) {
RequestType type;
helperRef = new WeakReference<>(fh);
fh.setLastTaskRef(new WeakReference<>(this));
res = new AtomicReference<>();
+ this.context = context.getApplicationContext();
theFetchers = fetchers;
if (theFetchers.length < 1){
throw new IllegalArgumentException("You have to put at least one Fetcher, idiot!");
}
if (theFetchers[0] instanceof ArrivalsFetcher){
type = RequestType.ARRIVALS;
} else if (theFetchers[0] instanceof StopsFinderByName){
type = RequestType.STOPS;
} else{
type = null;
}
t = type;
}
@Override
protected Object doInBackground(String... params) {
RecursionHelper r = new RecursionHelper<>(theFetchers);
boolean success=false;
Object result;
FragmentHelper fh = helperRef.get();
//If the FragmentHelper is null, that means the activity doesn't exist anymore
if (fh == null){
return null;
}
//Log.d(TAG,"refresh layout reference is: "+fh.isRefreshLayoutReferenceTrue());
while(r.valid()) {
if(this.isCancelled()) {
return null;
}
//get the data from the fetcher
switch (t){
case ARRIVALS:
ArrivalsFetcher f = (ArrivalsFetcher) r.getAndMoveForward();
Log.d(TAG,"Using the ArrivalsFetcher: "+f.getClass());
Stop lastSearchedBusStop = fh.getLastSuccessfullySearchedBusStop();
Palina p;
String stopID;
if(params.length>0)
stopID=params[0]; //(it's a Palina)
else if(lastSearchedBusStop!=null)
stopID = lastSearchedBusStop.ID; //(it's a Palina)
else {
publishProgress(Fetcher.result.QUERY_TOO_SHORT);
return null;
}
//Skip the FiveTAPIFetcher for the Metro Stops because it shows incomprehensible arrival times
if(f instanceof FiveTAPIFetcher && Integer.parseInt(stopID)>= 8200)
continue;
p= f.ReadArrivalTimesAll(stopID,res);
publishProgress(res.get());
if(f instanceof FiveTAPIFetcher){
AtomicReference gres = new AtomicReference<>();
List branches = ((FiveTAPIFetcher) f).getDirectionsForStop(stopID,gres);
if(gres.get() == Fetcher.result.OK){
p.addInfoFromRoutes(branches);
- Thread t = new Thread(new BranchInserter(branches,fh,stopID));
+ Thread t = new Thread(new BranchInserter(branches, context));
t.start();
otherActivities.add(t);
}
//put updated values into Database
}
if(lastSearchedBusStop != null && res.get()== Fetcher.result.OK) {
// check that we don't have the same stop
if(lastSearchedBusStop.ID.equals(p.ID)) {
// searched and it's the same
String sn = lastSearchedBusStop.getStopDisplayName();
if(sn != null) {
// "merge" Stop over Palina and we're good to go
p.mergeNameFrom(lastSearchedBusStop);
}
}
}
result = p;
//TODO: find a way to avoid overloading the user with toasts
break;
case STOPS:
StopsFinderByName finder = (StopsFinderByName) r.getAndMoveForward();
List resultList= finder.FindByName(params[0], this.res); //it's a List
Log.d(TAG,"Using the StopFinderByName: "+finder.getClass());
query =params[0];
result = resultList; //dummy result
break;
default:
result = null;
}
//find if it went well
if(res.get()== Fetcher.result.OK) {
//wait for other threads to finish
for(Thread t: otherActivities){
try {
t.join();
} catch (InterruptedException e) {
//do nothing
}
}
return result;
}
}
//at this point, we are sure that the result has been negative
failedAll=true;
return null;
}
@Override
protected void onProgressUpdate(Fetcher.result... values) {
FragmentHelper fh = helperRef.get();
if (fh!=null)
for (Fetcher.result r : values){
//TODO: make Toast
fh.showErrorMessage(r);
}
else {
Log.w(TAG,"We had to show some progress but activity was destroyed");
}
}
@Override
protected void onPostExecute(Object o) {
FragmentHelper fh = helperRef.get();
if(failedAll || o == null || fh == null){
//everything went bad
if(fh!=null) fh.toggleSpinner(false);
cancel(true);
//TODO: send message here
return;
}
if(isCancelled()) return;
switch (t){
case ARRIVALS:
Palina palina = (Palina) o;
fh.createOrUpdateStopFragment(palina);
break;
case STOPS:
//this should never be a problem
List stopList = (List) o;
if(query!=null && !isCancelled()) {
fh.createFragmentFor(stopList,query);
} else Log.e(TAG,"QUERY NULL, COULD NOT CREATE FRAGMENT");
break;
case DBUPDATE:
break;
}
}
@Override
protected void onCancelled() {
FragmentHelper fh = helperRef.get();
if (fh!=null) fh.toggleSpinner(false);
}
@Override
protected void onPreExecute() {
FragmentHelper fh = helperRef.get();
if (fh!=null) fh.toggleSpinner(true);
}
public enum RequestType {
ARRIVALS,STOPS,DBUPDATE
}
public class BranchInserter implements Runnable{
private final List routesToInsert;
- private final FragmentHelper fragmentHelper;
+ private final Context context;
+ private final NextGenDB nextGenDB;
- public BranchInserter(List routesToInsert,FragmentHelper fh,String stopID) {
+ public BranchInserter(List routesToInsert,@NonNull Context con) {
this.routesToInsert = routesToInsert;
- this.fragmentHelper = fh;
+ this.context = con;
+ nextGenDB = new NextGenDB(context);
}
@Override
public void run() {
ContentValues[] values = new ContentValues[routesToInsert.size()];
ArrayList connectionsVals = new ArrayList<>(routesToInsert.size()*4);
long starttime,endtime;
for (Route r:routesToInsert){
//if it has received an interrupt, stop
if(Thread.interrupted()) return;
//otherwise, build contentValues
final ContentValues cv = new ContentValues();
cv.put(BranchesTable.COL_BRANCHID,r.branchid);
cv.put(LinesTable.COLUMN_NAME,r.getName());
cv.put(BranchesTable.COL_DIRECTION,r.destinazione);
cv.put(BranchesTable.COL_DESCRIPTION,r.description);
for (int day :r.serviceDays) {
switch (day){
case Calendar.MONDAY:
cv.put(BranchesTable.COL_LUN,1);
break;
case Calendar.TUESDAY:
cv.put(BranchesTable.COL_MAR,1);
break;
case Calendar.WEDNESDAY:
cv.put(BranchesTable.COL_MER,1);
break;
case Calendar.THURSDAY:
cv.put(BranchesTable.COL_GIO,1);
break;
case Calendar.FRIDAY:
cv.put(BranchesTable.COL_VEN,1);
break;
case Calendar.SATURDAY:
cv.put(BranchesTable.COL_SAB,1);
break;
case Calendar.SUNDAY:
cv.put(BranchesTable.COL_DOM,1);
break;
}
}
if(r.type!=null) cv.put(BranchesTable.COL_TYPE, r.type.getCode());
cv.put(BranchesTable.COL_FESTIVO, r.festivo.getCode());
values[routesToInsert.indexOf(r)] = cv;
for(int i=0; i permissionDoneRunnables = new HashMap<>();
protected HashMap permissionAsked = new HashMap<>();
protected void setOption(String optionName, boolean value) {
SharedPreferences.Editor editor = getPreferences(MODE_PRIVATE).edit();
editor.putBoolean(optionName, value);
editor.commit();
}
protected boolean getOption(String optionName, boolean optDefault) {
SharedPreferences preferences = getPreferences(MODE_PRIVATE);
return preferences.getBoolean(optionName, optDefault);
}
protected SharedPreferences getMainSharedPreferences(){
return getSharedPreferences(getString(R.string.mainSharedPreferences),MODE_PRIVATE);
}
public void hideKeyboard() {
View view = getCurrentFocus();
if (view != null) {
((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE))
.hideSoftInputFromWindow(view.getWindowToken(),
InputMethodManager.HIDE_NOT_ALWAYS);
}
}
- public boolean isConnected() {
- ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
- NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
- return networkInfo != null && networkInfo.isConnected();
- }
public void showToastMessage(int messageID, boolean short_lenght) {
final int length = short_lenght ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG;
Toast.makeText(getApplicationContext(), messageID, length).show();
}
- public void assertLocationPermissions() {
- if(ContextCompat.checkSelfPermission(getApplicationContext(),Manifest.permission.ACCESS_FINE_LOCATION)!=PackageManager.PERMISSION_GRANTED){
- ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQUEST_POSITION);
- }
- }
-
public int askForPermissionIfNeeded(String permission, int requestID){
if(ContextCompat.checkSelfPermission(getApplicationContext(),permission)==PackageManager.PERMISSION_GRANTED){
return PERMISSION_OK;
}
//need to ask for the permission
//consider scenario when we have already asked for permission
boolean alreadyAsked = false;
Integer num_trials = 0;
synchronized (this){
if (permissionAsked.containsKey(permission)){
num_trials = permissionAsked.get(permission);
if (num_trials != null && num_trials > 3)
alreadyAsked = true;
}
}
Log.d(DEBUG_TAG,"Already asked for permission: "+permission+" -> "+num_trials);
if(!alreadyAsked){
ActivityCompat.requestPermissions(this,new String[]{permission}, requestID);
synchronized (this){
if (num_trials!=null){
permissionAsked.put(permission, num_trials+1);
}
}
return PERMISSION_ASKING;
} else {
return PERMISSION_NEG_CANNOT_ASK;
}
}
public void createSnackbar(int ViewID, String message,int duration){
Snackbar.make(findViewById(ViewID),message,duration);
}
/*
METHOD THAT MIGHT BE USEFUL LATER
public void assertPermissions(String[] permissions){
ArrayList permissionstoRequest = new ArrayList<>();
for(int i=0;i providers = mng.getProviders(cr, enabled);
+ Log.d(DEBUG_TAG, "Getting enabled location providers: ");
+ for (String s : providers) {
+ Log.d(DEBUG_TAG, "Provider " + s);
+ }
+ return providers.size() > 0;
+ }
+
+ public static void assertLocationPermissions(Context con, Activity activity) {
+ if(ContextCompat.checkSelfPermission(con, Manifest.permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED){
+ ActivityCompat.requestPermissions(activity,new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQUEST_POSITION);
+ }
+ }
+}