diff --git a/AndroidManifest.xml b/AndroidManifest.xml --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -22,7 +22,7 @@ android:label="@string/app_name" android:theme="@style/AppTheme.NoActionBar"> <activity - android:name=".ActivityMain" + android:name=".ActivityPrincipal" android:label="@string/app_name" android:screenOrientation="portrait" android:windowSoftInputMode="adjustResize"> @@ -68,7 +68,7 @@ <activity android:name=".ActivityAbout" android:label="@string/about" - android:parentActivityName=".ActivityMain" + android:parentActivityName=".ActivityPrincipal" android:theme="@style/AboutTheme"> <!-- API < 16: --> @@ -99,8 +99,10 @@ android:value=".ActivityMain"/> </activity> - <activity android:name=".ActivityPrincipal" - android:label="Principal" > + <activity + android:name=".ActivityMain" + android:screenOrientation="portrait" + android:label="@string/app_name" > </activity> <provider @@ -122,11 +124,11 @@ <activity android:name=".ActivitySettings" android:label="@string/title_activity_settings" - android:parentActivityName=".ActivityMain" + android:parentActivityName=".ActivityPrincipal" android:theme="@style/AppTheme"> <meta-data android:name="android.support.PARENT_ACTIVITY" - android:value="it.reyboz.bustorino.ActivityMain"/> + android:value="it.reyboz.bustorino.ActivityPrincipal"/> </activity> </application> diff --git a/build.gradle b/build.gradle --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ dependencies { - classpath 'com.android.tools.build:gradle:4.0.2' + classpath 'com.android.tools.build:gradle:4.1.3' } ext { //libraries versions @@ -92,7 +92,7 @@ implementation "androidx.work:work-runtime:$work_version" - implementation 'com.google.android.material:material:1.3.0' + implementation "com.google.android.material:material:1.3.0" implementation 'org.jsoup:jsoup:1.13.1' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Oct 01 23:03:06 CEST 2020 +#Sat Apr 24 16:03:07 CEST 2021 distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +zipStoreBase=GRADLE_USER_HOME diff --git a/res/layout/nav_header.xml b/res/layout/nav_header.xml --- a/res/layout/nav_header.xml +++ b/res/layout/nav_header.xml @@ -1,19 +1,22 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" - android:orientation="vertical" android:layout_width="match_parent" + android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/grey_100" + android:background="@color/teal_300" + android:gravity="bottom" + android:minHeight="120dp" + android:orientation="vertical" android:padding="16dp" - android:theme="@style/ThemeOverlay.AppCompat.Dark" - android:gravity="bottom"> - + android:theme="@style/ThemeOverlay.AppCompat.Dark"> + <!-- <ImageView android:id="@+id/nav_img" android:layout_width="match_parent" android:layout_height="100dp" android:minHeight="40dp" app:srcCompat="@drawable/ic_mars2020" /> + --> <TextView @@ -21,10 +24,10 @@ android:layout_height="wrap_content" - android:text="@string/app_name" + android:text="@string/app_name_full" android:textAppearance="@style/ThemeOverlay.AppCompat.Light" - android:textSize="20sp" - /> + android:textColor="#FFFFFF" + android:textSize="20sp" /> </LinearLayout> \ No newline at end of file diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -102,7 +102,7 @@ <string name="settings_group_database">Gestione del database</string> <string name="settings_reset_database">Comincia aggiornamento manuale del database</string> - + <string name="enable_position_message_map">Consenti l\'accesso alla posizione per mostrarla sulla mappa</string> <string name="enableGpsText">Abilitare il GPS</string> <string name="settings_search_radius">Raggio di ricerca</string> <string name="settings_experimental">Funzionalità sperimentali</string> @@ -134,10 +134,11 @@ <string name="too_many_permission_asks">Chiesto troppe volte per il permesso %1$s</string> <string name="permission_storage_maps_msg">Non si può usare questa funzionalità senza il permesso di archivio</string> <string name="storage_permission">di archivio</string> - <string name="message_crash">L\'app è andata in crash. Invia il report agli sviluppatori toccando OK. - \n Il report potrebbe contenere informazioni sulla tua configurazione del telefono, o sullo stato al momento del crash. - \n Tutte le informazioni sensibili nel report verranno utilizzate solo per scopi diagnostici dagli sviluppatori, e non verranno mai pubblicate e/o divulgate.</string> - <string name="acra_email_message">L\'applicazione è crashata, e il crash report è stato messo negli allegati. Se vuoi, descrivi cosa stavi facendo prima del crash: \n</string> + <string name="message_crash">Un bug ha fatto crashare l\'app! + \nPremi \"OK\" per inviare il report agli sviluppatori via email, così potranno scovare e risolvere il tuo bug! + \nIl report contiene piccole informazioni non sensibili sulla configurazione del tuo telefono e sullo stato dell\'app al momento del crash. + </string> + <string name="acra_email_message">L\'applicazione è crashata, e il crash report è stato messo negli allegati. Se vuoi, descrivi cosa stavi facendo prima che si interrompesse: \n</string> <string name="nav_arrivals_text">Arrivi</string> <string name="nav_map_text">Mappa</string> <string name="nav_favorites_text">Preferiti</string> @@ -145,5 +146,7 @@ <string name="drawer_close">Chiudi drawer</string> <string name="experiments">Esperimenti</string> <string name="donate_now">Offrici un caffè</string> + <string name="map">Mappa</string> + <string name="stop_search_view_title">Ricerca fermate</string> </resources> diff --git a/res/values/colors.xml b/res/values/colors.xml --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -14,5 +14,6 @@ <color name="accent">#009688</color>--> <color name="metro_red">#DE0908</color> <color name="blue_extraurbano">#2060DD</color> + <color name="white">#FFFFFF</color> </resources> \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -2,6 +2,7 @@ <resources> <string name="app_name" translatable="false">BusTO</string> + <string name="app_name_full" translatable="false">Libre BusTO</string> <string name="app_name_debug" translatable="false">BusTO Debug</string> <string name="app_description">You\'re using the latest in technology when it comes to respecting your privacy. </string> @@ -115,6 +116,7 @@ <string name="settings_group_database">Database management</string> <string name="settings_reset_database">Launch manual database update</string> + <string name="enable_position_message_map">Allow access to position to show it on the map</string> <string name="enableGpsText">Please enable GPS</string> <string name="database_update_message">Database update in progress…</string> <string name="bus_arriving_at">is arriving at</string> @@ -147,10 +149,9 @@ <string name="too_many_permission_asks">Asked for %1$s permission too many times</string> <string name="permission_storage_maps_msg">Cannot use the map with the storage permission!</string> <string name="storage_permission">storage</string> - <string name="message_crash">The application has crashed. If you want, you can send the send the report via email to - the developers by pressing \"OK\". - \n Note that sensitive information may be contained in the report, and if so, it will be only used for - diagnostic purposes by the developers, and never published. + <string name="message_crash">The application has crashed because you encountered a bug. + \nIf you want, you can help the developers by sending the crash report via email. + \nNote that no sensitive data is contained in the report, just small bits of info on your phone and app configuration/state. </string> <string name="acra_email_message">The application crashed and the crash report is in the attachments. Please describe what you were doing before the crash: \n @@ -162,4 +163,6 @@ <string name="drawer_close">Close navigation drawer</string> <string name="experiments">Experiments</string> <string name="donate_now">Buy us a coffee</string> + <string name="map">Map</string> + <string name="stop_search_view_title">Search by stop</string> </resources> diff --git a/src/debug/AndroidManifest.xml b/src/debug/AndroidManifest.xml --- a/src/debug/AndroidManifest.xml +++ b/src/debug/AndroidManifest.xml @@ -11,7 +11,7 @@ tools:replace="android:label" > <activity - android:name=".ActivityMain" + android:name=".ActivityPrincipal" android:label="@string/app_name_debug" tools:replace="android:label" /> diff --git a/src/it/reyboz/bustorino/ActivityMain.java b/src/it/reyboz/bustorino/ActivityMain.java --- a/src/it/reyboz/bustorino/ActivityMain.java +++ b/src/it/reyboz/bustorino/ActivityMain.java @@ -34,6 +34,7 @@ import androidx.appcompat.app.ActionBar; import androidx.appcompat.widget.Toolbar; import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; import androidx.preference.PreferenceManager; import androidx.work.BackoffPolicy; import androidx.work.Constraints; @@ -654,21 +655,19 @@ @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; + //final boolean canRunPosition = Build.VERSION.SDK_INT < Build.VERSION_CODES.M || getOption(LOCATION_PERMISSION_GIVEN, false); + final boolean noPermission = ContextCompat.checkSelfPermission(getApplicationContext(), + Manifest.permission.ACCESS_FINE_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(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); diff --git a/src/it/reyboz/bustorino/ActivityPrincipal.java b/src/it/reyboz/bustorino/ActivityPrincipal.java --- a/src/it/reyboz/bustorino/ActivityPrincipal.java +++ b/src/it/reyboz/bustorino/ActivityPrincipal.java @@ -1,13 +1,16 @@ package it.reyboz.bustorino; +import android.Manifest; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuItem; +import android.view.View; import android.widget.Toast; import androidx.annotation.NonNull; @@ -30,6 +33,7 @@ import com.google.android.material.navigation.NavigationView; import com.google.android.material.snackbar.Snackbar; +import java.util.Arrays; import java.util.concurrent.TimeUnit; import it.reyboz.bustorino.backend.Stop; @@ -82,11 +86,32 @@ drawerToggle.syncState(); mDrawer.addDrawerListener(drawerToggle); + mDrawer.addDrawerListener(new DrawerLayout.DrawerListener() { + @Override + public void onDrawerSlide(@NonNull View drawerView, float slideOffset) { + + } + + @Override + public void onDrawerOpened(@NonNull View drawerView) { + hideKeyboard(); + } + + @Override + public void onDrawerClosed(@NonNull View drawerView) { + + } + + @Override + public void onDrawerStateChanged(int newState) { + } + }); + + mNavView = findViewById(R.id.nvView); setupDrawerContent(mNavView); - /// LEGACY CODE //---------------------------- START INTENT CHECK QUEUE ------------------------------------ @@ -135,8 +160,6 @@ 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) @@ -189,6 +212,11 @@ return new ActionBarDrawerToggle(this, mDrawer, toolbar, R.string.drawer_open, R.string.drawer_close); } + + /** + * Setup drawer actions + * @param navigationView the navigation view on which to set the callbacks + */ private void setupDrawerContent(NavigationView navigationView) { navigationView.setNavigationItemSelectedListener( menuItem -> { @@ -213,7 +241,21 @@ return true; } else if(menuItem.getItemId() == R.id.nav_map_item){ closeDrawerIfOpen(); - createAndShowMapFragment(null); + final String permission = Manifest.permission.WRITE_EXTERNAL_STORAGE; + int result = askForPermissionIfNeeded(permission, STORAGE_PERMISSION_REQ); + switch (result) { + case PERMISSION_OK: + createAndShowMapFragment(null); + break; + case PERMISSION_ASKING: + permissionDoneRunnables.put(permission, + () -> createAndShowMapFragment(null)); + break; + case PERMISSION_NEG_CANNOT_ASK: + String storage_perm = getString(R.string.storage_permission); + String text = getString(R.string.too_many_permission_asks, storage_perm); + Toast.makeText(getApplicationContext(),text, Toast.LENGTH_LONG).show(); + } return true; } //selectDrawerItem(menuItem); @@ -256,6 +298,27 @@ return super.onCreateOptionsMenu(menu); } + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode==STORAGE_PERMISSION_REQ){ + final String storagePerm = 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(storagePerm)) { + Runnable toRun = permissionDoneRunnables.get(storagePerm); + if (toRun != null) + toRun.run(); + permissionDoneRunnables.remove(storagePerm); + } + } else { + //permission denied + showToastMessage(R.string.permission_storage_maps_msg, false); + } + } + } + @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { @@ -387,21 +450,35 @@ if (probableFragment!=null){ probableFragment.readyGUIfor(fragmentType); } + int titleResId; switch (fragmentType){ case MAP: mNavView.setCheckedItem(R.id.nav_map_item); + titleResId = R.string.map; break; case FAVORITES: mNavView.setCheckedItem(R.id.nav_favorites_item); + titleResId = R.string.nav_favorites_text; break; case ARRIVALS: - case NEARBY_STOPS: + titleResId = R.string.nav_arrivals_text; + mNavView.setCheckedItem(R.id.nav_arrivals); + break; case STOPS: + titleResId = R.string.stop_search_view_title; + mNavView.setCheckedItem(R.id.nav_arrivals); + break; case MAIN_SCREEN_FRAGMENT: + case NEARBY_STOPS: case NEARBY_ARRIVALS: + titleResId=R.string.app_name_full; mNavView.setCheckedItem(R.id.nav_arrivals); break; + default: + titleResId = 0; } + if(getSupportActionBar()!=null && titleResId!=0) + getSupportActionBar().setTitle(titleResId); } @Override diff --git a/src/it/reyboz/bustorino/BustoApp.java b/src/it/reyboz/bustorino/BustoApp.java --- a/src/it/reyboz/bustorino/BustoApp.java +++ b/src/it/reyboz/bustorino/BustoApp.java @@ -5,16 +5,20 @@ import org.acra.ACRA; import org.acra.BuildConfig; -import org.acra.annotation.AcraCore; -import org.acra.annotation.AcraDialog; -import org.acra.annotation.AcraMailSender; +import org.acra.ReportField; import org.acra.config.CoreConfigurationBuilder; import org.acra.config.DialogConfigurationBuilder; import org.acra.config.MailSenderConfigurationBuilder; import org.acra.data.StringFormat; +import static org.acra.ReportField.*; + public class BustoApp extends Application { + private static final ReportField[] REPORT_FIELDS = {REPORT_ID, APP_VERSION_CODE, APP_VERSION_NAME, + PACKAGE_NAME, PHONE_MODEL, BRAND, PRODUCT, ANDROID_VERSION, BUILD_CONFIG, CUSTOM_DATA, + IS_SILENT, STACK_TRACE, INITIAL_CONFIGURATION, CRASH_CONFIGURATION, DISPLAY, USER_COMMENT, + USER_APP_START_DATE, USER_CRASH_DATE, LOGCAT, SHARED_PREFERENCES}; @Override protected void attachBaseContext(Context base) { @@ -30,7 +34,9 @@ builder.getPluginConfigurationBuilder(DialogConfigurationBuilder.class).setResText(R.string.message_crash) .setResTheme(R.style.AppTheme) .setEnabled(true); + builder.setReportContent(REPORT_FIELDS); if (!it.reyboz.bustorino.BuildConfig.DEBUG) ACRA.init(this, builder); + } } diff --git a/src/it/reyboz/bustorino/backend/utils.java b/src/it/reyboz/bustorino/backend/utils.java --- a/src/it/reyboz/bustorino/backend/utils.java +++ b/src/it/reyboz/bustorino/backend/utils.java @@ -6,6 +6,7 @@ import android.net.NetworkInfo; import android.net.Uri; import android.util.Log; +import android.util.TypedValue; import android.view.View; public abstract class utils { @@ -24,9 +25,15 @@ return Math.abs(EarthRadius*c); } + /* public static int convertDipToPixels(Context con,float dips) { return (int) (dips * con.getResources().getDisplayMetrics().density + 0.5f); + } + */ + + public static float convertDipToPixels(Context con, float dp){ + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,con.getResources().getDisplayMetrics()); } public static int calculateNumColumnsFromSize(View containerView, int pixelsize){ int width = containerView.getWidth(); diff --git a/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java b/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java --- a/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java +++ b/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java @@ -185,6 +185,7 @@ } }); String displayName = getArguments().getString(STOP_TITLE); + if(displayName!=null) setTextViewMessage(String.format( getString(R.string.passages), displayName)); diff --git a/src/it/reyboz/bustorino/fragments/CommonFragmentListener.java b/src/it/reyboz/bustorino/fragments/CommonFragmentListener.java --- a/src/it/reyboz/bustorino/fragments/CommonFragmentListener.java +++ b/src/it/reyboz/bustorino/fragments/CommonFragmentListener.java @@ -1,5 +1,7 @@ package it.reyboz.bustorino.fragments; +import android.view.View; + public interface CommonFragmentListener { diff --git a/src/it/reyboz/bustorino/fragments/FavoritesFragment.java b/src/it/reyboz/bustorino/fragments/FavoritesFragment.java --- a/src/it/reyboz/bustorino/fragments/FavoritesFragment.java +++ b/src/it/reyboz/bustorino/fragments/FavoritesFragment.java @@ -23,6 +23,7 @@ import androidx.annotation.Nullable; import androidx.lifecycle.ViewModelProvider; +import java.util.ArrayList; import java.util.List; import it.reyboz.bustorino.ActivityFavorites; @@ -93,6 +94,8 @@ FavoritesViewModel model = new ViewModelProvider(this).get(FavoritesViewModel.class); model.getFavorites().observe(getViewLifecycleOwner(), this::showStops); + + showStops(new ArrayList<>()); return root; } @Override diff --git a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java --- a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java +++ b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java @@ -5,12 +5,12 @@ 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.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.AppCompatImageButton; @@ -38,6 +38,8 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.zxing.integration.android.IntentIntegrator; +import java.util.Map; + import it.reyboz.bustorino.R; import it.reyboz.bustorino.backend.ArrivalsFetcher; import it.reyboz.bustorino.backend.FiveTAPIFetcher; @@ -45,11 +47,14 @@ import it.reyboz.bustorino.backend.FiveTStopsFetcher; import it.reyboz.bustorino.backend.GTTJSONFetcher; import it.reyboz.bustorino.backend.GTTStopsFetcher; +import it.reyboz.bustorino.backend.Palina; import it.reyboz.bustorino.backend.StopsFinderByName; +import it.reyboz.bustorino.middleware.AppLocationManager; import it.reyboz.bustorino.middleware.AsyncDataDownload; +import it.reyboz.bustorino.util.LocationCriteria; import it.reyboz.bustorino.util.Permissions; -import static android.content.Context.LOCATION_SERVICE; +import static it.reyboz.bustorino.util.Permissions.LOCATION_PERMISSIONS; import static it.reyboz.bustorino.util.Permissions.LOCATION_PERMISSION_GIVEN; @@ -112,14 +117,74 @@ } }; + /// LOCATION STUFF /// + boolean pendingNearbyStopsRequest = false; + boolean locationPermissionGranted, locationPermissionAsked = false; + AppLocationManager locationManager; + private final LocationCriteria cr = new LocationCriteria(2000, 10000); + //Location + private AppLocationManager.LocationRequester requester = new AppLocationManager.LocationRequester() { + @Override + public void onLocationChanged(Location loc) { - /// LOCATION STUFF /// - boolean pendingNearbyStopsRequest = false; - LocationManager locmgr; + } + + @Override + public void onLocationStatusChanged(int status) { + + if(status == AppLocationManager.LOCATION_GPS_AVAILABLE && !isNearbyFragmentShown()){ + //request Stops + pendingNearbyStopsRequest = false; + + mainHandler.post(new NearbyStopsRequester(getContext(), cr)); + } + } + + @Override + public long getLastUpdateTimeMillis() { + return 50; + } + + @Override + public LocationCriteria getLocationCriteria() { + return cr; + } + + @Override + public void onLocationProviderAvailable() { + //Log.w(DEBUG_TAG, "pendingNearbyStopRequest: "+pendingNearbyStopsRequest); + if(!isNearbyFragmentShown()){ + pendingNearbyStopsRequest = false; + mainHandler.post(new NearbyStopsRequester(getContext(), cr)); + } + } + + @Override + public void onLocationDisabled() { + + } + }; + private final ActivityResultLauncher<String[]> requestPermissionLauncher = + registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<Map<String, Boolean>>() { + @Override + public void onActivityResult(Map<String, Boolean> result) { + if(result==null || result.get(Manifest.permission.ACCESS_COARSE_LOCATION) == null + ||result.get(Manifest.permission.ACCESS_FINE_LOCATION) ) return; + + if(result.get(Manifest.permission.ACCESS_COARSE_LOCATION) && result.get(Manifest.permission.ACCESS_FINE_LOCATION)){ + locationPermissionGranted = true; + Log.w(DEBUG_TAG, "Starting position"); + if (mListener!= null && getContext()!=null){ + if (locationManager==null) + locationManager = AppLocationManager.getInstance(getContext()); + locationManager.addLocationRequestFor(requester); + } + } + } + }); - private final Criteria cr = new Criteria(); //// ACTIVITY ATTACHED (LISTENER /// private CommonFragmentListener mListener; @@ -208,7 +273,7 @@ cr.setCostAllowed(true); cr.setPowerRequirement(Criteria.NO_REQUIREMENT); - locmgr = (LocationManager) getContext().getSystemService(LOCATION_SERVICE); + locationManager = AppLocationManager.getInstance(getContext()); Log.d(DEBUG_TAG, "OnCreateView, savedInstanceState null: "+(savedInstanceState==null)); @@ -272,16 +337,15 @@ if (setupOnAttached) { if (pendingStopID==null) //We want the nearby bus stops! - mainHandler.post(new NearbyStopsRequester(getContext(), cr, locListener)); + mainHandler.post(new NearbyStopsRequester(context, cr)); else{ ///TODO: if there is a stop displayed, we need to hold the update } - //If there are no providers available, then, wait for them setupOnAttached = false; - } else { } + } @Override public void onDetach() { @@ -295,8 +359,24 @@ public void onResume() { final Context con = getContext(); - if (con != null) - locmgr = (LocationManager) getContext().getSystemService(LOCATION_SERVICE); + Log.w(DEBUG_TAG, "OnResume called"); + if (con != null) { + if(locationManager==null) + locationManager = AppLocationManager.getInstance(con); + + if(Permissions.locationPermissionGranted(con)){ + Log.d(DEBUG_TAG, "Location permission OK"); + if(!locationManager.isRequesterRegistered(requester)) + locationManager.addLocationRequestFor(requester); + } else if(shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)){ + //we have already asked for the location, and we should show an explanation in order + // to ask again (TODO) + //do nothing + } else{ + //request permission + requestPermissionLauncher.launch(Permissions.LOCATION_PERMISSIONS); + } + } else { Log.w(DEBUG_TAG, "Context is null at onResume"); } @@ -324,7 +404,7 @@ @Override public void onPause() { //mainHandler = null; - locmgr = null; + locationManager.removeLocationRequestFor(requester); super.onPause(); } @@ -384,10 +464,8 @@ } ////////////////////////////////////// GUI HELPERS ///////////////////////////////////////////// - public void showKeyboard() { - if (getActivity() == null) - return; + if(getActivity() == null) return; InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); View view = searchMode == SEARCH_BY_ID ? busStopSearchByIDEditText : busStopSearchByNameEditText; imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); @@ -408,6 +486,10 @@ busStopSearchByNameEditText.setVisibility(View.VISIBLE); floatingActionButton.setImageResource(R.drawable.numeric); } + protected boolean isNearbyFragmentShown(){ + Fragment fragment = getChildFragmentManager().findFragmentByTag(NearbyStopsFragment.FRAGMENT_TAG); + return (fragment!= null && fragment.isVisible()); + } /** * Having that cursor at the left of the edit text makes me cancer. @@ -456,6 +538,17 @@ //actionHelpMenuItem.setVisible(false); } + void showNearbyStopsFragment(){ + 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, NearbyStopsFragment.FRAGMENT_TAG); + ft.commit(); + } + @Override public void showFloatingActionButton(boolean yes) { @@ -475,7 +568,7 @@ //if we are getting results, already, stop waiting for nearbyStops if (pendingNearbyStopsRequest && (fragmentType == FragmentKind.ARRIVALS || fragmentType == FragmentKind.STOPS)) { - locmgr.removeUpdates(locListener); + locationManager.removeLocationRequestFor(requester); pendingNearbyStopsRequest = false; } @@ -538,65 +631,48 @@ } } /////////// 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) { + /* + private void startStopRequest(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) { + public NearbyStopsRequester(Context appContext, Criteria criteria) { 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); + if(isNearbyFragmentShown()) { + //nothing to do + Log.w(DEBUG_TAG, "launched nearby fragment request but we already are showing"); + return; + } + + final boolean isOldVersion = Build.VERSION.SDK_INT < Build.VERSION_CODES.M; 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) { + if (!isOldVersion) { pendingNearbyStopsRequest = true; - Permissions.assertLocationPermissions(appContext,getActivity()); + //Permissions.assertLocationPermissions(appContext,getActivity()); + requestPermissionLauncher.launch(LOCATION_PERMISSIONS); Log.w(DEBUG_TAG, "Cannot get position: Asking permission, noPositionFromSys: " + noPermission); return; } else { @@ -604,35 +680,20 @@ } } 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) + AppLocationManager appLocationManager = AppLocationManager.getInstance(appContext); + final boolean haveProviders = appLocationManager.anyLocationProviderMatchesCriteria(cr); + if (haveProviders && 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(); + showNearbyStopsFragment(); 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); + } else if(!haveProviders){ + Log.e(DEBUG_TAG, "NO PROVIDERS FOR POSITION"); } } } } \ No newline at end of file diff --git a/src/it/reyboz/bustorino/fragments/MapFragment.java b/src/it/reyboz/bustorino/fragments/MapFragment.java --- a/src/it/reyboz/bustorino/fragments/MapFragment.java +++ b/src/it/reyboz/bustorino/fragments/MapFragment.java @@ -1,9 +1,9 @@ package it.reyboz.bustorino.fragments; import android.Manifest; +import android.annotation.SuppressLint; import android.content.Context; -import android.content.pm.PackageManager; import android.location.Location; import android.location.LocationManager; import android.os.AsyncTask; @@ -13,10 +13,13 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; +import android.widget.Toast; +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.app.ActivityCompat; import androidx.core.content.res.ResourcesCompat; import androidx.preference.PreferenceManager; @@ -40,6 +43,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Map; import it.reyboz.bustorino.R; import it.reyboz.bustorino.backend.Stop; @@ -47,8 +51,7 @@ import it.reyboz.bustorino.map.CustomInfoWindow; import it.reyboz.bustorino.map.LocationOverlay; import it.reyboz.bustorino.middleware.GeneralActivity; - -import static it.reyboz.bustorino.util.Permissions.PERMISSION_REQUEST_POSITION; +import it.reyboz.bustorino.util.Permissions; public class MapFragment extends BaseFragment { @@ -108,6 +111,30 @@ } }; + private final ActivityResultLauncher<String[]> positionRequestLauncher = + registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<Map<String, Boolean>>() { + @Override + @SuppressLint("MissingPermission") + public void onActivityResult(Map<String, Boolean> result) { + if(result.get(Manifest.permission.ACCESS_COARSE_LOCATION) && result.get(Manifest.permission.ACCESS_FINE_LOCATION)){ + + map.getOverlays().remove(mLocationOverlay); + startLocationOverlay(true); + if(getContext()==null || getContext().getSystemService(Context.LOCATION_SERVICE)==null) + return; + LocationManager locationManager = (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE); + Location userLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); + if (userLocation != null) { + map.getController().setZoom(POSITION_FOUND_ZOOM); + GeoPoint startPoint = new GeoPoint(userLocation); + setLocationFollowing(true); + map.getController().setCenter(startPoint); + } + } + else Log.w(DEBUG_TAG,"No location permission"); + } + }); + public MapFragment() { } public static MapFragment getInstance(){ @@ -181,13 +208,21 @@ btCenterMap.setOnClickListener(v -> { //Log.i(TAG, "centerMap clicked "); - final GeoPoint myPosition = mLocationOverlay.getMyLocation(); - map.getController().animateTo(myPosition); + if(Permissions.locationPermissionGranted(getContext())) { + final GeoPoint myPosition = mLocationOverlay.getMyLocation(); + map.getController().animateTo(myPosition); + } else + Toast.makeText(getContext(), R.string.enable_position_message_map, Toast.LENGTH_SHORT) + .show(); }); btFollowMe.setOnClickListener(v -> { //Log.i(TAG, "btFollowMe clicked "); - switchLocationFollowing(!followingLocation); + if(Permissions.locationPermissionGranted(getContext())) + setLocationFollowing(!followingLocation); + else + Toast.makeText(getContext(), R.string.enable_position_message_map, Toast.LENGTH_SHORT) + .show(); }); return root; @@ -261,8 +296,11 @@ * Switch following the location on and off * @param value true if we want to follow location */ - public void switchLocationFollowing(Boolean value){ + public void setLocationFollowing(Boolean value){ followingLocation = value; + if(mLocationOverlay==null || getContext() == null) + //nothing else to do + return; if (value){ mLocationOverlay.enableFollowLocation(); } else { @@ -282,6 +320,26 @@ } + /** + * Start the location overlay. Enable only when + * a) we know we have the permission + * b) the location map is set + */ + private void startLocationOverlay(boolean enableLocation){ + if(getActivity()== null) throw new IllegalStateException("Cannot enable LocationOverlay now"); + // Location Overlay + // from OpenBikeSharing (THANK GOD) + Log.d(DEBUG_TAG, "Starting position overlay"); + GpsMyLocationProvider imlp = new GpsMyLocationProvider(getActivity().getBaseContext()); + imlp.setLocationUpdateMinDistance(5); + imlp.setLocationUpdateMinTime(2000); + this.mLocationOverlay = new LocationOverlay(imlp,map, locationCallbacks); + if (enableLocation) mLocationOverlay.enableMyLocation(); + mLocationOverlay.setOptionsMenuEnabled(true); + + map.getOverlays().add(this.mLocationOverlay); + } + public void startMap(Bundle incoming, Bundle savedInstanceState) { //Check that we're attached GeneralActivity activity = getActivity() instanceof GeneralActivity ? (GeneralActivity) getActivity() : null; @@ -312,52 +370,48 @@ IMapController mapController = map.getController(); GeoPoint startPoint = null; - boolean havePositionPermission = true; - - if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - activity.askForPermissionIfNeeded(Manifest.permission.ACCESS_FINE_LOCATION, PERMISSION_REQUEST_POSITION); - havePositionPermission = false; - } - // Location Overlay - // from OpenBikeSharing (THANK GOD) - GpsMyLocationProvider imlp = new GpsMyLocationProvider(activity.getBaseContext()); - imlp.setLocationUpdateMinDistance(5); - imlp.setLocationUpdateMinTime(2000); - this.mLocationOverlay = new LocationOverlay(imlp,map, locationCallbacks); - mLocationOverlay.enableMyLocation(); - mLocationOverlay.setOptionsMenuEnabled(true); - + // set the center point if (marker != null) { startPoint = marker; mapController.setZoom(POSITION_FOUND_ZOOM); - switchLocationFollowing(false); + setLocationFollowing(false); } else if (savedInstanceState != null) { mapController.setZoom(savedInstanceState.getDouble(MAP_CURRENT_ZOOM_KEY)); mapController.setCenter(new GeoPoint(savedInstanceState.getDouble(MAP_CENTER_LAT_KEY), savedInstanceState.getDouble(MAP_CENTER_LON_KEY))); Log.d(DEBUG_TAG, "Location following from savedInstanceState: "+savedInstanceState.getBoolean(FOLLOWING_LOCAT_KEY)); - switchLocationFollowing(savedInstanceState.getBoolean(FOLLOWING_LOCAT_KEY)); + setLocationFollowing(savedInstanceState.getBoolean(FOLLOWING_LOCAT_KEY)); } else { Log.d(DEBUG_TAG, "No position found from intent or saved state"); boolean found = false; LocationManager locationManager = (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE); - if (locationManager != null) { + //check for permission + if (locationManager != null && Permissions.locationPermissionGranted(activity)) { + @SuppressLint("MissingPermission") Location userLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); if (userLocation != null) { mapController.setZoom(POSITION_FOUND_ZOOM); startPoint = new GeoPoint(userLocation); found = true; - switchLocationFollowing(true); + setLocationFollowing(true); + } + } else if(!Permissions.locationPermissionGranted(activity)){ + if(shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)){ + //TODO: show dialog for permission rationale + Toast.makeText(activity, R.string.enable_position_message_map, Toast.LENGTH_SHORT).show(); } + positionRequestLauncher.launch(Permissions.LOCATION_PERMISSIONS); + } if(!found){ startPoint = new GeoPoint(DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON); - mapController.setZoom(16.0); - switchLocationFollowing(false); + mapController.setZoom(17.0); + setLocationFollowing(false); } } + startLocationOverlay(Permissions.locationPermissionGranted(activity)); // set the minimum zoom level map.setMinZoomLevel(15.0); @@ -366,10 +420,6 @@ mapController.setCenter(startPoint); } - - - map.getOverlays().add(this.mLocationOverlay); - //add stops overlay map.getOverlays().add(this.stopsFolderOverlay); diff --git a/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java b/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java --- a/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java +++ b/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java @@ -68,6 +68,8 @@ public final static int TYPE_STOPS = 19, TYPE_ARRIVALS = 20; private int fragment_type; + public final static String FRAGMENT_TAG="NearbyStopsFrag"; + //data Bundle private final String BUNDLE_LOCATION = "location"; private final int LOADER_ID = 0; @@ -135,12 +137,13 @@ } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment + if (getContext() == null) throw new RuntimeException(); View root = inflater.inflate(R.layout.fragment_nearby_stops, container, false); gridRecyclerView = root.findViewById(R.id.stopGridRecyclerView); - gridLayoutManager = new AutoFitGridLayoutManager(getContext().getApplicationContext(), utils.convertDipToPixels(getContext(),COLUMN_WIDTH_DP)); + gridLayoutManager = new AutoFitGridLayoutManager(getContext().getApplicationContext(), Float.valueOf(utils.convertDipToPixels(getContext(),COLUMN_WIDTH_DP)).intValue()); gridRecyclerView.setLayoutManager(gridLayoutManager); gridRecyclerView.setHasFixedSize(false); circlingProgressBar = root.findViewById(R.id.loadingBar); @@ -226,7 +229,6 @@ + " must implement OnFragmentInteractionListener"); } Log.d(DEBUG_TAG, "OnAttach called"); - } @Override @@ -408,6 +410,8 @@ gridRecyclerView.setVisibility(View.VISIBLE); } messageTextView.setVisibility(View.GONE); + + if(mListener!=null) mListener.readyGUIfor(FragmentKind.NEARBY_STOPS); } private void showArrivalsInRecycler(List<Palina> palinas){ @@ -434,6 +438,8 @@ //arrivalsStopAdapter.notifyDataSetChanged(); showRecyclerHidingLoadMessage(); + if(mListener!=null) mListener.readyGUIfor(FragmentKind.NEARBY_ARRIVALS); + } private void setNoStopsLayout(){ @@ -601,7 +607,7 @@ @Override public LocationCriteria getLocationCriteria() { - return new LocationCriteria(60,TIME_INTERVAL_REQUESTS); + return new LocationCriteria(120,TIME_INTERVAL_REQUESTS); } @Override @@ -611,6 +617,16 @@ void resetUpdateTime(){ lastUpdateTime = -1; } + + @Override + public void onLocationProviderAvailable() { + + } + + @Override + public void onLocationDisabled() { + + } } /** diff --git a/src/it/reyboz/bustorino/map/LocationOverlay.java b/src/it/reyboz/bustorino/map/LocationOverlay.java --- a/src/it/reyboz/bustorino/map/LocationOverlay.java +++ b/src/it/reyboz/bustorino/map/LocationOverlay.java @@ -44,6 +44,7 @@ @Override public void disableFollowLocation() { + super.disableFollowLocation(); callbacks.onDisableFollowMyLocation(); } diff --git a/src/it/reyboz/bustorino/middleware/AppLocationManager.java b/src/it/reyboz/bustorino/middleware/AppLocationManager.java --- a/src/it/reyboz/bustorino/middleware/AppLocationManager.java +++ b/src/it/reyboz/bustorino/middleware/AppLocationManager.java @@ -17,7 +17,10 @@ */ package it.reyboz.bustorino.middleware; +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; @@ -25,7 +28,11 @@ import android.os.Bundle; import android.util.Log; import android.widget.Toast; + +import androidx.core.content.ContextCompat; + import it.reyboz.bustorino.util.LocationCriteria; +import it.reyboz.bustorino.util.Permissions; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -39,37 +46,42 @@ public static final int LOCATION_GPS_AVAILABLE = 22; public static final int LOCATION_UNAVAILABLE = -22; - private Context con; - private LocationManager locMan; + private final Context appContext; + private final 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<WeakReference<LocationRequester>> requestersRef = new ArrayList<>(); + private boolean isLocationPermissionGiven = false; + + private final ArrayList<WeakReference<LocationRequester>> requestersRef = new ArrayList<>(); - private AppLocationManager(Context con) { - this.con = con.getApplicationContext(); - locMan = (LocationManager) con.getSystemService(Context.LOCATION_SERVICE); + private AppLocationManager(Context context) { + this.appContext = context.getApplicationContext(); + locMan = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + isLocationPermissionGiven = checkLocationPermission(context); } public static AppLocationManager getInstance(Context con) { - if(instance==null) instance = new AppLocationManager(con.getApplicationContext()); + if(instance==null) instance = new AppLocationManager(con); return instance; } + + public static boolean checkLocationPermission(Context context){ + return ContextCompat.checkSelfPermission(context, + Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED; + } - private void requestGPSPositionUpdates(){ + private void requestGPSPositionUpdates() throws SecurityException{ final int timeinterval = (minimum_time_milli>0 && minimum_time_milli<Integer.MAX_VALUE)? minimum_time_milli : 2000; - try { - locMan.removeUpdates(this); - locMan.requestLocationUpdates(LocationManager.GPS_PROVIDER, timeinterval, 5, this); - } catch (SecurityException exc){ - exc.printStackTrace(); - Toast.makeText(con,"Cannot access GPS location",Toast.LENGTH_SHORT).show(); - } + + locMan.removeUpdates(this); + locMan.requestLocationUpdates(LocationManager.GPS_PROVIDER, timeinterval, 5, this); + } private void cleanAndUpdateRequesters(){ minimum_time_milli = Integer.MAX_VALUE; @@ -88,15 +100,19 @@ public void addLocationRequestFor(LocationRequester req){ boolean present = false; minimum_time_milli = Integer.MAX_VALUE; + int countNull = 0; ListIterator<WeakReference<LocationRequester>> iter = requestersRef.listIterator(); while(iter.hasNext()){ final LocationRequester cReq = iter.next().get(); - if(cReq==null) iter.remove(); - else if(cReq.equals(req)){ + if(cReq==null) { + countNull++; + iter.remove(); + } else if(cReq.equals(req)){ present = true; minimum_time_milli = Math.min(cReq.getLocationCriteria().getTimeInterval(),minimum_time_milli); } } + Log.d(DEBUG_TAG, countNull+" listeners have been removed because null"); if(!present) { WeakReference<LocationRequester> newref = new WeakReference<>(req); requestersRef.add(newref); @@ -104,7 +120,7 @@ Log.d(DEBUG_TAG,"Added new stop requester, instance of "+req.getClass().getSimpleName()); } if(requestersRef.size()>0){ - Log.d(DEBUG_TAG,"Requesting position updates"); + Log.d(DEBUG_TAG,"Requesting location updates"); requestGPSPositionUpdates(); } @@ -134,6 +150,12 @@ else cReq.onLocationStatusChanged(status); } } + public boolean isRequesterRegistered(LocationRequester requester){ + for(WeakReference<LocationRequester> regRef: requestersRef){ + if(regRef.get()!=null && regRef.get() ==requester) return true; + } + return false; + } @Override public void onLocationChanged(Location location) { @@ -176,21 +198,35 @@ } oldGPSLocStatus = status; } - Log.d(DEBUG_TAG, "Provider: "+provider+" status: "+status); + Log.d(DEBUG_TAG, "Provider status changed: "+provider+" status: "+status); } @Override public void onProviderEnabled(String provider) { + cleanAndUpdateRequesters(); requestGPSPositionUpdates(); Log.d(DEBUG_TAG, "Provider: "+provider+" enabled"); + for(WeakReference<LocationRequester> req: requestersRef){ + if(req.get()==null) continue; + req.get().onLocationProviderAvailable(); + } + } @Override public void onProviderDisabled(String provider) { - locMan.removeUpdates(this); + cleanAndUpdateRequesters(); + for(WeakReference<LocationRequester> req: requestersRef){ + if(req.get()==null) continue; + req.get().onLocationDisabled(); + } + //locMan.removeUpdates(this); Log.d(DEBUG_TAG, "Provider: "+provider+" disabled"); } + public boolean anyLocationProviderMatchesCriteria(Criteria cr) { + return Permissions.anyLocationProviderMatchesCriteria(locMan, cr, true); + } /** * Interface to be implemented to get the location request */ @@ -207,6 +243,16 @@ */ void onLocationStatusChanged(int status); + /** + * We have a location provider available + */ + void onLocationProviderAvailable(); + /** + * Called when location is disabled + */ + void onLocationDisabled(); + + /** * Give the last time of update the requester has * Set it to -1 in order to receive each new location diff --git a/src/it/reyboz/bustorino/middleware/GeneralActivity.java b/src/it/reyboz/bustorino/middleware/GeneralActivity.java --- a/src/it/reyboz/bustorino/middleware/GeneralActivity.java +++ b/src/it/reyboz/bustorino/middleware/GeneralActivity.java @@ -2,12 +2,16 @@ import android.Manifest; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.graphics.Rect; import android.net.ConnectivityManager; import android.net.NetworkInfo; import com.google.android.material.snackbar.Snackbar; + +import androidx.annotation.Nullable; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.appcompat.app.AppCompatActivity; @@ -20,6 +24,7 @@ import java.util.HashMap; import it.reyboz.bustorino.R; +import it.reyboz.bustorino.backend.utils; /** * Activity class that contains all the generally useful methods @@ -81,7 +86,7 @@ synchronized (this){ if (permissionAsked.containsKey(permission)){ num_trials = permissionAsked.get(permission); - if (num_trials != null && num_trials > 3) + if (num_trials != null && num_trials > 4) alreadyAsked = true; } @@ -121,4 +126,29 @@ ActivityCompat.requestPermissions(this,permissionstoRequest.toArray(new String[permissionstoRequest])); } */ + + //KEYBOARD STUFF + protected View getRootView() { + return findViewById(android.R.id.content); + } + + /** + * This method doesn't work, DO NOT USE + * @return if the keyboard is open + * TODO: fix this if you want + */ + @Deprecated + public Boolean isKeyboardOpen(){ + Rect visibleBounds = new Rect(); + this.getRootView().getWindowVisibleDisplayFrame(visibleBounds); + + double heightDiff = getRootView().getHeight() - visibleBounds.height(); + final double marginOfError = Math.round(utils.convertDipToPixels(this,50f)); + return heightDiff > marginOfError; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + } } diff --git a/src/it/reyboz/bustorino/util/LocationCriteria.java b/src/it/reyboz/bustorino/util/LocationCriteria.java --- a/src/it/reyboz/bustorino/util/LocationCriteria.java +++ b/src/it/reyboz/bustorino/util/LocationCriteria.java @@ -17,13 +17,21 @@ */ package it.reyboz.bustorino.util; +import android.location.Criteria; + /** * Own Location Criteria, because it's fun + * */ -public class LocationCriteria { +public class LocationCriteria extends Criteria { private final float minAccuracy; private final int timeInterval; + /** + * Constructor + * @param minAccuracy in meters + * @param timeInterval in milliseconds + */ public LocationCriteria(float minAccuracy,int timeInterval){ this.minAccuracy = minAccuracy; this.timeInterval = timeInterval; diff --git a/src/it/reyboz/bustorino/util/Permissions.java b/src/it/reyboz/bustorino/util/Permissions.java --- a/src/it/reyboz/bustorino/util/Permissions.java +++ b/src/it/reyboz/bustorino/util/Permissions.java @@ -8,8 +8,11 @@ import android.location.LocationManager; import android.util.Log; +import androidx.activity.result.ActivityResultCaller; +import androidx.activity.result.ActivityResultLauncher; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; import java.util.List; @@ -24,6 +27,9 @@ final static public int PERMISSION_ASKING = 11; final static public int PERMISSION_NEG_CANNOT_ASK = -3; + final static public String[] LOCATION_PERMISSIONS={Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION}; + public static boolean anyLocationProviderMatchesCriteria(LocationManager mng, Criteria cr, boolean enabled) { List<String> providers = mng.getProviders(cr, enabled); Log.d(DEBUG_TAG, "Getting enabled location providers: "); @@ -32,9 +38,18 @@ } return providers.size() > 0; } + public static boolean isPermissionGranted(Context con,String permission){ + return ContextCompat.checkSelfPermission(con, permission) == PackageManager.PERMISSION_GRANTED; + } + + public static boolean locationPermissionGranted(Context con){ + return isPermissionGranted(con, Manifest.permission.ACCESS_FINE_LOCATION) && + isPermissionGranted(con, Manifest.permission.ACCESS_COARSE_LOCATION); + } public static void assertLocationPermissions(Context con, Activity activity) { - if(ContextCompat.checkSelfPermission(con, Manifest.permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED){ + if(!isPermissionGranted(con, Manifest.permission.ACCESS_FINE_LOCATION) || + !isPermissionGranted(con,Manifest.permission.ACCESS_COARSE_LOCATION)){ ActivityCompat.requestPermissions(activity,new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQUEST_POSITION); } }