Java tutorial
/** * GPSApplication - Java Class for Android * Created by G.Capelli (BasicAirData) on 20/5/2016 * * 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 <http://www.gnu.org/licenses/>. * */ package eu.basicairdata.graziano.gpslogger; import android.Manifest; import android.app.Application; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.location.GpsSatellite; import android.location.GpsStatus; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.location.LocationProvider; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.support.v4.content.ContextCompat; import android.support.v7.preference.PreferenceManager; import android.util.Log; import android.util.SparseArray; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import java.io.File; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class GPSApplication extends Application implements GpsStatus.Listener, LocationListener { //private static final float M_TO_FT = 3.280839895f; private static final int NOT_AVAILABLE = -100000; //private static final int UM_METRIC_MS = 0; private static final int UM_METRIC_KMH = 1; //private static final int UM_IMPERIAL_FPS = 8; //private static final int UM_IMPERIAL_MPH = 9; private static final int STABILIZERVALUE = 3000; // The application discards fixes for 3000 ms (minimum) private static final int DEFAULTHANDLERTIMER = 5000; // The timer for turning off GPS on exit private static final int GPSUNAVAILABLEHANDLERTIMER = 7000; // The "GPS temporary unavailable" timer private int StabilizingSamples = 3; private static final int GPS_DISABLED = 0; private static final int GPS_OUTOFSERVICE = 1; private static final int GPS_TEMPORARYUNAVAILABLE = 2; private static final int GPS_SEARCHING = 3; private static final int GPS_STABILIZING = 4; private static final int GPS_OK = 5; // Preferences Variables // private boolean prefKeepScreenOn = true; // DONE in GPSActivity private boolean prefShowDecimalCoordinates = false; private int prefUM = UM_METRIC_KMH; private float prefGPSdistance = 0f; private long prefGPSupdatefrequency = 1000L; private boolean prefEGM96AltitudeCorrection = false; private double prefAltitudeCorrection = 0d; private boolean prefExportKML = true; private boolean prefExportGPX = true; private int prefGPXVersion = 100; // the version of the GPX schema private boolean prefExportTXT = false; private int prefKMLAltitudeMode = 0; private int prefShowTrackStatsType = 0; private int prefShowDirections = 0; private boolean LocationPermissionChecked = false; // If the flag is false the GPSActivity will check for Location Permission private boolean StoragePermissionChecked = false; // If the flag is false Storage Permission must be asked private EventBusMSGNormal DoIfGrantStoragePermission = null; // Store the message to send in case the user grant storage permission private LocationExtended PrevFix = null; private boolean isPrevFixRecorded = false; private LocationExtended PrevRecordedFix = null; private boolean MustUpdatePrefs = true; // True if preferences needs to be updated private boolean isCurrentTrackVisible = false; private boolean isContextMenuShareVisible = false; // True if "Share with ..." menu is visible private boolean isContextMenuViewVisible = false; // True if "View in *" menu is visible private String ViewInApp = ""; // The string of default app name for "View" // "" in case of selector // Singleton instance private static GPSApplication singleton; public static GPSApplication getInstance() { return singleton; } DatabaseHandler GPSDataBase; private String PlacemarkDescription = ""; private boolean Recording = false; private boolean PlacemarkRequest = false; private long OpenInViewer = -1; // The index to be opened in viewer private long Share = -1; // The index to be Shared private boolean isGPSLocationUpdatesActive = false; private int GPSStatus = GPS_SEARCHING; private boolean NewTrackFlag = false; // The variable that handle the double-click on "Track Finished" final Handler newtrackhandler = new Handler(); Runnable newtrackr = new Runnable() { @Override public void run() { NewTrackFlag = false; } }; private LocationManager mlocManager = null; // GPS LocationManager private int _NumberOfSatellites = 0; private int _NumberOfSatellitesUsedInFix = 0; private int _Stabilizer = StabilizingSamples; private int HandlerTimer = DEFAULTHANDLERTIMER; private LocationExtended _currentLocationExtended = null; private LocationExtended _currentPlacemark = null; private Track _currentTrack = null; private List<Track> _ArrayListTracks = Collections.synchronizedList(new ArrayList<Track>()); static SparseArray<Bitmap> thumbsArray = new SparseArray<>(); // The Array containing the Tracks Thumbnail Thumbnailer Th; Exporter Ex; private AsyncUpdateThreadClass asyncUpdateThread = new AsyncUpdateThreadClass(); // The handler that switches off the location updates after a time delay: final Handler handler = new Handler(); Runnable r = new Runnable() { @Override public void run() { setGPSLocationUpdates(false); } }; final Handler gpsunavailablehandler = new Handler(); Runnable unavailr = new Runnable() { @Override public void run() { if ((GPSStatus == GPS_OK) || (GPSStatus == GPS_STABILIZING)) { GPSStatus = GPS_TEMPORARYUNAVAILABLE; EventBus.getDefault().post(EventBusMSG.UPDATE_FIX); } } }; // ------------------------------------------------------------------------------------ Service Intent GPSServiceIntent; GPSService GPSLoggerService; boolean isGPSServiceBound = false; private ServiceConnection GPSServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { GPSService.LocalBinder binder = (GPSService.LocalBinder) service; GPSLoggerService = binder.getServiceInstance(); //Get instance of your service! Log.w("myApp", "[#] GPSApplication.java - GPSSERVICE CONNECTED - onServiceConnected event"); isGPSServiceBound = true; } @Override public void onServiceDisconnected(ComponentName arg0) { Log.w("myApp", "[#] GPSApplication.java - GPSSERVICE DISCONNECTED - onServiceDisconnected event"); isGPSServiceBound = false; } }; private void StartAndBindGPSService() { GPSServiceIntent = new Intent(GPSApplication.this, GPSService.class); //Start the service startService(GPSServiceIntent); //Bind to the service if (Build.VERSION.SDK_INT >= 14) bindService(GPSServiceIntent, GPSServiceConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT); else bindService(GPSServiceIntent, GPSServiceConnection, Context.BIND_AUTO_CREATE); Log.w("myApp", "[#] GPSApplication.java - StartAndBindGPSService"); } /* private void UnbindGPSService() { //UNUSED try { unbindService(GPSServiceConnection); //Unbind to the service Log.w("myApp", "[#] GPSApplication.java - Service unbound"); } catch (Exception e) { Log.w("myApp", "[#] GPSApplication.java - Unable to unbind the GPSService"); } } */ public void StopAndUnbindGPSService() { try { unbindService(GPSServiceConnection); //Unbind to the service Log.w("myApp", "[#] GPSApplication.java - Service unbound"); } catch (Exception e) { Log.w("myApp", "[#] GPSApplication.java - Unable to unbind the GPSService"); } try { stopService(GPSServiceIntent); //Stop the service Log.w("myApp", "[#] GPSApplication.java - Service stopped"); } catch (Exception e) { Log.w("myApp", "[#] GPSApplication.java - Unable to stop GPSService"); } } // ------------------------------------------------------------------------ Getters and Setters public boolean getNewTrackFlag() { return NewTrackFlag; } public void setNewTrackFlag(boolean newTrackFlag) { if (newTrackFlag) { NewTrackFlag = true; newtrackhandler.removeCallbacks(newtrackr); // Cancel the previous newtrackr handler newtrackhandler.postDelayed(newtrackr, 1500); // starts the new handler } else { NewTrackFlag = false; newtrackhandler.removeCallbacks(newtrackr); // Cancel the previous newtrackr handler } } public void setDoIfGrantStoragePermission(EventBusMSGNormal doIfGrantStoragePermission) { DoIfGrantStoragePermission = doIfGrantStoragePermission; } public EventBusMSGNormal getDoIfGrantStoragePermission() { return DoIfGrantStoragePermission; } public boolean isStoragePermissionChecked() { return StoragePermissionChecked; } public void setStoragePermissionChecked(boolean storagePermissionChecked) { StoragePermissionChecked = storagePermissionChecked; } public boolean isContextMenuShareVisible() { return isContextMenuShareVisible; } public boolean isContextMenuViewVisible() { return isContextMenuViewVisible; } public String getViewInApp() { return ViewInApp; } public boolean isLocationPermissionChecked() { return LocationPermissionChecked; } public void setLocationPermissionChecked(boolean locationPermissionChecked) { LocationPermissionChecked = locationPermissionChecked; } public void setHandlerTimer(int handlerTimer) { HandlerTimer = handlerTimer; } public int getHandlerTimer() { return HandlerTimer; } public int getGPSStatus() { return GPSStatus; } public int getPrefKMLAltitudeMode() { return prefKMLAltitudeMode; } public int getPrefGPXVersion() { return prefGPXVersion; } public void setOpenInViewer(long openInViewer) { OpenInViewer = openInViewer; } public long getOpenInViewer() { return OpenInViewer; } public long getShare() { return Share; } public void setShare(long share) { Share = share; } public double getPrefAltitudeCorrection() { return prefAltitudeCorrection; } public boolean getPrefEGM96AltitudeCorrection() { return prefEGM96AltitudeCorrection; } public boolean getPrefShowDecimalCoordinates() { return prefShowDecimalCoordinates; } public boolean getPrefExportKML() { return prefExportKML; } public boolean getPrefExportGPX() { return prefExportGPX; } public boolean getPrefExportTXT() { return prefExportTXT; } public int getPrefUM() { return prefUM; } public int getPrefShowTrackStatsType() { return prefShowTrackStatsType; } public int getPrefShowDirections() { return prefShowDirections; } public LocationExtended getCurrentLocationExtended() { return _currentLocationExtended == null ? null : _currentLocationExtended; } public void setPlacemarkDescription(String Description) { this.PlacemarkDescription = Description; } public Track getCurrentTrack() { return _currentTrack == null ? null : _currentTrack; } public int getNumberOfSatellites() { return _NumberOfSatellites; } public int getNumberOfSatellitesUsedInFix() { return _NumberOfSatellitesUsedInFix; } public boolean getRecording() { return Recording; } public void setRecording(boolean recordingState) { PrevRecordedFix = null; Recording = recordingState; } public boolean getPlacemarkRequest() { return PlacemarkRequest; } public void setPlacemarkRequest(boolean placemarkRequest) { PlacemarkRequest = placemarkRequest; } public List<Track> getTrackList() { return _ArrayListTracks; } public boolean isCurrentTrackVisible() { return isCurrentTrackVisible; } public void setisCurrentTrackVisible(boolean currentTrackVisible) { isCurrentTrackVisible = currentTrackVisible; } // -------------------------------------------------------------------------------------------- @Override public void onCreate() { super.onCreate(); singleton = this; StartAndBindGPSService(); EventBus.getDefault().register(this); mlocManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); // Location Manager File sd = new File(Environment.getExternalStorageDirectory() + "/GPSLogger"); // Create the Directories if not exist if (!sd.exists()) { sd.mkdir(); Log.w("myApp", "[#] GPSApplication.java - Folder created: " + sd.getAbsolutePath()); } sd = new File(Environment.getExternalStorageDirectory() + "/GPSLogger/AppData"); if (!sd.exists()) { sd.mkdir(); Log.w("myApp", "[#] GPSApplication.java - Folder created: " + sd.getAbsolutePath()); } sd = new File(getApplicationContext().getFilesDir() + "/Thumbnails"); if (!sd.exists()) { sd.mkdir(); Log.w("myApp", "[#] GPSApplication.java - Folder created: " + sd.getAbsolutePath()); } EGM96 egm96 = EGM96.getInstance(); // Load EGM Grid if (egm96 != null) { if (!egm96.isEGMGridLoaded()) { egm96.LoadGridFromFile(Environment.getExternalStorageDirectory() + "/GPSLogger/AppData/WW15MGH.DAC", getApplicationContext().getFilesDir() + "/WW15MGH.DAC"); } } GPSDataBase = new DatabaseHandler(this); // Initialize the Database // Prepare the current track if (GPSDataBase.getLastTrackID() == 0) GPSDataBase.addTrack(new Track()); // Creation of the first track if the DB is empty _currentTrack = GPSDataBase.getLastTrack(); // Get the last track LoadPreferences(); // Load Settings // ---------------------------------------------------------------------------------------- asyncUpdateThread.start(); AsyncTODO ast = new AsyncTODO(); ast.TaskType = "TASK_NEWTRACK"; ast.location = null; AsyncTODOQueue.add(ast); // Get max available VM memory, exceeding this amount will throw an // OutOfMemory exception. Stored in kilobytes as LruCache takes an // int in its constructor. Log.w("myApp", "[#] GPSApplication.java - Max available VM memory = " + (int) (Runtime.getRuntime().maxMemory() / 1024) + " kbytes"); } @Override public void onTerminate() { Log.w("myApp", "[#] GPSApplication.java - onTerminate"); EventBus.getDefault().unregister(this); StopAndUnbindGPSService(); super.onTerminate(); } @Subscribe public void onEvent(EventBusMSGLong msg) { if (msg.MSGType == EventBusMSG.TRACK_SETPROGRESS) { long trackid = msg.id; long progress = msg.Value; if ((trackid > 0) && (progress >= 0)) { synchronized (_ArrayListTracks) { for (Track T : _ArrayListTracks) { if (T.getId() == trackid) T.setProgress((int) progress); } } } return; } } @Subscribe public void onEvent(EventBusMSGNormal msg) { if (msg.MSGType == EventBusMSG.TRACK_EXPORTED) { long trackid = msg.id; if (trackid > 0) { synchronized (_ArrayListTracks) { for (Track T : _ArrayListTracks) { if (T.getId() == trackid) { T.setProgress(0); EventBus.getDefault().post(EventBusMSG.UPDATE_TRACKLIST); if (trackid == OpenInViewer) { OpenInViewer = -1; File file = new File( Environment.getExternalStorageDirectory() + "/GPSLogger/AppData/", T.getName() + ".kml"); Intent intent = new Intent(Intent.ACTION_VIEW); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setDataAndType(Uri.fromFile(file), "application/vnd.google-earth.kml+xml"); startActivity(intent); } if (trackid == Share) { Share = -1; EventBus.getDefault().post(new EventBusMSGNormal(EventBusMSG.INTENT_SEND, trackid)); } } } } } return; } if (msg.MSGType == EventBusMSG.EXPORT_TRACK) { long trackid = msg.id; Ex = new Exporter(trackid, prefExportKML, prefExportGPX, prefExportTXT, Environment.getExternalStorageDirectory() + "/GPSLogger"); Ex.start(); return; } if (msg.MSGType == EventBusMSG.SHARE_TRACK) { setShare(msg.id); Ex = new Exporter(Share, prefExportKML, prefExportGPX, prefExportTXT, Environment.getExternalStorageDirectory() + "/GPSLogger/AppData"); Ex.start(); return; } if (msg.MSGType == EventBusMSG.VIEW_TRACK) { setOpenInViewer(msg.id); Ex = new Exporter(OpenInViewer, true, false, false, Environment.getExternalStorageDirectory() + "/GPSLogger/AppData"); Ex.start(); return; } if (msg.MSGType == EventBusMSG.DELETE_TRACK) { AsyncTODO ast = new AsyncTODO(); ast.TaskType = "TASK_DELETE_TRACK " + msg.id; ast.location = null; AsyncTODOQueue.add(ast); return; } if (msg.MSGType == EventBusMSG.TOAST_UNABLE_TO_WRITE_THE_FILE) { long trackid = msg.id; if (trackid > 0) { synchronized (_ArrayListTracks) { for (Track T : _ArrayListTracks) { if (T.getId() == trackid) { T.setProgress(0); EventBus.getDefault().post(EventBusMSG.UPDATE_TRACKLIST); if (trackid == OpenInViewer) { OpenInViewer = -1; } if (trackid == Share) { Share = -1; } } } } } return; } } @Subscribe public void onEvent(Short msg) { if (msg == EventBusMSG.NEW_TRACK) { AsyncTODO ast = new AsyncTODO(); ast.TaskType = "TASK_NEWTRACK"; ast.location = null; AsyncTODOQueue.add(ast); return; } if (msg == EventBusMSG.ADD_PLACEMARK) { AsyncTODO ast = new AsyncTODO(); ast.TaskType = "TASK_ADDPLACEMARK"; ast.location = _currentPlacemark; _currentPlacemark.setDescription(PlacemarkDescription); AsyncTODOQueue.add(ast); return; } if (msg == EventBusMSG.APP_PAUSE) { handler.postDelayed(r, getHandlerTimer()); // Starts the switch-off handler (delayed by HandlerTimer) System.gc(); // Clear mem from released objects with Garbage Collector //UnbindGPSService(); return; } if (msg == EventBusMSG.APP_RESUME) { //Log.w("myApp", "[#] GPSApplication.java - Received EventBusMSG.APP_RESUME"); AsyncPrepareTracklistContextMenu asyncPrepareTracklistContextMenu = new AsyncPrepareTracklistContextMenu(); asyncPrepareTracklistContextMenu.start(); handler.removeCallbacks(r); // Cancel the switch-off handler setHandlerTimer(DEFAULTHANDLERTIMER); setGPSLocationUpdates(true); if (MustUpdatePrefs) { MustUpdatePrefs = false; LoadPreferences(); } StartAndBindGPSService(); return; } if (msg == EventBusMSG.UPDATE_SETTINGS) { MustUpdatePrefs = true; return; } } public void setGPSLocationUpdates(boolean state) { // Request permissions = https://developer.android.com/training/permissions/requesting.html if (!state && !getRecording() && isGPSLocationUpdatesActive && (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)) { GPSStatus = GPS_SEARCHING; mlocManager.removeGpsStatusListener(this); mlocManager.removeUpdates(this); isGPSLocationUpdatesActive = false; } if (state && !isGPSLocationUpdatesActive && (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)) { mlocManager.addGpsStatusListener(this); mlocManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, prefGPSupdatefrequency, 0, this); // Requires Location update isGPSLocationUpdatesActive = true; StabilizingSamples = (int) Math.ceil(STABILIZERVALUE / prefGPSupdatefrequency); } } public void updateGPSLocationFrequency() { if (isGPSLocationUpdatesActive && (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)) { mlocManager.removeGpsStatusListener(this); mlocManager.removeUpdates(this); StabilizingSamples = (int) Math.ceil(STABILIZERVALUE / prefGPSupdatefrequency); mlocManager.addGpsStatusListener(this); mlocManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, prefGPSupdatefrequency, 0, this); } } public void updateSats() { try { if ((mlocManager != null) && (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)) { GpsStatus gs = mlocManager.getGpsStatus(null); int sats_inview = 0; // Satellites in view; int sats_used = 0; // Satellites used in fix; if (gs != null) { Iterable<GpsSatellite> sats = gs.getSatellites(); for (GpsSatellite sat : sats) { sats_inview++; if (sat.usedInFix()) sats_used++; //Log.w("myApp", "[#] GPSApplication.java - updateSats: i=" + i); } _NumberOfSatellites = sats_inview; _NumberOfSatellitesUsedInFix = sats_used; } else { _NumberOfSatellites = NOT_AVAILABLE; _NumberOfSatellitesUsedInFix = NOT_AVAILABLE; } } else { _NumberOfSatellites = NOT_AVAILABLE; _NumberOfSatellitesUsedInFix = NOT_AVAILABLE; } } catch (NullPointerException e) { _NumberOfSatellites = NOT_AVAILABLE; _NumberOfSatellitesUsedInFix = NOT_AVAILABLE; Log.w("myApp", "[#] GPSApplication.java - updateSats: Caught NullPointerException: " + e); } //Log.w("myApp", "[#] GPSApplication.java - updateSats: Total=" + _NumberOfSatellites + " Used=" + _NumberOfSatellitesUsedInFix); } private class AsyncPrepareTracklistContextMenu extends Thread { public AsyncPrepareTracklistContextMenu() { } public void run() { isContextMenuShareVisible = false; isContextMenuViewVisible = false; ViewInApp = ""; final PackageManager pm = getPackageManager(); // ----- menu share Intent intent = new Intent(); intent.setAction(Intent.ACTION_SEND_MULTIPLE); intent.setType("text/xml"); // Verify the intent will resolve to at least one activity if ((intent.resolveActivity(pm) != null)) isContextMenuShareVisible = true; // ----- menu view intent = new Intent(Intent.ACTION_VIEW); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setType("application/vnd.google-earth.kml+xml"); ResolveInfo ri = pm.resolveActivity(intent, 0); // Find default app if (ri != null) { //Log.w("myApp", "[#] FragmentTracklist.java - Open with: " + ri.activityInfo.applicationInfo.loadLabel(getContext().getPackageManager())); List<ResolveInfo> lri = pm.queryIntentActivities(intent, 0); //Log.w("myApp", "[#] FragmentTracklist.java - Found " + lri.size() + " viewers:"); for (ResolveInfo tmpri : lri) { //Log.w("myApp", "[#] " + ri.activityInfo.applicationInfo.packageName + " - " + tmpri.activityInfo.applicationInfo.packageName); if (ri.activityInfo.applicationInfo.packageName .equals(tmpri.activityInfo.applicationInfo.packageName)) { ViewInApp = ri.activityInfo.applicationInfo.loadLabel(pm).toString(); //Log.w("myApp", "[#] DEFAULT --> " + tmpri.activityInfo.applicationInfo.loadLabel(getPackageManager())); } //else Log.w("myApp", "[#] " + tmpri.activityInfo.applicationInfo.loadLabel(getContext().getPackageManager())); } isContextMenuViewVisible = true; } Log.w("myApp", "[#] GPSApplication.java - Tracklist ContextMenu prepared"); } } // ------------------------------------------------------------------------- GpsStatus.Listener @Override public void onGpsStatusChanged(final int event) { switch (event) { case GpsStatus.GPS_EVENT_SATELLITE_STATUS: // TODO: get here the status of the GPS, and save into a GpsStatus to be used for satellites visualization; // Use GpsStatus getGpsStatus (GpsStatus status) // https://developer.android.com/reference/android/location/LocationManager.html#getGpsStatus(android.location.GpsStatus) updateSats(); break; } } // --------------------------------------------------------------------------- LocationListener @Override public void onLocationChanged(Location loc) { //if ((loc != null) && (loc.getProvider().equals(LocationManager.GPS_PROVIDER)) { if (loc != null) { // Location data is valid //Log.w("myApp", "[#] GPSApplication.java - onLocationChanged: provider=" + loc.getProvider()); if (loc.hasSpeed() && (loc.getSpeed() == 0)) loc.removeBearing(); // Removes bearing if the speed is zero LocationExtended eloc = new LocationExtended(loc); eloc.setNumberOfSatellites(getNumberOfSatellites()); eloc.setNumberOfSatellitesUsedInFix(getNumberOfSatellitesUsedInFix()); boolean ForceRecord = false; gpsunavailablehandler.removeCallbacks(unavailr); // Cancel the previous unavail countdown handler gpsunavailablehandler.postDelayed(unavailr, GPSUNAVAILABLEHANDLERTIMER); // starts the unavailability timeout (in 7 sec.) if (GPSStatus != GPS_OK) { if (GPSStatus != GPS_STABILIZING) { GPSStatus = GPS_STABILIZING; _Stabilizer = StabilizingSamples; EventBus.getDefault().post(EventBusMSG.UPDATE_FIX); } else _Stabilizer--; if (_Stabilizer == 0) GPSStatus = GPS_OK; PrevFix = eloc; PrevRecordedFix = eloc; isPrevFixRecorded = true; } // Save fix in case this is a STOP or a START (the speed is "old>0 and new=0" or "old=0 and new>0") if ((PrevFix != null) && (PrevFix.getLocation().hasSpeed()) && (eloc.getLocation().hasSpeed()) && (GPSStatus == GPS_OK) && (Recording) && (((eloc.getLocation().getSpeed() == 0) && (PrevFix.getLocation().getSpeed() != 0)) || ((eloc.getLocation().getSpeed() != 0) && (PrevFix.getLocation().getSpeed() == 0)))) { if (!isPrevFixRecorded) { // Record the old sample if not already recorded AsyncTODO ast = new AsyncTODO(); ast.TaskType = "TASK_ADDLOCATION"; ast.location = PrevFix; AsyncTODOQueue.add(ast); PrevRecordedFix = PrevFix; isPrevFixRecorded = true; } ForceRecord = true; // + Force to record the new } if (GPSStatus == GPS_OK) { AsyncTODO ast = new AsyncTODO(); if ((Recording) && ((prefGPSdistance == 0) || (PrevRecordedFix == null) || (ForceRecord) || (loc.distanceTo(PrevRecordedFix.getLocation()) >= prefGPSdistance))) { PrevRecordedFix = eloc; ast.TaskType = "TASK_ADDLOCATION"; ast.location = eloc; AsyncTODOQueue.add(ast); isPrevFixRecorded = true; } else { ast.TaskType = "TASK_UPDATEFIX"; ast.location = eloc; AsyncTODOQueue.add(ast); isPrevFixRecorded = false; } if (PlacemarkRequest) { _currentPlacemark = new LocationExtended(loc); _currentPlacemark.setNumberOfSatellites(getNumberOfSatellites()); _currentPlacemark.setNumberOfSatellitesUsedInFix(getNumberOfSatellitesUsedInFix()); PlacemarkRequest = false; EventBus.getDefault().post(EventBusMSG.UPDATE_TRACK); EventBus.getDefault().post(EventBusMSG.REQUEST_ADD_PLACEMARK); } PrevFix = eloc; } } } @Override public void onProviderDisabled(String provider) { GPSStatus = GPS_DISABLED; EventBus.getDefault().post(EventBusMSG.UPDATE_FIX); } @Override public void onProviderEnabled(String provider) { GPSStatus = GPS_SEARCHING; EventBus.getDefault().post(EventBusMSG.UPDATE_FIX); } @Override public void onStatusChanged(String provider, int status, Bundle extras) { // This is called when the GPS status changes switch (status) { case LocationProvider.OUT_OF_SERVICE: //Log.w("myApp", "[#] GPSApplication.java - GPS Out of Service"); gpsunavailablehandler.removeCallbacks(unavailr); // Cancel the previous unavail countdown handler GPSStatus = GPS_OUTOFSERVICE; EventBus.getDefault().post(EventBusMSG.UPDATE_FIX); //Toast.makeText( getApplicationContext(), "GPS Out of Service", Toast.LENGTH_SHORT).show(); break; case LocationProvider.TEMPORARILY_UNAVAILABLE: //Log.w("myApp", "[#] GPSApplication.java - GPS Temporarily Unavailable"); gpsunavailablehandler.removeCallbacks(unavailr); // Cancel the previous unavail countdown handler GPSStatus = GPS_TEMPORARYUNAVAILABLE; EventBus.getDefault().post(EventBusMSG.UPDATE_FIX); //Toast.makeText( getApplicationContext(), "GPS Temporarily Unavailable", Toast.LENGTH_SHORT).show(); break; case LocationProvider.AVAILABLE: gpsunavailablehandler.removeCallbacks(unavailr); // Cancel the previous unavail countdown handler //Log.w("myApp", "[#] GPSApplication.java - GPS Available: " + _NumberOfSatellites + " satellites"); break; } } public void UpdateTrackList() { long ID = GPSDataBase.getLastTrackID(); if (ID > 0) { synchronized (_ArrayListTracks) { _ArrayListTracks.clear(); _ArrayListTracks.addAll(GPSDataBase.getTracksList(0, ID - 1)); if ((ID > 1) && (GPSDataBase.getTrack(ID - 1) != null)) { String fname = (ID - 1) + ".png"; File file = new File(getApplicationContext().getFilesDir() + "/Thumbnails/", fname); if (!file.exists()) Th = new Thumbnailer(ID - 1); } if (_currentTrack.getNumberOfLocations() + _currentTrack.getNumberOfPlacemarks() > 0) { Log.w("myApp", "[#] GPSApplication.java - Update Tracklist: current track (" + _currentTrack.getId() + ") visible into the tracklist"); _ArrayListTracks.add(0, _currentTrack); } else Log.w("myApp", "[#] GPSApplication.java - Update Tracklist: current track not visible into the tracklist"); } EventBus.getDefault().post(EventBusMSG.UPDATE_TRACKLIST); //Log.w("myApp", "[#] GPSApplication.java - Update Tracklist: Added " + _ArrayListTracks.size() + " tracks"); } } // PREFERENCES LOADER ------------------------------------------------------------------------------ private void LoadPreferences() { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); // ---------Conversion from the previous versions of GPS Logger preferences if (preferences.contains("prefShowImperialUnits")) { // The old boolean setting for imperial units in v.1.1.5 Log.w("myApp", "[#] GPSApplication.java - Old setting prefShowImperialUnits present. Converting to new preference PrefUM."); boolean imperialUM = preferences.getBoolean("prefShowImperialUnits", false); SharedPreferences.Editor editor = preferences.edit(); editor.putString("prefUM", (imperialUM ? "8" : "0")); editor.remove("prefShowImperialUnits"); editor.commit(); } // ----------------------------------------------------------------------- //prefKeepScreenOn = preferences.getBoolean("prefKeepScreenOn", true); prefShowDecimalCoordinates = preferences.getBoolean("prefShowDecimalCoordinates", false); prefUM = Integer.valueOf(preferences.getString("prefUM", "0")) + Integer.valueOf(preferences.getString("prefUMSpeed", "1")); prefGPSdistance = Float.valueOf(preferences.getString("prefGPSdistance", "0")); prefEGM96AltitudeCorrection = preferences.getBoolean("prefEGM96AltitudeCorrection", false); prefAltitudeCorrection = Double.valueOf(preferences.getString("prefAltitudeCorrection", "0")); Log.w("myApp", "[#] GPSApplication.java - Manual Correction set to " + prefAltitudeCorrection + " m"); prefExportKML = preferences.getBoolean("prefExportKML", true); prefExportGPX = preferences.getBoolean("prefExportGPX", true); prefExportTXT = preferences.getBoolean("prefExportTXT", false); prefKMLAltitudeMode = Integer.valueOf(preferences.getString("prefKMLAltitudeMode", "1")); prefGPXVersion = Integer.valueOf(preferences.getString("prefGPXVersion", "100")); // Default value = v.1.0 prefShowTrackStatsType = Integer.valueOf(preferences.getString("prefShowTrackStatsType", "0")); prefShowDirections = Integer.valueOf(preferences.getString("prefShowDirections", "0")); StoragePermissionChecked = preferences.getBoolean("prefIsStoragePermissionChecked", false); long oldGPSupdatefrequency = prefGPSupdatefrequency; prefGPSupdatefrequency = Long.valueOf(preferences.getString("prefGPSupdatefrequency", "1000")); // ---------------------------------------------- Update the GPS Update Frequency if needed if (oldGPSupdatefrequency != prefGPSupdatefrequency) updateGPSLocationFrequency(); // ---------------------------------------------------------------- Load EGM Grid if needed EGM96 egm96 = EGM96.getInstance(); if (egm96 != null) { if (!egm96.isEGMGridLoaded()) { egm96.LoadGridFromFile(Environment.getExternalStorageDirectory() + "/GPSLogger/AppData/WW15MGH.DAC", getApplicationContext().getFilesDir() + "/WW15MGH.DAC"); } } // ------------------------------------------------------------------- Request of UI Update EventBus.getDefault().post(EventBusMSG.APPLY_SETTINGS); EventBus.getDefault().post(EventBusMSG.UPDATE_FIX); EventBus.getDefault().post(EventBusMSG.UPDATE_TRACK); EventBus.getDefault().post(EventBusMSG.UPDATE_TRACKLIST); } // THE THREAD THAT DOES ASYNCHRONOUS OPERATIONS --------------------------------------------------- class AsyncTODO { String TaskType; LocationExtended location; } private BlockingQueue<AsyncTODO> AsyncTODOQueue = new LinkedBlockingQueue<>(); private class AsyncUpdateThreadClass extends Thread { Track track; LocationExtended locationExtended; public AsyncUpdateThreadClass() { } public void run() { track = _currentTrack; EventBus.getDefault().post(EventBusMSG.UPDATE_TRACK); UpdateTrackList(); while (true) { AsyncTODO asyncTODO; try { asyncTODO = AsyncTODOQueue.take(); } catch (InterruptedException e) { Log.w("myApp", "[!] Buffer not available: " + e.getMessage()); break; } // Task: Create new track (if needed) if (asyncTODO.TaskType.equals("TASK_NEWTRACK")) { if ((track.getNumberOfLocations() != 0) || (track.getNumberOfPlacemarks() != 0)) { // ---- Delete 2 thumbs files forward - in case of user deleted DB in App manager (pngs could be already presents for the new IDS) String fname = (track.getId() + 1) + ".png"; File file = new File(getApplicationContext().getFilesDir() + "/Thumbnails/", fname); if (file.exists()) file.delete(); fname = (track.getId() + 2) + ".png"; file = new File(getApplicationContext().getFilesDir() + "/Thumbnails/", fname); if (file.exists()) file.delete(); track = new Track(); // ---- track.setId(GPSDataBase.addTrack(track)); Log.w("myApp", "[#] GPSApplication.java - TASK_NEWTRACK: " + track.getId()); _currentTrack = track; UpdateTrackList(); } else Log.w("myApp", "[#] GPSApplication.java - TASK_NEWTRACK: Track " + track.getId() + " already empty (New track not created)"); _currentTrack = track; EventBus.getDefault().post(EventBusMSG.UPDATE_TRACK); } // Task: Add location to current track if (asyncTODO.TaskType.equals("TASK_ADDLOCATION")) { locationExtended = new LocationExtended(asyncTODO.location.getLocation()); locationExtended.setNumberOfSatellites(asyncTODO.location.getNumberOfSatellites()); locationExtended .setNumberOfSatellitesUsedInFix(asyncTODO.location.getNumberOfSatellitesUsedInFix()); _currentLocationExtended = locationExtended; EventBus.getDefault().post(EventBusMSG.UPDATE_FIX); track.add(locationExtended); GPSDataBase.addLocationToTrack(locationExtended, track); _currentTrack = track; EventBus.getDefault().post(EventBusMSG.UPDATE_TRACK); if (_currentTrack.getNumberOfLocations() + _currentTrack.getNumberOfPlacemarks() == 1) UpdateTrackList(); } // Task: Add a placemark to current track if (asyncTODO.TaskType.equals("TASK_ADDPLACEMARK")) { locationExtended = new LocationExtended(asyncTODO.location.getLocation()); locationExtended.setDescription(asyncTODO.location.getDescription()); locationExtended.setNumberOfSatellites(asyncTODO.location.getNumberOfSatellites()); locationExtended .setNumberOfSatellitesUsedInFix(asyncTODO.location.getNumberOfSatellitesUsedInFix()); track.addPlacemark(locationExtended); GPSDataBase.addPlacemarkToTrack(locationExtended, track); _currentTrack = track; EventBus.getDefault().post(EventBusMSG.UPDATE_TRACK); if (_currentTrack.getNumberOfLocations() + _currentTrack.getNumberOfPlacemarks() == 1) UpdateTrackList(); } // Task: Update current Fix if (asyncTODO.TaskType.equals("TASK_UPDATEFIX")) { _currentLocationExtended = new LocationExtended(asyncTODO.location.getLocation()); _currentLocationExtended.setNumberOfSatellites(asyncTODO.location.getNumberOfSatellites()); _currentLocationExtended .setNumberOfSatellitesUsedInFix(asyncTODO.location.getNumberOfSatellitesUsedInFix()); EventBus.getDefault().post(EventBusMSG.UPDATE_FIX); } if (asyncTODO.TaskType.contains("TASK_DELETE_TRACK")) { Log.w("myApp", "[#] GPSApplication.java - Deleting Track ID = " + asyncTODO.TaskType.split(" ")[1]); if (Integer.valueOf(asyncTODO.TaskType.split(" ")[1]) >= 0) { long selectedtrackID = Integer.valueOf(asyncTODO.TaskType.split(" ")[1]); synchronized (_ArrayListTracks) { if (!_ArrayListTracks.isEmpty() && (selectedtrackID >= 0)) { int i = 0; boolean found = false; do { if (_ArrayListTracks.get(i).getId() == selectedtrackID) { found = true; GPSDataBase.DeleteTrack(_ArrayListTracks.get(i).getId()); Log.w("myApp", "[#] GPSApplication.java - Track " + _ArrayListTracks.get(i).getId() + " deleted."); _ArrayListTracks.remove(i); } i++; } while ((i < _ArrayListTracks.size()) && !found); //Log.w("myApp", "[#] GPSApplication.java - now DB Contains " + GPSDataBase.getLocationsTotalCount() + " locations"); //if (found) UpdateTrackList(); } } } } } } } // THE THREAD THAT GENERATES A TRACK THUMBNAIL ----------------------------------------------------- public class Thumbnailer { long Id; long NumberOfLocations; private Paint drawPaint = new Paint(); private Paint BGPaint = new Paint(); private Paint EndDotdrawPaint = new Paint(); private Paint EndDotBGPaint = new Paint(); private int Size = (int) (getResources().getDimension(R.dimen.thumbSize)); private int Margin = (int) Math.ceil(getResources().getDimension(R.dimen.thumbLineWidth) * 3); private int Size_Minus_Margins = Size - 2 * Margin; private double MinLatitude; private double MinLongitude; double Distance_Proportion; double DrawScale; double Lat_Offset; double Lon_Offset; private AsyncThumbnailThreadClass asyncThumbnailThreadClass = new AsyncThumbnailThreadClass(); public Thumbnailer(long ID) { Track track = GPSDataBase.getTrack(ID); //Log.w("myApp", "[#] GPSApplication.java - Bitmap Size = " + Size); if ((track.getNumberOfLocations() > 2) && (track.getDistance() >= 15) && (track.getValidMap() != 0)) { Id = track.getId(); NumberOfLocations = track.getNumberOfLocations(); // Setup Paints drawPaint.setColor(Color.parseColor("#c9c9c9")); drawPaint.setAntiAlias(true); drawPaint.setStrokeWidth(getResources().getDimension(R.dimen.thumbLineWidth)); //drawPaint.setStrokeWidth(2); drawPaint.setStyle(Paint.Style.STROKE); drawPaint.setStrokeJoin(Paint.Join.ROUND); drawPaint.setStrokeCap(Paint.Cap.ROUND); BGPaint.setColor(Color.BLACK); BGPaint.setAntiAlias(true); BGPaint.setStrokeWidth(getResources().getDimension(R.dimen.thumbLineWidth) * 3); //BGPaint.setStrokeWidth(6); BGPaint.setStyle(Paint.Style.STROKE); BGPaint.setStrokeJoin(Paint.Join.ROUND); BGPaint.setStrokeCap(Paint.Cap.ROUND); EndDotdrawPaint.setColor(Color.parseColor("#c9c9c9")); EndDotdrawPaint.setAntiAlias(true); EndDotdrawPaint.setStrokeWidth(getResources().getDimension(R.dimen.thumbLineWidth) * 2.5f); EndDotdrawPaint.setStyle(Paint.Style.STROKE); EndDotdrawPaint.setStrokeJoin(Paint.Join.ROUND); EndDotdrawPaint.setStrokeCap(Paint.Cap.ROUND); EndDotBGPaint.setColor(Color.BLACK); EndDotBGPaint.setAntiAlias(true); EndDotBGPaint.setStrokeWidth(getResources().getDimension(R.dimen.thumbLineWidth) * 4.5f); EndDotBGPaint.setStyle(Paint.Style.STROKE); EndDotBGPaint.setStrokeJoin(Paint.Join.ROUND); EndDotBGPaint.setStrokeCap(Paint.Cap.ROUND); // Calculate the drawing scale double Mid_Latitude = (track.getMax_Latitude() + track.getMin_Latitude()) / 2; double Angle_From_Equator = Math.abs(Mid_Latitude); Distance_Proportion = Math.cos(Math.toRadians(Angle_From_Equator)); //Log.w("myApp", "[#] GPSApplication.java - Distance_Proportion = " + Distance_Proportion); DrawScale = Math.max(track.getMax_Latitude() - track.getMin_Latitude(), Distance_Proportion * (track.getMax_Longitude() - track.getMin_Longitude())); Lat_Offset = Size_Minus_Margins * (1 - (track.getMax_Latitude() - track.getMin_Latitude()) / DrawScale) / 2; Lon_Offset = Size_Minus_Margins * (1 - (Distance_Proportion * (track.getMax_Longitude() - track.getMin_Longitude()) / DrawScale)) / 2; MinLatitude = track.getMin_Latitude(); MinLongitude = track.getMin_Longitude(); asyncThumbnailThreadClass.start(); } } private class AsyncThumbnailThreadClass extends Thread { public AsyncThumbnailThreadClass() { } public void run() { Thread.currentThread().setPriority(Thread.MIN_PRIORITY); String fname = Id + ".png"; File file = new File(getApplicationContext().getFilesDir() + "/Thumbnails/", fname); if (file.exists()) file.delete(); if (DrawScale > 0) { int GroupOfLocations = 50; Path path = new Path(); List<LatLng> latlngList = new ArrayList<>(); //Log.w("myApp", "[#] GPSApplication.java - Thumbnailer Thread started"); for (int i = 0; i < NumberOfLocations; i += GroupOfLocations) { latlngList.addAll(GPSDataBase.getLatLngList(Id, i, i + GroupOfLocations - 1)); } //Log.w("myApp", "[#] GPSApplication.java - Added " + latlngList.size() + " items to Path"); if (!latlngList.isEmpty()) { Bitmap ThumbBitmap = Bitmap.createBitmap(Size, Size, Bitmap.Config.ARGB_8888); Canvas ThumbCanvas = new Canvas(ThumbBitmap); for (int i = 0; i < latlngList.size(); i++) { if (i == 0) path.moveTo( (float) (Lon_Offset + Margin + Size_Minus_Margins * ((latlngList.get(i).Longitude - MinLongitude) * Distance_Proportion / DrawScale)), (float) (-Lat_Offset + Size - (Margin + Size_Minus_Margins * ((latlngList.get(i).Latitude - MinLatitude) / DrawScale)))); else path.lineTo( (float) (Lon_Offset + Margin + Size_Minus_Margins * ((latlngList.get(i).Longitude - MinLongitude) * Distance_Proportion / DrawScale)), (float) (-Lat_Offset + Size - (Margin + Size_Minus_Margins * ((latlngList.get(i).Latitude - MinLatitude) / DrawScale)))); } ThumbCanvas.drawPath(path, BGPaint); ThumbCanvas.drawPoint( (float) (Lon_Offset + Margin + Size_Minus_Margins * ((latlngList.get(latlngList.size() - 1).Longitude - MinLongitude) * Distance_Proportion / DrawScale)), (float) (-Lat_Offset + Size - (Margin + Size_Minus_Margins * ((latlngList.get(latlngList.size() - 1).Latitude - MinLatitude) / DrawScale))), EndDotBGPaint); ThumbCanvas.drawPath(path, drawPaint); ThumbCanvas.drawPoint( (float) (Lon_Offset + Margin + Size_Minus_Margins * ((latlngList.get(latlngList.size() - 1).Longitude - MinLongitude) * Distance_Proportion / DrawScale)), (float) (-Lat_Offset + Size - (Margin + Size_Minus_Margins * ((latlngList.get(latlngList.size() - 1).Latitude - MinLatitude) / DrawScale))), EndDotdrawPaint); try { FileOutputStream out = new FileOutputStream(file); //Log.w("myApp", "[#] GPSApplication.java - FileOutputStream out = new FileOutputStream(file)"); //boolean res = ThumbBitmap.compress(Bitmap.CompressFormat.PNG, 60, out); ThumbBitmap.compress(Bitmap.CompressFormat.PNG, 60, out); //Log.w("myApp", "[#] GPSApplication.java - ThumbBitmap.compress(Bitmap.CompressFormat.PNG, 60, out): " + res); out.flush(); //Log.w("myApp", "[#] GPSApplication.java - out.flush();"); out.close(); //Log.w("myApp", "[#] GPSApplication.java - out.close();"); } catch (Exception e) { e.printStackTrace(); //Log.w("myApp", "[#] GPSApplication.java - Unable to save: " + Environment.getExternalStorageDirectory() + "/GPSLogger/AppData/" + fname); } final String FilesDir = GPSApplication.getInstance().getApplicationContext().getFilesDir() .toString() + "/Thumbnails/"; String Filename = FilesDir + Id + ".png"; file = new File(Filename); if (file.exists()) { Bitmap bmp = BitmapFactory.decodeFile(Filename); if (bmp != null) { thumbsArray.put((int) Id, bmp); Log.w("myApp", "[#] GPSApplication.java - Loaded track " + Id + " thumbnail"); } } EventBus.getDefault().post(EventBusMSG.UPDATE_TRACKLIST); } } } } } }