/* * Copyright (C) 2013 Sean J. Barbeau * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.gpstest.util; import com.android.gpstest.DilutionOfPrecision; import android.content.Context; import android.content.res.Configuration; import android.hardware.Sensor; import android.hardware.SensorManager; import android.location.GnssMeasurement; import android.location.GnssNavigationMessage; import android.location.GnssStatus; import android.os.Build; import android.support.annotation.RequiresApi; import android.text.TextUtils; import android.util.Log; public class GpsTestUtil { private static final String TAG = "GpsTestUtil"; private static final String NMEA_OUTPUT_TAG = "GpsOutputNmea"; private static final String MEASURE_OUTPUT_TAG = "GpsOutputMeasure"; private static final String NM_OUTPUT_TAG = "GpsOutputNav"; private static StringBuilder mNmeaOutput = new StringBuilder(); /** * Returns the Global Navigation Satellite System (GNSS) for a satellite given the PRN. For * Android 6.0.1 (API Level 23) and lower. Android 7.0 and higher should use * * @param prn PRN value provided by the GpsSatellite.getPrn() method * @return GnssType for the given PRN */ @Deprecated public static GnssType getGnssType(int prn) { if (prn >= 65 && prn <= 96) { // See Issue #26 for details return GnssType.GLONASS; } else if (prn >= 193 && prn <= 200) { // See Issue #54 for details return GnssType.QZSS; } else if (prn >= 201 && prn <= 235) { // See Issue #54 for details return GnssType.BEIDOU; } else if (prn >= 301 && prn <= 330) { // See https://github.com/barbeau/gpstest/issues/58#issuecomment-252235124 for details return GnssType.GALILEO; } else { // Assume US NAVSTAR for now, since we don't have any other info on sat-to-PRN mappings return GnssType.NAVSTAR; } } /** * Returns the Global Navigation Satellite System (GNSS) for a satellite given the GnssStatus * constellation type. For Android 7.0 and higher. This is basically a translation to our * own GnssType enumeration that we use for Android 6.0.1 and lower. * * @param gnssConstellationType constellation type provided by the GnssStatus.getConstellationType() * method * @return GnssType for the given GnssStatus constellation type */ public static GnssType getGnssConstellationType(int gnssConstellationType) { switch (gnssConstellationType) { case GnssStatus.CONSTELLATION_GPS: return GnssType.NAVSTAR; case GnssStatus.CONSTELLATION_GLONASS: return GnssType.GLONASS; case GnssStatus.CONSTELLATION_BEIDOU: return GnssType.BEIDOU; case GnssStatus.CONSTELLATION_QZSS: return GnssType.QZSS; case GnssStatus.CONSTELLATION_GALILEO: return GnssType.GALILEO; default: // For now assume GPS NAVSTAR for any other systems we don't directly support return GnssType.NAVSTAR; } } /** * Returns true if this device supports the Sensor.TYPE_ROTATION_VECTOR sensor, false if it * doesn't * * @return true if this device supports the Sensor.TYPE_ROTATION_VECTOR sensor, false if it * doesn't */ public static boolean isRotationVectorSensorSupported(Context context) { SensorManager sensorManager = (SensorManager) context .getSystemService(Context.SENSOR_SERVICE); return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD && sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR) != null; } /** * Returns true if the app is running on a large screen device, false if it is not * * @return true if the app is running on a large screen device, false if it is not */ public static boolean isLargeScreen(Context context) { return (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE; } /** * Returns true if the device supports the Gnss status listener, false if it does not * * @return true if the device supports the Gnss status listener, false if it does not */ public static boolean isGnssStatusListenerSupported() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N; } /** * Creates a unique key to identify this satellite using a combination of both the svid and * constellation type * * @return a unique key to identify this satellite using a combination of both the svid and * constellation type */ public static String createGnssSatelliteKey(int svid, int constellationType) { return String.valueOf(svid) + " " + String.valueOf(constellationType); } /** * Converts screen dimension units from dp to pixels, based on algorithm defined in * http://developer.android.com/guide/practices/screens_support.html#dips-pels * * @param dp value in dp * @return value in pixels */ public static int dpToPixels(Context context, float dp) { // Get the screen's density scale final float scale = context.getResources().getDisplayMetrics().density; // Convert the dps to pixels, based on density scale return (int) (dp * scale + 0.5f); } /** * Outputs the provided nmea message and timestamp to log * * @param timestamp timestamp to write to the log, or Long.MIN_VALUE to not write a timestamp * to * log */ public static void writeNmeaToLog(String nmea, long timestamp) { mNmeaOutput.setLength(0); if (timestamp != Long.MIN_VALUE) { mNmeaOutput.append(timestamp); mNmeaOutput.append(","); } mNmeaOutput.append(nmea); Log.d(NMEA_OUTPUT_TAG, mNmeaOutput.toString()); } /** * Outputs the provided GNSS navigation message to log */ @RequiresApi(api = Build.VERSION_CODES.N) public static void writeNavMessageToLog(GnssNavigationMessage message) { Log.d(NM_OUTPUT_TAG, message.toString()); } /** * Outputs the provided GNSS measurement to log */ @RequiresApi(api = Build.VERSION_CODES.N) public static void writeGnssMeasurementToLog(GnssMeasurement measurement) { Log.d(MEASURE_OUTPUT_TAG, measurement.toString()); } /** * Given a $GPGGA or $GNGNS NMEA sentence, return the altitude above mean sea level (geoid * altitude), * or null if the altitude can't be parsed. * * Example inputs are: * $GPGGA,032739.0,2804.732835,N,08224.639709,W,1,08,0.8,19.2,M,-24.0,M,,*5B * $GNGNS,015002.0,2804.733672,N,08224.631117,W,AAN,09,1.1,78.9,-24.0,,*23 * * Example outputs would be: * 19.2 * 78.9 * * @param nmeaSentence a $GPGGA or $GNGNS NMEA sentence * @return the altitude above mean sea level (geoid altitude), or null if altitude can't be * parsed */ public static Double getAltitudeMeanSeaLevel(String nmeaSentence) { final int ALTITUDE_INDEX = 9; String[] tokens = nmeaSentence.split(","); if (nmeaSentence.startsWith("$GPGGA") || nmeaSentence.startsWith("$GNGNS")) { String altitude = tokens[ALTITUDE_INDEX]; if (!TextUtils.isEmpty(altitude)) { return Double.parseDouble(altitude); } else { Log.w(TAG, "Couldn't parse geoid altitude from NMEA: " + nmeaSentence); return null; } } else { Log.w(TAG, "Input must be a $GPGGA or $GNGNS NMEA: " + nmeaSentence); return null; } } /** * Given a $GNGSA or $GPGSA NMEA sentence, return the dilution of precision, or null if dilution of * precision can't be parsed. * * Example inputs are: * $GPGSA,A,3,03,14,16,22,23,26,,,,,,,3.6,1.8,3.1*38 * $GNGSA,A,3,03,14,16,22,23,26,,,,,,,3.6,1.8,3.1,1*3B * * Example output is: * PDOP is 3.6, HDOP is 1.8, and VDOP is 3.1 * * @param nmeaSentence a $GNGSA or $GPGSA NMEA sentence * @return the dilution of precision, or null if dilution of precision can't be parsed */ public static DilutionOfPrecision getDop(String nmeaSentence) { final int PDOP_INDEX = 15; final int HDOP_INDEX = 16; final int VDOP_INDEX = 17; String[] tokens = nmeaSentence.split(","); if (nmeaSentence.startsWith("$GNGSA") || nmeaSentence.startsWith("$GPGSA")) { String pdop = tokens[PDOP_INDEX]; String hdop = tokens[HDOP_INDEX]; String vdop = tokens[VDOP_INDEX]; // See https://github.com/barbeau/gpstest/issues/71#issuecomment-263169174 if (vdop.contains("*")) { vdop = vdop.split("\\*")[0]; } if (!TextUtils.isEmpty(pdop) && !TextUtils.isEmpty(hdop) && !TextUtils.isEmpty(vdop)) { DilutionOfPrecision dop = null; try { dop = new DilutionOfPrecision(Double.valueOf(pdop), Double.valueOf(hdop), Double.valueOf(vdop)); } catch (NumberFormatException e) { // See https://github.com/barbeau/gpstest/issues/71#issuecomment-263169174 Log.e(TAG, "Invalid DOP values in NMEA: " + nmeaSentence); } return dop; } else { Log.w(TAG, "Empty DOP values in NMEA: " + nmeaSentence); return null; } } else { Log.w(TAG, "Input must be a $GNGSA NMEA: " + nmeaSentence); return null; } } }