diff --git a/GNSSLogger/.gitignore b/GNSSLogger/.gitignore new file mode 100644 index 0000000..39fb081 --- /dev/null +++ b/GNSSLogger/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/GNSSLogger/GNSSLogger.iml b/GNSSLogger/GNSSLogger.iml new file mode 100644 index 0000000..0cd6d47 --- /dev/null +++ b/GNSSLogger/GNSSLogger.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/GNSSLogger/app/.gitignore b/GNSSLogger/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/GNSSLogger/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/GNSSLogger/app/app.iml b/GNSSLogger/app/app.iml new file mode 100644 index 0000000..af54888 --- /dev/null +++ b/GNSSLogger/app/app.iml @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/GNSSLogger/app/build.gradle b/GNSSLogger/app/build.gradle new file mode 100644 index 0000000..0c14983 --- /dev/null +++ b/GNSSLogger/app/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 24 + buildToolsVersion "23.0.1" + defaultConfig { + applicationId "com.google.android.apps.location.gps.gnsslogger" + minSdkVersion 24 + targetSdkVersion 24 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile 'com.android.support:appcompat-v7:24.2.1' + testCompile 'junit:junit:4.12' + compile 'com.android.support:design:24.2.1' + compile 'com.android.support:support-v13:24.2.1' +} diff --git a/GNSSLogger/app/proguard-rules.pro b/GNSSLogger/app/proguard-rules.pro new file mode 100644 index 0000000..83d4526 --- /dev/null +++ b/GNSSLogger/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /media/build/master/prebuilts/fullsdk/linux/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/GNSSLogger/app/src/main/AndroidManifest.xml b/GNSSLogger/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a6441be --- /dev/null +++ b/GNSSLogger/app/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + diff --git a/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/FileLogger.java b/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/FileLogger.java new file mode 100644 index 0000000..eb4e138 --- /dev/null +++ b/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/FileLogger.java @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * 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.google.android.apps.location.gps.gnsslogger; + +import android.content.Context; +import android.content.Intent; +import android.location.GnssClock; +import android.location.GnssMeasurement; +import android.location.GnssMeasurementsEvent; +import android.location.GnssNavigationMessage; +import android.location.GnssStatus; +import android.location.Location; +import android.location.LocationManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.os.SystemClock; +import android.util.Log; +import android.widget.Toast; +import com.google.android.apps.location.gps.gnsslogger.LoggerFragment.UIFragmentComponent; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileFilter; +import java.io.FileWriter; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +/** + * A GNSS logger to store information to a file. + */ +public class FileLogger implements GnssListener { + + private static final String TAG = "FileLogger"; + private static final String FILE_PREFIX = "pseudoranges"; + private static final String ERROR_WRITING_FILE = "Problem writing to file."; + private static final String COMMENT_START = "# "; + private static final char RECORD_DELIMITER = ','; + private static final String VERSION_TAG = "Version: "; + private static final String FILE_VERSION = "1.4.0.0, Platform: N"; + + private static final int MAX_FILES_STORED = 100; + private static final int MINIMUM_USABLE_FILE_SIZE_BYTES = 1000; + + private final Context mContext; + + private final Object mFileLock = new Object(); + private BufferedWriter mFileWriter; + private File mFile; + + private UIFragmentComponent mUiComponent; + + public synchronized UIFragmentComponent getUiComponent() { + return mUiComponent; + } + + public synchronized void setUiComponent(UIFragmentComponent value) { + mUiComponent = value; + } + + public FileLogger(Context context) { + this.mContext = context; + } + + /** + * Start a new file logging process. + */ + public void startNewLog() { + synchronized (mFileLock) { + File baseDirectory; + String state = Environment.getExternalStorageState(); + if (Environment.MEDIA_MOUNTED.equals(state)) { + baseDirectory = new File(Environment.getExternalStorageDirectory(), FILE_PREFIX); + baseDirectory.mkdirs(); + } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { + logError("Cannot write to external storage."); + return; + } else { + logError("Cannot read external storage."); + return; + } + + SimpleDateFormat formatter = new SimpleDateFormat("yyy_MM_dd_HH_mm_ss"); + Date now = new Date(); + String fileName = String.format("%s_log_%s.txt", FILE_PREFIX, formatter.format(now)); + File currentFile = new File(baseDirectory, fileName); + String currentFilePath = currentFile.getAbsolutePath(); + BufferedWriter currentFileWriter; + try { + currentFileWriter = new BufferedWriter(new FileWriter(currentFile)); + } catch (IOException e) { + logException("Could not open file: " + currentFilePath, e); + return; + } + + // initialize the contents of the file + try { + currentFileWriter.write(COMMENT_START); + currentFileWriter.newLine(); + currentFileWriter.write(COMMENT_START); + currentFileWriter.write("Header Description:"); + currentFileWriter.newLine(); + currentFileWriter.write(COMMENT_START); + currentFileWriter.newLine(); + currentFileWriter.write(COMMENT_START); + currentFileWriter.write(VERSION_TAG); + currentFileWriter.write(FILE_VERSION); + currentFileWriter.newLine(); + currentFileWriter.write(COMMENT_START); + currentFileWriter.newLine(); + currentFileWriter.write(COMMENT_START); + currentFileWriter.write( + "Raw,ElapsedRealtimeMillis,TimeNanos,LeapSecond,TimeUncertaintyNanos,FullBiasNanos," + + "BiasNanos,BiasUncertaintyNanos,DriftNanosPerSecond,DriftUncertaintyNanosPerSecond," + + "HardwareClockDiscontinuityCount, Svid,TimeOffsetNanos,State,ReceivedSvTimeNanos," + + "ReceivedSvTimeUncertaintyNanos,Cn0DbHz,PseudorangeRateMetersPerSecond," + + "PseudorangeRateUncertaintyMetersPerSecond," + + "AccumulatedDeltaRangeState,AccumulatedDeltaRangeMeters," + + "AccumulatedDeltaRangeUncertaintyMeters,CarrierFrequencyHz,CarrierCycles," + + "CarrierPhase,CarrierPhaseUncertainty,MultipathIndicator,SnrInDb," + + "ConstellationType"); + currentFileWriter.newLine(); + currentFileWriter.write(COMMENT_START); + currentFileWriter.newLine(); + currentFileWriter.write(COMMENT_START); + currentFileWriter.write( + "Fix,Provider,Latitude,Longitude,Altitude,Speed,Accuracy,(UTC)TimeInMs"); + currentFileWriter.newLine(); + currentFileWriter.write(COMMENT_START); + currentFileWriter.newLine(); + currentFileWriter.write(COMMENT_START); + currentFileWriter.write("Nav,Svid,Type,Status,MessageId,Sub-messageId,Data(Bytes)"); + currentFileWriter.newLine(); + currentFileWriter.write(COMMENT_START); + currentFileWriter.newLine(); + } catch (IOException e) { + logException("Count not initialize file: " + currentFilePath, e); + return; + } + + if (mFileWriter != null) { + try { + mFileWriter.close(); + } catch (IOException e) { + logException("Unable to close all file streams.", e); + return; + } + } + + mFile = currentFile; + mFileWriter = currentFileWriter; + Toast.makeText(mContext, "File opened: " + currentFilePath, Toast.LENGTH_SHORT).show(); + + // To make sure that files do not fill up the external storage: + // - Remove all empty files + FileFilter filter = new FileToDeleteFilter(mFile); + for (File existingFile : baseDirectory.listFiles(filter)) { + existingFile.delete(); + } + // - Trim the number of files with data + File[] existingFiles = baseDirectory.listFiles(); + int filesToDeleteCount = existingFiles.length - MAX_FILES_STORED; + if (filesToDeleteCount > 0) { + Arrays.sort(existingFiles); + for (int i = 0; i < filesToDeleteCount; ++i) { + existingFiles[i].delete(); + } + } + } + } + + /** + * Send the current log via email or other options selected from a pop menu shown to the user. A + * new log is started when calling this function. + */ + public void send() { + if (mFile == null) { + return; + } + + Intent emailIntent = new Intent(Intent.ACTION_SEND); + emailIntent.setType("*/*"); + emailIntent.putExtra(Intent.EXTRA_SUBJECT, "SensorLog"); + emailIntent.putExtra(Intent.EXTRA_TEXT, ""); + // attach the file + emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(mFile)); + getUiComponent().startActivity(Intent.createChooser(emailIntent, "Send log..")); + if (mFileWriter != null) { + try { + mFileWriter.close(); + mFileWriter = null; + } catch (IOException e) { + logException("Unable to close all file streams.", e); + return; + } + } + } + + @Override + public void onProviderEnabled(String provider) {} + + @Override + public void onProviderDisabled(String provider) {} + + @Override + public void onLocationChanged(Location location) { + if (location.getProvider().equals(LocationManager.GPS_PROVIDER)) { + synchronized (mFileLock) { + if (mFileWriter == null) { + return; + } + String locationStream = + String.format( + Locale.US, + "Fix,%s,%f,%f,%f,%f,%f,%d", + location.getProvider(), + location.getLatitude(), + location.getLongitude(), + location.getAltitude(), + location.getSpeed(), + location.getAccuracy(), + location.getTime()); + try { + mFileWriter.write(locationStream); + mFileWriter.newLine(); + } catch (IOException e) { + logException(ERROR_WRITING_FILE, e); + } + } + } + } + + @Override + public void onLocationStatusChanged(String provider, int status, Bundle extras) {} + + @Override + public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) { + synchronized (mFileLock) { + if (mFileWriter == null) { + return; + } + GnssClock gnssClock = event.getClock(); + for (GnssMeasurement measurement : event.getMeasurements()) { + try { + writeGnssMeasurementToFile(gnssClock, measurement); + } catch (IOException e) { + logException(ERROR_WRITING_FILE, e); + } + } + } + } + + @Override + public void onGnssMeasurementsStatusChanged(int status) {} + + @Override + public void onGnssNavigationMessageReceived(GnssNavigationMessage navigationMessage) { + synchronized (mFileLock) { + if (mFileWriter == null) { + return; + } + StringBuilder builder = new StringBuilder("Nav"); + builder.append(RECORD_DELIMITER); + builder.append(navigationMessage.getSvid()); + builder.append(RECORD_DELIMITER); + builder.append(navigationMessage.getType()); + builder.append(RECORD_DELIMITER); + + int status = navigationMessage.getStatus(); + builder.append(status); + builder.append(RECORD_DELIMITER); + builder.append(navigationMessage.getMessageId()); + builder.append(RECORD_DELIMITER); + builder.append(navigationMessage.getSubmessageId()); + byte[] data = navigationMessage.getData(); + for (byte word : data) { + builder.append(RECORD_DELIMITER); + builder.append(word); + } + try { + mFileWriter.write(builder.toString()); + mFileWriter.newLine(); + } catch (IOException e) { + logException(ERROR_WRITING_FILE, e); + } + } + } + + @Override + public void onGnssNavigationMessageStatusChanged(int status) {} + + @Override + public void onGnssStatusChanged(GnssStatus gnssStatus) {} + + @Override + public void onNmeaReceived(long timestamp, String s) {} + + @Override + public void onListenerRegistration(String listener, boolean result) {} + + private void writeGnssMeasurementToFile(GnssClock clock, GnssMeasurement measurement) + throws IOException { + String clockStream = + String.format( + "Raw,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s", + SystemClock.elapsedRealtime(), + clock.getTimeNanos(), + clock.hasLeapSecond() ? clock.getLeapSecond() : "", + clock.hasTimeUncertaintyNanos() ? clock.getTimeUncertaintyNanos() : "", + clock.getFullBiasNanos(), + clock.hasBiasNanos() ? clock.getBiasNanos() : "", + clock.hasBiasUncertaintyNanos() ? clock.getBiasUncertaintyNanos() : "", + clock.hasDriftNanosPerSecond() ? clock.getDriftNanosPerSecond() : "", + clock.hasDriftUncertaintyNanosPerSecond() + ? clock.getDriftUncertaintyNanosPerSecond() + : "", + clock.getHardwareClockDiscontinuityCount() + ","); + mFileWriter.write(clockStream); + + String measurementStream = + String.format( + "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s", + measurement.getSvid(), + measurement.getTimeOffsetNanos(), + measurement.getState(), + measurement.getReceivedSvTimeNanos(), + measurement.getReceivedSvTimeUncertaintyNanos(), + measurement.getCn0DbHz(), + measurement.getPseudorangeRateMetersPerSecond(), + measurement.getPseudorangeRateUncertaintyMetersPerSecond(), + measurement.getAccumulatedDeltaRangeState(), + measurement.getAccumulatedDeltaRangeMeters(), + measurement.getAccumulatedDeltaRangeUncertaintyMeters(), + measurement.hasCarrierFrequencyHz() ? measurement.getCarrierFrequencyHz() : "", + measurement.hasCarrierCycles() ? measurement.getCarrierCycles() : "", + measurement.hasCarrierPhase() ? measurement.getCarrierPhase() : "", + measurement.hasCarrierPhaseUncertainty() + ? measurement.getCarrierPhaseUncertainty() + : "", + measurement.getMultipathIndicator(), + measurement.hasSnrInDb() ? measurement.getSnrInDb() : "", + measurement.getConstellationType()); + mFileWriter.write(measurementStream); + mFileWriter.newLine(); + } + + private void logException(String errorMessage, Exception e) { + Log.e(GnssContainer.TAG + TAG, errorMessage, e); + Toast.makeText(mContext, errorMessage, Toast.LENGTH_LONG).show(); + } + + private void logError(String errorMessage) { + Log.e(GnssContainer.TAG + TAG, errorMessage); + Toast.makeText(mContext, errorMessage, Toast.LENGTH_LONG).show(); + } + + /** + * Implements a {@link FileFilter} to delete files that are not in the + * {@link FileToDeleteFilter#mRetainedFiles}. + */ + private static class FileToDeleteFilter implements FileFilter { + private final List mRetainedFiles; + + public FileToDeleteFilter(File... retainedFiles) { + this.mRetainedFiles = Arrays.asList(retainedFiles); + } + + /** + * Returns {@code true} to delete the file, and {@code false} to keep the file. + * + *

Files are deleted if they are not in the {@link FileToDeleteFilter#mRetainedFiles} list. + */ + @Override + public boolean accept(File pathname) { + if (pathname == null || !pathname.exists()) { + return false; + } + if (mRetainedFiles.contains(pathname)) { + return false; + } + return pathname.length() < MINIMUM_USABLE_FILE_SIZE_BYTES; + } + } +} diff --git a/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/GnssContainer.java b/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/GnssContainer.java new file mode 100644 index 0000000..df0d53a --- /dev/null +++ b/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/GnssContainer.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * 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.google.android.apps.location.gps.gnsslogger; + +import android.content.Context; +import android.location.GnssMeasurementsEvent; +import android.location.GnssNavigationMessage; +import android.location.GnssStatus; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.location.OnNmeaMessageListener; +import android.os.Bundle; +import android.os.SystemClock; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * A container for GPS related API calls, it binds the {@link LocationManager} with {@link UiLogger} + */ +public class GnssContainer { + + public static final String TAG = "GnssLogger"; + + private static final long LOCATION_RATE_GPS_MS = TimeUnit.SECONDS.toMillis(1L); + private static final long LOCATION_RATE_NETWORK_MS = TimeUnit.SECONDS.toMillis(60L); + + private boolean mLogLocations = true; + private boolean mLogNavigationMessages = true; + private boolean mLogMeasurements = true; + private boolean mLogStatuses = true; + private boolean mLogNmeas = true; + + private final List mLoggers; + + private final LocationManager mLocationManager; + private final LocationListener mLocationListener = + new LocationListener() { + + @Override + public void onProviderEnabled(String provider) { + if (mLogLocations) { + for (GnssListener logger : mLoggers) { + logger.onProviderEnabled(provider); + } + } + } + + @Override + public void onProviderDisabled(String provider) { + if (mLogLocations) { + for (GnssListener logger : mLoggers) { + logger.onProviderDisabled(provider); + } + } + } + + @Override + public void onLocationChanged(Location location) { + if (mLogLocations) { + for (GnssListener logger : mLoggers) { + logger.onLocationChanged(location); + } + } + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + if (mLogLocations) { + for (GnssListener logger : mLoggers) { + logger.onLocationStatusChanged(provider, status, extras); + } + } + } + }; + + private final GnssMeasurementsEvent.Callback gnssMeasurementsEventListener = + new GnssMeasurementsEvent.Callback() { + @Override + public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) { + if (mLogMeasurements) { + for (GnssListener logger : mLoggers) { + logger.onGnssMeasurementsReceived(event); + } + } + } + + @Override + public void onStatusChanged(int status) { + if (mLogMeasurements) { + for (GnssListener logger : mLoggers) { + logger.onGnssMeasurementsStatusChanged(status); + } + } + } + }; + + private final GnssNavigationMessage.Callback gnssNavigationMessageListener = + new GnssNavigationMessage.Callback() { + @Override + public void onGnssNavigationMessageReceived(GnssNavigationMessage event) { + if (mLogNavigationMessages) { + for (GnssListener logger : mLoggers) { + logger.onGnssNavigationMessageReceived(event); + } + } + } + + @Override + public void onStatusChanged(int status) { + if (mLogNavigationMessages) { + for (GnssListener logger : mLoggers) { + logger.onGnssNavigationMessageStatusChanged(status); + } + } + } + }; + + private final GnssStatus.Callback gnssStatusListener = + new GnssStatus.Callback() { + @Override + public void onStarted() {} + + @Override + public void onStopped() {} + + @Override + public void onSatelliteStatusChanged(GnssStatus status) { + for (GnssListener logger : mLoggers) { + logger.onGnssStatusChanged(status); + } + } + }; + + private final OnNmeaMessageListener nmeaListener = + new OnNmeaMessageListener() { + @Override + public void onNmeaMessage(String s, long l) { + if (mLogNmeas) { + for (GnssListener logger : mLoggers) { + logger.onNmeaReceived(l, s); + } + } + } + }; + + public GnssContainer(Context context, GnssListener... loggers) { + this.mLoggers = Arrays.asList(loggers); + mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + } + + public LocationManager getLocationManager() { + return mLocationManager; + } + + public void setLogLocations(boolean value) { + mLogLocations = value; + } + + public boolean canLogLocations() { + return mLogLocations; + } + + public void setLogNavigationMessages(boolean value) { + mLogNavigationMessages = value; + } + + public boolean canLogNavigationMessages() { + return mLogNavigationMessages; + } + + public void setLogMeasurements(boolean value) { + mLogMeasurements = value; + } + + public boolean canLogMeasurements() { + return mLogMeasurements; + } + + public void setLogStatuses(boolean value) { + mLogStatuses = value; + } + + public boolean canLogStatuses() { + return mLogStatuses; + } + + public void setLogNmeas(boolean value) { + mLogNmeas = value; + } + + public boolean canLogNmeas() { + return mLogNmeas; + } + + public void registerLocation() { + boolean isGpsProviderEnabled = mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); + if (isGpsProviderEnabled) { + mLocationManager.requestLocationUpdates( + LocationManager.NETWORK_PROVIDER, + LOCATION_RATE_NETWORK_MS, + 0.0f /* minDistance */, + mLocationListener); + mLocationManager.requestLocationUpdates( + LocationManager.GPS_PROVIDER, + LOCATION_RATE_GPS_MS, + 0.0f /* minDistance */, + mLocationListener); + } + logRegistration("LocationUpdates", isGpsProviderEnabled); + } + + public void unregisterLocation() { + mLocationManager.removeUpdates(mLocationListener); + } + + public void registerMeasurements() { + logRegistration( + "GnssMeasurements", + mLocationManager.registerGnssMeasurementsCallback(gnssMeasurementsEventListener)); + } + + public void unregisterMeasurements() { + mLocationManager.unregisterGnssMeasurementsCallback(gnssMeasurementsEventListener); + } + + public void registerNavigation() { + logRegistration( + "GpsNavigationMessage", + mLocationManager.registerGnssNavigationMessageCallback(gnssNavigationMessageListener)); + } + + public void unregisterNavigation() { + mLocationManager.unregisterGnssNavigationMessageCallback(gnssNavigationMessageListener); + } + + public void registerGnssStatus() { + logRegistration("GnssStatus", mLocationManager.registerGnssStatusCallback(gnssStatusListener)); + } + + public void unregisterGpsStatus() { + mLocationManager.unregisterGnssStatusCallback(gnssStatusListener); + } + + public void registerNmea() { + logRegistration("Nmea", mLocationManager.addNmeaListener(nmeaListener)); + } + + public void unregisterNmea() { + mLocationManager.removeNmeaListener(nmeaListener); + } + + public void registerAll() { + registerLocation(); + registerMeasurements(); + registerNavigation(); + registerGnssStatus(); + registerNmea(); + } + + public void unregisterAll() { + unregisterLocation(); + unregisterMeasurements(); + unregisterNavigation(); + unregisterGpsStatus(); + unregisterNmea(); + } + + private void logRegistration(String listener, boolean result) { + for (GnssListener logger : mLoggers) { + logger.onListenerRegistration(listener, result); + } + } +} diff --git a/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/GnssListener.java b/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/GnssListener.java new file mode 100644 index 0000000..9065c8f --- /dev/null +++ b/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/GnssListener.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * 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.google.android.apps.location.gps.gnsslogger; + +import android.location.GnssMeasurementsEvent; +import android.location.GnssNavigationMessage; +import android.location.GnssStatus; +import android.location.Location; +import android.location.LocationListener; +import android.location.OnNmeaMessageListener; +import android.os.Bundle; + +/** A class representing an interface for logging GPS information. */ +public interface GnssListener { + + /** @see LocationListener#onProviderEnabled(String) */ + void onProviderEnabled(String provider); + /** @see LocationListener#onProviderDisabled(String) */ + void onProviderDisabled(String provider); + /** @see LocationListener#onLocationChanged(Location) */ + void onLocationChanged(Location location); + /** @see LocationListener#onStatusChanged(String, int, Bundle) */ + void onLocationStatusChanged(String provider, int status, Bundle extras); + /** + * @see android.location.GnssMeasurementsEvent.Callback# + * onGnssMeasurementsReceived(GnssMeasurementsEvent) + */ + void onGnssMeasurementsReceived(GnssMeasurementsEvent event); + /** @see GnssMeasurementsEvent.Callback#onStatusChanged(int) */ + void onGnssMeasurementsStatusChanged(int status); + /** + * @see GnssNavigationMessage.Callback# + * onGnssNavigationMessageReceived(GnssNavigationMessage) + */ + void onGnssNavigationMessageReceived(GnssNavigationMessage event); + /** @see GnssNavigationMessage.Callback#onStatusChanged(int) */ + void onGnssNavigationMessageStatusChanged(int status); + /** @see GnssStatus.Callback#onSatelliteStatusChanged(GnssStatus) */ + void onGnssStatusChanged(GnssStatus gnssStatus); + /** Called when the listener is registered to listen to GNSS events */ + void onListenerRegistration(String listener, boolean result); + /** @see OnNmeaMessageListener#onNmeaMessage(String, long) */ + void onNmeaReceived(long l, String s); +} diff --git a/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/HelpDialog.java b/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/HelpDialog.java new file mode 100644 index 0000000..4938a5c --- /dev/null +++ b/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/HelpDialog.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * 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.google.android.apps.location.gps.gnsslogger; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.net.MailTo; +import android.net.Uri; +import android.os.Bundle; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +public class HelpDialog extends Dialog { + + private static Context mContext = null; + + public HelpDialog(Context context) { + super(context); + mContext = context; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + setContentView(R.layout.help); + WebView help = (WebView)findViewById(R.id.helpView); + help.setWebViewClient(new WebViewClient(){ + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + if(url.startsWith("mailto:")){ + MailTo mt = MailTo.parse(url); + Intent emailIntent = new Intent(Intent.ACTION_SEND); + emailIntent.setType("*/*"); + emailIntent.putExtra(Intent.EXTRA_SUBJECT, "GNSSLogger Feedback"); + emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[] { mt.getTo()}); + emailIntent.putExtra(Intent.EXTRA_TEXT, ""); + mContext.startActivity(Intent.createChooser(emailIntent, "Send Feedback...")); + return true; + } + else{ + view.loadUrl(url); + } + return true; + } + }); + + String helpText = readRawTextFile(R.raw.help_contents); + help.loadData(helpText, "text/html; charset=utf-8", "utf-8"); + } + + private String readRawTextFile(int id) { + InputStream inputStream = mContext.getResources().openRawResource(id); + InputStreamReader in = new InputStreamReader(inputStream); + BufferedReader buf = new BufferedReader(in); + String line; + StringBuilder text = new StringBuilder(); + try { + while (( line = buf.readLine()) != null) + text.append(line); + } catch (IOException e) { + return null; + } + return text.toString(); + } +} diff --git a/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/LoggerFragment.java b/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/LoggerFragment.java new file mode 100644 index 0000000..e0445a3 --- /dev/null +++ b/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/LoggerFragment.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * 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.google.android.apps.location.gps.gnsslogger; + +import android.app.Activity; +import android.app.Fragment; +import android.content.Intent; +import android.os.Bundle; +import android.text.Editable; +import android.text.SpannableStringBuilder; +import android.text.style.ForegroundColorSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ScrollView; +import android.widget.TextView; +import android.widget.Toast; + +/** The UI fragment that hosts a logging view. */ +public class LoggerFragment extends Fragment { + + private TextView mLogView; + private ScrollView mScrollView; + private FileLogger mFileLogger; + private UiLogger mUiLogger; + + private final UIFragmentComponent mUiComponent = new UIFragmentComponent(); + + public void setUILogger(UiLogger value) { + mUiLogger = value; + } + + public void setFileLogger(FileLogger value) { + mFileLogger = value; + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View newView = inflater.inflate(R.layout.fragment_log, container, false /* attachToRoot */); + mLogView = (TextView) newView.findViewById(R.id.log_view); + mScrollView = (ScrollView) newView.findViewById(R.id.log_scroll); + + UiLogger currentUiLogger = mUiLogger; + if (currentUiLogger != null) { + currentUiLogger.setUiFragmentComponent(mUiComponent); + } + FileLogger currentFileLogger = mFileLogger; + if (currentFileLogger != null) { + currentFileLogger.setUiComponent(mUiComponent); + } + + Button start = (Button) newView.findViewById(R.id.start_log); + start.setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View view) { + mScrollView.fullScroll(View.FOCUS_UP); + } + }); + + Button end = (Button) newView.findViewById(R.id.end_log); + end.setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View view) { + mScrollView.fullScroll(View.FOCUS_DOWN); + } + }); + + Button clear = (Button) newView.findViewById(R.id.clear_log); + clear.setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View view) { + mLogView.setText(""); + } + }); + + final Button startLog = (Button) newView.findViewById(R.id.start_logs); + final Button sendFile = (Button) newView.findViewById(R.id.send_file); + + startLog.setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View view) { + startLog.setEnabled(false); + sendFile.setEnabled(true); + Toast.makeText(getContext(), "Starting log...", Toast.LENGTH_LONG).show(); + mFileLogger.startNewLog(); + } + }); + + sendFile.setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View view) { + startLog.setEnabled(true); + sendFile.setEnabled(false); + Toast.makeText(getContext(), "Sending file...", Toast.LENGTH_LONG).show(); + mFileLogger.send(); + } + }); + + return newView; + } + + /** + * A facade for UI and Activity related operations that are required for {@link GnssListener}s. + */ + public class UIFragmentComponent { + + private static final int MAX_LENGTH = 12000; + private static final int LOWER_THRESHOLD = (int) (MAX_LENGTH * 0.5); + + public synchronized void logTextFragment(final String tag, final String text, int color) { + final SpannableStringBuilder builder = new SpannableStringBuilder(); + builder.append(tag).append(" | ").append(text).append("\n"); + builder.setSpan( + new ForegroundColorSpan(color), + 0 /* start */, + builder.length(), + SpannableStringBuilder.SPAN_INCLUSIVE_EXCLUSIVE); + + Activity activity = getActivity(); + if (activity == null) { + return; + } + activity.runOnUiThread( + new Runnable() { + @Override + public void run() { + mLogView.append(builder); + Editable editable = mLogView.getEditableText(); + int length = editable.length(); + if (length > MAX_LENGTH) { + editable.delete(0, length - LOWER_THRESHOLD); + } + } + }); + } + + public void startActivity(Intent intent) { + getActivity().startActivity(intent); + } + } +} diff --git a/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/MainActivity.java b/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/MainActivity.java new file mode 100644 index 0000000..737d5a7 --- /dev/null +++ b/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/MainActivity.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * 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.google.android.apps.location.gps.gnsslogger; + +import android.Manifest; +import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentManager; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.support.design.widget.TabLayout; +import android.support.design.widget.TabLayout.TabLayoutOnPageChangeListener; +import android.support.v13.app.FragmentPagerAdapter; +import android.support.v13.app.FragmentStatePagerAdapter; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.support.v4.view.ViewPager; +import android.support.v7.app.AppCompatActivity; + +import java.util.Locale; + +/** The activity for the application. */ +public class MainActivity extends AppCompatActivity { + + private static final int LOCATION_REQUEST_ID = 1; + private static final String[] REQUIRED_PERMISSIONS = { + Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.WRITE_EXTERNAL_STORAGE + }; + private static final int NUMBER_OF_FRAGMENTS = 2; + private static final int FRAGMENT_INDEX_SETTING = 0; + private static final int FRAGMENT_INDEX_LOGGER = 1; + + private GnssContainer mGnssContainer; + private UiLogger mUiLogger; + private FileLogger mFileLogger; + private Fragment[] mFragments; + + @Override + protected void onStart() { + super.onStart(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + requestPermissionAndSetupFragments(this); + } + + /** + * A {@link FragmentPagerAdapter} that returns a fragment corresponding to one of the + * sections/tabs/pages. + */ + public class ViewPagerAdapter extends FragmentStatePagerAdapter { + + public ViewPagerAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public Fragment getItem(int position) { + switch (position) { + case FRAGMENT_INDEX_SETTING: + return mFragments[FRAGMENT_INDEX_SETTING]; + case FRAGMENT_INDEX_LOGGER: + return mFragments[FRAGMENT_INDEX_LOGGER]; + default: + throw new IllegalArgumentException("Invalid section: " + position); + } + } + + @Override + public int getCount() { + // Show total pages. + return 2; + } + + @Override + public CharSequence getPageTitle(int position) { + Locale locale = Locale.getDefault(); + switch (position) { + case 0: + return getString(R.string.title_settings).toUpperCase(locale); + case 1: + return getString(R.string.title_log).toUpperCase(locale); + default: + return super.getPageTitle(position); + } + } + } + + @Override + public void onRequestPermissionsResult( + int requestCode, String permissions[], int[] grantResults) { + if (requestCode == LOCATION_REQUEST_ID) { + // If request is cancelled, the result arrays are empty. + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + setupFragments(); + } + } + } + + private void setupFragments() { + mUiLogger = new UiLogger(); + mFileLogger = new FileLogger(getApplicationContext()); + mGnssContainer = new GnssContainer(getApplicationContext(), mUiLogger, mFileLogger); + mFragments = new Fragment[NUMBER_OF_FRAGMENTS]; + SettingsFragment settingsFragment = new SettingsFragment(); + settingsFragment.setGpsContainer(mGnssContainer); + mFragments[FRAGMENT_INDEX_SETTING] = settingsFragment; + + LoggerFragment loggerFragment = new LoggerFragment(); + loggerFragment.setUILogger(mUiLogger); + loggerFragment.setFileLogger(mFileLogger); + mFragments[FRAGMENT_INDEX_LOGGER] = loggerFragment; + + + // The viewpager that will host the section contents. + ViewPager viewPager = (ViewPager) findViewById(R.id.pager); + viewPager.setOffscreenPageLimit(2); + ViewPagerAdapter adapter = new ViewPagerAdapter(getFragmentManager()); + viewPager.setAdapter(adapter); + + TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout); + tabLayout.setTabsFromPagerAdapter(adapter); + + // Set a listener via setOnTabSelectedListener(OnTabSelectedListener) to be notified when any + // tab's selection state has been changed. + tabLayout.setOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(viewPager)); + + // Use a TabLayout.TabLayoutOnPageChangeListener to forward the scroll and selection changes to + // this layout + viewPager.addOnPageChangeListener(new TabLayoutOnPageChangeListener(tabLayout)); + } + + private boolean hasPermissions(Activity activity) { + if (Build.VERSION.SDK_INT < VERSION_CODES.M) { + // Permissions granted at install time. + return true; + } + for (String p : REQUIRED_PERMISSIONS) { + if (ContextCompat.checkSelfPermission(activity, p) != PackageManager.PERMISSION_GRANTED) { + return false; + } + } + return true; + } + + private void requestPermissionAndSetupFragments(final Activity activity) { + if (hasPermissions(activity)) { + setupFragments(); + } else { + ActivityCompat.requestPermissions(activity, REQUIRED_PERMISSIONS, LOCATION_REQUEST_ID); + } + } +} diff --git a/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/SettingsFragment.java b/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/SettingsFragment.java new file mode 100644 index 0000000..33f002e --- /dev/null +++ b/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/SettingsFragment.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * 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.google.android.apps.location.gps.gnsslogger; + +import android.app.Fragment; +import android.location.LocationManager; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.Switch; +import android.widget.TextView; +import android.widget.Toast; +import com.google.android.apps.location.gps.gnsslogger.GnssContainer; +import java.lang.reflect.InvocationTargetException; +import android.widget.Button; + +/** + * The UI fragment showing a set of configurable settings for the client to request GPS data. + */ +public class SettingsFragment extends Fragment { + + public static final String TAG = ":SettingsFragment"; + private GnssContainer mGpsContainer; + private HelpDialog helpDialog; + + public void setGpsContainer(GnssContainer value) { + mGpsContainer = value; + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_main, container, false /* attachToRoot */); + + final Switch registerLocation = (Switch) view.findViewById(R.id.register_location); + final TextView registerLocationLabel = + (TextView) view.findViewById(R.id.register_location_label); + //set the switch to OFF + registerLocation.setChecked(false); + registerLocationLabel.setText("Switch is OFF"); + registerLocation.setOnCheckedChangeListener( + new OnCheckedChangeListener() { + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + + if (isChecked) { + mGpsContainer.registerLocation(); + registerLocationLabel.setText("Switch is ON"); + } else { + mGpsContainer.unregisterLocation(); + registerLocationLabel.setText("Switch is OFF"); + } + } + }); + + final Switch registerMeasurements = (Switch) view.findViewById(R.id.register_measurements); + final TextView registerMeasurementsLabel = + (TextView) view.findViewById(R.id.register_measurement_label); + //set the switch to OFF + registerMeasurements.setChecked(false); + registerMeasurementsLabel.setText("Switch is OFF"); + registerMeasurements.setOnCheckedChangeListener( + new OnCheckedChangeListener() { + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + + if (isChecked) { + mGpsContainer.registerMeasurements(); + registerMeasurementsLabel.setText("Switch is ON"); + } else { + mGpsContainer.unregisterMeasurements(); + registerMeasurementsLabel.setText("Switch is OFF"); + } + } + }); + + final Switch registerNavigation = (Switch) view.findViewById(R.id.register_navigation); + final TextView registerNavigationLabel = + (TextView) view.findViewById(R.id.register_navigation_label); + //set the switch to OFF + registerNavigation.setChecked(false); + registerNavigationLabel.setText("Switch is OFF"); + registerNavigation.setOnCheckedChangeListener( + new OnCheckedChangeListener() { + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + + if (isChecked) { + mGpsContainer.registerNavigation(); + registerNavigationLabel.setText("Switch is ON"); + } else { + mGpsContainer.unregisterNavigation(); + registerNavigationLabel.setText("Switch is OFF"); + } + } + }); + + final Switch registerGpsStatus = (Switch) view.findViewById(R.id.register_status); + final TextView registerGpsStatusLabel = + (TextView) view.findViewById(R.id.register_status_label); + //set the switch to OFF + registerGpsStatus.setChecked(false); + registerGpsStatusLabel.setText("Switch is OFF"); + registerGpsStatus.setOnCheckedChangeListener( + new OnCheckedChangeListener() { + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + + if (isChecked) { + mGpsContainer.registerGnssStatus(); + registerGpsStatusLabel.setText("Switch is ON"); + } else { + mGpsContainer.unregisterGpsStatus(); + registerGpsStatusLabel.setText("Switch is OFF"); + } + } + }); + + final Switch registerNmea = (Switch) view.findViewById(R.id.register_nmea); + final TextView registerNmeaLabel = (TextView) view.findViewById(R.id.register_nmea_label); + //set the switch to OFF + registerNmea.setChecked(false); + registerNmeaLabel.setText("Switch is OFF"); + registerNmea.setOnCheckedChangeListener( + new OnCheckedChangeListener() { + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + + if (isChecked) { + mGpsContainer.registerNmea(); + registerNmeaLabel.setText("Switch is ON"); + } else { + mGpsContainer.unregisterNmea(); + registerNmeaLabel.setText("Switch is OFF"); + } + } + }); + + Button help = (Button) view.findViewById(R.id.help); + helpDialog = new HelpDialog(getContext()); + helpDialog.setTitle("Help contents"); + helpDialog.create(); + + help.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + helpDialog.show(); + } + }); + + Button exit = (Button) view.findViewById(R.id.exit); + exit.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + getActivity().finishAffinity(); + } + }); + + TextView swInfo = (TextView) view.findViewById(R.id.sw_info); + + java.lang.reflect.Method method; + LocationManager locationManager = mGpsContainer.getLocationManager(); + try { + method = locationManager.getClass().getMethod("getGnssYearOfHardware"); + int hwYear = (int) method.invoke(locationManager); + if (hwYear == 0) { + swInfo.append("HW Year: " + "2015 or older \n"); + } else { + swInfo.append("HW Year: " + hwYear + "\n"); + } + + } catch (NoSuchMethodException e) { + logException("No such method exception: ", e); + return null; + } catch (IllegalAccessException e) { + logException("Illegal Access exception: ", e); + return null; + } catch (InvocationTargetException e) { + logException("Invocation Target Exception: ", e); + return null; + } + + String platfromVersionString = Build.VERSION.RELEASE; + swInfo.append("Platform: " + platfromVersionString + "\n"); + int apiLivelInt = Build.VERSION.SDK_INT; + swInfo.append("Api Level: " + apiLivelInt); + + return view; + } + + private void logException(String errorMessage, Exception e) { + Log.e(GnssContainer.TAG + TAG, errorMessage, e); + Toast.makeText(getContext(), errorMessage, Toast.LENGTH_LONG).show(); + } +} diff --git a/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/UiLogger.java b/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/UiLogger.java new file mode 100644 index 0000000..3fddf42 --- /dev/null +++ b/GNSSLogger/app/src/main/java/com/google/android/apps/location/gps/gnsslogger/UiLogger.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * 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.google.android.apps.location.gps.gnsslogger; + +import android.graphics.Color; +import android.location.GnssMeasurementsEvent; +import android.location.GnssNavigationMessage; +import android.location.GnssStatus; +import android.location.Location; +import android.location.LocationProvider; +import android.os.Bundle; +import android.util.Log; +import com.google.android.apps.location.gps.gnsslogger.LoggerFragment.UIFragmentComponent; +import java.util.concurrent.TimeUnit; + +/** + * A class representing a UI logger for the application. Its responsibility is show information in + * the UI. + */ +public class UiLogger implements GnssListener { + + private static final long EARTH_RADIUS_METERS = 6371000; + private static final int USED_COLOR = Color.rgb(0x4a, 0x5f, 0x70); + + public UiLogger() {} + + private UIFragmentComponent mUiFragmentComponent; + + public synchronized UIFragmentComponent getUiFragmentComponent() { + return mUiFragmentComponent; + } + + public synchronized void setUiFragmentComponent(UIFragmentComponent value) { + mUiFragmentComponent = value; + } + + @Override + public void onProviderEnabled(String provider) { + logLocationEvent("onProviderEnabled: " + provider); + } + + @Override + public void onProviderDisabled(String provider) { + logLocationEvent("onProviderDisabled: " + provider); + } + + @Override + public void onLocationChanged(Location location) { + logLocationEvent("onLocationChanged: " + location); + } + + @Override + public void onLocationStatusChanged(String provider, int status, Bundle extras) { + String message = + String.format( + "onStatusChanged: provider=%s, status=%s, extras=%s", + provider, locationStatusToString(status), extras); + logLocationEvent(message); + } + + @Override + public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) { + logMeasurementEvent("onGnsssMeasurementsReceived: " + event); + } + + @Override + public void onGnssMeasurementsStatusChanged(int status) { + logMeasurementEvent("onStatusChanged: " + gnssMeasurementsStatusToString(status)); + } + + @Override + public void onGnssNavigationMessageReceived(GnssNavigationMessage event) { + logNavigationMessageEvent("onGnssNavigationMessageReceived: " + event); + } + + @Override + public void onGnssNavigationMessageStatusChanged(int status) { + logNavigationMessageEvent("onStatusChanged: " + getGnssNavigationMessageStatus(status)); + } + + @Override + public void onGnssStatusChanged(GnssStatus gnssStatus) { + logStatusEvent("onGnssStatusChanged: " + gnssStatusToString(gnssStatus)); + } + + @Override + public void onNmeaReceived(long timestamp, String s) { + logNmeaEvent(String.format("onNmeaReceived: timestamp=%d, %s", timestamp, s)); + } + + @Override + public void onListenerRegistration(String listener, boolean result) { + logEvent("Registration", String.format("add%sListener: %b", listener, result), USED_COLOR); + } + + private void logMeasurementEvent(String event) { + logEvent("Measurement", event, USED_COLOR); + } + + private void logNavigationMessageEvent(String event) { + logEvent("NavigationMsg", event, USED_COLOR); + } + + private void logStatusEvent(String event) { + logEvent("Status", event, USED_COLOR); + } + + private void logNmeaEvent(String event) { + logEvent("Nmea", event, USED_COLOR); + } + + private void logEvent(String tag, String message, int color) { + String composedTag = GnssContainer.TAG + tag; + Log.d(composedTag, message); + logText(tag, message, color); + } + + private void logText(String tag, String text, int color) { + UIFragmentComponent component = getUiFragmentComponent(); + if (component != null) { + component.logTextFragment(tag, text, color); + } + } + + private String locationStatusToString(int status) { + switch (status) { + case LocationProvider.AVAILABLE: + return "AVAILABLE"; + case LocationProvider.OUT_OF_SERVICE: + return "OUT_OF_SERVICE"; + case LocationProvider.TEMPORARILY_UNAVAILABLE: + return "TEMPORARILY_UNAVAILABLE"; + default: + return ""; + } + } + + private String gnssMeasurementsStatusToString(int status) { + switch (status) { + case GnssMeasurementsEvent.Callback.STATUS_NOT_SUPPORTED: + return "NOT_SUPPORTED"; + case GnssMeasurementsEvent.Callback.STATUS_READY: + return "READY"; + case GnssMeasurementsEvent.Callback.STATUS_LOCATION_DISABLED: + return "GNSS_LOCATION_DISABLED"; + default: + return ""; + } + } + + private String getGnssNavigationMessageStatus(int status) { + switch (status) { + case GnssNavigationMessage.STATUS_UNKNOWN: + return "Status Unknown"; + case GnssNavigationMessage.STATUS_PARITY_PASSED: + return "READY"; + case GnssNavigationMessage.STATUS_PARITY_REBUILT: + return "Status Parity Rebuilt"; + default: + return ""; + } + } + + private String gnssStatusToString(GnssStatus gnssStatus) { + + StringBuilder builder = new StringBuilder("SATELLITE_STATUS | [Satellites:\n"); + for (int i = 0; i < gnssStatus.getSatelliteCount(); i++) { + builder + .append("Constellation = ") + .append(getConstellationName(gnssStatus.getConstellationType(i))) + .append(", "); + builder.append("Svid = ").append(gnssStatus.getSvid(i)).append(", "); + builder.append("Cn0DbHz = ").append(gnssStatus.getCn0DbHz(i)).append(", "); + builder.append("Elevation = ").append(gnssStatus.getElevationDegrees(i)).append(", "); + builder.append("Azimuth = ").append(gnssStatus.getAzimuthDegrees(i)).append(", "); + builder.append("hasEphemeris = ").append(gnssStatus.hasEphemerisData(i)).append(", "); + builder.append("hasAlmanac = ").append(gnssStatus.hasAlmanacData(i)).append(", "); + builder.append("usedInFix = ").append(gnssStatus.usedInFix(i)).append("\n"); + } + builder.append("]"); + return builder.toString(); + } + + private void logLocationEvent(String event) { + logEvent("Location", event, USED_COLOR); + } + + private String getConstellationName(int id) { + switch (id) { + case 1: + return "GPS"; + case 2: + return "SBAS"; + case 3: + return "GLONASS"; + case 4: + return "QZSS"; + case 5: + return "BEIDOU"; + case 6: + return "GALILEO"; + default: + return "UNKNOWN"; + } + } +} diff --git a/GNSSLogger/app/src/main/res/drawable/ic_launcher.png b/GNSSLogger/app/src/main/res/drawable/ic_launcher.png new file mode 100644 index 0000000..a1019b2 Binary files /dev/null and b/GNSSLogger/app/src/main/res/drawable/ic_launcher.png differ diff --git a/GNSSLogger/app/src/main/res/layout/activity_main.xml b/GNSSLogger/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..b95955c --- /dev/null +++ b/GNSSLogger/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/GNSSLogger/app/src/main/res/layout/fragment_log.xml b/GNSSLogger/app/src/main/res/layout/fragment_log.xml new file mode 100644 index 0000000..16b47b4 --- /dev/null +++ b/GNSSLogger/app/src/main/res/layout/fragment_log.xml @@ -0,0 +1,67 @@ + + + + + +