Java tutorial
/* * Copyright (C) 2016, 2017 Oliver Bell * Copyright (C) 2010, 2011, 2012 Herbert von Broeuschmeul * Copyright (C) 2010, 2011, 2012 BluetoothGPS4Droid Project * Copyright (C) 2011, 2012 UsbGPS4Droid Project * * This file is part of UsbGPS4Droid. * * UsbGPS4Droid 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. * * UsbGPS4Droid 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 UsbGPS4Droid. If not, see <http://www.gnu.org/licenses/>. */ package org.broeuschmeul.android.gps.usb.provider.driver; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.broeuschmeul.android.gps.nmea.util.NmeaParser; import org.broeuschmeul.android.gps.sirf.util.SirfUtils; import org.broeuschmeul.android.gps.usb.provider.BuildConfig; import org.broeuschmeul.android.gps.usb.provider.R; import org.broeuschmeul.android.gps.usb.provider.USBGpsApplication; import org.broeuschmeul.android.gps.usb.provider.ui.GpsInfoActivity; import org.broeuschmeul.android.gps.usb.provider.util.SuperuserManager; import android.Manifest; import android.annotation.SuppressLint; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.Intent; import android.content.pm.PackageManager; import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbManager; import android.location.LocationManager; import android.os.Build; import android.os.Handler; import android.preference.PreferenceManager; import android.app.AppOpsManager; import android.os.Bundle; import android.os.SystemClock; import android.provider.Settings; import android.support.v4.app.NotificationCompat; import android.support.v4.content.ContextCompat; import android.util.Log; /** * This class is used to establish and manage the connection with the bluetooth GPS. * * @author Herbert von Broeuschmeul */ public class USBGpsManager { /** * Tag used for log messages */ private static final String LOG_TAG = USBGpsManager.class.getSimpleName(); // Has more connections logs private boolean debug = true; private UsbManager usbManager = null; private static final String ACTION_USB_PERMISSION = "org.broeuschmeul.android.gps.usb.provider.driver.USBGpsManager.USB_PERMISSION"; /** * Used to listen for nmea updates from UsbGpsManager */ public interface NmeaListener { void onNmeaReceived(long timestamp, String nmea); } private final BroadcastReceiver permissionAndDetachReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (ACTION_USB_PERMISSION.equals(action)) { synchronized (this) { UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { if (device != null) { if (usbManager.hasPermission(device)) { debugLog("We have permission, good!"); if (enabled) { openConnection(device); } } } } else { debugLog("permission denied for device " + device); } } } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) { synchronized (this) { if (connectedGps != null && enabled) { connectedGps.close(); } } } } }; /** * A utility class used to manage the communication with the bluetooth GPS whn the connection has been established. * It is used to read NMEA data from the GPS or to send SIRF III binary commands or SIRF III NMEA commands to the GPS. * You should run the main read loop in one thread and send the commands in a separate one. * * @author Herbert von Broeuschmeul */ private class ConnectedGps extends Thread { /** * GPS bluetooth socket used for communication. */ private final File gpsDev; private final UsbDevice gpsUsbDev; private final UsbInterface intf; private UsbEndpoint endpointIn; private UsbEndpoint endpointOut; private final UsbDeviceConnection connection; private boolean closed = false; /** * GPS InputStream from which we read data. */ private final InputStream in; /** * GPS output stream to which we send data (SIRF III binary commands). */ private final OutputStream out; /** * GPS output stream to which we send data (SIRF III NMEA commands). */ private final PrintStream out2; /** * A boolean which indicates if the GPS is ready to receive data. * In fact we consider that the GPS is ready when it begins to sends data... */ private boolean ready = false; public ConnectedGps(UsbDevice device) { this(device, defaultDeviceSpeed); } public ConnectedGps(UsbDevice device, String deviceSpeed) { this.gpsDev = null; this.gpsUsbDev = device; debugLog("Searching interfaces, found " + String.valueOf(device.getInterfaceCount())); UsbInterface foundInterface = null; for (int j = 0; j < device.getInterfaceCount(); j++) { debugLog("Checking interface number " + String.valueOf(j)); UsbInterface deviceInterface = device.getInterface(j); debugLog("Found interface of class " + String.valueOf(deviceInterface.getInterfaceClass())); // Finds an endpoint for the device by looking through all the device endpoints // and finding which one supports, debugLog("Searching endpoints of interface, found " + String.valueOf(deviceInterface.getEndpointCount())); UsbEndpoint foundInEndpoint = null; UsbEndpoint foundOutEndpoint = null; for (int i = deviceInterface.getEndpointCount() - 1; i > -1; i--) { debugLog("Checking endpoint number " + String.valueOf(i)); UsbEndpoint interfaceEndpoint = deviceInterface.getEndpoint(i); if (interfaceEndpoint.getDirection() == UsbConstants.USB_DIR_IN) { debugLog("Found IN Endpoint of type: " + String.valueOf(interfaceEndpoint.getType())); if (interfaceEndpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { debugLog("Is correct in endpoint"); foundInEndpoint = interfaceEndpoint; } } if (interfaceEndpoint.getDirection() == UsbConstants.USB_DIR_OUT) { debugLog("Found OUT Endpoint of type: " + String.valueOf(interfaceEndpoint.getType())); if (interfaceEndpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { debugLog("Is correct out endpoint"); foundOutEndpoint = interfaceEndpoint; } } if ((foundInEndpoint != null) && (foundOutEndpoint != null)) { endpointIn = foundInEndpoint; endpointOut = foundOutEndpoint; break; } } if ((endpointIn != null) && (endpointOut != null)) { foundInterface = deviceInterface; break; } } intf = foundInterface; // endpointIn = intf.getEndpoint(2); final int TIMEOUT = 100; // final int TIMEOUT = 0; connection = usbManager.openDevice(device); if (intf != null) { debugLog("claiming interface"); boolean resclaim = connection.claimInterface(intf, true); debugLog("data claim " + resclaim); } InputStream tmpIn = null; OutputStream tmpOut = null; PrintStream tmpOut2 = null; tmpIn = new InputStream() { private byte[] buffer = new byte[128]; private byte[] usbBuffer = new byte[64]; private byte[] oneByteBuffer = new byte[1]; private ByteBuffer bufferWrite = ByteBuffer.wrap(buffer); private ByteBuffer bufferRead = (ByteBuffer) ByteBuffer.wrap(buffer).limit(0); private boolean closed = false; @Override public int read() throws IOException { int b = 0; //if (BuildConfig.DEBUG || debug) Log.d(LOG_TAG, "trying to read data"); int nb = 0; while ((nb == 0) && (!closed)) { nb = this.read(oneByteBuffer, 0, 1); } if (nb > 0) { b = oneByteBuffer[0]; } else { // TODO : if nb = 0 then we have a pb b = -1; Log.e(LOG_TAG, "data read() error code: " + nb); } if (b <= 0) { Log.e(LOG_TAG, "data read() error: char " + b); } //if (BuildConfig.DEBUG || debug) Log.d(LOG_TAG, "data: " + b + " char: " + (char)b); return b; } /* (non-Javadoc) * @see java.io.InputStream#available() */ @Override public int available() throws IOException { // TODO Auto-generated method stub //if (BuildConfig.DEBUG || debug) Log.d(LOG_TAG, "data available "+bufferRead.remaining()); return bufferRead.remaining(); } /* (non-Javadoc) * @see java.io.InputStream#mark(int) */ @Override public void mark(int readlimit) { // TODO Auto-generated method stub //if (BuildConfig.DEBUG || debug) Log.d(LOG_TAG, "data mark"); super.mark(readlimit); } /* (non-Javadoc) * @see java.io.InputStream#markSupported() */ @Override public boolean markSupported() { // TODO Auto-generated method stub //if (BuildConfig.DEBUG || debug) Log.d(LOG_TAG, "data markSupported"); return super.markSupported(); } /* (non-Javadoc) * @see java.io.InputStream#read(byte[], int, int) */ @Override public int read(byte[] buffer, int offset, int length) throws IOException { // if (BuildConfig.DEBUG || debug) Log.d(LOG_TAG, "data read buffer - offset: " + offset + " length: " + length); int nb = 0; ByteBuffer out = ByteBuffer.wrap(buffer, offset, length); if ((!bufferRead.hasRemaining()) && (!closed)) { // if (BuildConfig.DEBUG || debug) Log.i(LOG_TAG, "data read buffer empty " + Arrays.toString(usbBuffer)); int n = connection.bulkTransfer(endpointIn, usbBuffer, 64, 10000); // if (BuildConfig.DEBUG || debug) Log.w(LOG_TAG, "data read: nb: " + n + " " + Arrays.toString(usbBuffer)); if (n > 0) { if (n > bufferWrite.remaining()) { bufferRead.rewind(); bufferWrite.clear(); } bufferWrite.put(usbBuffer, 0, n); bufferRead.limit(bufferWrite.position()); // if (BuildConfig.DEBUG || debug) Log.d(LOG_TAG, "data read: nb: " + n + " current: " + bufferRead.position() + " limit: " + bufferRead.limit() + " " + Arrays.toString(bufferRead.array())); } else { if (BuildConfig.DEBUG || debug) Log.e(LOG_TAG, "data read(buffer...) error: " + nb); } } if (bufferRead.hasRemaining()) { // if (BuildConfig.DEBUG || debug) Log.d(LOG_TAG, "data : asked: " + length + " current: " + bufferRead.position() + " limit: " + bufferRead.limit() + " " + Arrays.toString(bufferRead.array())); nb = Math.min(bufferRead.remaining(), length); out.put(bufferRead.array(), bufferRead.position() + bufferRead.arrayOffset(), nb); bufferRead.position(bufferRead.position() + nb); // if (BuildConfig.DEBUG || debug) Log.d(LOG_TAG, "data : given: " + nb + " current: " + bufferRead.position() + " limit: " + bufferRead.limit() + " " + Arrays.toString(bufferRead.array())); // if (BuildConfig.DEBUG || debug) Log.d(LOG_TAG, "data : given: " + nb + " offset: " + offset + " " + Arrays.toString(buffer)); } return nb; } /* (non-Javadoc) * @see java.io.InputStream#read(byte[]) */ @Override public int read(byte[] buffer) throws IOException { // TODO Auto-generated method stub log("data read buffer"); return super.read(buffer); } /* (non-Javadoc) * @see java.io.InputStream#reset() */ @Override public synchronized void reset() throws IOException { // TODO Auto-generated method stub log("data reset"); super.reset(); } /* (non-Javadoc) * @see java.io.InputStream#skip(long) */ @Override public long skip(long byteCount) throws IOException { // TODO Auto-generated method stub log("data skip"); return super.skip(byteCount); } /* (non-Javadoc) * @see java.io.InputStream#close() */ @Override public void close() throws IOException { super.close(); closed = true; } }; tmpOut = new OutputStream() { private byte[] buffer = new byte[128]; private byte[] usbBuffer = new byte[64]; private byte[] oneByteBuffer = new byte[1]; private ByteBuffer bufferWrite = ByteBuffer.wrap(buffer); private ByteBuffer bufferRead = (ByteBuffer) ByteBuffer.wrap(buffer).limit(0); private boolean closed = false; @Override public void write(int oneByte) throws IOException { //if (BuildConfig.DEBUG || debug) // Log.d(LOG_TAG, "trying to write data (one byte): " + oneByte + " char: " + (char) oneByte); oneByteBuffer[0] = (byte) oneByte; this.write(oneByteBuffer, 0, 1); //if (BuildConfig.DEBUG || debug) // Log.d(LOG_TAG, "writen data (one byte): " + oneByte + " char: " + (char) oneByte); } /* (non-Javadoc) * @see java.io.OutputStream#write(byte[], int, int) */ @Override public void write(byte[] buffer, int offset, int count) throws IOException { //if (BuildConfig.DEBUG || debug) // Log.d(LOG_TAG, "trying to write data : " + Arrays.toString(buffer) + " offset " + offset + " count: " + count); bufferWrite.clear(); bufferWrite.put(buffer, offset, count); //if (BuildConfig.DEBUG || debug) // Log.d(LOG_TAG, "trying to write data : " + Arrays.toString(this.buffer)); int n = 0; if (!closed) { n = connection.bulkTransfer(endpointOut, this.buffer, count, TIMEOUT); } else { if (BuildConfig.DEBUG || debug) Log.e(LOG_TAG, "error while trying to write data: outputStream closed"); } if (n != count) { if (BuildConfig.DEBUG || debug) { Log.e(LOG_TAG, "error while trying to write data: " + Arrays.toString(this.buffer)); Log.e(LOG_TAG, "error while trying to write data: " + n + " bytes written when expecting " + count); } throw new IOException("error while trying to write data: " + Arrays.toString(this.buffer)); } //if (BuildConfig.DEBUG || debug) // Log.d(LOG_TAG, "writen data (one byte): " + Arrays.toString(this.buffer)); } /* (non-Javadoc) * @see java.io.OutputStream#close() */ @Override public void close() throws IOException { // TODO Auto-generated method stub super.close(); closed = true; } /* (non-Javadoc) * @see java.io.OutputStream#flush() */ @Override public void flush() throws IOException { // TODO Auto-generated method stub super.flush(); } /* (non-Javadoc) * @see java.io.OutputStream#write(byte[]) */ @Override public void write(byte[] buffer) throws IOException { // TODO Auto-generated method stub super.write(buffer); } }; try { if (tmpOut != null) { tmpOut2 = new PrintStream(tmpOut, false, "US-ASCII"); } } catch (UnsupportedEncodingException e) { if (BuildConfig.DEBUG || debug) Log.e(LOG_TAG, "error while getting usb output streams", e); } in = tmpIn; out = tmpOut; out2 = tmpOut2; // We couldn't find an endpoint if (endpointIn == null || endpointOut == null) { if (BuildConfig.DEBUG || debug) Log.e(LOG_TAG, "We couldn't find an endpoint for the device, notifying"); disable(R.string.msg_gps_provider_cant_connect); close(); return; } final int[] speedList = { Integer.valueOf(deviceSpeed), 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 }; // final List<String> speedList = Arrays.asList(new String[]{"1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200"}); final byte[] data = { (byte) 0xC0, 0x12, 0x00, 0x00, 0x00, 0x00, 0x08 }; final ByteBuffer connectionSpeedBuffer = ByteBuffer.wrap(data, 0, 4) .order(java.nio.ByteOrder.LITTLE_ENDIAN); final byte[] sirfBin2Nmea = SirfUtils .genSirfCommandFromPayload(callingService.getString(R.string.sirf_bin_to_nmea)); final byte[] datax = new byte[7]; final ByteBuffer connectionSpeedInfoBuffer = ByteBuffer.wrap(datax, 0, 7) .order(java.nio.ByteOrder.LITTLE_ENDIAN); final int res1 = connection.controlTransfer(0x21, 34, 0, 0, null, 0, TIMEOUT); if (sirfGps) { debugLog("trying to switch from SiRF binaray to NMEA"); try { connection.bulkTransfer(endpointOut, sirfBin2Nmea, sirfBin2Nmea.length, TIMEOUT); } catch (NullPointerException e) { if (BuildConfig.DEBUG || debug) Log.e(LOG_TAG, "Connection error"); close(); return; } } if (setDeviceSpeed) { debugLog("Setting connection speed to: " + deviceSpeed); try { connectionSpeedBuffer.putInt(0, Integer.valueOf(deviceSpeed)); // Put the value in connection.controlTransfer(0x21, 32, 0, 0, data, 7, TIMEOUT); // Set baudrate } catch (NullPointerException e) { if (BuildConfig.DEBUG || debug) Log.e(LOG_TAG, "Could not set speed"); close(); } /* connection.controlTransfer(0x40, 0, 0, 0, null, 0, 0); //reset connection.controlTransfer(0x40, 0, 1, 0, null, 0, 0); //clear Rx connection.controlTransfer(0x40, 0, 2, 0, null, 0, 0); //clear Tx connection.controlTransfer(0x40, 0x02, 0x0000, 0, null, 0, 0); //flow control none connection.controlTransfer(0x40, 0x03, Integer.valueOf(deviceSpeed), 0, null, 0, 0); //baudrate 9600 connection.controlTransfer(0x40, 0x04, 0x0008, 0, null, 0, 0); //data bit 8, parity none, stop bit 1, tx off */ } else { Thread autoConf = new Thread() { /* (non-Javadoc) * @see java.lang.Thread#run() */ @Override public void run() { // final byte[] data = { (byte) 0xC0, 0x12, 0x00, 0x00, 0x00, 0x00, 0x08 }; // final ByteBuffer connectionSpeedBuffer = ByteBuffer.wrap(data, 0, 4).order(java.nio.ByteOrder.LITTLE_ENDIAN); // final byte[] sirfBin2Nmea = SirfUtils.genSirfCommandFromPayload(callingService.getString(R.string.sirf_bin_to_nmea)); // final byte[] datax = new byte[7]; // final ByteBuffer connectionSpeedInfoBuffer = ByteBuffer.wrap(datax,0,7).order(java.nio.ByteOrder.LITTLE_ENDIAN); try { // Get the current data rate from the device and transfer it into datax int res0 = connection.controlTransfer(0xA1, 33, 0, 0, datax, 7, TIMEOUT); // Datax is used in a byte buffer which this now turns into an integer // and sets how preference speed to that speed USBGpsManager.this.deviceSpeed = Integer.toString(connectionSpeedInfoBuffer.getInt(0)); // logs the bytes we got debugLog("info connection: " + Arrays.toString(datax)); debugLog("info connection speed: " + USBGpsManager.this.deviceSpeed); Thread.sleep(4000); debugLog("trying to use speed in range: " + Arrays.toString(speedList)); for (int speed : speedList) { if (!ready && !closed) { // set a new datarate USBGpsManager.this.deviceSpeed = Integer.toString(speed); debugLog("trying to use speed " + speed); debugLog("initializing connection: " + speed + " baud and 8N1 (0 bits no parity 1 stop bit"); // Put that data rate into a new data byte array connectionSpeedBuffer.putInt(0, speed); // And set the device to that data rate int res2 = connection.controlTransfer(0x21, 32, 0, 0, data, 7, TIMEOUT); if (sirfGps) { debugLog("trying to switch from SiRF binaray to NMEA"); connection.bulkTransfer(endpointOut, sirfBin2Nmea, sirfBin2Nmea.length, TIMEOUT); } debugLog("data init " + res1 + " " + res2); Thread.sleep(4000); } } // And get the current data rate again res0 = connection.controlTransfer(0xA1, 33, 0, 0, datax, 7, TIMEOUT); debugLog("info connection: " + Arrays.toString(datax)); debugLog("info connection speed: " + connectionSpeedInfoBuffer.getInt(0)); if (!closed) { Thread.sleep(10000); } } catch (InterruptedException e) { if (BuildConfig.DEBUG || debug) Log.e(LOG_TAG, "autoconf thread interrupted", e); } finally { if ((!closed) && (!ready) || (lastRead + 4000 < SystemClock.uptimeMillis())) { setMockLocationProviderOutOfService(); if (BuildConfig.DEBUG || debug) Log.e(LOG_TAG, "Something went wrong in auto config"); // cleanly closing everything... ConnectedGps.this.close(); USBGpsManager.this.disableIfNeeded(); } } } }; debugLog("trying to find speed"); ready = false; autoConf.start(); } } public boolean isReady() { return ready; } private long lastRead = 0; public void run() { try { BufferedReader reader = new BufferedReader(new InputStreamReader(in, "US-ASCII"), 128); // Sentence to read from the device String s; long now = SystemClock.uptimeMillis(); // we will wait more at the beginning of the connection // but if we don't get a signal after 45 seconds we can assume the device // is not usable lastRead = now + 45000; while ((enabled) && (now < lastRead + 4000) && (!closed)) { try { s = reader.readLine(); } catch (IOException e) { s = null; } if (s != null) { //Log.v(LOG_TAG, "data: "+System.currentTimeMillis()+" "+s); if (notifyNmeaSentence(s + "\r\n")) { ready = true; lastRead = SystemClock.uptimeMillis(); if (problemNotified) { problemNotified = false; // reset eventual disabling cause setDisableReason(0); // connection is good so resetting the number of connection try debugLog("connection is good so resetting the number of connection retries"); nbRetriesRemaining = maxConnectionRetries; notificationManager.cancel(R.string.connection_problem_notification_title); } } } else { log("data: not ready " + System.currentTimeMillis()); SystemClock.sleep(100); } // SystemClock.sleep(10); now = SystemClock.uptimeMillis(); } if (now > lastRead + 4000) { if (BuildConfig.DEBUG || debug) Log.e(LOG_TAG, "Read timeout in read thread"); } else if (closed) { debugLog("Device connection closing, stopping read thread"); } else { debugLog("Provider disabled, stopping read thread"); } } catch (Exception e) { if (BuildConfig.DEBUG || debug) Log.e(LOG_TAG, "error while getting data", e); setMockLocationProviderOutOfService(); } finally { // cleanly closing everything... debugLog("Closing read thread"); this.close(); disableIfNeeded(); } } /** * Write to the connected OutStream. * * @param buffer The bytes to write */ public void write(byte[] buffer) { try { do { Thread.sleep(100); } while ((enabled) && (!ready) && (!closed)); if ((enabled) && (ready) && (!closed)) { out.write(buffer); out.flush(); } } catch (IOException | InterruptedException e) { if (BuildConfig.DEBUG || debug) Log.e(LOG_TAG, "Exception during write", e); } } /** * Write to the connected OutStream. * * @param buffer The data to write */ public void write(String buffer) { try { do { Thread.sleep(100); } while ((enabled) && (!ready) && (!closed)); if ((enabled) && (ready) && (!closed)) { out2.print(buffer); out2.flush(); } } catch (InterruptedException e) { if (BuildConfig.DEBUG || debug) Log.e(LOG_TAG, "Exception during write", e); } } public void close() { ready = false; closed = true; try { debugLog("closing USB GPS output stream"); in.close(); } catch (IOException e) { if (BuildConfig.DEBUG || debug) Log.e(LOG_TAG, "error while closing GPS NMEA output stream", e); } finally { try { debugLog("closing USB GPS input streams"); out2.close(); out.close(); } catch (IOException e) { if (BuildConfig.DEBUG || debug) Log.e(LOG_TAG, "error while closing GPS input streams", e); } finally { debugLog("releasing usb interface for connection: " + connection); boolean released = false; if (intf != null) { released = connection.releaseInterface(intf); } if (released) { debugLog("usb interface released for connection: " + connection); } else if (intf != null) { debugLog("unable to release usb interface for connection: " + connection); } else { debugLog("no interface to release"); } debugLog("closing usb connection: " + connection); connection.close(); } } } } private boolean timeSetAlready; private boolean shouldSetTime; private Service callingService; private UsbDevice gpsDev; private NmeaParser parser; private boolean enabled = false; private ExecutorService notificationPool; private ScheduledExecutorService connectionAndReadingPool; private final List<NmeaListener> nmeaListeners = Collections.synchronizedList(new LinkedList<NmeaListener>()); private LocationManager locationManager; private SharedPreferences sharedPreferences; private ConnectedGps connectedGps; private int disableReason = 0; private NotificationCompat.Builder connectionProblemNotificationBuilder; private NotificationCompat.Builder serviceStoppedNotificationBuilder; private Context appContext; private NotificationManager notificationManager; private int maxConnectionRetries; private int nbRetriesRemaining; private boolean problemNotified = false; private boolean connected = false; private boolean setDeviceSpeed = false; private boolean sirfGps = false; private String deviceSpeed = "auto"; private String defaultDeviceSpeed = "4800"; private int gpsProductId = 8963; private int gpsVendorId = 1659; /** * @param callingService * @param vendorId * @param productId * @param maxRetries */ public USBGpsManager(Service callingService, int vendorId, int productId, int maxRetries) { this.gpsVendorId = vendorId; this.gpsProductId = productId; this.callingService = callingService; this.maxConnectionRetries = maxRetries + 1; this.nbRetriesRemaining = maxConnectionRetries; this.appContext = callingService.getApplicationContext(); this.parser = new NmeaParser(10f, this.appContext); locationManager = (LocationManager) callingService.getSystemService(Context.LOCATION_SERVICE); sharedPreferences = PreferenceManager.getDefaultSharedPreferences(callingService); deviceSpeed = sharedPreferences.getString(USBGpsProviderService.PREF_GPS_DEVICE_SPEED, callingService.getString(R.string.defaultGpsDeviceSpeed)); shouldSetTime = sharedPreferences.getBoolean(USBGpsProviderService.PREF_SET_TIME, false); timeSetAlready = true; defaultDeviceSpeed = callingService.getString(R.string.defaultGpsDeviceSpeed); setDeviceSpeed = !deviceSpeed.equals(callingService.getString(R.string.autoGpsDeviceSpeed)); sirfGps = sharedPreferences.getBoolean(USBGpsProviderService.PREF_SIRF_GPS, false); notificationManager = (NotificationManager) callingService.getSystemService(Context.NOTIFICATION_SERVICE); parser.setLocationManager(locationManager); Intent stopIntent = new Intent(USBGpsProviderService.ACTION_STOP_GPS_PROVIDER); PendingIntent stopPendingIntent = PendingIntent.getService(appContext, 0, stopIntent, PendingIntent.FLAG_CANCEL_CURRENT); connectionProblemNotificationBuilder = new NotificationCompat.Builder(appContext) .setContentIntent(stopPendingIntent).setSmallIcon(R.drawable.ic_stat_notify); Intent restartIntent = new Intent(USBGpsProviderService.ACTION_START_GPS_PROVIDER); PendingIntent restartPendingIntent = PendingIntent.getService(appContext, 0, restartIntent, PendingIntent.FLAG_CANCEL_CURRENT); serviceStoppedNotificationBuilder = new NotificationCompat.Builder(appContext) .setContentIntent(restartPendingIntent).setSmallIcon(R.drawable.ic_stat_notify) .setContentTitle( appContext.getString(R.string.service_closed_because_connection_problem_notification_title)) .setContentText( appContext.getString(R.string.service_closed_because_connection_problem_notification)); usbManager = (UsbManager) callingService.getSystemService(Service.USB_SERVICE); } private void setDisableReason(int reasonId) { disableReason = reasonId; } /** * @return */ public int getDisableReason() { return disableReason; } /** * @return true if the bluetooth GPS is enabled */ public synchronized boolean isEnabled() { return enabled; } public boolean isMockLocationEnabled() { // Checks if mock location is enabled in settings boolean isMockLocation; try { //If marshmallow or higher then we need to check that this app is set as the provider if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { AppOpsManager opsManager = (AppOpsManager) appContext.getSystemService(Context.APP_OPS_SERVICE); isMockLocation = opsManager.checkOp(AppOpsManager.OPSTR_MOCK_LOCATION, android.os.Process.myUid(), BuildConfig.APPLICATION_ID) == AppOpsManager.MODE_ALLOWED; } else { // Anything below it then we just need to check the tickbox is checked. isMockLocation = !android.provider.Settings.Secure .getString(appContext.getContentResolver(), "mock_location").equals("0"); } } catch (Exception e) { return false; } return isMockLocation; } /** * Starts the connection for the given usb gps device * @param device GPS device */ private void openConnection(UsbDevice device) { if (!getDeviceFromAttached().equals(device)) { return; } // After 10 seconds we can assume the GPS must have the // correct time and so we are ready to assume the GPS can // set the correct time new Handler(appContext.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { timeSetAlready = false; } }, 10000); connected = true; if (setDeviceSpeed) { log("will set device speed: " + deviceSpeed); } else { log("will use default device speed: " + defaultDeviceSpeed); deviceSpeed = defaultDeviceSpeed; } log("starting usb reading task"); connectedGps = new ConnectedGps(device, deviceSpeed); if (isEnabled()) { connectionAndReadingPool.execute(connectedGps); log("usb reading thread started"); } } private UsbDevice getDeviceFromAttached() { debugLog("Checking all connected devices"); for (UsbDevice connectedDevice : usbManager.getDeviceList().values()) { debugLog("Checking device: " + connectedDevice.getProductId() + " " + connectedDevice.getVendorId()); if (connectedDevice.getVendorId() == gpsVendorId & connectedDevice.getProductId() == gpsProductId) { debugLog("Found correct device"); return connectedDevice; } } return null; } /** * Enables the USB GPS Provider. * * @return */ public synchronized boolean enable() { IntentFilter permissionFilter = new IntentFilter(ACTION_USB_PERMISSION); permissionFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); notificationManager.cancel(R.string.service_closed_because_connection_problem_notification_title); if (!enabled) { log("enabling USB GPS manager"); if (!isMockLocationEnabled()) { if (BuildConfig.DEBUG || debug) Log.e(LOG_TAG, "Mock location provider OFF"); disable(R.string.msg_mock_location_disabled); return this.enabled; } else if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(callingService, Manifest.permission.ACCESS_FINE_LOCATION)) { if (BuildConfig.DEBUG || debug) Log.e(LOG_TAG, "No location permission given"); disable(R.string.msg_no_location_permission); return this.enabled; } else { gpsDev = getDeviceFromAttached(); // This thread will be run by the executor at a delay of 1 second, and will be // run again if the read thread dies. It will run until maximum number of retries // is exceeded Runnable connectThread = new Runnable() { @Override public void run() { try { debugLog("Starting connect thread"); connected = false; gpsDev = getDeviceFromAttached(); if (nbRetriesRemaining > 0) { if (connectedGps != null) { connectedGps.close(); } if (gpsDev != null) { debugLog("GPS device: " + gpsDev.getDeviceName()); PendingIntent permissionIntent = PendingIntent.getBroadcast(callingService, 0, new Intent(ACTION_USB_PERMISSION), 0); UsbDevice device = gpsDev; if (device != null && usbManager.hasPermission(device)) { debugLog("We have permission, good!"); openConnection(device); } else if (device != null) { debugLog("We don't have permission, so requesting..."); usbManager.requestPermission(device, permissionIntent); } else { if (BuildConfig.DEBUG || debug) Log.e(LOG_TAG, "Error while establishing connection: no device - " + gpsVendorId + ": " + gpsProductId); disable(R.string.msg_usb_provider_device_not_connected); } } else { if (BuildConfig.DEBUG || debug) Log.e(LOG_TAG, "Device not connected"); } } } catch (Exception e) { e.printStackTrace(); } finally { nbRetriesRemaining--; if (!connected) { disableIfNeeded(); } } } }; if (gpsDev != null) { this.enabled = true; callingService.registerReceiver(permissionAndDetachReceiver, permissionFilter); debugLog("USB GPS manager enabled"); notificationPool = Executors.newSingleThreadExecutor(); debugLog("starting connection and reading thread"); connectionAndReadingPool = Executors.newSingleThreadScheduledExecutor(); debugLog("starting connection to socket task"); connectionAndReadingPool.scheduleWithFixedDelay(connectThread, 1000, 1000, TimeUnit.MILLISECONDS); if (sirfGps) { enableSirfConfig(sharedPreferences); } } } if (!this.enabled) { if (BuildConfig.DEBUG || debug) Log.e(LOG_TAG, "Error while establishing connection: no device"); disable(R.string.msg_usb_provider_device_not_connected); } } return this.enabled; } /** * Disables the USB GPS Provider if the maximal number of connection retries is exceeded. * This is used when there are possibly non fatal connection problems. * In these cases the provider will try to reconnect with the usb device * and only after a given retries number will give up and shutdown the service. */ private synchronized void disableIfNeeded() { if (enabled) { problemNotified = true; if (nbRetriesRemaining > 0) { // Unable to connect if (BuildConfig.DEBUG || debug) Log.e(LOG_TAG, "Connection ended"); String pbMessage = appContext.getResources().getQuantityString( R.plurals.connection_problem_notification, nbRetriesRemaining, nbRetriesRemaining); Notification connectionProblemNotification = connectionProblemNotificationBuilder .setWhen(System.currentTimeMillis()) .setContentTitle(appContext.getString(R.string.connection_problem_notification_title)) .setContentText(pbMessage).setNumber(1 + maxConnectionRetries - nbRetriesRemaining).build(); notificationManager.notify(R.string.connection_problem_notification_title, connectionProblemNotification); } else { disable(R.string.msg_two_many_connection_problems); } } } /** * Disables the USB GPS provider. * <p> * It will: * <ul> * <li>close the connection with the bluetooth device</li> * <li>disable the Mock Location Provider used for the Usb GPS</li> * <li>stop the UsbGPS4Droid service</li> * </ul> * The reasonId parameter indicates the reason to close the bluetooth provider. * If its value is zero, it's a normal shutdown (normally, initiated by the user). * If it's non-zero this value should correspond a valid localized string id (res/values..../...) * which will be used to display a notification. * * @param reasonId the reason to close the bluetooth provider. */ public synchronized void disable(int reasonId) { debugLog("disabling USB GPS manager reason: " + callingService.getString(reasonId)); setDisableReason(reasonId); disable(); } /** * Disables the Usb GPS provider. * <p> * It will: * <ul> * <li>close the connection with the bluetooth device</li> * <li>disable the Mock Location Provider used for the bluetooth GPS</li> * <li>stop the BlueGPS4Droid service</li> * </ul> * If the bluetooth provider is closed because of a problem, a notification is displayed. */ public synchronized void disable() { notificationManager.cancel(R.string.connection_problem_notification_title); if (getDisableReason() != 0) { NotificationCompat.Builder partialServiceStoppedNotification = serviceStoppedNotificationBuilder .setWhen(System.currentTimeMillis()).setAutoCancel(true) .setContentTitle(appContext .getString(R.string.service_closed_because_connection_problem_notification_title)) .setContentText( appContext.getString(R.string.service_closed_because_connection_problem_notification, appContext.getString(getDisableReason()))); // Make the correct notification to direct the user to the correct setting if (getDisableReason() == R.string.msg_mock_location_disabled) { PendingIntent mockLocationsSettingsIntent = PendingIntent.getActivity(appContext, 0, new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS), PendingIntent.FLAG_CANCEL_CURRENT); partialServiceStoppedNotification.setContentIntent(mockLocationsSettingsIntent) .setStyle(new NotificationCompat.BigTextStyle().bigText(appContext.getString( R.string.service_closed_because_connection_problem_notification, appContext.getString(R.string.msg_mock_location_disabled_full)))); } else if (getDisableReason() == R.string.msg_no_location_permission) { PendingIntent mockLocationsSettingsIntent = PendingIntent.getActivity(appContext, 0, new Intent(callingService, GpsInfoActivity.class), PendingIntent.FLAG_CANCEL_CURRENT); USBGpsApplication.setLocationNotAsked(); partialServiceStoppedNotification.setContentIntent(mockLocationsSettingsIntent) .setStyle(new NotificationCompat.BigTextStyle().bigText(appContext.getString( R.string.service_closed_because_connection_problem_notification, appContext.getString(R.string.msg_no_location_permission)))); } Notification serviceStoppedNotification = partialServiceStoppedNotification.build(); notificationManager.notify(R.string.service_closed_because_connection_problem_notification_title, serviceStoppedNotification); sharedPreferences.edit() .putInt(appContext.getString(R.string.pref_disable_reason_key), getDisableReason()).apply(); } if (enabled) { debugLog("disabling USB GPS manager"); callingService.unregisterReceiver(permissionAndDetachReceiver); enabled = false; connectionAndReadingPool.shutdown(); Runnable closeAndShutdown = new Runnable() { @Override public void run() { try { connectionAndReadingPool.awaitTermination(10, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } if (!connectionAndReadingPool.isTerminated()) { connectionAndReadingPool.shutdownNow(); if (connectedGps != null) { connectedGps.close(); } } } }; notificationPool.execute(closeAndShutdown); nmeaListeners.clear(); disableMockLocationProvider(); notificationPool.shutdown(); callingService.stopSelf(); SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putBoolean(USBGpsProviderService.PREF_START_GPS_PROVIDER, false); editor.apply(); debugLog("USB GPS manager disabled"); } } /** * Enables the Mock GPS Location Provider used for the bluetooth GPS. * In fact, it delegates to the NMEA parser. * * @param gpsName the name of the Location Provider to use for the bluetooth GPS * @param force true if we want to force auto-activation of the mock location provider (and bypass user preference). */ public void enableMockLocationProvider(String gpsName, boolean force) { if (parser != null) { debugLog("enabling mock locations provider: " + gpsName); parser.enableMockLocationProvider(gpsName, force); } } /** * Enables the Mock GPS Location Provider used for the bluetooth GPS. * In fact, it delegates to the NMEA parser. * * @param gpsName the name of the Location Provider to use for the bluetooth GPS */ public void enableMockLocationProvider(String gpsName) { if (parser != null) { debugLog("enabling mock locations provider: " + gpsName); boolean force = sharedPreferences.getBoolean(USBGpsProviderService.PREF_FORCE_ENABLE_PROVIDER, true); parser.enableMockLocationProvider(gpsName, force); } } /** * Disables the current Mock GPS Location Provider used for the bluetooth GPS. * In fact, it delegates to the NMEA parser. * * @see NmeaParser#disableMockLocationProvider() */ public void disableMockLocationProvider() { if (parser != null) { debugLog("disabling mock locations provider"); parser.disableMockLocationProvider(); } } /** * Getter use to know if the Mock GPS Listener used for the bluetooth GPS is enabled or not. * In fact, it delegates to the NMEA parser. * * @return true if the Mock GPS Listener used for the bluetooth GPS is enabled. * @see NmeaParser#isMockGpsEnabled() */ public boolean isMockGpsEnabled() { boolean mockGpsEnabled = false; if (parser != null) { mockGpsEnabled = parser.isMockGpsEnabled(); } return mockGpsEnabled; } /** * Getter for the name of the current Mock Location Provider in use. * In fact, it delegates to the NMEA parser. * * @return the Mock Location Provider name used for the bluetooth GPS * @see NmeaParser#getMockLocationProvider() */ public String getMockLocationProvider() { String mockLocationProvider = null; if (parser != null) { mockLocationProvider = parser.getMockLocationProvider(); } return mockLocationProvider; } /** * Indicates that the bluetooth GPS Provider is out of service. * In fact, it delegates to the NMEA parser. * * @see NmeaParser#setMockLocationProviderOutOfService() */ private void setMockLocationProviderOutOfService() { if (parser != null) { parser.setMockLocationProviderOutOfService(); } } /** * Adds an NMEA listener. * In fact, it delegates to the NMEA parser. * * @param listener a {@link NmeaListener} object to register * @return true if the listener was successfully added */ public boolean addNmeaListener(NmeaListener listener) { if (!nmeaListeners.contains(listener)) { debugLog("adding new NMEA listener"); nmeaListeners.add(listener); } return true; } /** * Removes an NMEA listener. * In fact, it delegates to the NMEA parser. * * @param listener a {@link NmeaListener} object to remove */ public void removeNmeaListener(NmeaListener listener) { debugLog("removing NMEA listener"); nmeaListeners.remove(listener); } /** * Sets the system time to the given UTC time value * @param time UTC value HHmmss.SSS */ @SuppressLint("SimpleDateFormat") private void setSystemTime(String time) { long parseTime = parser.parseNmeaTime(time); Log.v(LOG_TAG, "What?: " + parseTime); String timeFormatToybox = new SimpleDateFormat("MMddhhmmyyyy.ss").format(new Date(parseTime)); String timeFormatToolbox = new SimpleDateFormat("yyyyMMdd.hhmmss").format(new Date(parseTime)); debugLog("Setting system time to: " + timeFormatToybox); SuperuserManager suManager = SuperuserManager.getInstance(); debugLog("toolbox date -s " + timeFormatToolbox + "; toybox date " + timeFormatToybox + "; am broadcast -a android.intent.action.TIME_SET"); if (suManager.hasPermission()) { suManager.asyncExecute("toolbox date -s " + timeFormatToolbox + "; toybox date " + timeFormatToybox + "; am broadcast -a android.intent.action.TIME_SET"); } else { sharedPreferences.edit().putBoolean(USBGpsProviderService.PREF_SET_TIME, false).apply(); } } /** * Notifies the reception of a NMEA sentence from the USB GPS to registered NMEA listeners. * * @param nmeaSentence the complete NMEA sentence received from the USB GPS (i.e. $....*XY where XY is the checksum) * @return true if the input string is a valid NMEA sentence, false otherwise. */ private boolean notifyNmeaSentence(final String nmeaSentence) { boolean res = false; if (enabled) { log("parsing and notifying NMEA sentence: " + nmeaSentence); String sentence = null; try { if (shouldSetTime && !timeSetAlready) { parser.clearLastSentenceTime(); } sentence = parser.parseNmeaSentence(nmeaSentence); if (shouldSetTime && !timeSetAlready) { if (!parser.getLastSentenceTime().isEmpty()) { setSystemTime(parser.getLastSentenceTime()); timeSetAlready = true; } } } catch (SecurityException e) { if (BuildConfig.DEBUG || debug) Log.e(LOG_TAG, "error while parsing NMEA sentence: " + nmeaSentence, e); // a priori Mock Location is disabled sentence = null; disable(R.string.msg_mock_location_disabled); } catch (Exception e) { if (BuildConfig.DEBUG || debug) { Log.e(LOG_TAG, "Sentence not parsable"); Log.e(LOG_TAG, nmeaSentence); } e.printStackTrace(); } final String recognizedSentence = sentence; final long timestamp = System.currentTimeMillis(); if (recognizedSentence != null) { res = true; log("notifying NMEA sentence: " + recognizedSentence); ((USBGpsApplication) appContext).notifyNewSentence(recognizedSentence.replaceAll("(\\r|\\n)", "")); synchronized (nmeaListeners) { for (final NmeaListener listener : nmeaListeners) { notificationPool.execute(new Runnable() { @Override public void run() { listener.onNmeaReceived(timestamp, recognizedSentence); } }); } } } } return res; } /** * Sends a NMEA sentence to the bluetooth GPS. * * @param command the complete NMEA sentence (i.e. $....*XY where XY is the checksum). */ public void sendPackagedNmeaCommand(final String command) { log("sending NMEA sentence: " + command); connectedGps.write(command); log("sent NMEA sentence: " + command); } /** * Sends a SIRF III binary command to the bluetooth GPS. * * @param commandHexa an hexadecimal string representing a complete binary command * (i.e. with the <em>Start Sequence</em>, <em>Payload Length</em>, <em>Payload</em>, <em>Message Checksum</em> and <em>End Sequence</em>). */ public void sendPackagedSirfCommand(final String commandHexa) { final byte[] command = SirfUtils.genSirfCommand(commandHexa); log("sendind SIRF sentence: " + commandHexa); connectedGps.write(command); log("sent SIRF sentence: " + commandHexa); } /** * Sends a NMEA sentence to the bluetooth GPS. * * @param sentence the NMEA sentence without the first "$", the last "*" and the checksum. */ public void sendNmeaCommand(String sentence) { String command = String.format((Locale) null, "$%s*%02X\r\n", sentence, parser.computeChecksum(sentence)); sendPackagedNmeaCommand(command); } /** * Sends a SIRF III binary command to the bluetooth GPS. * * @param payload an hexadecimal string representing the payload of the binary command * (i.e. without <em>Start Sequence</em>, <em>Payload Length</em>, <em>Message Checksum</em> and <em>End Sequence</em>). */ public void sendSirfCommand(String payload) { String command = SirfUtils.createSirfCommandFromPayload(payload); sendPackagedSirfCommand(command); } private void enableNMEA(boolean enable) { // SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(callingService); // String deviceSpeed = sharedPreferences.getString(USBGpsProviderService.PREF_GPS_DEVICE_SPEED, callingService.getString(R.string.defaultGpsDeviceSpeed)); if (deviceSpeed.equals(callingService.getString(R.string.autoGpsDeviceSpeed))) { deviceSpeed = callingService.getString(R.string.defaultGpsDeviceSpeed); } SystemClock.sleep(400); if (enable) { // int gll = (sharedPreferences.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_GLL, false)) ? 1 : 0 ; // int vtg = (sharedPreferences.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_VTG, false)) ? 1 : 0 ; // int gsa = (sharedPreferences.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_GSA, false)) ? 5 : 0 ; // int gsv = (sharedPreferences.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_GSV, false)) ? 5 : 0 ; // int zda = (sharedPreferences.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_ZDA, false)) ? 1 : 0 ; // int mss = 0; // int epe = 0; // int gga = 1; // int rmc = 1; // String command = getString(R.string.sirf_bin_to_nmea_38400_alt, gga, gll, gsa, gsv, rmc, vtg, mss, epe, zda); // String command = getString(R.string.sirf_bin_to_nmea_alt, gga, gll, gsa, gsv, rmc, vtg, mss, epe, zda, Integer.parseInt(deviceSpeed)); String command = callingService.getString(R.string.sirf_bin_to_nmea); this.sendSirfCommand(command); } else { // this.sendNmeaCommand(callingService.getString(R.string.sirf_nmea_to_binary)); this.sendNmeaCommand( callingService.getString(R.string.sirf_nmea_to_binary_alt, Integer.parseInt(deviceSpeed))); } SystemClock.sleep(400); } private void enableNmeaGGA(boolean enable) { if (enable) { this.sendNmeaCommand(callingService.getString(R.string.sirf_nmea_gga_on)); } else { this.sendNmeaCommand(callingService.getString(R.string.sirf_nmea_gga_off)); } } private void enableNmeaGLL(boolean enable) { if (enable) { this.sendNmeaCommand(callingService.getString(R.string.sirf_nmea_gll_on)); } else { this.sendNmeaCommand(callingService.getString(R.string.sirf_nmea_gll_off)); } } private void enableNmeaGSA(boolean enable) { if (enable) { this.sendNmeaCommand(callingService.getString(R.string.sirf_nmea_gsa_on)); } else { this.sendNmeaCommand(callingService.getString(R.string.sirf_nmea_gsa_off)); } } private void enableNmeaGSV(boolean enable) { if (enable) { this.sendNmeaCommand(callingService.getString(R.string.sirf_nmea_gsv_on)); } else { this.sendNmeaCommand(callingService.getString(R.string.sirf_nmea_gsv_off)); } } private void enableNmeaRMC(boolean enable) { if (enable) { this.sendNmeaCommand(callingService.getString(R.string.sirf_nmea_rmc_on)); } else { this.sendNmeaCommand(callingService.getString(R.string.sirf_nmea_rmc_off)); } } private void enableNmeaVTG(boolean enable) { if (enable) { this.sendNmeaCommand(callingService.getString(R.string.sirf_nmea_vtg_on)); } else { this.sendNmeaCommand(callingService.getString(R.string.sirf_nmea_vtg_off)); } } private void enableNmeaZDA(boolean enable) { if (enable) { this.sendNmeaCommand(callingService.getString(R.string.sirf_nmea_zda_on)); } else { this.sendNmeaCommand(callingService.getString(R.string.sirf_nmea_zda_off)); } } private void enableSBAS(boolean enable) { if (enable) { this.sendNmeaCommand(callingService.getString(R.string.sirf_nmea_sbas_on)); } else { this.sendNmeaCommand(callingService.getString(R.string.sirf_nmea_sbas_off)); } } public void enableSirfConfig(final Bundle extra) { debugLog("spooling SiRF config: " + extra); if (isEnabled()) { notificationPool.execute(new Runnable() { @Override public void run() { while ((enabled) && ((!connected) || (connectedGps == null) || (!connectedGps.isReady()))) { debugLog("writing thread is not ready"); SystemClock.sleep(500); } if (isEnabled() && (connected) && (connectedGps != null) && (connectedGps.isReady())) { debugLog("init SiRF config: " + extra); if (extra.containsKey(USBGpsProviderService.PREF_SIRF_ENABLE_GGA)) { enableNmeaGGA(extra.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_GGA, true)); } if (extra.containsKey(USBGpsProviderService.PREF_SIRF_ENABLE_RMC)) { enableNmeaRMC(extra.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_RMC, true)); } if (extra.containsKey(USBGpsProviderService.PREF_SIRF_ENABLE_GLL)) { enableNmeaGLL(extra.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_GLL, false)); } if (extra.containsKey(USBGpsProviderService.PREF_SIRF_ENABLE_VTG)) { enableNmeaVTG(extra.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_VTG, false)); } if (extra.containsKey(USBGpsProviderService.PREF_SIRF_ENABLE_GSA)) { enableNmeaGSA(extra.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_GSA, false)); } if (extra.containsKey(USBGpsProviderService.PREF_SIRF_ENABLE_GSV)) { enableNmeaGSV(extra.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_GSV, false)); } if (extra.containsKey(USBGpsProviderService.PREF_SIRF_ENABLE_ZDA)) { enableNmeaZDA(extra.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_ZDA, false)); } if (extra.containsKey(USBGpsProviderService.PREF_SIRF_ENABLE_STATIC_NAVIGATION)) { enableStaticNavigation(extra .getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_STATIC_NAVIGATION, false)); } else if (extra.containsKey(USBGpsProviderService.PREF_SIRF_ENABLE_NMEA)) { enableNMEA(extra.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_NMEA, true)); } if (extra.containsKey(USBGpsProviderService.PREF_SIRF_ENABLE_SBAS)) { enableSBAS(extra.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_SBAS, true)); } debugLog("initialized SiRF config: " + extra); } } }); } } public void enableSirfConfig(final SharedPreferences extra) { debugLog("spooling SiRF config: " + extra); if (isEnabled()) { notificationPool.execute(new Runnable() { @Override public void run() { while ((enabled) && ((!connected) || (connectedGps == null) || (!connectedGps.isReady()))) { debugLog("writing thread is not ready"); SystemClock.sleep(500); } if (isEnabled() && (connected) && (connectedGps != null) && (connectedGps.isReady())) { debugLog("init SiRF config: " + extra); if (extra.contains(USBGpsProviderService.PREF_SIRF_ENABLE_GLL)) { enableNmeaGLL(extra.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_GLL, false)); } if (extra.contains(USBGpsProviderService.PREF_SIRF_ENABLE_VTG)) { enableNmeaVTG(extra.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_VTG, false)); } if (extra.contains(USBGpsProviderService.PREF_SIRF_ENABLE_GSA)) { enableNmeaGSA(extra.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_GSA, false)); } if (extra.contains(USBGpsProviderService.PREF_SIRF_ENABLE_GSV)) { enableNmeaGSV(extra.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_GSV, false)); } if (extra.contains(USBGpsProviderService.PREF_SIRF_ENABLE_ZDA)) { enableNmeaZDA(extra.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_ZDA, false)); } if (extra.contains(USBGpsProviderService.PREF_SIRF_ENABLE_STATIC_NAVIGATION)) { enableStaticNavigation(extra .getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_STATIC_NAVIGATION, false)); } else if (extra.contains(USBGpsProviderService.PREF_SIRF_ENABLE_NMEA)) { enableNMEA(extra.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_NMEA, true)); } if (extra.contains(USBGpsProviderService.PREF_SIRF_ENABLE_SBAS)) { enableSBAS(extra.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_SBAS, true)); } sendNmeaCommand(callingService.getString(R.string.sirf_nmea_gga_on)); sendNmeaCommand(callingService.getString(R.string.sirf_nmea_rmc_on)); if (extra.contains(USBGpsProviderService.PREF_SIRF_ENABLE_GGA)) { enableNmeaGGA(extra.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_GGA, true)); } if (extra.contains(USBGpsProviderService.PREF_SIRF_ENABLE_RMC)) { enableNmeaRMC(extra.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_RMC, true)); } } } }); } } private void enableStaticNavigation(boolean enable) { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(callingService); boolean isInNmeaMode = sharedPreferences.getBoolean(USBGpsProviderService.PREF_SIRF_ENABLE_NMEA, true); if (isInNmeaMode) { enableNMEA(false); } if (enable) { this.sendSirfCommand(callingService.getString(R.string.sirf_bin_static_nav_on)); } else { this.sendSirfCommand(callingService.getString(R.string.sirf_bin_static_nav_off)); } if (isInNmeaMode) { enableNMEA(true); } } private void log(String message) { if (BuildConfig.DEBUG) Log.d(LOG_TAG, message); } private void debugLog(String message) { if (BuildConfig.DEBUG || debug) Log.d(LOG_TAG, message); } }