diff --git a/src/androidTest/java/com/google/android/apps/mytracks/BootReceiverTest.java b/src/androidTest/java/com/google/android/apps/mytracks/BootReceiverTest.java index 736593309..5f572443c 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/BootReceiverTest.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/BootReceiverTest.java @@ -38,63 +38,63 @@ import java.util.List; * @author Youtao Liu */ @RunWith(AndroidJUnit4.class) -public class BootReceiverTest { - private static final String SERVICE_NAME = "com.google.android.apps.mytracks.services.TrackRecordingService"; +public class BootReceiverTest { + private static final String SERVICE_NAME = "com.google.android.apps.mytracks.services.TrackRecordingService"; - /** - * Tests the behavior when receive notification which is the phone boot. - */ - @Test - public void testOnReceive_startService() { - // Make sure no TrackRecordingService - Intent stopIntent = new Intent(InstrumentationRegistry.getInstrumentation().getContext(), TrackRecordingService.class); - InstrumentationRegistry.getInstrumentation().getContext().stopService(stopIntent); - Assert.assertFalse(isServiceExisted(InstrumentationRegistry.getInstrumentation().getContext(), SERVICE_NAME)); + /** + * Tests the behavior when receive notification which is the phone boot. + */ + @Test + public void testOnReceive_startService() { + // Make sure no TrackRecordingService + Intent stopIntent = new Intent(InstrumentationRegistry.getInstrumentation().getContext(), TrackRecordingService.class); + InstrumentationRegistry.getInstrumentation().getContext().stopService(stopIntent); + Assert.assertFalse(isServiceExisted(InstrumentationRegistry.getInstrumentation().getContext(), SERVICE_NAME)); - BootReceiver bootReceiver = new BootReceiver(); - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_BOOT_COMPLETED); - bootReceiver.onReceive(InstrumentationRegistry.getInstrumentation().getContext(), intent); - // Check if the service is started - Assert.assertFalse(isServiceExisted(InstrumentationRegistry.getInstrumentation().getContext(), SERVICE_NAME)); - } - - /** - * Tests the behavior when receive notification which is not the phone boot. - */ - @Test - public void testOnReceive_noStartService() { - // Make sure no TrackRecordingService - Intent stopIntent = new Intent(InstrumentationRegistry.getInstrumentation().getContext(), TrackRecordingService.class); - InstrumentationRegistry.getInstrumentation().getContext().stopService(stopIntent); - Assert.assertFalse(isServiceExisted(InstrumentationRegistry.getInstrumentation().getContext(), SERVICE_NAME)); - - BootReceiver bootReceiver = new BootReceiver(); - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_BUG_REPORT); - bootReceiver.onReceive(InstrumentationRegistry.getInstrumentation().getContext(), intent); - - // Check if the service is not started - Assert.assertFalse(isServiceExisted(InstrumentationRegistry.getInstrumentation().getContext(), SERVICE_NAME)); - } - - /** - * Checks if a service is started in a context. - * - * @param context the context for checking a service - * @param serviceName the service name to find if existed - */ - private boolean isServiceExisted(Context context, String serviceName) { - ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - List serviceList = activityManager.getRunningServices(Integer.MAX_VALUE); - - for (int i = 0; i < serviceList.size(); i++) { - RunningServiceInfo serviceInfo = serviceList.get(i); - ComponentName componentName = serviceInfo.service; - if (componentName.getClassName().equals(serviceName)) { - return true; - } + BootReceiver bootReceiver = new BootReceiver(); + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_BOOT_COMPLETED); + bootReceiver.onReceive(InstrumentationRegistry.getInstrumentation().getContext(), intent); + // Check if the service is started + Assert.assertFalse(isServiceExisted(InstrumentationRegistry.getInstrumentation().getContext(), SERVICE_NAME)); + } + + /** + * Tests the behavior when receive notification which is not the phone boot. + */ + @Test + public void testOnReceive_noStartService() { + // Make sure no TrackRecordingService + Intent stopIntent = new Intent(InstrumentationRegistry.getInstrumentation().getContext(), TrackRecordingService.class); + InstrumentationRegistry.getInstrumentation().getContext().stopService(stopIntent); + Assert.assertFalse(isServiceExisted(InstrumentationRegistry.getInstrumentation().getContext(), SERVICE_NAME)); + + BootReceiver bootReceiver = new BootReceiver(); + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_BUG_REPORT); + bootReceiver.onReceive(InstrumentationRegistry.getInstrumentation().getContext(), intent); + + // Check if the service is not started + Assert.assertFalse(isServiceExisted(InstrumentationRegistry.getInstrumentation().getContext(), SERVICE_NAME)); + } + + /** + * Checks if a service is started in a context. + * + * @param context the context for checking a service + * @param serviceName the service name to find if existed + */ + private boolean isServiceExisted(Context context, String serviceName) { + ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + List serviceList = activityManager.getRunningServices(Integer.MAX_VALUE); + + for (int i = 0; i < serviceList.size(); i++) { + RunningServiceInfo serviceInfo = serviceList.get(i); + ComponentName componentName = serviceInfo.service; + if (componentName.getClassName().equals(serviceName)) { + return true; + } + } + return false; } - return false; - } } \ No newline at end of file diff --git a/src/androidTest/java/com/google/android/apps/mytracks/ChartValueSeriesTest.java b/src/androidTest/java/com/google/android/apps/mytracks/ChartValueSeriesTest.java index 0ed84912c..0b878e2c0 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/ChartValueSeriesTest.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/ChartValueSeriesTest.java @@ -1,12 +1,12 @@ /* * Copyright 2010 Google Inc. - * + * * 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 @@ -26,62 +26,62 @@ import org.junit.runner.RunWith; /** * Tests {@link ChartValueSeries}. - * + * * @author Sandor Dornbush */ @RunWith(AndroidJUnit4.class) public class ChartValueSeriesTest { - private ChartValueSeries series; + private ChartValueSeries series; - @Before - protected void setUp() { - series = new ChartValueSeries( - InstrumentationRegistry.getInstrumentation().getContext(), - Integer.MIN_VALUE, - Integer.MAX_VALUE, - new int[] {100, 1000 }, - R.string.description_elevation_metric, - R.string.description_elevation_imperial, - R.color.chart_elevation_fill, - R.color.chart_elevation_border); - } + @Before + protected void setUp() { + series = new ChartValueSeries( + InstrumentationRegistry.getInstrumentation().getContext(), + Integer.MIN_VALUE, + Integer.MAX_VALUE, + new int[]{100, 1000}, + R.string.description_elevation_metric, + R.string.description_elevation_imperial, + R.color.chart_elevation_fill, + R.color.chart_elevation_border); + } - public void testInitialConditions() { - Assert.assertEquals(1, series.getInterval()); - Assert.assertEquals(0, series.getMinMarkerValue()); - Assert.assertEquals(5, series.getMaxMarkerValue()); - Assert.assertTrue(series.isEnabled()); - } + public void testInitialConditions() { + Assert.assertEquals(1, series.getInterval()); + Assert.assertEquals(0, series.getMinMarkerValue()); + Assert.assertEquals(5, series.getMaxMarkerValue()); + Assert.assertTrue(series.isEnabled()); + } - public void testEnabled() { - series.setEnabled(false); - Assert.assertFalse(series.isEnabled()); - } + public void testEnabled() { + series.setEnabled(false); + Assert.assertFalse(series.isEnabled()); + } - public void testSmallUpdates() { - series.update(0); - series.update(10); - series.updateDimension(); - Assert.assertEquals(100, series.getInterval()); - Assert.assertEquals(0, series.getMinMarkerValue()); - Assert.assertEquals(500, series.getMaxMarkerValue()); - } + public void testSmallUpdates() { + series.update(0); + series.update(10); + series.updateDimension(); + Assert.assertEquals(100, series.getInterval()); + Assert.assertEquals(0, series.getMinMarkerValue()); + Assert.assertEquals(500, series.getMaxMarkerValue()); + } - public void testBigUpdates() { - series.update(0); - series.update(901); - series.updateDimension(); - Assert.assertEquals(1000, series.getInterval()); - Assert.assertEquals(0, series.getMinMarkerValue()); - Assert.assertEquals(5000, series.getMaxMarkerValue()); - } + public void testBigUpdates() { + series.update(0); + series.update(901); + series.updateDimension(); + Assert.assertEquals(1000, series.getInterval()); + Assert.assertEquals(0, series.getMinMarkerValue()); + Assert.assertEquals(5000, series.getMaxMarkerValue()); + } - public void testNotZeroBasedUpdates() { - series.update(220); - series.update(250); - series.updateDimension(); - Assert.assertEquals(100, series.getInterval()); - Assert.assertEquals(200, series.getMinMarkerValue()); - Assert.assertEquals(700, series.getMaxMarkerValue()); - } + public void testNotZeroBasedUpdates() { + series.update(220); + series.update(250); + series.updateDimension(); + Assert.assertEquals(100, series.getInterval()); + Assert.assertEquals(200, series.getMinMarkerValue()); + Assert.assertEquals(700, series.getMaxMarkerValue()); + } } diff --git a/src/androidTest/java/com/google/android/apps/mytracks/TrackStubUtils.java b/src/androidTest/java/com/google/android/apps/mytracks/TrackStubUtils.java index 5c9b8ca0a..773f655ba 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/TrackStubUtils.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/TrackStubUtils.java @@ -20,72 +20,71 @@ import android.location.Location; import com.google.android.apps.mytracks.content.SensorDataSetLocation; import com.google.android.apps.mytracks.content.Track; -import com.google.android.apps.mytracks.content.sensor.Sensor.SensorDataSet; import com.google.android.apps.mytracks.content.sensor.SensorDataSet; /** * Commons utilities for creating stubs of track, location. The class will be * enriched if needs more similar stubs for test. - * + * * @author Youtao Liu */ public class TrackStubUtils { - static final String LOCATION_PROVIDER = "gps"; - static final double INITIAL_LATITUDE = 22; - static final double INITIAL_LONGITUDE = 22; - public static final double INITIAL_ALTITUDE = 22; - static final float INITIAL_ACCURACY = 5; - static final float INITIAL_SPEED = 10; - public static final long INITIAL_TIME = 1000L; - static final float INITIAL_BEARING = 3.0f; - // Used to change the value of latitude, longitude, and altitude. - static final double DIFFERENCE = 0.01; + public static final double INITIAL_ALTITUDE = 22; + public static final long INITIAL_TIME = 1000L; + static final String LOCATION_PROVIDER = "gps"; + static final double INITIAL_LATITUDE = 22; + static final double INITIAL_LONGITUDE = 22; + static final float INITIAL_ACCURACY = 5; + static final float INITIAL_SPEED = 10; + static final float INITIAL_BEARING = 3.0f; + // Used to change the value of latitude, longitude, and altitude. + static final double DIFFERENCE = 0.01; - /** - * Gets a a {@link Track} stub with specified number of locations. - * - * @param numberOfLocations the number of locations for the track - * @return a track stub. - */ - public static Track createTrack(int numberOfLocations) { - Track track = new Track(); - for (int i = 0; i < numberOfLocations; i++) { - track.addLocation(createSensorDataSetLocation(INITIAL_LATITUDE + i * DIFFERENCE, INITIAL_LONGITUDE - + i * DIFFERENCE, INITIAL_ALTITUDE + i * DIFFERENCE)); + /** + * Gets a a {@link Track} stub with specified number of locations. + * + * @param numberOfLocations the number of locations for the track + * @return a track stub. + */ + public static Track createTrack(int numberOfLocations) { + Track track = new Track(); + for (int i = 0; i < numberOfLocations; i++) { + track.addLocation(createSensorDataSetLocation(INITIAL_LATITUDE + i * DIFFERENCE, INITIAL_LONGITUDE + + i * DIFFERENCE, INITIAL_ALTITUDE + i * DIFFERENCE)); + } + + return track; } - return track; - } + /** + * Create a MyTracks location with default values. + * + * @return a track stub. + */ + public static SensorDataSetLocation createSensorDataSetLocation() { + return createSensorDataSetLocation(INITIAL_LATITUDE, INITIAL_LONGITUDE, INITIAL_ALTITUDE); + } - /** - * Create a MyTracks location with default values. - * - * @return a track stub. - */ - public static SensorDataSetLocation createSensorDataSetLocation() { - return createSensorDataSetLocation(INITIAL_LATITUDE, INITIAL_LONGITUDE, INITIAL_ALTITUDE); - } + /** + * Creates a {@link SensorDataSetLocation} stub with specified values. + * + * @return a SensorDataSetLocation stub. + */ + public static SensorDataSetLocation createSensorDataSetLocation(double latitude, double longitude, + double altitude) { + // Initial Location + Location loc = new Location(LOCATION_PROVIDER); + loc.setLatitude(latitude); + loc.setLongitude(longitude); + loc.setAltitude(altitude); + loc.setAccuracy(INITIAL_ACCURACY); + loc.setSpeed(INITIAL_SPEED); + loc.setTime(INITIAL_TIME); + loc.setBearing(INITIAL_BEARING); + SensorDataSet sd = new SensorDataSet(100, 100); - /** - * Creates a {@link SensorDataSetLocation} stub with specified values. - * - * @return a SensorDataSetLocation stub. - */ - public static SensorDataSetLocation createSensorDataSetLocation(double latitude, double longitude, - double altitude) { - // Initial Location - Location loc = new Location(LOCATION_PROVIDER); - loc.setLatitude(latitude); - loc.setLongitude(longitude); - loc.setAltitude(altitude); - loc.setAccuracy(INITIAL_ACCURACY); - loc.setSpeed(INITIAL_SPEED); - loc.setTime(INITIAL_TIME); - loc.setBearing(INITIAL_BEARING); - SensorDataSet sd = new SensorDataSet(100, 100); - - return new SensorDataSetLocation(loc, sd); - } + return new SensorDataSetLocation(loc, sd); + } } diff --git a/src/androidTest/java/com/google/android/apps/mytracks/content/CustomContentProviderTest.java b/src/androidTest/java/com/google/android/apps/mytracks/content/CustomContentProviderTest.java index 226c1d3cd..398771d95 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/content/CustomContentProviderTest.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/content/CustomContentProviderTest.java @@ -1,12 +1,12 @@ /* * Copyright 2012 Google Inc. - * + * * 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 @@ -30,114 +30,114 @@ import org.junit.runner.RunWith; /** * Tests {@link CustomContentProvider}. - * + * * @author Youtao Liu */ @RunWith(AndroidJUnit4.class) public class CustomContentProviderTest { - private static final String DATABASE_NAME = "test.db"; + private static final String DATABASE_NAME = "test.db"; - private SQLiteDatabase db; - private CustomContentProvider customContentProvider; + private SQLiteDatabase db; + private CustomContentProvider customContentProvider; - @Before - protected void setUp() { - InstrumentationRegistry.getInstrumentation().getContext().deleteDatabase(DATABASE_NAME); - db = (new DatabaseHelper(InstrumentationRegistry.getInstrumentation().getContext(), DATABASE_NAME)).getWritableDatabase(); - customContentProvider = new CustomContentProvider(); - } - - /** - * Tests {@link CustomContentProvider.DatabaseHelper#onCreate(SQLiteDatabase)}. - */ - public void testDatabaseHelper_OnCreate() { - Assert.assertTrue(hasTable(TracksColumns.TABLE_NAME)); - Assert.assertTrue(hasTable(TrackPointsColumns.TABLE_NAME)); - Assert.assertTrue(hasTable(WaypointsColumns.TABLE_NAME)); - } - - /** - * Tests {@link CustomContentProvider#onCreate(android.content.Context)}. - */ - public void testOnCreate() { - Assert.assertTrue(customContentProvider.onCreate(InstrumentationRegistry.getInstrumentation().getContext())); - } - - /** - * Tests {@link CustomContentProvider#getType(Uri)}. - */ - public void testGetType() { - Assert.assertEquals(TracksColumns.CONTENT_TYPE, customContentProvider.getType(TracksColumns.CONTENT_URI)); - Assert.assertEquals( - TrackPointsColumns.CONTENT_TYPE, customContentProvider.getType(TrackPointsColumns.CONTENT_URI)); - Assert.assertEquals( - WaypointsColumns.CONTENT_TYPE, customContentProvider.getType(WaypointsColumns.CONTENT_URI)); - } - - /** - * Creates a table, containing one column. - * - * @param table the table name - */ - private void createTable(String table) { - db.execSQL("CREATE TABLE " + table + " (test INTEGER)"); - } - - /** - * Drops a table in database. - * - * @param table the table name - */ - private void dropTable(String table) { - db.execSQL("Drop TABLE " + table); - } - - /** - * Returns true if the table exists. - * - * @param table the table name - */ - private boolean hasTable(String table) { - try { - db.rawQuery("select count(*) from " + table, null); - return true; - } catch (Exception e) { - return false; + @Before + protected void setUp() { + InstrumentationRegistry.getInstrumentation().getContext().deleteDatabase(DATABASE_NAME); + db = (new DatabaseHelper(InstrumentationRegistry.getInstrumentation().getContext(), DATABASE_NAME)).getWritableDatabase(); + customContentProvider = new CustomContentProvider(); } - } - /** - * Returns true if the column in the table exists. - * - * @param table the table name - * @param column the column name - */ - private boolean hasColumn(String table, String column) { - try { - db.execSQL("SElECT count(*) from " + table + " order by " + column); - } catch (Exception e) { - if (e.getMessage().indexOf("no such column") > -1) { - return false; - } + /** + * Tests {@link CustomContentProvider.DatabaseHelper#onCreate(SQLiteDatabase)}. + */ + public void testDatabaseHelper_OnCreate() { + Assert.assertTrue(hasTable(TracksColumns.TABLE_NAME)); + Assert.assertTrue(hasTable(TrackPointsColumns.TABLE_NAME)); + Assert.assertTrue(hasTable(WaypointsColumns.TABLE_NAME)); } - return true; - } - /** - * Sets up upgrade. - * - * @param oldVersion thd old database version - */ - private void setupUpgrade(int oldVersion) { - dropTable(TracksColumns.TABLE_NAME); - dropTable(TrackPointsColumns.TABLE_NAME); - dropTable(WaypointsColumns.TABLE_NAME); - createTable(TracksColumns.TABLE_NAME); - createTable(TrackPointsColumns.TABLE_NAME); - createTable(WaypointsColumns.TABLE_NAME); + /** + * Tests {@link CustomContentProvider#onCreate(android.content.Context)}. + */ + public void testOnCreate() { + Assert.assertTrue(customContentProvider.onCreate(InstrumentationRegistry.getInstrumentation().getContext())); + } - DatabaseHelper databaseHelper = new DatabaseHelper(InstrumentationRegistry.getInstrumentation().getContext()); - databaseHelper.onUpgrade(db, oldVersion, CustomContentProvider.DATABASE_VERSION); - } + /** + * Tests {@link CustomContentProvider#getType(Uri)}. + */ + public void testGetType() { + Assert.assertEquals(TracksColumns.CONTENT_TYPE, customContentProvider.getType(TracksColumns.CONTENT_URI)); + Assert.assertEquals( + TrackPointsColumns.CONTENT_TYPE, customContentProvider.getType(TrackPointsColumns.CONTENT_URI)); + Assert.assertEquals( + WaypointsColumns.CONTENT_TYPE, customContentProvider.getType(WaypointsColumns.CONTENT_URI)); + } + + /** + * Creates a table, containing one column. + * + * @param table the table name + */ + private void createTable(String table) { + db.execSQL("CREATE TABLE " + table + " (test INTEGER)"); + } + + /** + * Drops a table in database. + * + * @param table the table name + */ + private void dropTable(String table) { + db.execSQL("Drop TABLE " + table); + } + + /** + * Returns true if the table exists. + * + * @param table the table name + */ + private boolean hasTable(String table) { + try { + db.rawQuery("select count(*) from " + table, null); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * Returns true if the column in the table exists. + * + * @param table the table name + * @param column the column name + */ + private boolean hasColumn(String table, String column) { + try { + db.execSQL("SElECT count(*) from " + table + " order by " + column); + } catch (Exception e) { + if (e.getMessage().indexOf("no such column") > -1) { + return false; + } + } + return true; + } + + /** + * Sets up upgrade. + * + * @param oldVersion thd old database version + */ + private void setupUpgrade(int oldVersion) { + dropTable(TracksColumns.TABLE_NAME); + dropTable(TrackPointsColumns.TABLE_NAME); + dropTable(WaypointsColumns.TABLE_NAME); + createTable(TracksColumns.TABLE_NAME); + createTable(TrackPointsColumns.TABLE_NAME); + createTable(WaypointsColumns.TABLE_NAME); + + DatabaseHelper databaseHelper = new DatabaseHelper(InstrumentationRegistry.getInstrumentation().getContext()); + databaseHelper.onUpgrade(db, oldVersion, CustomContentProvider.DATABASE_VERSION); + } } \ No newline at end of file diff --git a/src/androidTest/java/com/google/android/apps/mytracks/content/CustomContentProviderUtilsImplTest.java b/src/androidTest/java/com/google/android/apps/mytracks/content/CustomContentProviderUtilsImplTest.java index f3463ef3a..6bc893e65 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/content/CustomContentProviderUtilsImplTest.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/content/CustomContentProviderUtilsImplTest.java @@ -1,12 +1,12 @@ /* * Copyright 2010 Google Inc. - * + * * 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 @@ -50,863 +50,863 @@ import java.util.concurrent.atomic.AtomicInteger; */ @RunWith(AndroidJUnit4.class) public class CustomContentProviderUtilsImplTest { - private Context context; - private ContentProviderUtils providerUtils; - - private static final String NAME_PREFIX = "test name"; - private static final String MOCK_DESC = "Mock Next Waypoint Desc!"; - private static final String TEST_DESC = "Test Desc!"; - private static final String TEST_DESC_NEW = "Test Desc new!"; - private double INITIAL_LATITUDE = 37.0; - private double INITIAL_LONGITUDE = -57.0; - private double ALTITUDE_INTERVAL = 2.5; + private static final String NAME_PREFIX = "test name"; + private static final String MOCK_DESC = "Mock Next Waypoint Desc!"; + private static final String TEST_DESC = "Test Desc!"; + private static final String TEST_DESC_NEW = "Test Desc new!"; + private Context context; + private ContentProviderUtils providerUtils; + private double INITIAL_LATITUDE = 37.0; + private double INITIAL_LONGITUDE = -57.0; + private double ALTITUDE_INTERVAL = 2.5; - @Before - protected void setUp() { - MockContentResolver mockContentResolver = new MockContentResolver(); - RenamingDelegatingContext targetContext = new RenamingDelegatingContext( - getContext(), getContext(), "test."); - context = new MockContext(mockContentResolver, targetContext); - CustomContentProvider provider = new CustomContentProvider(); - provider.attachInfo(context, null); - mockContentResolver.addProvider(ContentProviderUtils.AUTHORITY, provider); - setContext(context); + @Before + protected void setUp() { + MockContentResolver mockContentResolver = new MockContentResolver(); + RenamingDelegatingContext targetContext = new RenamingDelegatingContext( + getContext(), getContext(), "test."); + context = new MockContext(mockContentResolver, targetContext); + CustomContentProvider provider = new CustomContentProvider(); + provider.attachInfo(context, null); + mockContentResolver.addProvider(ContentProviderUtils.AUTHORITY, provider); + setContext(context); - providerUtils = ContentProviderUtils.Factory.get(context); - providerUtils.deleteAllTracks(context); - } - - public void testLocationIterator_noPoints() { - testIterator(1, 0, 1, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); - } - - public void testLocationIterator_customFactory() { - final Location location = new Location("test_location"); - final AtomicInteger counter = new AtomicInteger(); - testIterator(1, 15, 4, false, new LocationFactory() { - @Override - public Location createLocation() { - counter.incrementAndGet(); - return location; - } - }); - // Make sure we were called exactly as many times as we had track points. - Assert.assertEquals(15, counter.get()); - } - - public void testLocationIterator_nullFactory() { - try { - testIterator(1, 15, 4, false, null); - fail("Expecting IllegalArgumentException"); - } catch (IllegalArgumentException e) { - // Expected. - } - } - - public void testLocationIterator_noBatchAscending() { - testIterator(1, 50, 100, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); - testIterator(2, 50, 50, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); - } - - public void testLocationIterator_noBatchDescending() { - testIterator(1, 50, 100, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); - testIterator(2, 50, 50, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); - } - - public void testLocationIterator_batchAscending() { - testIterator(1, 50, 11, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); - testIterator(2, 50, 25, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); - } - - public void testLocationIterator_batchDescending() { - testIterator(1, 50, 11, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); - testIterator(2, 50, 25, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); - } - - public void testLocationIterator_largeTrack() { - testIterator(1, 20000, 2000, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); - } - - private List testIterator(long trackId, int numPoints, int batchSize, - boolean descending, LocationFactory locationFactory) { - long lastPointId = initializeTrack(trackId, numPoints); - ((ContentProviderUtilsImpl) providerUtils).setDefaultCursorBatchSize(batchSize); - List locations = new ArrayList(numPoints); - LocationIterator it = providerUtils.getTrackPointLocationIterator( - trackId, -1L, descending, locationFactory); - try { - while (it.hasNext()) { - Location loc = it.next(); - Assert.assertNotNull(loc); - locations.add(loc); - // Make sure the IDs are returned in the right order. - Assert.assertEquals(descending ? lastPointId - locations.size() + 1 - : lastPointId - numPoints + locations.size(), it.getLocationId()); - } - Assert.assertEquals(numPoints, locations.size()); - } finally { - it.close(); - } - return locations; - } - - private long initializeTrack(long id, int numPoints) { - Track track = new Track(); - track.setId(id); - track.setName("Test: " + id); - track.setNumberOfPoints(numPoints); - providerUtils.insertTrack(track); - track = providerUtils.getTrack(id); - Assert.assertNotNull(track); - - Location[] locations = new Location[numPoints]; - for (int i = 0; i < numPoints; ++i) { - Location loc = new Location("test"); - loc.setLatitude(37.0 + (double) i / 10000.0); - loc.setLongitude(57.0 - (double) i / 10000.0); - loc.setAccuracy((float) i / 100.0f); - loc.setAltitude(i * 2.5); - locations[i] = loc; - } - providerUtils.bulkInsertTrackPoint(locations, numPoints, id); - - // Load all inserted locations. - long lastPointId = -1; - int counter = 0; - LocationIterator it = providerUtils.getTrackPointLocationIterator(id, -1L, false, - ContentProviderUtils.DEFAULT_LOCATION_FACTORY); - try { - while (it.hasNext()) { - it.next(); - lastPointId = it.getLocationId(); - counter++; - } - } finally { - it.close(); + providerUtils = ContentProviderUtils.Factory.get(context); + providerUtils.deleteAllTracks(context); } - Assert.assertTrue(numPoints == 0 || lastPointId > 0); - Assert.assertEquals(numPoints, track.getNumberOfPoints()); - Assert.assertEquals(numPoints, counter); - - return lastPointId; - } - - /** - * Tests the method {@link ContentProviderUtilsImpl#createTrack(Cursor)}. - */ - @UsesMocks(Cursor.class) - public void testCreateTrack() { - Cursor cursorMock = AndroidMock.createNiceMock(Cursor.class); - int startColumnIndex = 1; - int columnIndex = startColumnIndex; - AndroidMock.expect(cursorMock.getColumnIndexOrThrow(TracksColumns._ID)) - .andReturn(columnIndex++); - AndroidMock.expect(cursorMock.getColumnIndexOrThrow(TracksColumns.NAME)).andReturn( - columnIndex++); - columnIndex = startColumnIndex; - // Id - AndroidMock.expect(cursorMock.isNull(columnIndex++)).andReturn(false); - // Name - AndroidMock.expect(cursorMock.isNull(columnIndex++)).andReturn(false); - long trackId = System.currentTimeMillis(); - columnIndex = startColumnIndex; - // Id - AndroidMock.expect(cursorMock.getLong(columnIndex++)).andReturn(trackId); - // Name - String name = NAME_PREFIX + trackId; - AndroidMock.expect(cursorMock.getString(columnIndex++)).andReturn(name); - AndroidMock.replay(cursorMock); - Track track = providerUtils.createTrack(cursorMock); - Assert.assertEquals(trackId, track.getId()); - Assert.assertEquals(name, track.getName()); - AndroidMock.verify(cursorMock); - } - - /** - * Tests the method {@link ContentProviderUtilsImpl#deleteAllTracks(Context)} - */ - public void testDeleteAllTracks() { - // Insert track, points and waypoint at first. - long trackId = System.currentTimeMillis(); - Track track = getTrack(trackId, 10); - insertTrackWithLocations(track); - Waypoint waypoint = new Waypoint(); - providerUtils.insertWaypoint(waypoint); - ContentResolver contentResolver = context.getContentResolver(); - Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, - TracksColumns._ID); - Assert.assertEquals(1, tracksCursor.getCount()); - Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, - null, TrackPointsColumns._ID); - Assert.assertEquals(10, tracksPointsCursor.getCount()); - Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, - null, WaypointsColumns._ID); - Assert.assertEquals(1, waypointCursor.getCount()); - // Delete all. - providerUtils.deleteAllTracks(context); - // Check whether all have been deleted. - tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, - TracksColumns._ID); - Assert.assertEquals(0, tracksCursor.getCount()); - tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, - null, TrackPointsColumns._ID); - Assert.assertEquals(0, tracksPointsCursor.getCount()); - waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, - null, WaypointsColumns._ID); - Assert.assertEquals(0, waypointCursor.getCount()); - } - - /** - * Tests the method {@link ContentProviderUtilsImpl#deleteTrack(Context, long)}. - */ - public void testDeleteTrack() { - // Insert three tracks, points of two tracks and way point of one track. - long trackId = System.currentTimeMillis(); - Track track = getTrack(trackId, 10); - - providerUtils.insertTrack(track); - insertTrackWithLocations(getTrack(trackId + 1, 10)); - insertTrackWithLocations(getTrack(trackId + 2, 10)); - - Waypoint waypoint = new Waypoint(); - waypoint.setTrackId(trackId); - providerUtils.insertWaypoint(waypoint); - - ContentResolver contentResolver = context.getContentResolver(); - Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, - TracksColumns._ID); - Assert.assertEquals(3, tracksCursor.getCount()); - Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, - null, TrackPointsColumns._ID); - Assert.assertEquals(20, tracksPointsCursor.getCount()); - Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, - null, WaypointsColumns._ID); - Assert.assertEquals(1, waypointCursor.getCount()); - // Delete one track. - providerUtils.deleteTrack(context, trackId); - // Check whether all data of a track has been deleted. - tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, - TracksColumns._ID); - Assert.assertEquals(2, tracksCursor.getCount()); - tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, - null, TrackPointsColumns._ID); - Assert.assertEquals(20, tracksPointsCursor.getCount()); - waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, - null, WaypointsColumns._ID); - Assert.assertEquals(0, waypointCursor.getCount()); - } - - /** - * Tests the method {@link ContentProviderUtilsImpl#getAllTracks()} - */ - public void testGetAllTracks() { - int initialTrackNumber = providerUtils.getAllTracks().size(); - long trackId = System.currentTimeMillis(); - providerUtils.insertTrack(getTrack(trackId, 0)); - List allTracks = providerUtils.getAllTracks(); - Assert.assertEquals(initialTrackNumber + 1, allTracks.size()); - Assert.assertEquals(trackId, allTracks.get(allTracks.size() - 1).getId()); - } - - /** - * Tests the method {@link ContentProviderUtilsImpl#getLastTrack()} - */ - public void testGetLastTrack() { - long trackId = System.currentTimeMillis(); - providerUtils.insertTrack(getTrack(trackId, 0)); - Assert.assertEquals(trackId, providerUtils.getLastTrack().getId()); - } - - /** - * Tests the method {@link ContentProviderUtilsImpl#getTrack(long)} - */ - public void testGetTrack() { - long trackId = System.currentTimeMillis(); - providerUtils.insertTrack(getTrack(trackId, 0)); - Assert.assertNotNull(providerUtils.getTrack(trackId)); - } - - /** - * Tests the method {@link ContentProviderUtilsImpl#updateTrack(Track)} - */ - public void testUpdateTrack() { - long trackId = System.currentTimeMillis(); - Track track = getTrack(trackId, 0); - String nameOld = "name1"; - String nameNew = "name2"; - track.setName(nameOld); - providerUtils.insertTrack(track); - Assert.assertEquals(nameOld, providerUtils.getTrack(trackId).getName()); - track.setName(nameNew); - providerUtils.updateTrack(track); - Assert.assertEquals(nameNew, providerUtils.getTrack(trackId).getName()); - } - - /** - * Tests the method {@link ContentProviderUtilsImpl#createContentValues(Waypoint)}. - */ - public void testCreateContentValues_waypoint() { - long trackId = System.currentTimeMillis(); - Track track = getTrack(trackId, 10); - providerUtils.insertTrack(track); - // Bottom - long startTime = 1000L; - // AverageSpeed - double minGrade = -20.11; - TripStatistics statistics = new TripStatistics(); - statistics.setStartTime(startTime); - statistics.setStopTime(2500L); - statistics.setTotalTime(1500L); - statistics.setMovingTime(700L); - statistics.setTotalDistance(750.0); - statistics.setTotalElevationGain(50.0); - statistics.setMaxSpeed(60.0); - statistics.setMaxElevation(1250.0); - statistics.setMinElevation(1200.0); - statistics.setMaxGrade(15.0); - statistics.setMinGrade(minGrade); - statistics.setBounds(-10000, 20000, 30000, -40000); - // Insert at first. - Waypoint waypoint = new Waypoint(); - waypoint.setDescription(TEST_DESC); - waypoint.setType(WaypointType.STATISTICS); - waypoint.setTripStatistics(statistics); - - Location loc = new Location("test"); - loc.setLatitude(22); - loc.setLongitude(22); - loc.setAccuracy((float) 1 / 100.0f); - loc.setAltitude(2.5); - waypoint.setLocation(loc); - providerUtils.insertWaypoint(waypoint); - - ContentProviderUtilsImpl contentProviderUtils = new ContentProviderUtilsImpl( - new MockContentResolver()); - - long waypointId = System.currentTimeMillis(); - waypoint.setId(waypointId); - ContentValues contentValues = contentProviderUtils.createContentValues(waypoint); - Assert.assertEquals(waypointId, contentValues.get(WaypointsColumns._ID)); - Assert.assertEquals(22 * 1000000, contentValues.get(WaypointsColumns.LONGITUDE)); - Assert.assertEquals(TEST_DESC, contentValues.get(WaypointsColumns.DESCRIPTION)); - Assert.assertEquals(startTime, contentValues.get(WaypointsColumns.STARTTIME)); - Assert.assertEquals(minGrade, contentValues.get(WaypointsColumns.MINGRADE)); - } - - /** - * Tests the method {@link ContentProviderUtilsImpl#createWaypoint(Cursor)}. - */ - @UsesMocks(Cursor.class) - public void testCreateWaypoint() { - Cursor cursorMock = AndroidMock.createNiceMock(Cursor.class); - int startColumnIndex = 1; - int columnIndex = startColumnIndex; - AndroidMock.expect(cursorMock.getColumnIndexOrThrow(WaypointsColumns._ID)) - .andReturn(columnIndex++); - AndroidMock.expect(cursorMock.getColumnIndexOrThrow(WaypointsColumns.NAME)).andReturn( - columnIndex++); - AndroidMock.expect(cursorMock.getColumnIndexOrThrow(WaypointsColumns.TRACKID)).andReturn( - columnIndex++); - columnIndex = startColumnIndex; - // Id - AndroidMock.expect(cursorMock.isNull(columnIndex++)).andReturn(false); - // Name - AndroidMock.expect(cursorMock.isNull(columnIndex++)).andReturn(false); - // trackIdIndex - AndroidMock.expect(cursorMock.isNull(columnIndex++)).andReturn(false); - long id = System.currentTimeMillis(); - columnIndex = startColumnIndex; - // Id - AndroidMock.expect(cursorMock.getLong(columnIndex++)).andReturn(id); - // Name - String name = NAME_PREFIX + id; - AndroidMock.expect(cursorMock.getString(columnIndex++)).andReturn(name); - // trackIdIndex - long trackId = 11L; - AndroidMock.expect(cursorMock.getLong(columnIndex++)).andReturn(trackId); - AndroidMock.replay(cursorMock); - Waypoint waypoint = providerUtils.createWaypoint(cursorMock); - Assert.assertEquals(id, waypoint.getId()); - Assert.assertEquals(name, waypoint.getName()); - Assert.assertEquals(trackId, waypoint.getTrackId()); - AndroidMock.verify(cursorMock); - } - - /** - * Tests the method - * {@link ContentProviderUtilsImpl#deleteWaypoint(Context, long, DescriptionGenerator)} - * when there is only one waypoint in the track. - */ - public void testDeleteWaypoint_onlyOneWayPoint() { - long trackId = System.currentTimeMillis(); - Track track = getTrack(trackId, 10); - providerUtils.insertTrack(track); - - // Insert at first. - Waypoint waypoint1 = new Waypoint(); - waypoint1.setDescription(TEST_DESC); - waypoint1.setTrackId(trackId); - waypoint1.setType(WaypointType.STATISTICS); - providerUtils.insertWaypoint(waypoint1); - - // Delete - DescriptionGenerator descriptionGenerator = new DescriptionGenerator() { - - @Override - public String generateWaypointDescription(TripStatistics tripStatistics) { - return CustomContentProviderUtilsImplTest.MOCK_DESC; - } - - @Override - public String generateTrackDescription(Track aTrack, Vector distances, - Vector elevations, boolean html) { - return null; - } - }; - providerUtils.deleteWaypoint(context, 1, descriptionGenerator); - - Assert.assertNull(providerUtils.getWaypoint(1)); - } - - /** - * Tests the method - * {@link ContentProviderUtilsImpl#deleteWaypoint(Context, long, DescriptionGenerator)} - * when there is more than one waypoint in the track. - */ - public void testDeleteWaypoint_hasNextWayPoint() { - long trackId = System.currentTimeMillis(); - Track track = getTrack(trackId, 10); - providerUtils.insertTrack(track); - - TripStatistics statistics = new TripStatistics(); - statistics.setStartTime(1000L); - statistics.setStopTime(2500L); - statistics.setTotalTime(1500L); - statistics.setMovingTime(700L); - statistics.setTotalDistance(750.0); - statistics.setTotalElevationGain(50.0); - statistics.setMaxSpeed(60.0); - statistics.setMaxElevation(1250.0); - statistics.setMinElevation(1200.0); - statistics.setMaxGrade(15.0); - statistics.setMinGrade(-25.0); - statistics.setBounds(-10000, 20000, 30000, -40000); - // Insert at first. - Waypoint waypoint1 = new Waypoint(); - waypoint1.setDescription(Long.toString(trackId)); - waypoint1.setTrackId(trackId); - waypoint1.setType(WaypointType.STATISTICS); - waypoint1.setTripStatistics(statistics); - providerUtils.insertWaypoint(waypoint1); - Waypoint waypoint2 = new Waypoint(); - waypoint2.setDescription(Long.toString(trackId)); - waypoint2.setTrackId(trackId); - waypoint2.setType(WaypointType.STATISTICS); - waypoint2.setTripStatistics(statistics); - providerUtils.insertWaypoint(waypoint2); - - // Delete - DescriptionGenerator descriptionGenerator = new DescriptionGenerator() { - @Override - public String generateWaypointDescription(TripStatistics tripStatistics) { - return CustomContentProviderUtilsImplTest.MOCK_DESC; - } - - @Override - public String generateTrackDescription(Track aTrack, Vector distances, - Vector elevations, boolean html) { - return null; - } - }; - providerUtils.deleteWaypoint(context, 1, descriptionGenerator); - - Assert.assertNull(providerUtils.getWaypoint(1)); - Assert.assertEquals(CustomContentProviderUtilsImplTest.MOCK_DESC, providerUtils.getWaypoint(2) - .getDescription()); - } - - /** - * Tests the method {@link ContentProviderUtilsImpl#getFirstWaypointId(long)}. - */ - public void testGetFirstWaypointId() { - long trackId = System.currentTimeMillis(); - Track track = getTrack(trackId, 10); - providerUtils.insertTrack(track); - - Waypoint waypoint1 = new Waypoint(); - waypoint1.setTrackId(trackId); - Waypoint waypoint2 = new Waypoint(); - waypoint2.setTrackId(trackId); - providerUtils.insertWaypoint(waypoint1); - providerUtils.insertWaypoint(waypoint2); - - Assert.assertEquals(-1L, providerUtils.getFirstWaypointId(-1)); - Assert.assertEquals(1L, providerUtils.getFirstWaypointId(trackId)); - } - - /** - * Tests the method {@link ContentProviderUtilsImpl#getNextWaypointNumber(long, WaypointType)}. - */ - public void testGetNextWaypointNumber() { - long trackId = System.currentTimeMillis(); - Track track = getTrack(trackId, 10); - providerUtils.insertTrack(track); - - Waypoint waypoint1 = new Waypoint(); - waypoint1.setType(WaypointType.STATISTICS); - waypoint1.setTrackId(trackId); - Waypoint waypoint2 = new Waypoint(); - waypoint2.setType(WaypointType.WAYPOINT); - waypoint2.setTrackId(trackId); - Waypoint waypoint3 = new Waypoint(); - waypoint3.setType(WaypointType.STATISTICS); - waypoint3.setTrackId(trackId); - Waypoint waypoint4 = new Waypoint(); - waypoint4.setType(WaypointType.WAYPOINT); - waypoint4.setTrackId(trackId); - providerUtils.insertWaypoint(waypoint1); - providerUtils.insertWaypoint(waypoint2); - providerUtils.insertWaypoint(waypoint3); - providerUtils.insertWaypoint(waypoint4); - - Assert.assertEquals(2, providerUtils.getNextWaypointNumber(trackId, WaypointType.STATISTICS)); - Assert.assertEquals(3, providerUtils.getNextWaypointNumber(trackId, WaypointType.WAYPOINT)); - } - - /** - * Tests the method - * {@link ContentProviderUtils#getLastWaypoint(long, WaypointType)}. - */ - public void testGetLastStatisticsWaypoint() { - long trackId = System.currentTimeMillis(); - Track track = getTrack(trackId, 10); - providerUtils.insertTrack(track); - - Waypoint waypoint1 = new Waypoint(); - waypoint1.setTrackId(trackId); - waypoint1.setType(WaypointType.STATISTICS); - waypoint1.setDescription("Desc1"); - Waypoint waypoint2 = new Waypoint(); - waypoint2.setTrackId(trackId); - waypoint2.setType(WaypointType.STATISTICS); - waypoint2.setDescription("Desc2"); - Waypoint waypoint3 = new Waypoint(); - waypoint3.setTrackId(trackId); - waypoint3.setType(WaypointType.WAYPOINT); - waypoint3.setDescription("Desc3"); - providerUtils.insertWaypoint(waypoint1); - providerUtils.insertWaypoint(waypoint2); - providerUtils.insertWaypoint(waypoint3); - - Assert.assertEquals("Desc2", providerUtils.getLastWaypoint(trackId, WaypointType.STATISTICS).getDescription()); - } - - /** - * Tests the method {@link ContentProviderUtilsImpl#insertWaypoint(Waypoint)} and - * {@link ContentProviderUtilsImpl#getWaypoint(long)}. - */ - public void testInsertAndGetWaypoint() { - long trackId = System.currentTimeMillis(); - Track track = getTrack(trackId, 10); - providerUtils.insertTrack(track); - - Waypoint waypoint = new Waypoint(); - waypoint.setDescription(TEST_DESC); - waypoint.setTrackId(trackId); - providerUtils.insertWaypoint(waypoint); - - Assert.assertEquals(TEST_DESC, providerUtils.getWaypoint(1).getDescription()); - } - - /** - * Tests the method {@link ContentProviderUtilsImpl#updateWaypoint(Waypoint)}. - */ - public void testUpdateWaypoint() { - long trackId = System.currentTimeMillis(); - Track track = getTrack(trackId, 10); - providerUtils.insertTrack(track); - // Insert at first. - Waypoint waypoint = new Waypoint(); - waypoint.setDescription(TEST_DESC); - waypoint.setTrackId(trackId); - providerUtils.insertWaypoint(waypoint); - // Update - waypoint = providerUtils.getWaypoint(1); - waypoint.setDescription(TEST_DESC_NEW); - providerUtils.updateWaypoint(waypoint); - - Assert.assertEquals(TEST_DESC_NEW, providerUtils.getWaypoint(1).getDescription()); - } - - /** - * Tests the method {@link ContentProviderUtilsImpl#bulkInsertTrackPoint(Location[], - * int, long)}. - */ - public void testBulkInsertTrackPoint() { - // Insert track, point at first. - long trackId = System.currentTimeMillis(); - Track track = getTrack(trackId, 10); - insertTrackWithLocations(track); - - providerUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), -1, trackId); - Assert.assertEquals(20, providerUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount()); - providerUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), 8, trackId); - Assert.assertEquals(28, providerUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount()); - } - - /** - * Tests the method {@link ContentProviderUtilsImpl#createTrackPoint(Cursor)}. - */ - @UsesMocks(Cursor.class) - public void testCreateTrackPoint() { - Cursor cursorMock = AndroidMock.createNiceMock(Cursor.class); - - // Set index. - int index = 1; - // Id - AndroidMock.expect(cursorMock.getColumnIndex(TrackPointsColumns._ID)).andReturn(index++); - // Longitude - AndroidMock.expect(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE)).andReturn( - index++); - // Latitude - AndroidMock.expect(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE)).andReturn( - index++); - // Time - AndroidMock.expect(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.TIME)) - .andReturn(index++); - // Speed - AndroidMock.expect(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.SPEED)).andReturn( - index++); - // Sensor - AndroidMock.expect(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE)).andReturn( - index++); - - // Set return value of isNull(). - index = 2; - // Longitude - AndroidMock.expect(cursorMock.isNull(index++)).andReturn(false); - // Latitude - AndroidMock.expect(cursorMock.isNull(index++)).andReturn(false); - // Time - AndroidMock.expect(cursorMock.isNull(index++)).andReturn(false); - // Speed - AndroidMock.expect(cursorMock.isNull(index++)).andReturn(false); - // Sensor - AndroidMock.expect(cursorMock.isNull(index++)).andReturn(false); - - // Set return value of isNull(). - index = 2; - // Longitude - int longitude = 11; - AndroidMock.expect(cursorMock.getInt(index++)).andReturn(longitude * 1000000); - // Latitude. - int latitude = 22; - AndroidMock.expect(cursorMock.getInt(index++)).andReturn(latitude * 1000000); - // Time - long time = System.currentTimeMillis(); - AndroidMock.expect(cursorMock.getLong(index++)).andReturn(time); - // Speed - float speed = 2.2f; - AndroidMock.expect(cursorMock.getFloat(index++)).andReturn(speed); - // Sensor - byte[] sensor = "Sensor state".getBytes(); - AndroidMock.expect(cursorMock.getBlob(index++)).andReturn(sensor); - - AndroidMock.replay(cursorMock); - Location location = providerUtils.createTrackPoint(cursorMock); - Assert.assertEquals((double) longitude, location.getLongitude()); - Assert.assertEquals((double) latitude, location.getLatitude()); - Assert.assertEquals(time, location.getTime()); - Assert.assertEquals(speed, location.getSpeed()); - AndroidMock.verify(cursorMock); - } - - /** - * Tests the method - * {@link ContentProviderUtilsImpl#insertTrackPoint(Location, long)}. - */ - public void testInsertTrackPoint() { - // Insert track, point at first. - long trackId = System.currentTimeMillis(); - Track track = getTrack(trackId, 10); - insertTrackWithLocations(track); - - providerUtils.insertTrackPoint(createLocation(22), trackId); - Assert.assertEquals(11, providerUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount()); - } - - /** - * Tests the method {@link ContentProviderUtilsImpl#getFirstTrackPointId(long)}. - */ - public void testGetFirstTrackPointId() { - // Insert track, point at first. - long trackId = System.currentTimeMillis(); - Track track = getTrack(trackId, 10); - insertTrackWithLocations(track); - - Assert.assertEquals(1, providerUtils.getFirstTrackPointId(trackId)); - } - - /** - * Tests the method {@link ContentProviderUtilsImpl#getLastTrackPointId(long)}. - */ - public void testGetLastTrackPointId() { - // Insert track, point at first. - long trackId = System.currentTimeMillis(); - Track track = getTrack(trackId, 10); - insertTrackWithLocations(track); - - Assert.assertEquals(10, providerUtils.getLastTrackPointId(trackId)); - } - - /** - * Tests the method {@link ContentProviderUtilsImpl#getLastValidTrackPoint(long)}. - */ - public void testGetLastValidTrackPoint() { - // Insert track, points at first. - long trackId = System.currentTimeMillis(); - Track track = getTrack(trackId, 10); - insertTrackWithLocations(track); - - Location lastLocation = providerUtils.getLastValidTrackPoint(trackId); - checkLocation(9, lastLocation); - } - - /** - * Tests the method - * {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)} - * in descending. - */ - public void testGetTrackPointCursor_desc() { - // Insert track, points at first. - long trackId = System.currentTimeMillis(); - Track track = getTrack(trackId, 10); - insertTrackWithLocations(track); - - Cursor cursor = providerUtils.getTrackPointCursor(trackId, 2L, 5, true); - Assert.assertEquals(2, cursor.getCount()); - } - - /** - * Tests the method - * {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)} - * in ascending. - */ - public void testGetTrackPointCursor_asc() { - // Insert track, points at first. - long trackId = System.currentTimeMillis(); - Track track = getTrack(trackId, 10); - insertTrackWithLocations(track); - - Cursor cursor = providerUtils.getTrackPointCursor(trackId, 2L, 5, false); - Assert.assertEquals(5, cursor.getCount()); - } - - /** - * Tests the method - * {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} - * in descending. - */ - public void testGetTrackPointLocationIterator_desc() { - // Insert track, points at first. - long trackId = System.currentTimeMillis(); - Track track = getTrack(trackId, 10); - insertTrackWithLocations(track); - - long startTrackPointId = 2L; - - LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, - startTrackPointId, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); - for (int i = 1; i >= 0; i--) { - Assert.assertTrue(locationIterator.hasNext()); - Location location = locationIterator.next(); - Assert.assertEquals(2 + (i - 1), locationIterator.getLocationId()); - checkLocation(i, location); + public void testLocationIterator_noPoints() { + testIterator(1, 0, 1, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); } - Assert.assertFalse(locationIterator.hasNext()); - } - - /** - * Tests the method - * {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} - * in ascending. - */ - public void testGetTrackPointLocationIterator_asc() { - // Insert track, point at first. - long trackId = System.currentTimeMillis(); - Track track = getTrack(trackId, 10); - insertTrackWithLocations(track); - long startTrackPointId = 2L; - - LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, - startTrackPointId, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); - - for (int i = 1; i < 10; i++) { - Assert.assertTrue(locationIterator.hasNext()); - Location location = locationIterator.next(); - Assert.assertEquals(2 + (i - 1), locationIterator.getLocationId()); - checkLocation(i, location); + public void testLocationIterator_customFactory() { + final Location location = new Location("test_location"); + final AtomicInteger counter = new AtomicInteger(); + testIterator(1, 15, 4, false, new LocationFactory() { + @Override + public Location createLocation() { + counter.incrementAndGet(); + return location; + } + }); + // Make sure we were called exactly as many times as we had track points. + Assert.assertEquals(15, counter.get()); } - Assert.assertFalse(locationIterator.hasNext()); - } - /** - * Simulates a track which is used for testing. - * - * @param id the id of the track - * @param numPoints the location number in the track - * @return the simulated track - */ - private Track getTrack(long id,int numPoints) { - Track track = new Track(); - track.setId(id); - track.setName("Test: " + id); - track.setNumberOfPoints(numPoints); - for(int i = 0; i < numPoints; i++) { - track.addLocation(createLocation(i)); + public void testLocationIterator_nullFactory() { + try { + testIterator(1, 15, 4, false, null); + fail("Expecting IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // Expected. + } + } + + public void testLocationIterator_noBatchAscending() { + testIterator(1, 50, 100, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + testIterator(2, 50, 50, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + } + + public void testLocationIterator_noBatchDescending() { + testIterator(1, 50, 100, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + testIterator(2, 50, 50, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + } + + public void testLocationIterator_batchAscending() { + testIterator(1, 50, 11, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + testIterator(2, 50, 25, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + } + + public void testLocationIterator_batchDescending() { + testIterator(1, 50, 11, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + testIterator(2, 50, 25, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + } + + public void testLocationIterator_largeTrack() { + testIterator(1, 20000, 2000, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + } + + private List testIterator(long trackId, int numPoints, int batchSize, + boolean descending, LocationFactory locationFactory) { + long lastPointId = initializeTrack(trackId, numPoints); + ((ContentProviderUtilsImpl) providerUtils).setDefaultCursorBatchSize(batchSize); + List locations = new ArrayList(numPoints); + LocationIterator it = providerUtils.getTrackPointLocationIterator( + trackId, -1L, descending, locationFactory); + try { + while (it.hasNext()) { + Location loc = it.next(); + Assert.assertNotNull(loc); + locations.add(loc); + // Make sure the IDs are returned in the right order. + Assert.assertEquals(descending ? lastPointId - locations.size() + 1 + : lastPointId - numPoints + locations.size(), it.getLocationId()); + } + Assert.assertEquals(numPoints, locations.size()); + } finally { + it.close(); + } + return locations; + } + + private long initializeTrack(long id, int numPoints) { + Track track = new Track(); + track.setId(id); + track.setName("Test: " + id); + track.setNumberOfPoints(numPoints); + providerUtils.insertTrack(track); + track = providerUtils.getTrack(id); + Assert.assertNotNull(track); + + Location[] locations = new Location[numPoints]; + for (int i = 0; i < numPoints; ++i) { + Location loc = new Location("test"); + loc.setLatitude(37.0 + (double) i / 10000.0); + loc.setLongitude(57.0 - (double) i / 10000.0); + loc.setAccuracy((float) i / 100.0f); + loc.setAltitude(i * 2.5); + locations[i] = loc; + } + providerUtils.bulkInsertTrackPoint(locations, numPoints, id); + + // Load all inserted locations. + long lastPointId = -1; + int counter = 0; + LocationIterator it = providerUtils.getTrackPointLocationIterator(id, -1L, false, + ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + try { + while (it.hasNext()) { + it.next(); + lastPointId = it.getLocationId(); + counter++; + } + } finally { + it.close(); + } + + Assert.assertTrue(numPoints == 0 || lastPointId > 0); + Assert.assertEquals(numPoints, track.getNumberOfPoints()); + Assert.assertEquals(numPoints, counter); + + return lastPointId; + } + + /** + * Tests the method {@link ContentProviderUtilsImpl#createTrack(Cursor)}. + */ + @UsesMocks(Cursor.class) + public void testCreateTrack() { + Cursor cursorMock = AndroidMock.createNiceMock(Cursor.class); + int startColumnIndex = 1; + int columnIndex = startColumnIndex; + AndroidMock.expect(cursorMock.getColumnIndexOrThrow(TracksColumns._ID)) + .andReturn(columnIndex++); + AndroidMock.expect(cursorMock.getColumnIndexOrThrow(TracksColumns.NAME)).andReturn( + columnIndex++); + columnIndex = startColumnIndex; + // Id + AndroidMock.expect(cursorMock.isNull(columnIndex++)).andReturn(false); + // Name + AndroidMock.expect(cursorMock.isNull(columnIndex++)).andReturn(false); + long trackId = System.currentTimeMillis(); + columnIndex = startColumnIndex; + // Id + AndroidMock.expect(cursorMock.getLong(columnIndex++)).andReturn(trackId); + // Name + String name = NAME_PREFIX + trackId; + AndroidMock.expect(cursorMock.getString(columnIndex++)).andReturn(name); + AndroidMock.replay(cursorMock); + Track track = providerUtils.createTrack(cursorMock); + Assert.assertEquals(trackId, track.getId()); + Assert.assertEquals(name, track.getName()); + AndroidMock.verify(cursorMock); + } + + /** + * Tests the method {@link ContentProviderUtilsImpl#deleteAllTracks(Context)} + */ + public void testDeleteAllTracks() { + // Insert track, points and waypoint at first. + long trackId = System.currentTimeMillis(); + Track track = getTrack(trackId, 10); + insertTrackWithLocations(track); + Waypoint waypoint = new Waypoint(); + providerUtils.insertWaypoint(waypoint); + ContentResolver contentResolver = context.getContentResolver(); + Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, + TracksColumns._ID); + Assert.assertEquals(1, tracksCursor.getCount()); + Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, + null, TrackPointsColumns._ID); + Assert.assertEquals(10, tracksPointsCursor.getCount()); + Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, + null, WaypointsColumns._ID); + Assert.assertEquals(1, waypointCursor.getCount()); + // Delete all. + providerUtils.deleteAllTracks(context); + // Check whether all have been deleted. + tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, + TracksColumns._ID); + Assert.assertEquals(0, tracksCursor.getCount()); + tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, + null, TrackPointsColumns._ID); + Assert.assertEquals(0, tracksPointsCursor.getCount()); + waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, + null, WaypointsColumns._ID); + Assert.assertEquals(0, waypointCursor.getCount()); + } + + /** + * Tests the method {@link ContentProviderUtilsImpl#deleteTrack(Context, long)}. + */ + public void testDeleteTrack() { + // Insert three tracks, points of two tracks and way point of one track. + long trackId = System.currentTimeMillis(); + Track track = getTrack(trackId, 10); + + providerUtils.insertTrack(track); + insertTrackWithLocations(getTrack(trackId + 1, 10)); + insertTrackWithLocations(getTrack(trackId + 2, 10)); + + Waypoint waypoint = new Waypoint(); + waypoint.setTrackId(trackId); + providerUtils.insertWaypoint(waypoint); + + ContentResolver contentResolver = context.getContentResolver(); + Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, + TracksColumns._ID); + Assert.assertEquals(3, tracksCursor.getCount()); + Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, + null, TrackPointsColumns._ID); + Assert.assertEquals(20, tracksPointsCursor.getCount()); + Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, + null, WaypointsColumns._ID); + Assert.assertEquals(1, waypointCursor.getCount()); + // Delete one track. + providerUtils.deleteTrack(context, trackId); + // Check whether all data of a track has been deleted. + tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, + TracksColumns._ID); + Assert.assertEquals(2, tracksCursor.getCount()); + tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI, null, null, + null, TrackPointsColumns._ID); + Assert.assertEquals(20, tracksPointsCursor.getCount()); + waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, + null, WaypointsColumns._ID); + Assert.assertEquals(0, waypointCursor.getCount()); + } + + /** + * Tests the method {@link ContentProviderUtilsImpl#getAllTracks()} + */ + public void testGetAllTracks() { + int initialTrackNumber = providerUtils.getAllTracks().size(); + long trackId = System.currentTimeMillis(); + providerUtils.insertTrack(getTrack(trackId, 0)); + List allTracks = providerUtils.getAllTracks(); + Assert.assertEquals(initialTrackNumber + 1, allTracks.size()); + Assert.assertEquals(trackId, allTracks.get(allTracks.size() - 1).getId()); + } + + /** + * Tests the method {@link ContentProviderUtilsImpl#getLastTrack()} + */ + public void testGetLastTrack() { + long trackId = System.currentTimeMillis(); + providerUtils.insertTrack(getTrack(trackId, 0)); + Assert.assertEquals(trackId, providerUtils.getLastTrack().getId()); + } + + /** + * Tests the method {@link ContentProviderUtilsImpl#getTrack(long)} + */ + public void testGetTrack() { + long trackId = System.currentTimeMillis(); + providerUtils.insertTrack(getTrack(trackId, 0)); + Assert.assertNotNull(providerUtils.getTrack(trackId)); + } + + /** + * Tests the method {@link ContentProviderUtilsImpl#updateTrack(Track)} + */ + public void testUpdateTrack() { + long trackId = System.currentTimeMillis(); + Track track = getTrack(trackId, 0); + String nameOld = "name1"; + String nameNew = "name2"; + track.setName(nameOld); + providerUtils.insertTrack(track); + Assert.assertEquals(nameOld, providerUtils.getTrack(trackId).getName()); + track.setName(nameNew); + providerUtils.updateTrack(track); + Assert.assertEquals(nameNew, providerUtils.getTrack(trackId).getName()); + } + + /** + * Tests the method {@link ContentProviderUtilsImpl#createContentValues(Waypoint)}. + */ + public void testCreateContentValues_waypoint() { + long trackId = System.currentTimeMillis(); + Track track = getTrack(trackId, 10); + providerUtils.insertTrack(track); + // Bottom + long startTime = 1000L; + // AverageSpeed + double minGrade = -20.11; + TripStatistics statistics = new TripStatistics(); + statistics.setStartTime(startTime); + statistics.setStopTime(2500L); + statistics.setTotalTime(1500L); + statistics.setMovingTime(700L); + statistics.setTotalDistance(750.0); + statistics.setTotalElevationGain(50.0); + statistics.setMaxSpeed(60.0); + statistics.setMaxElevation(1250.0); + statistics.setMinElevation(1200.0); + statistics.setMaxGrade(15.0); + statistics.setMinGrade(minGrade); + statistics.setBounds(-10000, 20000, 30000, -40000); + // Insert at first. + Waypoint waypoint = new Waypoint(); + waypoint.setDescription(TEST_DESC); + waypoint.setType(WaypointType.STATISTICS); + waypoint.setTripStatistics(statistics); + + Location loc = new Location("test"); + loc.setLatitude(22); + loc.setLongitude(22); + loc.setAccuracy((float) 1 / 100.0f); + loc.setAltitude(2.5); + waypoint.setLocation(loc); + providerUtils.insertWaypoint(waypoint); + + ContentProviderUtilsImpl contentProviderUtils = new ContentProviderUtilsImpl( + new MockContentResolver()); + + long waypointId = System.currentTimeMillis(); + waypoint.setId(waypointId); + ContentValues contentValues = contentProviderUtils.createContentValues(waypoint); + Assert.assertEquals(waypointId, contentValues.get(WaypointsColumns._ID)); + Assert.assertEquals(22 * 1000000, contentValues.get(WaypointsColumns.LONGITUDE)); + Assert.assertEquals(TEST_DESC, contentValues.get(WaypointsColumns.DESCRIPTION)); + Assert.assertEquals(startTime, contentValues.get(WaypointsColumns.STARTTIME)); + Assert.assertEquals(minGrade, contentValues.get(WaypointsColumns.MINGRADE)); + } + + /** + * Tests the method {@link ContentProviderUtilsImpl#createWaypoint(Cursor)}. + */ + @UsesMocks(Cursor.class) + public void testCreateWaypoint() { + Cursor cursorMock = AndroidMock.createNiceMock(Cursor.class); + int startColumnIndex = 1; + int columnIndex = startColumnIndex; + AndroidMock.expect(cursorMock.getColumnIndexOrThrow(WaypointsColumns._ID)) + .andReturn(columnIndex++); + AndroidMock.expect(cursorMock.getColumnIndexOrThrow(WaypointsColumns.NAME)).andReturn( + columnIndex++); + AndroidMock.expect(cursorMock.getColumnIndexOrThrow(WaypointsColumns.TRACKID)).andReturn( + columnIndex++); + columnIndex = startColumnIndex; + // Id + AndroidMock.expect(cursorMock.isNull(columnIndex++)).andReturn(false); + // Name + AndroidMock.expect(cursorMock.isNull(columnIndex++)).andReturn(false); + // trackIdIndex + AndroidMock.expect(cursorMock.isNull(columnIndex++)).andReturn(false); + long id = System.currentTimeMillis(); + columnIndex = startColumnIndex; + // Id + AndroidMock.expect(cursorMock.getLong(columnIndex++)).andReturn(id); + // Name + String name = NAME_PREFIX + id; + AndroidMock.expect(cursorMock.getString(columnIndex++)).andReturn(name); + // trackIdIndex + long trackId = 11L; + AndroidMock.expect(cursorMock.getLong(columnIndex++)).andReturn(trackId); + AndroidMock.replay(cursorMock); + Waypoint waypoint = providerUtils.createWaypoint(cursorMock); + Assert.assertEquals(id, waypoint.getId()); + Assert.assertEquals(name, waypoint.getName()); + Assert.assertEquals(trackId, waypoint.getTrackId()); + AndroidMock.verify(cursorMock); + } + + /** + * Tests the method + * {@link ContentProviderUtilsImpl#deleteWaypoint(Context, long, DescriptionGenerator)} + * when there is only one waypoint in the track. + */ + public void testDeleteWaypoint_onlyOneWayPoint() { + long trackId = System.currentTimeMillis(); + Track track = getTrack(trackId, 10); + providerUtils.insertTrack(track); + + // Insert at first. + Waypoint waypoint1 = new Waypoint(); + waypoint1.setDescription(TEST_DESC); + waypoint1.setTrackId(trackId); + waypoint1.setType(WaypointType.STATISTICS); + providerUtils.insertWaypoint(waypoint1); + + // Delete + DescriptionGenerator descriptionGenerator = new DescriptionGenerator() { + + @Override + public String generateWaypointDescription(TripStatistics tripStatistics) { + return CustomContentProviderUtilsImplTest.MOCK_DESC; + } + + @Override + public String generateTrackDescription(Track aTrack, Vector distances, + Vector elevations, boolean html) { + return null; + } + }; + providerUtils.deleteWaypoint(context, 1, descriptionGenerator); + + Assert.assertNull(providerUtils.getWaypoint(1)); + } + + /** + * Tests the method + * {@link ContentProviderUtilsImpl#deleteWaypoint(Context, long, DescriptionGenerator)} + * when there is more than one waypoint in the track. + */ + public void testDeleteWaypoint_hasNextWayPoint() { + long trackId = System.currentTimeMillis(); + Track track = getTrack(trackId, 10); + providerUtils.insertTrack(track); + + TripStatistics statistics = new TripStatistics(); + statistics.setStartTime(1000L); + statistics.setStopTime(2500L); + statistics.setTotalTime(1500L); + statistics.setMovingTime(700L); + statistics.setTotalDistance(750.0); + statistics.setTotalElevationGain(50.0); + statistics.setMaxSpeed(60.0); + statistics.setMaxElevation(1250.0); + statistics.setMinElevation(1200.0); + statistics.setMaxGrade(15.0); + statistics.setMinGrade(-25.0); + statistics.setBounds(-10000, 20000, 30000, -40000); + // Insert at first. + Waypoint waypoint1 = new Waypoint(); + waypoint1.setDescription(Long.toString(trackId)); + waypoint1.setTrackId(trackId); + waypoint1.setType(WaypointType.STATISTICS); + waypoint1.setTripStatistics(statistics); + providerUtils.insertWaypoint(waypoint1); + Waypoint waypoint2 = new Waypoint(); + waypoint2.setDescription(Long.toString(trackId)); + waypoint2.setTrackId(trackId); + waypoint2.setType(WaypointType.STATISTICS); + waypoint2.setTripStatistics(statistics); + providerUtils.insertWaypoint(waypoint2); + + // Delete + DescriptionGenerator descriptionGenerator = new DescriptionGenerator() { + @Override + public String generateWaypointDescription(TripStatistics tripStatistics) { + return CustomContentProviderUtilsImplTest.MOCK_DESC; + } + + @Override + public String generateTrackDescription(Track aTrack, Vector distances, + Vector elevations, boolean html) { + return null; + } + }; + providerUtils.deleteWaypoint(context, 1, descriptionGenerator); + + Assert.assertNull(providerUtils.getWaypoint(1)); + Assert.assertEquals(CustomContentProviderUtilsImplTest.MOCK_DESC, providerUtils.getWaypoint(2) + .getDescription()); + } + + /** + * Tests the method {@link ContentProviderUtilsImpl#getFirstWaypointId(long)}. + */ + public void testGetFirstWaypointId() { + long trackId = System.currentTimeMillis(); + Track track = getTrack(trackId, 10); + providerUtils.insertTrack(track); + + Waypoint waypoint1 = new Waypoint(); + waypoint1.setTrackId(trackId); + Waypoint waypoint2 = new Waypoint(); + waypoint2.setTrackId(trackId); + providerUtils.insertWaypoint(waypoint1); + providerUtils.insertWaypoint(waypoint2); + + Assert.assertEquals(-1L, providerUtils.getFirstWaypointId(-1)); + Assert.assertEquals(1L, providerUtils.getFirstWaypointId(trackId)); + } + + /** + * Tests the method {@link ContentProviderUtilsImpl#getNextWaypointNumber(long, WaypointType)}. + */ + public void testGetNextWaypointNumber() { + long trackId = System.currentTimeMillis(); + Track track = getTrack(trackId, 10); + providerUtils.insertTrack(track); + + Waypoint waypoint1 = new Waypoint(); + waypoint1.setType(WaypointType.STATISTICS); + waypoint1.setTrackId(trackId); + Waypoint waypoint2 = new Waypoint(); + waypoint2.setType(WaypointType.WAYPOINT); + waypoint2.setTrackId(trackId); + Waypoint waypoint3 = new Waypoint(); + waypoint3.setType(WaypointType.STATISTICS); + waypoint3.setTrackId(trackId); + Waypoint waypoint4 = new Waypoint(); + waypoint4.setType(WaypointType.WAYPOINT); + waypoint4.setTrackId(trackId); + providerUtils.insertWaypoint(waypoint1); + providerUtils.insertWaypoint(waypoint2); + providerUtils.insertWaypoint(waypoint3); + providerUtils.insertWaypoint(waypoint4); + + Assert.assertEquals(2, providerUtils.getNextWaypointNumber(trackId, WaypointType.STATISTICS)); + Assert.assertEquals(3, providerUtils.getNextWaypointNumber(trackId, WaypointType.WAYPOINT)); + } + + /** + * Tests the method + * {@link ContentProviderUtils#getLastWaypoint(long, WaypointType)}. + */ + public void testGetLastStatisticsWaypoint() { + long trackId = System.currentTimeMillis(); + Track track = getTrack(trackId, 10); + providerUtils.insertTrack(track); + + Waypoint waypoint1 = new Waypoint(); + waypoint1.setTrackId(trackId); + waypoint1.setType(WaypointType.STATISTICS); + waypoint1.setDescription("Desc1"); + Waypoint waypoint2 = new Waypoint(); + waypoint2.setTrackId(trackId); + waypoint2.setType(WaypointType.STATISTICS); + waypoint2.setDescription("Desc2"); + Waypoint waypoint3 = new Waypoint(); + waypoint3.setTrackId(trackId); + waypoint3.setType(WaypointType.WAYPOINT); + waypoint3.setDescription("Desc3"); + providerUtils.insertWaypoint(waypoint1); + providerUtils.insertWaypoint(waypoint2); + providerUtils.insertWaypoint(waypoint3); + + Assert.assertEquals("Desc2", providerUtils.getLastWaypoint(trackId, WaypointType.STATISTICS).getDescription()); + } + + /** + * Tests the method {@link ContentProviderUtilsImpl#insertWaypoint(Waypoint)} and + * {@link ContentProviderUtilsImpl#getWaypoint(long)}. + */ + public void testInsertAndGetWaypoint() { + long trackId = System.currentTimeMillis(); + Track track = getTrack(trackId, 10); + providerUtils.insertTrack(track); + + Waypoint waypoint = new Waypoint(); + waypoint.setDescription(TEST_DESC); + waypoint.setTrackId(trackId); + providerUtils.insertWaypoint(waypoint); + + Assert.assertEquals(TEST_DESC, providerUtils.getWaypoint(1).getDescription()); + } + + /** + * Tests the method {@link ContentProviderUtilsImpl#updateWaypoint(Waypoint)}. + */ + public void testUpdateWaypoint() { + long trackId = System.currentTimeMillis(); + Track track = getTrack(trackId, 10); + providerUtils.insertTrack(track); + // Insert at first. + Waypoint waypoint = new Waypoint(); + waypoint.setDescription(TEST_DESC); + waypoint.setTrackId(trackId); + providerUtils.insertWaypoint(waypoint); + // Update + waypoint = providerUtils.getWaypoint(1); + waypoint.setDescription(TEST_DESC_NEW); + providerUtils.updateWaypoint(waypoint); + + Assert.assertEquals(TEST_DESC_NEW, providerUtils.getWaypoint(1).getDescription()); + } + + /** + * Tests the method {@link ContentProviderUtilsImpl#bulkInsertTrackPoint(Location[], + * int, long)}. + */ + public void testBulkInsertTrackPoint() { + // Insert track, point at first. + long trackId = System.currentTimeMillis(); + Track track = getTrack(trackId, 10); + insertTrackWithLocations(track); + + providerUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), -1, trackId); + Assert.assertEquals(20, providerUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount()); + providerUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), 8, trackId); + Assert.assertEquals(28, providerUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount()); + } + + /** + * Tests the method {@link ContentProviderUtilsImpl#createTrackPoint(Cursor)}. + */ + @UsesMocks(Cursor.class) + public void testCreateTrackPoint() { + Cursor cursorMock = AndroidMock.createNiceMock(Cursor.class); + + // Set index. + int index = 1; + // Id + AndroidMock.expect(cursorMock.getColumnIndex(TrackPointsColumns._ID)).andReturn(index++); + // Longitude + AndroidMock.expect(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE)).andReturn( + index++); + // Latitude + AndroidMock.expect(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE)).andReturn( + index++); + // Time + AndroidMock.expect(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.TIME)) + .andReturn(index++); + // Speed + AndroidMock.expect(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.SPEED)).andReturn( + index++); + // Sensor + AndroidMock.expect(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE)).andReturn( + index++); + + // Set return value of isNull(). + index = 2; + // Longitude + AndroidMock.expect(cursorMock.isNull(index++)).andReturn(false); + // Latitude + AndroidMock.expect(cursorMock.isNull(index++)).andReturn(false); + // Time + AndroidMock.expect(cursorMock.isNull(index++)).andReturn(false); + // Speed + AndroidMock.expect(cursorMock.isNull(index++)).andReturn(false); + // Sensor + AndroidMock.expect(cursorMock.isNull(index++)).andReturn(false); + + // Set return value of isNull(). + index = 2; + // Longitude + int longitude = 11; + AndroidMock.expect(cursorMock.getInt(index++)).andReturn(longitude * 1000000); + // Latitude. + int latitude = 22; + AndroidMock.expect(cursorMock.getInt(index++)).andReturn(latitude * 1000000); + // Time + long time = System.currentTimeMillis(); + AndroidMock.expect(cursorMock.getLong(index++)).andReturn(time); + // Speed + float speed = 2.2f; + AndroidMock.expect(cursorMock.getFloat(index++)).andReturn(speed); + // Sensor + byte[] sensor = "Sensor state".getBytes(); + AndroidMock.expect(cursorMock.getBlob(index++)).andReturn(sensor); + + AndroidMock.replay(cursorMock); + Location location = providerUtils.createTrackPoint(cursorMock); + Assert.assertEquals((double) longitude, location.getLongitude()); + Assert.assertEquals((double) latitude, location.getLatitude()); + Assert.assertEquals(time, location.getTime()); + Assert.assertEquals(speed, location.getSpeed()); + AndroidMock.verify(cursorMock); + } + + /** + * Tests the method + * {@link ContentProviderUtilsImpl#insertTrackPoint(Location, long)}. + */ + public void testInsertTrackPoint() { + // Insert track, point at first. + long trackId = System.currentTimeMillis(); + Track track = getTrack(trackId, 10); + insertTrackWithLocations(track); + + providerUtils.insertTrackPoint(createLocation(22), trackId); + Assert.assertEquals(11, providerUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount()); + } + + /** + * Tests the method {@link ContentProviderUtilsImpl#getFirstTrackPointId(long)}. + */ + public void testGetFirstTrackPointId() { + // Insert track, point at first. + long trackId = System.currentTimeMillis(); + Track track = getTrack(trackId, 10); + insertTrackWithLocations(track); + + Assert.assertEquals(1, providerUtils.getFirstTrackPointId(trackId)); + } + + /** + * Tests the method {@link ContentProviderUtilsImpl#getLastTrackPointId(long)}. + */ + public void testGetLastTrackPointId() { + // Insert track, point at first. + long trackId = System.currentTimeMillis(); + Track track = getTrack(trackId, 10); + insertTrackWithLocations(track); + + Assert.assertEquals(10, providerUtils.getLastTrackPointId(trackId)); + } + + /** + * Tests the method {@link ContentProviderUtilsImpl#getLastValidTrackPoint(long)}. + */ + public void testGetLastValidTrackPoint() { + // Insert track, points at first. + long trackId = System.currentTimeMillis(); + Track track = getTrack(trackId, 10); + insertTrackWithLocations(track); + + Location lastLocation = providerUtils.getLastValidTrackPoint(trackId); + checkLocation(9, lastLocation); + } + + /** + * Tests the method + * {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)} + * in descending. + */ + public void testGetTrackPointCursor_desc() { + // Insert track, points at first. + long trackId = System.currentTimeMillis(); + Track track = getTrack(trackId, 10); + insertTrackWithLocations(track); + + Cursor cursor = providerUtils.getTrackPointCursor(trackId, 2L, 5, true); + Assert.assertEquals(2, cursor.getCount()); + } + + /** + * Tests the method + * {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)} + * in ascending. + */ + public void testGetTrackPointCursor_asc() { + // Insert track, points at first. + long trackId = System.currentTimeMillis(); + Track track = getTrack(trackId, 10); + insertTrackWithLocations(track); + + Cursor cursor = providerUtils.getTrackPointCursor(trackId, 2L, 5, false); + Assert.assertEquals(5, cursor.getCount()); + } + + /** + * Tests the method + * {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} + * in descending. + */ + public void testGetTrackPointLocationIterator_desc() { + // Insert track, points at first. + long trackId = System.currentTimeMillis(); + Track track = getTrack(trackId, 10); + insertTrackWithLocations(track); + + long startTrackPointId = 2L; + + LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, + startTrackPointId, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + for (int i = 1; i >= 0; i--) { + Assert.assertTrue(locationIterator.hasNext()); + Location location = locationIterator.next(); + Assert.assertEquals(2 + (i - 1), locationIterator.getLocationId()); + checkLocation(i, location); + } + Assert.assertFalse(locationIterator.hasNext()); + } + + /** + * Tests the method + * {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} + * in ascending. + */ + public void testGetTrackPointLocationIterator_asc() { + // Insert track, point at first. + long trackId = System.currentTimeMillis(); + Track track = getTrack(trackId, 10); + insertTrackWithLocations(track); + + long startTrackPointId = 2L; + + LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, + startTrackPointId, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + + for (int i = 1; i < 10; i++) { + Assert.assertTrue(locationIterator.hasNext()); + Location location = locationIterator.next(); + Assert.assertEquals(2 + (i - 1), locationIterator.getLocationId()); + checkLocation(i, location); + } + Assert.assertFalse(locationIterator.hasNext()); + } + + /** + * Simulates a track which is used for testing. + * + * @param id the id of the track + * @param numPoints the location number in the track + * @return the simulated track + */ + private Track getTrack(long id, int numPoints) { + Track track = new Track(); + track.setId(id); + track.setName("Test: " + id); + track.setNumberOfPoints(numPoints); + for (int i = 0; i < numPoints; i++) { + track.addLocation(createLocation(i)); + } + return track; + } + + /** + * Creates a location. + * + * @param i the index to set the value of location. + * @return created location + */ + private Location createLocation(int i) { + + Location loc = new Location("test"); + loc.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0); + loc.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0); + loc.setAccuracy((float) i / 100.0f); + loc.setAltitude(i * ALTITUDE_INTERVAL); + return loc; + } + + /** + * Checks the value of a location. + * + * @param i the index of this location which created in the method + * {@link CustomContentProviderUtilsImplTest#getTrack(long, int)} + * @param location the location to be checked + */ + private void checkLocation(int i, Location location) { + Assert.assertEquals(INITIAL_LATITUDE + (double) i / 10000.0, location.getLatitude()); + Assert.assertEquals(INITIAL_LONGITUDE - (double) i / 10000.0, location.getLongitude()); + Assert.assertEquals((float) i / 100.0f, location.getAccuracy()); + Assert.assertEquals(i * ALTITUDE_INTERVAL, location.getAltitude()); + } + + /** + * Inserts a track with locations into the database. + * + * @param track track to be inserted + */ + private void insertTrackWithLocations(Track track) { + providerUtils.insertTrack(track); + providerUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), track + .getLocations().size(), track.getId()); } - return track; - } - - /** - * Creates a location. - * @param i the index to set the value of location. - * @return created location - */ - private Location createLocation(int i) { - - Location loc = new Location("test"); - loc.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0); - loc.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0); - loc.setAccuracy((float) i / 100.0f); - loc.setAltitude(i * ALTITUDE_INTERVAL); - return loc; - } - - /** - * Checks the value of a location. - * - * @param i the index of this location which created in the method - * {@link CustomContentProviderUtilsImplTest#getTrack(long, int)} - * @param location the location to be checked - */ - private void checkLocation(int i, Location location) { - Assert.assertEquals(INITIAL_LATITUDE + (double) i / 10000.0, location.getLatitude()); - Assert.assertEquals(INITIAL_LONGITUDE - (double) i / 10000.0, location.getLongitude()); - Assert.assertEquals((float) i / 100.0f, location.getAccuracy()); - Assert.assertEquals(i * ALTITUDE_INTERVAL, location.getAltitude()); - } - - /** - * Inserts a track with locations into the database. - * - * @param track track to be inserted - */ - private void insertTrackWithLocations(Track track) { - providerUtils.insertTrack(track); - providerUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), track - .getLocations().size(), track.getId()); - } } diff --git a/src/androidTest/java/com/google/android/apps/mytracks/content/DescriptionGeneratorImplTest.java b/src/androidTest/java/com/google/android/apps/mytracks/content/DescriptionGeneratorImplTest.java index 51e77e74c..1b38a5092 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/content/DescriptionGeneratorImplTest.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/content/DescriptionGeneratorImplTest.java @@ -1,12 +1,12 @@ /* * Copyright 2012 Google Inc. - * + * * 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 @@ -35,170 +35,170 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class DescriptionGeneratorImplTest { - private static final long START_TIME = 1288721514000L; - private DescriptionGeneratorImpl descriptionGenerator; + private static final long START_TIME = 1288721514000L; + private DescriptionGeneratorImpl descriptionGenerator; - @Before - protected void setUp() throws Exception { - descriptionGenerator = new DescriptionGeneratorImpl(InstrumentationRegistry.getInstrumentation().getContext()); - } + @Before + protected void setUp() throws Exception { + descriptionGenerator = new DescriptionGeneratorImpl(InstrumentationRegistry.getInstrumentation().getContext()); + } - /** - * Tests {@link DescriptionGeneratorImpl#generateTrackDescription(Track, - * java.util.Vector, java.util.Vector, boolean)}. - */ - public void testGenerateTrackDescription() { - Track track = new Track(); - TripStatistics stats = new TripStatistics(); - stats.setTotalDistance(20000); - stats.setTotalTime(600000); - stats.setMovingTime(300000); - stats.setMaxSpeed(100); - stats.setMaxElevation(550); - stats.setMinElevation(-500); - stats.setTotalElevationGain(6000); - stats.setMaxGrade(0.42); - stats.setMinGrade(0.11); - stats.setStartTime(START_TIME); - track.setTripStatistics(stats); - track.setCategory("hiking"); - String expected = "Created by" - + " TODO on Android

" - + "Name: -
" - + "Activity type: hiking
" - + "Description: -
" - + "Total distance: 20.00 km (12.4 mi)
" - + "Total time: 10:00
" - + "Moving time: 05:00
" - + "Average speed: 120.00 km/h (74.6 mi/h)
" - + "Average moving speed: 240.00 km/h (149.1 mi/h)
" - + "Max speed: 360.00 km/h (223.7 mi/h)
" - + "Average pace: 0:30 min/km (0:48 min/mi)
" - + "Average moving pace: 0:15 min/km (0:24 min/mi)
" - + "Fastest pace: 0:10 min/km (0:16 min/mi)
" - + "Max elevation: 550 m (1804 ft)
" - + "Min elevation: -500 m (-1640 ft)
" - + "Elevation gain: 6000 m (19685 ft)
" - + "Max grade: 42 %
" - + "Min grade: 11 %
" - + "Recorded: " + StringUtils.formatDateTime(InstrumentationRegistry.getInstrumentation().getContext(), START_TIME) + "
"; - Assert.assertEquals(expected, descriptionGenerator.generateTrackDescription(track, null, null, true)); - } + /** + * Tests {@link DescriptionGeneratorImpl#generateTrackDescription(Track, + * java.util.Vector, java.util.Vector, boolean)}. + */ + public void testGenerateTrackDescription() { + Track track = new Track(); + TripStatistics stats = new TripStatistics(); + stats.setTotalDistance(20000); + stats.setTotalTime(600000); + stats.setMovingTime(300000); + stats.setMaxSpeed(100); + stats.setMaxElevation(550); + stats.setMinElevation(-500); + stats.setTotalElevationGain(6000); + stats.setMaxGrade(0.42); + stats.setMinGrade(0.11); + stats.setStartTime(START_TIME); + track.setTripStatistics(stats); + track.setCategory("hiking"); + String expected = "Created by" + + " TODO on Android

" + + "Name: -
" + + "Activity type: hiking
" + + "Description: -
" + + "Total distance: 20.00 km (12.4 mi)
" + + "Total time: 10:00
" + + "Moving time: 05:00
" + + "Average speed: 120.00 km/h (74.6 mi/h)
" + + "Average moving speed: 240.00 km/h (149.1 mi/h)
" + + "Max speed: 360.00 km/h (223.7 mi/h)
" + + "Average pace: 0:30 min/km (0:48 min/mi)
" + + "Average moving pace: 0:15 min/km (0:24 min/mi)
" + + "Fastest pace: 0:10 min/km (0:16 min/mi)
" + + "Max elevation: 550 m (1804 ft)
" + + "Min elevation: -500 m (-1640 ft)
" + + "Elevation gain: 6000 m (19685 ft)
" + + "Max grade: 42 %
" + + "Min grade: 11 %
" + + "Recorded: " + StringUtils.formatDateTime(InstrumentationRegistry.getInstrumentation().getContext(), START_TIME) + "
"; + Assert.assertEquals(expected, descriptionGenerator.generateTrackDescription(track, null, null, true)); + } - /** - * Tests {@link DescriptionGeneratorImpl#generateWaypointDescription(TripStatistics)}. - */ - public void testGenerateWaypointDescription() { - Waypoint waypoint = new Waypoint(); - TripStatistics stats = new TripStatistics(); - stats.setTotalDistance(20000); - stats.setTotalTime(600000); - stats.setMovingTime(300000); - stats.setMaxSpeed(100); - stats.setMaxElevation(550); - stats.setMinElevation(-500); - stats.setTotalElevationGain(6000); - stats.setMaxGrade(0.42); - stats.setMinGrade(0.11); - stats.setStartTime(START_TIME); - waypoint.setTripStatistics(stats); - String expected = "Total distance: 20.00 km (12.4 mi)\n" - + "Total time: 10:00\n" - + "Moving time: 05:00\n" - + "Average speed: 120.00 km/h (74.6 mi/h)\n" - + "Average moving speed: 240.00 km/h (149.1 mi/h)\n" - + "Max speed: 360.00 km/h (223.7 mi/h)\n" - + "Average pace: 0:30 min/km (0:48 min/mi)\n" - + "Average moving pace: 0:15 min/km (0:24 min/mi)\n" - + "Fastest pace: 0:10 min/km (0:16 min/mi)\n" - + "Max elevation: 550 m (1804 ft)\n" - + "Min elevation: -500 m (-1640 ft)\n" - + "Elevation gain: 6000 m (19685 ft)\n" - + "Max grade: 42 %\n" - + "Min grade: 11 %\n" - + "Recorded: " + StringUtils.formatDateTime(InstrumentationRegistry.getInstrumentation().getContext(), START_TIME) + "\n"; - Assert.assertEquals( - expected, descriptionGenerator.generateWaypointDescription(waypoint.getTripStatistics())); - } + /** + * Tests {@link DescriptionGeneratorImpl#generateWaypointDescription(TripStatistics)}. + */ + public void testGenerateWaypointDescription() { + Waypoint waypoint = new Waypoint(); + TripStatistics stats = new TripStatistics(); + stats.setTotalDistance(20000); + stats.setTotalTime(600000); + stats.setMovingTime(300000); + stats.setMaxSpeed(100); + stats.setMaxElevation(550); + stats.setMinElevation(-500); + stats.setTotalElevationGain(6000); + stats.setMaxGrade(0.42); + stats.setMinGrade(0.11); + stats.setStartTime(START_TIME); + waypoint.setTripStatistics(stats); + String expected = "Total distance: 20.00 km (12.4 mi)\n" + + "Total time: 10:00\n" + + "Moving time: 05:00\n" + + "Average speed: 120.00 km/h (74.6 mi/h)\n" + + "Average moving speed: 240.00 km/h (149.1 mi/h)\n" + + "Max speed: 360.00 km/h (223.7 mi/h)\n" + + "Average pace: 0:30 min/km (0:48 min/mi)\n" + + "Average moving pace: 0:15 min/km (0:24 min/mi)\n" + + "Fastest pace: 0:10 min/km (0:16 min/mi)\n" + + "Max elevation: 550 m (1804 ft)\n" + + "Min elevation: -500 m (-1640 ft)\n" + + "Elevation gain: 6000 m (19685 ft)\n" + + "Max grade: 42 %\n" + + "Min grade: 11 %\n" + + "Recorded: " + StringUtils.formatDateTime(InstrumentationRegistry.getInstrumentation().getContext(), START_TIME) + "\n"; + Assert.assertEquals( + expected, descriptionGenerator.generateWaypointDescription(waypoint.getTripStatistics())); + } - /** - * Tests {@link DescriptionGeneratorImpl#writeDistance(double, StringBuilder, - * int, String)}. - */ - public void testWriteDistance() { - StringBuilder builder = new StringBuilder(); - descriptionGenerator.writeDistance(1100, builder, R.string.description_total_distance, "
"); - Assert.assertEquals("Total distance: 1.10 km (0.7 mi)
", builder.toString()); - } + /** + * Tests {@link DescriptionGeneratorImpl#writeDistance(double, StringBuilder, + * int, String)}. + */ + public void testWriteDistance() { + StringBuilder builder = new StringBuilder(); + descriptionGenerator.writeDistance(1100, builder, R.string.description_total_distance, "
"); + Assert.assertEquals("Total distance: 1.10 km (0.7 mi)
", builder.toString()); + } - /** - * Tests {@link DescriptionGeneratorImpl#writeTime(long, StringBuilder, int, - * String)}. - */ - public void testWriteTime() { - StringBuilder builder = new StringBuilder(); - descriptionGenerator.writeTime(1000, builder, R.string.description_total_time, "
"); - Assert.assertEquals("Total time: 00:01
", builder.toString()); - } + /** + * Tests {@link DescriptionGeneratorImpl#writeTime(long, StringBuilder, int, + * String)}. + */ + public void testWriteTime() { + StringBuilder builder = new StringBuilder(); + descriptionGenerator.writeTime(1000, builder, R.string.description_total_time, "
"); + Assert.assertEquals("Total time: 00:01
", builder.toString()); + } - /** - * Tests {@link DescriptionGeneratorImpl#writeSpeed(double, StringBuilder, - * int, String)}. - */ - public void testWriteSpeed() { - StringBuilder builder = new StringBuilder(); - descriptionGenerator.writeSpeed(1.1, builder, R.string.description_average_speed, "\n"); - Assert.assertEquals("Average speed: 3.96 km/h (2.5 mi/h)\n", builder.toString()); - } + /** + * Tests {@link DescriptionGeneratorImpl#writeSpeed(double, StringBuilder, + * int, String)}. + */ + public void testWriteSpeed() { + StringBuilder builder = new StringBuilder(); + descriptionGenerator.writeSpeed(1.1, builder, R.string.description_average_speed, "\n"); + Assert.assertEquals("Average speed: 3.96 km/h (2.5 mi/h)\n", builder.toString()); + } - /** - * Tests {@link DescriptionGeneratorImpl#writeElevation(double, StringBuilder, - * int, String)}. - */ - public void testWriteElevation() { - StringBuilder builder = new StringBuilder(); - descriptionGenerator.writeElevation(4.2, builder, R.string.description_min_elevation, "
"); - Assert.assertEquals("Min elevation: 4 m (14 ft)
", builder.toString()); - } + /** + * Tests {@link DescriptionGeneratorImpl#writeElevation(double, StringBuilder, + * int, String)}. + */ + public void testWriteElevation() { + StringBuilder builder = new StringBuilder(); + descriptionGenerator.writeElevation(4.2, builder, R.string.description_min_elevation, "
"); + Assert.assertEquals("Min elevation: 4 m (14 ft)
", builder.toString()); + } - /** - * Tests {@link DescriptionGeneratorImpl#writePace(double, StringBuilder, int, - * String)}. - */ - public void testWritePace() { - StringBuilder builder = new StringBuilder(); - descriptionGenerator.writePace(1.1, builder, R.string.description_average_pace_in_minute, "\n"); - Assert.assertEquals("Average pace: 15:09 min/km (24:23 min/mi)\n", builder.toString()); - } + /** + * Tests {@link DescriptionGeneratorImpl#writePace(double, StringBuilder, int, + * String)}. + */ + public void testWritePace() { + StringBuilder builder = new StringBuilder(); + descriptionGenerator.writePace(1.1, builder, R.string.description_average_pace_in_minute, "\n"); + Assert.assertEquals("Average pace: 15:09 min/km (24:23 min/mi)\n", builder.toString()); + } - /** - * Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, - * int, String)}. - */ - public void testWriteGrade() { - StringBuilder builder = new StringBuilder(); - descriptionGenerator.writeGrade(.042, builder, R.string.description_max_grade, "
"); - Assert.assertEquals("Max grade: 4 %
", builder.toString()); - } + /** + * Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, + * int, String)}. + */ + public void testWriteGrade() { + StringBuilder builder = new StringBuilder(); + descriptionGenerator.writeGrade(.042, builder, R.string.description_max_grade, "
"); + Assert.assertEquals("Max grade: 4 %
", builder.toString()); + } - /** - * Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, - * int, String)} with a NaN. - */ - public void testWriteGrade_nan() { - StringBuilder builder = new StringBuilder(); - descriptionGenerator.writeGrade(Double.NaN, builder, R.string.description_max_grade, "
"); - Assert.assertEquals("Max grade: 0 %
", builder.toString()); - } + /** + * Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, + * int, String)} with a NaN. + */ + public void testWriteGrade_nan() { + StringBuilder builder = new StringBuilder(); + descriptionGenerator.writeGrade(Double.NaN, builder, R.string.description_max_grade, "
"); + Assert.assertEquals("Max grade: 0 %
", builder.toString()); + } - /** - * Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, - * int, String)} with an infinite number. - */ - public void testWriteGrade_infinite() { - StringBuilder builder = new StringBuilder(); - descriptionGenerator.writeGrade( - Double.POSITIVE_INFINITY, builder, R.string.description_max_grade, "
"); - Assert.assertEquals("Max grade: 0 %
", builder.toString()); - } + /** + * Tests {@link DescriptionGeneratorImpl#writeGrade(double, StringBuilder, + * int, String)} with an infinite number. + */ + public void testWriteGrade_infinite() { + StringBuilder builder = new StringBuilder(); + descriptionGenerator.writeGrade( + Double.POSITIVE_INFINITY, builder, R.string.description_max_grade, "
"); + Assert.assertEquals("Max grade: 0 %
", builder.toString()); + } } diff --git a/src/androidTest/java/com/google/android/apps/mytracks/content/SearchEngineTest.java b/src/androidTest/java/com/google/android/apps/mytracks/content/SearchEngineTest.java index dfbf55ea5..2e2d91bab 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/content/SearchEngineTest.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/content/SearchEngineTest.java @@ -1,12 +1,12 @@ /* * Copyright 2012 Google Inc. - * + * * 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 @@ -48,281 +48,281 @@ import java.util.List; @RunWith(AndroidJUnit4.class) public class SearchEngineTest { - private static final Location HERE = new Location("gps"); - private static final long NOW = 1234567890000L; // After OLDEST_ALLOWED_TIMESTAMP - private ContentProviderUtils providerUtils; - private SearchEngine engine; + private static final Location HERE = new Location("gps"); + private static final long NOW = 1234567890000L; // After OLDEST_ALLOWED_TIMESTAMP + private ContentProviderUtils providerUtils; + private SearchEngine engine; - @Before - protected void setUp() { - MockContentResolver mockContentResolver = new MockContentResolver(); - RenamingDelegatingContext targetContext = new RenamingDelegatingContext( - getContext(), getContext(), "test."); - MockContext context = new MockContext(mockContentResolver, targetContext); - CustomContentProvider provider = new CustomContentProvider(); - provider.attachInfo(context, null); - mockContentResolver.addProvider(ContentProviderUtils.AUTHORITY, provider); - setContext(context); + @Before + protected void setUp() { + MockContentResolver mockContentResolver = new MockContentResolver(); + RenamingDelegatingContext targetContext = new RenamingDelegatingContext( + getContext(), getContext(), "test."); + MockContext context = new MockContext(mockContentResolver, targetContext); + CustomContentProvider provider = new CustomContentProvider(); + provider.attachInfo(context, null); + mockContentResolver.addProvider(ContentProviderUtils.AUTHORITY, provider); + setContext(context); - providerUtils = ContentProviderUtils.Factory.get(context); - engine = new SearchEngine(providerUtils); - } - - @After - protected void tearDown() throws Exception { - providerUtils.deleteAllTracks(getContext()); - } - - private long insertTrack(String title, String description, String category, double distance, long hoursAgo) { - Track track = new Track(); - track.setName(title); - track.setDescription(description); - track.setCategory(category); - - TripStatistics stats = track.getTripStatistics(); - if (hoursAgo > 0) { - // Started twice hoursAgo, so the average time is hoursAgo. - stats.setStartTime(NOW - hoursAgo * 1000L * 60L * 60L * 2); - stats.setStopTime(NOW); + providerUtils = ContentProviderUtils.Factory.get(context); + engine = new SearchEngine(providerUtils); } - int latitude = (int) ((HERE.getLatitude() + distance) * 1E6); - int longitude = (int) ((HERE.getLongitude() + distance) * 1E6); - stats.setBounds(latitude, longitude, latitude, longitude); - - Uri uri = providerUtils.insertTrack(track); - return ContentUris.parseId(uri); - } - - private long insertTrack(String title, String description, String category) { - return insertTrack(title, description, category, 0, -1); - } - - private long insertTrack(String title, double distance) { - return insertTrack(title, "", "", distance, -1); - } - - private long insertTrack(String title, long hoursAgo) { - return insertTrack(title, "", "", 0.0, hoursAgo); - } - - private long insertWaypoint(String title, String description, String category, double distance, long hoursAgo, long trackId) { - Waypoint waypoint = new Waypoint(); - waypoint.setName(title); - waypoint.setDescription(description); - waypoint.setCategory(category); - waypoint.setTrackId(trackId); - - Location location = new Location(HERE); - location.setLatitude(location.getLatitude() + distance); - location.setLongitude(location.getLongitude() + distance); - if (hoursAgo >= 0) { - location.setTime(NOW - hoursAgo * 1000L * 60L * 60L); + @After + protected void tearDown() throws Exception { + providerUtils.deleteAllTracks(getContext()); } - waypoint.setLocation(location); - Uri uri = providerUtils.insertWaypoint(waypoint); - return ContentUris.parseId(uri); - } + private long insertTrack(String title, String description, String category, double distance, long hoursAgo) { + Track track = new Track(); + track.setName(title); + track.setDescription(description); + track.setCategory(category); - private long insertWaypoint(String title, String description, String category) { - return insertWaypoint(title, description, category, 0.0, -1, -1); - } + TripStatistics stats = track.getTripStatistics(); + if (hoursAgo > 0) { + // Started twice hoursAgo, so the average time is hoursAgo. + stats.setStartTime(NOW - hoursAgo * 1000L * 60L * 60L * 2); + stats.setStopTime(NOW); + } - private long insertWaypoint(String title, double distance) { - return insertWaypoint(title, "", "", distance, -1, -1); - } + int latitude = (int) ((HERE.getLatitude() + distance) * 1E6); + int longitude = (int) ((HERE.getLongitude() + distance) * 1E6); + stats.setBounds(latitude, longitude, latitude, longitude); - private long insertWaypoint(String title, long hoursAgo) { - return insertWaypoint(title, "", "", 0.0, hoursAgo, -1); - } - - private long insertWaypoint(String title, long hoursAgo, long trackId) { - return insertWaypoint(title, "", "", 0.0, hoursAgo, trackId); - } - - public void testSearchText() { - // Insert 7 tracks (purposefully out of result order): - // - one which won't match - // - one which will match the description - // - one which will match the category - // - one which will match the title - // - one which will match in title and category - // - one which will match in title and description - // - one which will match in all fields - insertTrack("bb", "cc", "dd"); - long descriptionMatchId = insertTrack("bb", "aa", "cc"); - long categoryMatchId = insertTrack("bb", "cc", "aa"); - long titleMatchId = insertTrack("aa", "bb", "cc"); - long titleCategoryMatchId = insertTrack("aa", "bb", "ca"); - long titleDescriptionMatchId = insertTrack("aa", "ba", "cc"); - long allMatchId = insertTrack("aa", "ba", "ca"); - - SearchQuery query = new SearchQuery("a", null, -1, NOW); - ArrayList results = new ArrayList(engine.search(query)); - - // Title > Description > Category. - assertTrackResults(results, - allMatchId, titleDescriptionMatchId, titleCategoryMatchId, titleMatchId, descriptionMatchId, - categoryMatchId); - } - - public void testSearchWaypointText() { - // Insert 7 waypoints (purposefully out of result order): - // - one which won't match - // - one which will match the description - // - one which will match the category - // - one which will match the title - // - one which will match in title and category - // - one which will match in title and description - // - one which will match in all fields - insertWaypoint("bb", "cc", "dd"); - long descriptionMatchId = insertWaypoint("bb", "aa", "cc"); - long categoryMatchId = insertWaypoint("bb", "cc", "aa"); - long titleMatchId = insertWaypoint("aa", "bb", "cc"); - long titleCategoryMatchId = insertWaypoint("aa", "bb", "ca"); - long titleDescriptionMatchId = insertWaypoint("aa", "ba", "cc"); - long allMatchId = insertWaypoint("aa", "ba", "ca"); - - SearchQuery query = new SearchQuery("a", null, -1, NOW); - ArrayList results = new ArrayList(engine.search(query)); - - // Title > Description > Category. - assertWaypointResults(results, - allMatchId, titleDescriptionMatchId, titleCategoryMatchId, titleMatchId, descriptionMatchId, - categoryMatchId); - } - - public void testSearchMixedText() { - // Insert 5 entries (purposefully out of result order): - // - one waypoint which will match by description - // - one waypoint which won't match - // - one waypoint which will match by title - // - one track which won't match - // - one track which will match by title - long descriptionWaypointId = insertWaypoint("bb", "aa", "cc"); - insertWaypoint("bb", "cc", "dd"); - long titleWaypointId = insertWaypoint("aa", "bb", "cc"); - insertTrack("bb", "cc", "dd"); - long trackId = insertTrack("aa", "bb", "cc"); - - SearchQuery query = new SearchQuery("a", null, -1, NOW); - ArrayList results = new ArrayList(engine.search(query)); - - // Title > Description > Category. - assertEquals(results.toString(), 3, results.size()); - assertTrackResult(trackId, results.get(0)); - assertWaypointResult(titleWaypointId, results.get(1)); - assertWaypointResult(descriptionWaypointId, results.get(2)); - } - - public void testSearchTrackDistance() { - // All results match text, but they're at difference distances from the user. - long farFarAwayId = insertTrack("aa", 0.3); - long nearId = insertTrack("ab", 0.1); - long farId = insertTrack("ac", 0.2); - - SearchQuery query = new SearchQuery("a", HERE, -1, NOW); - ArrayList results = new ArrayList(engine.search(query)); - - // Distance order. - assertTrackResults(results, nearId, farId, farFarAwayId); - } - - public void testSearchWaypointDistance() { - // All results match text, but they're at difference distances from the user. - long farFarAwayId = insertWaypoint("aa", 0.3); - long nearId = insertWaypoint("ab", 0.1); - long farId = insertWaypoint("ac", 0.2); - - SearchQuery query = new SearchQuery("a", HERE, -1, NOW); - ArrayList results = new ArrayList(engine.search(query)); - - // Distance order. - assertWaypointResults(results, nearId, farId, farFarAwayId); - } - - public void testSearchTrackRecent() { - // All results match text, but they're were recorded at different times. - long oldestId = insertTrack("aa", 3); - long recentId = insertTrack("ab", 1); - long oldId = insertTrack("ac", 2); - - SearchQuery query = new SearchQuery("a", null, -1, NOW); - ArrayList results = new ArrayList(engine.search(query)); - - // Reverse time order. - assertTrackResults(results, recentId, oldId, oldestId); - } - - public void testSearchWaypointRecent() { - // All results match text, but they're were recorded at different times. - long oldestId = insertWaypoint("aa", 2); - long recentId = insertWaypoint("ab", 0); - long oldId = insertWaypoint("ac", 1); - - SearchQuery query = new SearchQuery("a", null, -1, NOW); - ArrayList results = new ArrayList(engine.search(query)); - - // Reverse time order. - assertWaypointResults(results, recentId, oldId, oldestId); - } - - public void testSearchCurrentTrack() { - // All results match text, but one of them is the current track. - long currentId = insertTrack("ab", 1); - long otherId = insertTrack("aa", 1); - - SearchQuery query = new SearchQuery("a", null, currentId, NOW); - ArrayList results = new ArrayList(engine.search(query)); - - // Current track should be demoted. - assertTrackResults(results, otherId, currentId); - } - - public void testSearchCurrentTrackWaypoint() { - // All results match text, but one of them is in the current track. - long otherId = insertWaypoint("aa", 1, 456); - long currentId = insertWaypoint("ab", 1, 123); - - SearchQuery query = new SearchQuery("a", null, 123, NOW); - ArrayList results = new ArrayList(engine.search(query)); - - // Waypoint in current track should be promoted. - assertWaypointResults(results, currentId, otherId); - } - - private void assertTrackResult(long trackId, ScoredResult result) { - assertNotNull("Not a track", result.track); - assertNull("Ambiguous result", result.waypoint); - assertEquals(trackId, result.track.getId()); - } - - private void assertTrackResults(List results, long... trackIds) { - String errMsg = "Expected IDs=" + Arrays.toString(trackIds) + "; results=" + results; - assertEquals(results.size(), trackIds.length); - for (int i = 0; i < results.size(); i++) { - ScoredResult result = results.get(i); - assertNotNull(errMsg, result.track); - assertNull(errMsg, result.waypoint); - assertEquals(errMsg, trackIds[i], result.track.getId()); + Uri uri = providerUtils.insertTrack(track); + return ContentUris.parseId(uri); } - } - private void assertWaypointResult(long waypointId, ScoredResult result) { - assertNotNull("Not a waypoint", result.waypoint); - assertNull("Ambiguous result", result.track); - assertEquals(waypointId, result.waypoint.getId()); - } - - private void assertWaypointResults(List results, long... waypointIds) { - String errMsg = "Expected IDs=" + Arrays.toString(waypointIds) + "; results=" + results; - assertEquals(results.size(), waypointIds.length); - for (int i = 0; i < results.size(); i++) { - ScoredResult result = results.get(i); - assertNotNull(errMsg, result.waypoint); - assertNull(errMsg, result.track); - assertEquals(errMsg, waypointIds[i], result.waypoint.getId()); + private long insertTrack(String title, String description, String category) { + return insertTrack(title, description, category, 0, -1); + } + + private long insertTrack(String title, double distance) { + return insertTrack(title, "", "", distance, -1); + } + + private long insertTrack(String title, long hoursAgo) { + return insertTrack(title, "", "", 0.0, hoursAgo); + } + + private long insertWaypoint(String title, String description, String category, double distance, long hoursAgo, long trackId) { + Waypoint waypoint = new Waypoint(); + waypoint.setName(title); + waypoint.setDescription(description); + waypoint.setCategory(category); + waypoint.setTrackId(trackId); + + Location location = new Location(HERE); + location.setLatitude(location.getLatitude() + distance); + location.setLongitude(location.getLongitude() + distance); + if (hoursAgo >= 0) { + location.setTime(NOW - hoursAgo * 1000L * 60L * 60L); + } + waypoint.setLocation(location); + + Uri uri = providerUtils.insertWaypoint(waypoint); + return ContentUris.parseId(uri); + } + + private long insertWaypoint(String title, String description, String category) { + return insertWaypoint(title, description, category, 0.0, -1, -1); + } + + private long insertWaypoint(String title, double distance) { + return insertWaypoint(title, "", "", distance, -1, -1); + } + + private long insertWaypoint(String title, long hoursAgo) { + return insertWaypoint(title, "", "", 0.0, hoursAgo, -1); + } + + private long insertWaypoint(String title, long hoursAgo, long trackId) { + return insertWaypoint(title, "", "", 0.0, hoursAgo, trackId); + } + + public void testSearchText() { + // Insert 7 tracks (purposefully out of result order): + // - one which won't match + // - one which will match the description + // - one which will match the category + // - one which will match the title + // - one which will match in title and category + // - one which will match in title and description + // - one which will match in all fields + insertTrack("bb", "cc", "dd"); + long descriptionMatchId = insertTrack("bb", "aa", "cc"); + long categoryMatchId = insertTrack("bb", "cc", "aa"); + long titleMatchId = insertTrack("aa", "bb", "cc"); + long titleCategoryMatchId = insertTrack("aa", "bb", "ca"); + long titleDescriptionMatchId = insertTrack("aa", "ba", "cc"); + long allMatchId = insertTrack("aa", "ba", "ca"); + + SearchQuery query = new SearchQuery("a", null, -1, NOW); + ArrayList results = new ArrayList(engine.search(query)); + + // Title > Description > Category. + assertTrackResults(results, + allMatchId, titleDescriptionMatchId, titleCategoryMatchId, titleMatchId, descriptionMatchId, + categoryMatchId); + } + + public void testSearchWaypointText() { + // Insert 7 waypoints (purposefully out of result order): + // - one which won't match + // - one which will match the description + // - one which will match the category + // - one which will match the title + // - one which will match in title and category + // - one which will match in title and description + // - one which will match in all fields + insertWaypoint("bb", "cc", "dd"); + long descriptionMatchId = insertWaypoint("bb", "aa", "cc"); + long categoryMatchId = insertWaypoint("bb", "cc", "aa"); + long titleMatchId = insertWaypoint("aa", "bb", "cc"); + long titleCategoryMatchId = insertWaypoint("aa", "bb", "ca"); + long titleDescriptionMatchId = insertWaypoint("aa", "ba", "cc"); + long allMatchId = insertWaypoint("aa", "ba", "ca"); + + SearchQuery query = new SearchQuery("a", null, -1, NOW); + ArrayList results = new ArrayList(engine.search(query)); + + // Title > Description > Category. + assertWaypointResults(results, + allMatchId, titleDescriptionMatchId, titleCategoryMatchId, titleMatchId, descriptionMatchId, + categoryMatchId); + } + + public void testSearchMixedText() { + // Insert 5 entries (purposefully out of result order): + // - one waypoint which will match by description + // - one waypoint which won't match + // - one waypoint which will match by title + // - one track which won't match + // - one track which will match by title + long descriptionWaypointId = insertWaypoint("bb", "aa", "cc"); + insertWaypoint("bb", "cc", "dd"); + long titleWaypointId = insertWaypoint("aa", "bb", "cc"); + insertTrack("bb", "cc", "dd"); + long trackId = insertTrack("aa", "bb", "cc"); + + SearchQuery query = new SearchQuery("a", null, -1, NOW); + ArrayList results = new ArrayList(engine.search(query)); + + // Title > Description > Category. + assertEquals(results.toString(), 3, results.size()); + assertTrackResult(trackId, results.get(0)); + assertWaypointResult(titleWaypointId, results.get(1)); + assertWaypointResult(descriptionWaypointId, results.get(2)); + } + + public void testSearchTrackDistance() { + // All results match text, but they're at difference distances from the user. + long farFarAwayId = insertTrack("aa", 0.3); + long nearId = insertTrack("ab", 0.1); + long farId = insertTrack("ac", 0.2); + + SearchQuery query = new SearchQuery("a", HERE, -1, NOW); + ArrayList results = new ArrayList(engine.search(query)); + + // Distance order. + assertTrackResults(results, nearId, farId, farFarAwayId); + } + + public void testSearchWaypointDistance() { + // All results match text, but they're at difference distances from the user. + long farFarAwayId = insertWaypoint("aa", 0.3); + long nearId = insertWaypoint("ab", 0.1); + long farId = insertWaypoint("ac", 0.2); + + SearchQuery query = new SearchQuery("a", HERE, -1, NOW); + ArrayList results = new ArrayList(engine.search(query)); + + // Distance order. + assertWaypointResults(results, nearId, farId, farFarAwayId); + } + + public void testSearchTrackRecent() { + // All results match text, but they're were recorded at different times. + long oldestId = insertTrack("aa", 3); + long recentId = insertTrack("ab", 1); + long oldId = insertTrack("ac", 2); + + SearchQuery query = new SearchQuery("a", null, -1, NOW); + ArrayList results = new ArrayList(engine.search(query)); + + // Reverse time order. + assertTrackResults(results, recentId, oldId, oldestId); + } + + public void testSearchWaypointRecent() { + // All results match text, but they're were recorded at different times. + long oldestId = insertWaypoint("aa", 2); + long recentId = insertWaypoint("ab", 0); + long oldId = insertWaypoint("ac", 1); + + SearchQuery query = new SearchQuery("a", null, -1, NOW); + ArrayList results = new ArrayList(engine.search(query)); + + // Reverse time order. + assertWaypointResults(results, recentId, oldId, oldestId); + } + + public void testSearchCurrentTrack() { + // All results match text, but one of them is the current track. + long currentId = insertTrack("ab", 1); + long otherId = insertTrack("aa", 1); + + SearchQuery query = new SearchQuery("a", null, currentId, NOW); + ArrayList results = new ArrayList(engine.search(query)); + + // Current track should be demoted. + assertTrackResults(results, otherId, currentId); + } + + public void testSearchCurrentTrackWaypoint() { + // All results match text, but one of them is in the current track. + long otherId = insertWaypoint("aa", 1, 456); + long currentId = insertWaypoint("ab", 1, 123); + + SearchQuery query = new SearchQuery("a", null, 123, NOW); + ArrayList results = new ArrayList(engine.search(query)); + + // Waypoint in current track should be promoted. + assertWaypointResults(results, currentId, otherId); + } + + private void assertTrackResult(long trackId, ScoredResult result) { + assertNotNull("Not a track", result.track); + assertNull("Ambiguous result", result.waypoint); + assertEquals(trackId, result.track.getId()); + } + + private void assertTrackResults(List results, long... trackIds) { + String errMsg = "Expected IDs=" + Arrays.toString(trackIds) + "; results=" + results; + assertEquals(results.size(), trackIds.length); + for (int i = 0; i < results.size(); i++) { + ScoredResult result = results.get(i); + assertNotNull(errMsg, result.track); + assertNull(errMsg, result.waypoint); + assertEquals(errMsg, trackIds[i], result.track.getId()); + } + } + + private void assertWaypointResult(long waypointId, ScoredResult result) { + assertNotNull("Not a waypoint", result.waypoint); + assertNull("Ambiguous result", result.track); + assertEquals(waypointId, result.waypoint.getId()); + } + + private void assertWaypointResults(List results, long... waypointIds) { + String errMsg = "Expected IDs=" + Arrays.toString(waypointIds) + "; results=" + results; + assertEquals(results.size(), waypointIds.length); + for (int i = 0; i < results.size(); i++) { + ScoredResult result = results.get(i); + assertNotNull(errMsg, result.waypoint); + assertNull(errMsg, result.track); + assertEquals(errMsg, waypointIds[i], result.waypoint.getId()); + } } - } } diff --git a/src/androidTest/java/com/google/android/apps/mytracks/content/TrackDataHubTest.java b/src/androidTest/java/com/google/android/apps/mytracks/content/TrackDataHubTest.java index f2cefa3cc..b8015bc57 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/content/TrackDataHubTest.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/content/TrackDataHubTest.java @@ -1,12 +1,12 @@ /* * Copyright 2011 Google Inc. - * + * * 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 @@ -54,756 +54,755 @@ import static com.google.android.testing.mocking.AndroidMock.isA; /** * Tests for {@link TrackDataHub}. - * + * * @author Rodrigo Damazio */ @RunWith(AndroidJUnit4.class) public class TrackDataHubTest { - private static final long TRACK_ID = 42L; - private static final int TARGET_POINTS = 50; - - private MockContext context; - private SharedPreferences sharedPreferences; - private ContentProviderUtils contentProviderUtils; - private DataSource dataSource; - private TrackDataManager trackDataManager; - private TrackDataHub trackDataHub; - private TrackDataListener trackDataListener1; - private TrackDataListener trackDataListener2; - private Capture preferenceChangeListenerCapture = new Capture< - SharedPreferences.OnSharedPreferenceChangeListener>(); - - @Before - @UsesMocks({ ContentProviderUtils.class, DataSource.class, TrackDataListener.class }) - @Override - protected void setUp() { - context = new MockContext(new MockContentResolver(), new RenamingDelegatingContext( - getContext(), getContext(), "test.")); - sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); - contentProviderUtils = AndroidMock.createMock(ContentProviderUtils.class); - dataSource = AndroidMock.createMock(DataSource.class, context); - trackDataManager = new TrackDataManager(); - trackDataHub = new TrackDataHub( - context, trackDataManager, contentProviderUtils, TARGET_POINTS) { - @Override - protected DataSource newDataSource() { - return dataSource; - } - - @Override - protected void runInHanderThread(Runnable runnable) { - // Run everything in the same thread - runnable.run(); - } - }; - - trackDataListener1 = AndroidMock.createStrictMock( - "trackDataListener1", TrackDataListener.class); - trackDataListener2 = AndroidMock.createStrictMock( - "trackDataListener2", TrackDataListener.class); - PreferencesUtils.setLong(context, R.string.recording_track_id_key, TRACK_ID); - } - - @Override - protected void tearDown() throws Exception { - AndroidMock.reset(dataSource); - - // Expect everything to be unregistered. - dataSource.unregisterContentObserver(isA(ContentObserver.class)); - AndroidMock.expectLastCall().times(3); - dataSource.unregisterOnSharedPreferenceChangeListener( - isA(OnSharedPreferenceChangeListener.class)); - AndroidMock.replay(dataSource); - - trackDataHub.stop(); - trackDataHub = null; - super.tearDown(); - } - - /** - * Tests registering for tracks table update. - */ - public void testTracksTableUpdate() { - - // Register two listeners - Capture contentObserverCapture = new Capture(); - Track track = new Track(); - expect(contentProviderUtils.getTrack(TRACK_ID)).andStubReturn(track); - dataSource.registerContentObserver( - eq(TracksColumns.CONTENT_URI), capture(contentObserverCapture)); - trackDataListener1.onTrackUpdated(track); - trackDataListener2.onTrackUpdated(track); - replay(); - - trackDataHub.start(); - trackDataHub.loadTrack(TRACK_ID); - trackDataHub.registerTrackDataListener( - trackDataListener1, EnumSet.of(TrackDataType.TRACKS_TABLE)); - trackDataHub.registerTrackDataListener( - trackDataListener2, EnumSet.of(TrackDataType.TRACKS_TABLE)); - verifyAndReset(); - - // Causes tracks table update - ContentObserver contentObserver = contentObserverCapture.getValue(); - expect(contentProviderUtils.getTrack(TRACK_ID)).andStubReturn(track); - trackDataListener1.onTrackUpdated(track); - trackDataListener2.onTrackUpdated(track); - replay(); - - contentObserver.onChange(false); - verifyAndReset(); - - // Unregister one listener - expect(contentProviderUtils.getTrack(TRACK_ID)).andStubReturn(track); - trackDataListener2.onTrackUpdated(track); - replay(); - - trackDataHub.unregisterTrackDataListener(trackDataListener1); - contentObserver.onChange(false); - verifyAndReset(); - - // Unregister the second listener - dataSource.unregisterContentObserver(contentObserver); - replay(); - - trackDataHub.unregisterTrackDataListener(trackDataListener2); - contentObserver.onChange(false); - verifyAndReset(); - } - - /** - * Tests registering for waypoints table update. - */ - public void testWaypointsTableUpdate() { - Waypoint waypoint1 = new Waypoint(); - Waypoint waypoint2 = new Waypoint(); - Waypoint waypoint3 = new Waypoint(); - Waypoint waypoint4 = new Waypoint(); - Location location = new Location("gps"); - location.setLatitude(10.0); - location.setLongitude(8.0); - waypoint1.setLocation(location); - waypoint2.setLocation(location); - waypoint3.setLocation(location); - waypoint4.setLocation(location); - - // Register two listeners - Capture contentObserverCapture = new Capture(); - expect(contentProviderUtils.getWaypointCursor( - eq(TRACK_ID), AndroidMock.leq(-1L), eq(TrackDataHub.MAX_DISPLAYED_WAYPOINTS))) - .andStubAnswer(new FixedSizeCursorAnswer(2)); - expect(contentProviderUtils.createWaypoint(isA(Cursor.class))) - .andReturn(waypoint1).andReturn(waypoint2).andReturn(waypoint1).andReturn(waypoint2); - dataSource.registerContentObserver( - eq(WaypointsColumns.CONTENT_URI), capture(contentObserverCapture)); - trackDataListener1.clearWaypoints(); - trackDataListener2.clearWaypoints(); - trackDataListener1.onNewWaypoint(waypoint1); - trackDataListener2.onNewWaypoint(waypoint1); - trackDataListener1.onNewWaypoint(waypoint2); - trackDataListener2.onNewWaypoint(waypoint2); - trackDataListener1.onNewWaypointsDone(); - trackDataListener2.onNewWaypointsDone(); - replay(); - - trackDataHub.start(); - trackDataHub.loadTrack(TRACK_ID); - trackDataHub.registerTrackDataListener( - trackDataListener1, EnumSet.of(TrackDataType.WAYPOINTS_TABLE)); - trackDataHub.registerTrackDataListener( - trackDataListener2, EnumSet.of(TrackDataType.WAYPOINTS_TABLE)); - verifyAndReset(); - - // Cause waypoints table update - ContentObserver contentObserver = contentObserverCapture.getValue(); - expect(contentProviderUtils.getWaypointCursor( - eq(TRACK_ID), AndroidMock.leq(-1L), eq(TrackDataHub.MAX_DISPLAYED_WAYPOINTS))) - .andStubAnswer(new FixedSizeCursorAnswer(3)); - expect(contentProviderUtils.createWaypoint(isA(Cursor.class))) - .andReturn(waypoint1).andReturn(waypoint2).andReturn(waypoint3); - trackDataListener1.clearWaypoints(); - trackDataListener2.clearWaypoints(); - trackDataListener1.onNewWaypoint(waypoint1); - trackDataListener2.onNewWaypoint(waypoint1); - trackDataListener1.onNewWaypoint(waypoint2); - trackDataListener2.onNewWaypoint(waypoint2); - trackDataListener1.onNewWaypoint(waypoint3); - trackDataListener2.onNewWaypoint(waypoint3); - trackDataListener1.onNewWaypointsDone(); - trackDataListener2.onNewWaypointsDone(); - replay(); - - contentObserver.onChange(false); - verifyAndReset(); - - // Unregister one listener - expect(contentProviderUtils.getWaypointCursor( - eq(TRACK_ID), AndroidMock.leq(-1L), eq(TrackDataHub.MAX_DISPLAYED_WAYPOINTS))) - .andStubAnswer(new FixedSizeCursorAnswer(4)); - expect(contentProviderUtils.createWaypoint(isA(Cursor.class))) - .andReturn(waypoint1).andReturn(waypoint2).andReturn(waypoint3).andReturn(waypoint4); - trackDataListener2.clearWaypoints(); - trackDataListener2.onNewWaypoint(waypoint1); - trackDataListener2.onNewWaypoint(waypoint2); - trackDataListener2.onNewWaypoint(waypoint3); - trackDataListener2.onNewWaypoint(waypoint4); - trackDataListener2.onNewWaypointsDone(); - replay(); - - trackDataHub.unregisterTrackDataListener(trackDataListener1); - contentObserver.onChange(false); - verifyAndReset(); - - // Unregister the second listener - dataSource.unregisterContentObserver(contentObserver); - replay(); - - trackDataHub.unregisterTrackDataListener(trackDataListener2); - contentObserver.onChange(false); - verifyAndReset(); - } - - /** - * Tests track points table update. - */ - public void testTrackPointsTableUpdate() { - // Register one listener - Capture contentObserverCapture = new Capture(); - dataSource.registerContentObserver( - eq(TrackPointsColumns.CONTENT_URI), capture(contentObserverCapture)); - - FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5); - expect(contentProviderUtils.getTrackPointLocationIterator( - eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator); - expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(10L); - trackDataListener1.clearTrackPoints(); - locationIterator.expectLocationsDelivered(trackDataListener1); - trackDataListener1.onNewTrackPointsDone(); - replay(); - - trackDataHub.start(); - trackDataHub.loadTrack(TRACK_ID); - trackDataHub.registerTrackDataListener( - trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE)); - verifyAndReset(); - - // Register a second listener - locationIterator = new FixedSizeLocationIterator(1, 10, 5); - expect(contentProviderUtils.getTrackPointLocationIterator( - eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator); - expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(10L); - trackDataListener2.clearTrackPoints(); - locationIterator.expectLocationsDelivered(trackDataListener2); - trackDataListener2.onNewTrackPointsDone(); - replay(); - - trackDataHub.registerTrackDataListener( - trackDataListener2, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE)); - verifyAndReset(); - - // Deliver more points - should go to both listeners without clearing - ContentObserver contentObserver = contentObserverCapture.getValue(); - locationIterator = new FixedSizeLocationIterator(11, 10, 1); - expect(contentProviderUtils.getTrackPointLocationIterator( - eq(TRACK_ID), eq(11L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator); - expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(20L); - locationIterator.expectLocationsDelivered(trackDataListener1); - locationIterator.expectLocationsDelivered(trackDataListener2); - trackDataListener1.onNewTrackPointsDone(); - trackDataListener2.onNewTrackPointsDone(); - replay(); - - contentObserver.onChange(false); - verifyAndReset(); - - // Unregister one listener and change track - locationIterator = new FixedSizeLocationIterator(101, 10); - expect(contentProviderUtils.getTrackPointLocationIterator( - eq(TRACK_ID + 1), eq(0L), eq(false), isA(LocationFactory.class))) - .andReturn(locationIterator); - expect(contentProviderUtils.getLastTrackPointId(TRACK_ID + 1)).andReturn(110L); - trackDataListener2.clearTrackPoints(); - locationIterator.expectLocationsDelivered(trackDataListener2); - trackDataListener2.onNewTrackPointsDone(); - replay(); - - trackDataHub.unregisterTrackDataListener(trackDataListener1); - trackDataHub.loadTrack(TRACK_ID + 1); - verifyAndReset(); - } - - /** - * Tests track points table update with registering the same listener. - */ - public void testTrackPointsTableUpdate_reRegister() { - - // Register one listener - Capture contentObserverCapture = new Capture(); - dataSource.registerContentObserver( - eq(TrackPointsColumns.CONTENT_URI), capture(contentObserverCapture)); - - FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5); - expect(contentProviderUtils.getTrackPointLocationIterator( - eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator); - expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(10L); - - trackDataListener1.clearTrackPoints(); - locationIterator.expectLocationsDelivered(trackDataListener1); - trackDataListener1.onNewTrackPointsDone(); - replay(); - - trackDataHub.start(); - trackDataHub.loadTrack(TRACK_ID); - trackDataHub.registerTrackDataListener( - trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE)); - verifyAndReset(); - - // Unregister the listener - ContentObserver observer = contentObserverCapture.getValue(); - dataSource.unregisterContentObserver(observer); - replay(); - - trackDataHub.unregisterTrackDataListener(trackDataListener1); - verifyAndReset(); - - // Register again - dataSource.registerContentObserver( - eq(TrackPointsColumns.CONTENT_URI), capture(contentObserverCapture)); - locationIterator = new FixedSizeLocationIterator(1, 10, 5); - expect(contentProviderUtils.getTrackPointLocationIterator( - eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator); - expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(10L); - trackDataListener1.clearTrackPoints(); - locationIterator.expectLocationsDelivered(trackDataListener1); - trackDataListener1.onNewTrackPointsDone(); - replay(); - - trackDataHub.registerTrackDataListener( - trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE)); - verifyAndReset(); - } - - /** - * Tests tracks point able change. Register a listener after a track change. - */ - public void testTrackPointsTableUpdate_reRegisterAfterTrackChange() { - - // Register one listener - Capture observerCapture = new Capture(); - dataSource.registerContentObserver( - eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture)); - - FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5); - expect(contentProviderUtils.getTrackPointLocationIterator( - eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator); - expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(10L); - trackDataListener1.clearTrackPoints(); - locationIterator.expectLocationsDelivered(trackDataListener1); - trackDataListener1.onNewTrackPointsDone(); - replay(); - - trackDataHub.start(); - trackDataHub.loadTrack(TRACK_ID); - trackDataHub.registerTrackDataListener( - trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE)); - verifyAndReset(); - - // Unregister the listener - ContentObserver observer = observerCapture.getValue(); - dataSource.unregisterContentObserver(observer); - replay(); - - trackDataHub.unregisterTrackDataListener(trackDataListener1); - verifyAndReset(); - - // Register the listener after a new track - dataSource.registerContentObserver( - eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture)); - locationIterator = new FixedSizeLocationIterator(1, 10); - expect(contentProviderUtils.getTrackPointLocationIterator( - eq(TRACK_ID + 1), eq(0L), eq(false), isA(LocationFactory.class))) - .andReturn(locationIterator); - expect(contentProviderUtils.getLastTrackPointId(TRACK_ID + 1)).andReturn(10L); - trackDataListener1.clearTrackPoints(); - locationIterator.expectLocationsDelivered(trackDataListener1); - trackDataListener1.onNewTrackPointsDone(); - replay(); - - trackDataHub.loadTrack(TRACK_ID + 1); - trackDataHub.registerTrackDataListener( - trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE)); - verifyAndReset(); - } - - /** - * Tests track points table update with large track sampling. - */ - public void testTrackPointsTableUpdate_largeTrackSampling() { - Capture contentObserverCapture = new Capture(); - dataSource.registerContentObserver( - eq(TrackPointsColumns.CONTENT_URI), capture(contentObserverCapture)); - - expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(200L); - AndroidMock.expectLastCall().anyTimes(); - FixedSizeLocationIterator locationIterator1 = new FixedSizeLocationIterator( - 1, 200, 4, 25, 71, 120); - expect(contentProviderUtils.getTrackPointLocationIterator( - eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator1); - FixedSizeLocationIterator locationIterator2 = new FixedSizeLocationIterator( - 1, 200, 4, 25, 71, 120); - expect(contentProviderUtils.getTrackPointLocationIterator( - eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator2); - - trackDataListener1.clearTrackPoints(); - locationIterator1.expectSampledLocationsDelivered(trackDataListener1, 4, false); - trackDataListener1.onNewTrackPointsDone(); - trackDataListener2.clearTrackPoints(); - locationIterator2.expectSampledLocationsDelivered(trackDataListener2, 4, true); - trackDataListener2.onNewTrackPointsDone(); - replay(); - - trackDataHub.start(); - trackDataHub.loadTrack(TRACK_ID); - trackDataHub.registerTrackDataListener( - trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE)); - trackDataHub.registerTrackDataListener(trackDataListener2, EnumSet.of( - TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE, TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE)); - verifyAndReset(); - } - - /** - * Tests track points table update with resampling. - */ - public void testTrackPointsTableUpdate_resampling() { - Capture observerCapture = new Capture(); - dataSource.registerContentObserver( - eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture)); - - // Deliver 30 points (no sampling happens) - FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 30, 5); - expect(contentProviderUtils.getTrackPointLocationIterator( - eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator); - expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(30L); - - trackDataListener1.clearTrackPoints(); - locationIterator.expectLocationsDelivered(trackDataListener1); - trackDataListener1.onNewTrackPointsDone(); - replay(); - - trackDataHub.start(); - trackDataHub.loadTrack(TRACK_ID); - trackDataHub.registerTrackDataListener( - trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE)); - verifyAndReset(); - - // Now deliver 30 more (incrementally sampled) - ContentObserver observer = observerCapture.getValue(); - locationIterator = new FixedSizeLocationIterator(31, 30); - expect(contentProviderUtils.getTrackPointLocationIterator( - eq(TRACK_ID), eq(31L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator); - expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(60L); - locationIterator.expectSampledLocationsDelivered(trackDataListener1, 2, false); - trackDataListener1.onNewTrackPointsDone(); - replay(); - - observer.onChange(false); - verifyAndReset(); - - // Now another 30 (triggers resampling) - locationIterator = new FixedSizeLocationIterator(1, 90); - expect(contentProviderUtils.getTrackPointLocationIterator( - eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator); - expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(90L); - trackDataListener1.clearTrackPoints(); - locationIterator.expectSampledLocationsDelivered(trackDataListener1, 2, false); - trackDataListener1.onNewTrackPointsDone(); - replay(); - - observer.onChange(false); - verifyAndReset(); - } - - /** - * Tests preferences change. - */ - public void testPreferencesChange() throws Exception { - - // Register two listeners - PreferencesUtils.setString( - context, R.string.stats_rate_key, PreferencesUtils.STATS_RATE_DEFAULT); - PreferencesUtils.setString( - context, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT); - PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, - PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT); - PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, - PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT); - - dataSource.registerOnSharedPreferenceChangeListener(capture(preferenceChangeListenerCapture)); - expect(trackDataListener1.onMetricUnitsChanged(true)).andReturn(false); - expect(trackDataListener1.onReportSpeedChanged(true)).andReturn(false); - expect( - trackDataListener1.onRecordingGpsAccuracy(PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT)) - .andReturn(false); - expect(trackDataListener1.onRecordingDistanceIntervalChanged( - PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT)).andReturn(false); - expect(trackDataListener1.onMapTypeChanged(PreferencesUtils.MAP_TYPE_DEFAULT)).andReturn(false); - - expect(trackDataListener2.onMetricUnitsChanged(true)).andReturn(false); - expect(trackDataListener2.onReportSpeedChanged(true)).andReturn(false); - expect( - trackDataListener2.onRecordingGpsAccuracy(PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT)) - .andReturn(false); - expect(trackDataListener2.onRecordingDistanceIntervalChanged( - PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT)).andReturn(false); - expect(trackDataListener2.onMapTypeChanged(PreferencesUtils.MAP_TYPE_DEFAULT)).andReturn(false); - replay(); - - trackDataHub.start(); - trackDataHub.registerTrackDataListener( - trackDataListener1, EnumSet.of(TrackDataType.PREFERENCE)); - trackDataHub.registerTrackDataListener( - trackDataListener2, EnumSet.of(TrackDataType.PREFERENCE)); - verifyAndReset(); - - // Change report speed to false - expect(trackDataListener1.onReportSpeedChanged(false)).andReturn(false); - expect(trackDataListener2.onReportSpeedChanged(false)).andReturn(false); - replay(); - - PreferencesUtils.setString( - context, R.string.stats_rate_key, context.getString(R.string.stats_rate_pace)); - OnSharedPreferenceChangeListener listener = preferenceChangeListenerCapture.getValue(); - listener.onSharedPreferenceChanged( - sharedPreferences, PreferencesUtils.getKey(context, R.string.stats_rate_key)); - verifyAndReset(); - - // Change metric units to false - expect(trackDataListener1.onMetricUnitsChanged(false)).andReturn(false); - expect(trackDataListener2.onMetricUnitsChanged(false)).andReturn(false); - replay(); - - String imperialUnits = context.getString(R.string.stats_units_imperial); - PreferencesUtils.setString(context, R.string.stats_units_key, imperialUnits); - listener.onSharedPreferenceChanged( - sharedPreferences, PreferencesUtils.getKey(context, R.string.stats_units_key)); - verifyAndReset(); - } - - /** - * Replays mocks. - */ - private void replay() { - AndroidMock.replay(contentProviderUtils, dataSource, trackDataListener1, trackDataListener2); - } - - /** - * Verifies and resets mocks. - */ - private void verifyAndReset() { - AndroidMock.verify(contentProviderUtils, dataSource, trackDataListener1, trackDataListener2); - AndroidMock.reset(contentProviderUtils, dataSource, trackDataListener1, trackDataListener2); - } - - /** - * Fixed size cursor answer. - * - * @author Jimmy Shih - */ - private static class FixedSizeCursorAnswer implements IAnswer { - private final int size; - - public FixedSizeCursorAnswer(int size) { - this.size = size; + private static final long TRACK_ID = 42L; + private static final int TARGET_POINTS = 50; + + private MockContext context; + private SharedPreferences sharedPreferences; + private ContentProviderUtils contentProviderUtils; + private DataSource dataSource; + private TrackDataManager trackDataManager; + private TrackDataHub trackDataHub; + private TrackDataListener trackDataListener1; + private TrackDataListener trackDataListener2; + private Capture preferenceChangeListenerCapture = new Capture< + SharedPreferences.OnSharedPreferenceChangeListener>(); + + @Before + @UsesMocks({ContentProviderUtils.class, DataSource.class, TrackDataListener.class}) + @Override + protected void setUp() { + context = new MockContext(new MockContentResolver(), new RenamingDelegatingContext( + getContext(), getContext(), "test.")); + sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); + contentProviderUtils = AndroidMock.createMock(ContentProviderUtils.class); + dataSource = AndroidMock.createMock(DataSource.class, context); + trackDataManager = new TrackDataManager(); + trackDataHub = new TrackDataHub( + context, trackDataManager, contentProviderUtils, TARGET_POINTS) { + @Override + protected DataSource newDataSource() { + return dataSource; + } + + @Override + protected void runInHanderThread(Runnable runnable) { + // Run everything in the same thread + runnable.run(); + } + }; + + trackDataListener1 = AndroidMock.createStrictMock( + "trackDataListener1", TrackDataListener.class); + trackDataListener2 = AndroidMock.createStrictMock( + "trackDataListener2", TrackDataListener.class); + PreferencesUtils.setLong(context, R.string.recording_track_id_key, TRACK_ID); } @Override - public Cursor answer() throws Throwable { - MatrixCursor cursor = new MatrixCursor(new String[] { BaseColumns._ID }); - for (long i = 0; i < size; i++) { - cursor.addRow(new Object[] { i }); - } - return cursor; - } - } + protected void tearDown() throws Exception { + AndroidMock.reset(dataSource); - /** - * Fixed size location iterator. - * - * @author Jimmy Shih - */ - private static class FixedSizeLocationIterator implements LocationIterator { - private final long startId; - private final Location[] locations; - private final Set splitIndexSet = new HashSet(); - private int currentIndex = -1; + // Expect everything to be unregistered. + dataSource.unregisterContentObserver(isA(ContentObserver.class)); + AndroidMock.expectLastCall().times(3); + dataSource.unregisterOnSharedPreferenceChangeListener( + isA(OnSharedPreferenceChangeListener.class)); + AndroidMock.replay(dataSource); - public FixedSizeLocationIterator(long startId, int size) { - this(startId, size, null); + trackDataHub.stop(); + trackDataHub = null; + super.tearDown(); } - public FixedSizeLocationIterator(long startId, int size, int... splitIndexes) { - this.startId = startId; - this.locations = new Location[size]; + /** + * Tests registering for tracks table update. + */ + public void testTracksTableUpdate() { - for (int i = 0; i < size; i++) { + // Register two listeners + Capture contentObserverCapture = new Capture(); + Track track = new Track(); + expect(contentProviderUtils.getTrack(TRACK_ID)).andStubReturn(track); + dataSource.registerContentObserver( + eq(TracksColumns.CONTENT_URI), capture(contentObserverCapture)); + trackDataListener1.onTrackUpdated(track); + trackDataListener2.onTrackUpdated(track); + replay(); + + trackDataHub.start(); + trackDataHub.loadTrack(TRACK_ID); + trackDataHub.registerTrackDataListener( + trackDataListener1, EnumSet.of(TrackDataType.TRACKS_TABLE)); + trackDataHub.registerTrackDataListener( + trackDataListener2, EnumSet.of(TrackDataType.TRACKS_TABLE)); + verifyAndReset(); + + // Causes tracks table update + ContentObserver contentObserver = contentObserverCapture.getValue(); + expect(contentProviderUtils.getTrack(TRACK_ID)).andStubReturn(track); + trackDataListener1.onTrackUpdated(track); + trackDataListener2.onTrackUpdated(track); + replay(); + + contentObserver.onChange(false); + verifyAndReset(); + + // Unregister one listener + expect(contentProviderUtils.getTrack(TRACK_ID)).andStubReturn(track); + trackDataListener2.onTrackUpdated(track); + replay(); + + trackDataHub.unregisterTrackDataListener(trackDataListener1); + contentObserver.onChange(false); + verifyAndReset(); + + // Unregister the second listener + dataSource.unregisterContentObserver(contentObserver); + replay(); + + trackDataHub.unregisterTrackDataListener(trackDataListener2); + contentObserver.onChange(false); + verifyAndReset(); + } + + /** + * Tests registering for waypoints table update. + */ + public void testWaypointsTableUpdate() { + Waypoint waypoint1 = new Waypoint(); + Waypoint waypoint2 = new Waypoint(); + Waypoint waypoint3 = new Waypoint(); + Waypoint waypoint4 = new Waypoint(); Location location = new Location("gps"); - location.setLatitude(-15.0 + i / 1000.0); - location.setLongitude(37 + i / 1000.0); - location.setAltitude(i); - locations[i] = location; - } + location.setLatitude(10.0); + location.setLongitude(8.0); + waypoint1.setLocation(location); + waypoint2.setLocation(location); + waypoint3.setLocation(location); + waypoint4.setLocation(location); - if (splitIndexes != null) { - for (int splitIndex : splitIndexes) { - splitIndexSet.add(splitIndex); + // Register two listeners + Capture contentObserverCapture = new Capture(); + expect(contentProviderUtils.getWaypointCursor( + eq(TRACK_ID), AndroidMock.leq(-1L), eq(TrackDataHub.MAX_DISPLAYED_WAYPOINTS))) + .andStubAnswer(new FixedSizeCursorAnswer(2)); + expect(contentProviderUtils.createWaypoint(isA(Cursor.class))) + .andReturn(waypoint1).andReturn(waypoint2).andReturn(waypoint1).andReturn(waypoint2); + dataSource.registerContentObserver( + eq(WaypointsColumns.CONTENT_URI), capture(contentObserverCapture)); + trackDataListener1.clearWaypoints(); + trackDataListener2.clearWaypoints(); + trackDataListener1.onNewWaypoint(waypoint1); + trackDataListener2.onNewWaypoint(waypoint1); + trackDataListener1.onNewWaypoint(waypoint2); + trackDataListener2.onNewWaypoint(waypoint2); + trackDataListener1.onNewWaypointsDone(); + trackDataListener2.onNewWaypointsDone(); + replay(); - Location splitLocation = locations[splitIndex]; - splitLocation.setLatitude(100.0); - splitLocation.setLongitude(200.0); + trackDataHub.start(); + trackDataHub.loadTrack(TRACK_ID); + trackDataHub.registerTrackDataListener( + trackDataListener1, EnumSet.of(TrackDataType.WAYPOINTS_TABLE)); + trackDataHub.registerTrackDataListener( + trackDataListener2, EnumSet.of(TrackDataType.WAYPOINTS_TABLE)); + verifyAndReset(); + + // Cause waypoints table update + ContentObserver contentObserver = contentObserverCapture.getValue(); + expect(contentProviderUtils.getWaypointCursor( + eq(TRACK_ID), AndroidMock.leq(-1L), eq(TrackDataHub.MAX_DISPLAYED_WAYPOINTS))) + .andStubAnswer(new FixedSizeCursorAnswer(3)); + expect(contentProviderUtils.createWaypoint(isA(Cursor.class))) + .andReturn(waypoint1).andReturn(waypoint2).andReturn(waypoint3); + trackDataListener1.clearWaypoints(); + trackDataListener2.clearWaypoints(); + trackDataListener1.onNewWaypoint(waypoint1); + trackDataListener2.onNewWaypoint(waypoint1); + trackDataListener1.onNewWaypoint(waypoint2); + trackDataListener2.onNewWaypoint(waypoint2); + trackDataListener1.onNewWaypoint(waypoint3); + trackDataListener2.onNewWaypoint(waypoint3); + trackDataListener1.onNewWaypointsDone(); + trackDataListener2.onNewWaypointsDone(); + replay(); + + contentObserver.onChange(false); + verifyAndReset(); + + // Unregister one listener + expect(contentProviderUtils.getWaypointCursor( + eq(TRACK_ID), AndroidMock.leq(-1L), eq(TrackDataHub.MAX_DISPLAYED_WAYPOINTS))) + .andStubAnswer(new FixedSizeCursorAnswer(4)); + expect(contentProviderUtils.createWaypoint(isA(Cursor.class))) + .andReturn(waypoint1).andReturn(waypoint2).andReturn(waypoint3).andReturn(waypoint4); + trackDataListener2.clearWaypoints(); + trackDataListener2.onNewWaypoint(waypoint1); + trackDataListener2.onNewWaypoint(waypoint2); + trackDataListener2.onNewWaypoint(waypoint3); + trackDataListener2.onNewWaypoint(waypoint4); + trackDataListener2.onNewWaypointsDone(); + replay(); + + trackDataHub.unregisterTrackDataListener(trackDataListener1); + contentObserver.onChange(false); + verifyAndReset(); + + // Unregister the second listener + dataSource.unregisterContentObserver(contentObserver); + replay(); + + trackDataHub.unregisterTrackDataListener(trackDataListener2); + contentObserver.onChange(false); + verifyAndReset(); + } + + /** + * Tests track points table update. + */ + public void testTrackPointsTableUpdate() { + // Register one listener + Capture contentObserverCapture = new Capture(); + dataSource.registerContentObserver( + eq(TrackPointsColumns.CONTENT_URI), capture(contentObserverCapture)); + + FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5); + expect(contentProviderUtils.getTrackPointLocationIterator( + eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator); + expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(10L); + trackDataListener1.clearTrackPoints(); + locationIterator.expectLocationsDelivered(trackDataListener1); + trackDataListener1.onNewTrackPointsDone(); + replay(); + + trackDataHub.start(); + trackDataHub.loadTrack(TRACK_ID); + trackDataHub.registerTrackDataListener( + trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE)); + verifyAndReset(); + + // Register a second listener + locationIterator = new FixedSizeLocationIterator(1, 10, 5); + expect(contentProviderUtils.getTrackPointLocationIterator( + eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator); + expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(10L); + trackDataListener2.clearTrackPoints(); + locationIterator.expectLocationsDelivered(trackDataListener2); + trackDataListener2.onNewTrackPointsDone(); + replay(); + + trackDataHub.registerTrackDataListener( + trackDataListener2, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE)); + verifyAndReset(); + + // Deliver more points - should go to both listeners without clearing + ContentObserver contentObserver = contentObserverCapture.getValue(); + locationIterator = new FixedSizeLocationIterator(11, 10, 1); + expect(contentProviderUtils.getTrackPointLocationIterator( + eq(TRACK_ID), eq(11L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator); + expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(20L); + locationIterator.expectLocationsDelivered(trackDataListener1); + locationIterator.expectLocationsDelivered(trackDataListener2); + trackDataListener1.onNewTrackPointsDone(); + trackDataListener2.onNewTrackPointsDone(); + replay(); + + contentObserver.onChange(false); + verifyAndReset(); + + // Unregister one listener and change track + locationIterator = new FixedSizeLocationIterator(101, 10); + expect(contentProviderUtils.getTrackPointLocationIterator( + eq(TRACK_ID + 1), eq(0L), eq(false), isA(LocationFactory.class))) + .andReturn(locationIterator); + expect(contentProviderUtils.getLastTrackPointId(TRACK_ID + 1)).andReturn(110L); + trackDataListener2.clearTrackPoints(); + locationIterator.expectLocationsDelivered(trackDataListener2); + trackDataListener2.onNewTrackPointsDone(); + replay(); + + trackDataHub.unregisterTrackDataListener(trackDataListener1); + trackDataHub.loadTrack(TRACK_ID + 1); + verifyAndReset(); + } + + /** + * Tests track points table update with registering the same listener. + */ + public void testTrackPointsTableUpdate_reRegister() { + + // Register one listener + Capture contentObserverCapture = new Capture(); + dataSource.registerContentObserver( + eq(TrackPointsColumns.CONTENT_URI), capture(contentObserverCapture)); + + FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5); + expect(contentProviderUtils.getTrackPointLocationIterator( + eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator); + expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(10L); + + trackDataListener1.clearTrackPoints(); + locationIterator.expectLocationsDelivered(trackDataListener1); + trackDataListener1.onNewTrackPointsDone(); + replay(); + + trackDataHub.start(); + trackDataHub.loadTrack(TRACK_ID); + trackDataHub.registerTrackDataListener( + trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE)); + verifyAndReset(); + + // Unregister the listener + ContentObserver observer = contentObserverCapture.getValue(); + dataSource.unregisterContentObserver(observer); + replay(); + + trackDataHub.unregisterTrackDataListener(trackDataListener1); + verifyAndReset(); + + // Register again + dataSource.registerContentObserver( + eq(TrackPointsColumns.CONTENT_URI), capture(contentObserverCapture)); + locationIterator = new FixedSizeLocationIterator(1, 10, 5); + expect(contentProviderUtils.getTrackPointLocationIterator( + eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator); + expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(10L); + trackDataListener1.clearTrackPoints(); + locationIterator.expectLocationsDelivered(trackDataListener1); + trackDataListener1.onNewTrackPointsDone(); + replay(); + + trackDataHub.registerTrackDataListener( + trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE)); + verifyAndReset(); + } + + /** + * Tests tracks point able change. Register a listener after a track change. + */ + public void testTrackPointsTableUpdate_reRegisterAfterTrackChange() { + + // Register one listener + Capture observerCapture = new Capture(); + dataSource.registerContentObserver( + eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture)); + + FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5); + expect(contentProviderUtils.getTrackPointLocationIterator( + eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator); + expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(10L); + trackDataListener1.clearTrackPoints(); + locationIterator.expectLocationsDelivered(trackDataListener1); + trackDataListener1.onNewTrackPointsDone(); + replay(); + + trackDataHub.start(); + trackDataHub.loadTrack(TRACK_ID); + trackDataHub.registerTrackDataListener( + trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE)); + verifyAndReset(); + + // Unregister the listener + ContentObserver observer = observerCapture.getValue(); + dataSource.unregisterContentObserver(observer); + replay(); + + trackDataHub.unregisterTrackDataListener(trackDataListener1); + verifyAndReset(); + + // Register the listener after a new track + dataSource.registerContentObserver( + eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture)); + locationIterator = new FixedSizeLocationIterator(1, 10); + expect(contentProviderUtils.getTrackPointLocationIterator( + eq(TRACK_ID + 1), eq(0L), eq(false), isA(LocationFactory.class))) + .andReturn(locationIterator); + expect(contentProviderUtils.getLastTrackPointId(TRACK_ID + 1)).andReturn(10L); + trackDataListener1.clearTrackPoints(); + locationIterator.expectLocationsDelivered(trackDataListener1); + trackDataListener1.onNewTrackPointsDone(); + replay(); + + trackDataHub.loadTrack(TRACK_ID + 1); + trackDataHub.registerTrackDataListener( + trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE)); + verifyAndReset(); + } + + /** + * Tests track points table update with large track sampling. + */ + public void testTrackPointsTableUpdate_largeTrackSampling() { + Capture contentObserverCapture = new Capture(); + dataSource.registerContentObserver( + eq(TrackPointsColumns.CONTENT_URI), capture(contentObserverCapture)); + + expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(200L); + AndroidMock.expectLastCall().anyTimes(); + FixedSizeLocationIterator locationIterator1 = new FixedSizeLocationIterator( + 1, 200, 4, 25, 71, 120); + expect(contentProviderUtils.getTrackPointLocationIterator( + eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator1); + FixedSizeLocationIterator locationIterator2 = new FixedSizeLocationIterator( + 1, 200, 4, 25, 71, 120); + expect(contentProviderUtils.getTrackPointLocationIterator( + eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator2); + + trackDataListener1.clearTrackPoints(); + locationIterator1.expectSampledLocationsDelivered(trackDataListener1, 4, false); + trackDataListener1.onNewTrackPointsDone(); + trackDataListener2.clearTrackPoints(); + locationIterator2.expectSampledLocationsDelivered(trackDataListener2, 4, true); + trackDataListener2.onNewTrackPointsDone(); + replay(); + + trackDataHub.start(); + trackDataHub.loadTrack(TRACK_ID); + trackDataHub.registerTrackDataListener( + trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE)); + trackDataHub.registerTrackDataListener(trackDataListener2, EnumSet.of( + TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE, TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE)); + verifyAndReset(); + } + + /** + * Tests track points table update with resampling. + */ + public void testTrackPointsTableUpdate_resampling() { + Capture observerCapture = new Capture(); + dataSource.registerContentObserver( + eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture)); + + // Deliver 30 points (no sampling happens) + FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 30, 5); + expect(contentProviderUtils.getTrackPointLocationIterator( + eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator); + expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(30L); + + trackDataListener1.clearTrackPoints(); + locationIterator.expectLocationsDelivered(trackDataListener1); + trackDataListener1.onNewTrackPointsDone(); + replay(); + + trackDataHub.start(); + trackDataHub.loadTrack(TRACK_ID); + trackDataHub.registerTrackDataListener( + trackDataListener1, EnumSet.of(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE)); + verifyAndReset(); + + // Now deliver 30 more (incrementally sampled) + ContentObserver observer = observerCapture.getValue(); + locationIterator = new FixedSizeLocationIterator(31, 30); + expect(contentProviderUtils.getTrackPointLocationIterator( + eq(TRACK_ID), eq(31L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator); + expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(60L); + locationIterator.expectSampledLocationsDelivered(trackDataListener1, 2, false); + trackDataListener1.onNewTrackPointsDone(); + replay(); + + observer.onChange(false); + verifyAndReset(); + + // Now another 30 (triggers resampling) + locationIterator = new FixedSizeLocationIterator(1, 90); + expect(contentProviderUtils.getTrackPointLocationIterator( + eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class))).andReturn(locationIterator); + expect(contentProviderUtils.getLastTrackPointId(TRACK_ID)).andReturn(90L); + trackDataListener1.clearTrackPoints(); + locationIterator.expectSampledLocationsDelivered(trackDataListener1, 2, false); + trackDataListener1.onNewTrackPointsDone(); + replay(); + + observer.onChange(false); + verifyAndReset(); + } + + /** + * Tests preferences change. + */ + public void testPreferencesChange() throws Exception { + + // Register two listeners + PreferencesUtils.setString( + context, R.string.stats_rate_key, PreferencesUtils.STATS_RATE_DEFAULT); + PreferencesUtils.setString( + context, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT); + PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, + PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT); + PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, + PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT); + + dataSource.registerOnSharedPreferenceChangeListener(capture(preferenceChangeListenerCapture)); + expect(trackDataListener1.onMetricUnitsChanged(true)).andReturn(false); + expect(trackDataListener1.onReportSpeedChanged(true)).andReturn(false); + expect( + trackDataListener1.onRecordingGpsAccuracy(PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT)) + .andReturn(false); + expect(trackDataListener1.onRecordingDistanceIntervalChanged( + PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT)).andReturn(false); + expect(trackDataListener1.onMapTypeChanged(PreferencesUtils.MAP_TYPE_DEFAULT)).andReturn(false); + + expect(trackDataListener2.onMetricUnitsChanged(true)).andReturn(false); + expect(trackDataListener2.onReportSpeedChanged(true)).andReturn(false); + expect( + trackDataListener2.onRecordingGpsAccuracy(PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT)) + .andReturn(false); + expect(trackDataListener2.onRecordingDistanceIntervalChanged( + PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT)).andReturn(false); + expect(trackDataListener2.onMapTypeChanged(PreferencesUtils.MAP_TYPE_DEFAULT)).andReturn(false); + replay(); + + trackDataHub.start(); + trackDataHub.registerTrackDataListener( + trackDataListener1, EnumSet.of(TrackDataType.PREFERENCE)); + trackDataHub.registerTrackDataListener( + trackDataListener2, EnumSet.of(TrackDataType.PREFERENCE)); + verifyAndReset(); + + // Change report speed to false + expect(trackDataListener1.onReportSpeedChanged(false)).andReturn(false); + expect(trackDataListener2.onReportSpeedChanged(false)).andReturn(false); + replay(); + + PreferencesUtils.setString( + context, R.string.stats_rate_key, context.getString(R.string.stats_rate_pace)); + OnSharedPreferenceChangeListener listener = preferenceChangeListenerCapture.getValue(); + listener.onSharedPreferenceChanged( + sharedPreferences, PreferencesUtils.getKey(context, R.string.stats_rate_key)); + verifyAndReset(); + + // Change metric units to false + expect(trackDataListener1.onMetricUnitsChanged(false)).andReturn(false); + expect(trackDataListener2.onMetricUnitsChanged(false)).andReturn(false); + replay(); + + String imperialUnits = context.getString(R.string.stats_units_imperial); + PreferencesUtils.setString(context, R.string.stats_units_key, imperialUnits); + listener.onSharedPreferenceChanged( + sharedPreferences, PreferencesUtils.getKey(context, R.string.stats_units_key)); + verifyAndReset(); + } + + /** + * Replays mocks. + */ + private void replay() { + AndroidMock.replay(contentProviderUtils, dataSource, trackDataListener1, trackDataListener2); + } + + /** + * Verifies and resets mocks. + */ + private void verifyAndReset() { + AndroidMock.verify(contentProviderUtils, dataSource, trackDataListener1, trackDataListener2); + AndroidMock.reset(contentProviderUtils, dataSource, trackDataListener1, trackDataListener2); + } + + /** + * Tests the method {@link TrackDataHub#start()}. + */ + public void testRegisterTracksTableListener() { + Capture observerCapture = new Capture(); + dataSource.registerContentObserver(eq(TracksColumns.CONTENT_URI), capture(observerCapture)); + Track track = TrackStubUtils.createTrack(1); + expect(contentProviderUtils.getTrack(capture(new Capture()))).andReturn(track); + // Make the track id is unique. + PreferencesUtils.setLong(context, R.string.recording_track_id_key, System.currentTimeMillis()); + trackDataListener1.onTrackUpdated(track); + replay(); + trackDataHub.start(); + trackDataHub.registerTrackDataListener( + trackDataListener1, EnumSet.of(TrackDataType.TRACKS_TABLE)); + verifyAndReset(); + } + + /** + * Tests the method {@link TrackDataHub#start()}. + */ + public void testRegisterWaypointsTableListener() { + Capture observerCapture = new Capture(); + dataSource.registerContentObserver(eq(WaypointsColumns.CONTENT_URI), capture(observerCapture)); + expect(contentProviderUtils.getWaypointCursor( + capture(new Capture()), capture(new Capture()), + capture(new Capture()))).andReturn(null); + trackDataListener1.clearWaypoints(); + trackDataListener1.onNewWaypointsDone(); + replay(); + trackDataHub.start(); + trackDataHub.registerTrackDataListener( + trackDataListener1, EnumSet.of(TrackDataType.WAYPOINTS_TABLE)); + verifyAndReset(); + } + + /** + * Tests the method {@link TrackDataHub#notifyPreferenceChanged(String)} when + * the key is R.string.min_required_accuracy_key. + */ + public void testNotifyPreferenceChanged_minRequiredAccuracy() { + int value = 1; + PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, value); + trackDataHub.notifyPreferenceChanged(PreferencesUtils + .getKey(context, R.string.recording_gps_accuracy_key)); + assertEquals(value, trackDataHub.getRecordingGpsAccuracy()); + PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, value + 1); + trackDataHub.notifyPreferenceChanged(PreferencesUtils + .getKey(context, R.string.recording_gps_accuracy_key)); + assertEquals(value + 1, trackDataHub.getRecordingGpsAccuracy()); + } + + /** + * Tests the method {@link TrackDataHub#notifyPreferenceChanged(String)} when + * the key is R.string.metric_units_key. + */ + public void testNotifyPreferenceChanged_metricUnitsNoNotify() { + String imperialUnits = context.getString(R.string.stats_units_imperial); + + PreferencesUtils.setString(context, R.string.stats_units_key, imperialUnits); + trackDataHub.notifyPreferenceChanged( + PreferencesUtils.getKey(context, R.string.stats_units_key)); + assertEquals(false, trackDataHub.isMetricUnits()); + PreferencesUtils.setString( + context, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT); + trackDataHub.notifyPreferenceChanged( + PreferencesUtils.getKey(context, R.string.stats_units_key)); + assertEquals(true, trackDataHub.isMetricUnits()); + } + + /** + * Tests the method {@link TrackDataHub#notifyPreferenceChanged(String)} when + * the key is R.string.metric_units_key. + */ + public void testNotifyPreferenceChanged_reportSpeedNoNotify() { + String value = context.getString(R.string.stats_rate_pace); + PreferencesUtils.setString(context, R.string.stats_rate_key, value); + trackDataHub.notifyPreferenceChanged(PreferencesUtils + .getKey(context, R.string.stats_rate_key)); + assertEquals(false, trackDataHub.isReportSpeed()); + + value = context.getString(R.string.stats_rate_speed); + PreferencesUtils.setString(context, R.string.stats_rate_key, value); + trackDataHub.notifyPreferenceChanged(PreferencesUtils + .getKey(context, R.string.stats_rate_key)); + assertEquals(true, trackDataHub.isReportSpeed()); + } + + /** + * Fixed size cursor answer. + * + * @author Jimmy Shih + */ + private static class FixedSizeCursorAnswer implements IAnswer { + private final int size; + + public FixedSizeCursorAnswer(int size) { + this.size = size; } - } - } - public void expectLocationsDelivered(TrackDataListener listener) { - for (int i = 0; i < locations.length; i++) { - if (splitIndexSet.contains(i)) { - listener.onSegmentSplit(locations[i]); - } else { - listener.onSampledInTrackPoint(locations[i]); + @Override + public Cursor answer() throws Throwable { + MatrixCursor cursor = new MatrixCursor(new String[]{BaseColumns._ID}); + for (long i = 0; i < size; i++) { + cursor.addRow(new Object[]{i}); + } + return cursor; } - } } - public void expectSampledLocationsDelivered( - TrackDataListener listener, int sampleFrequency, boolean includeSampledOut) { - boolean includeNext = false; - for (int i = 0; i < locations.length; i++) { - if (splitIndexSet.contains(i)) { - listener.onSegmentSplit(locations[i]); - includeNext = true; - } else if (includeNext || (i % sampleFrequency == 0)) { - listener.onSampledInTrackPoint(locations[i]); - includeNext = false; - } else if (includeSampledOut) { - listener.onSampledOutTrackPoint(locations[i]); + /** + * Fixed size location iterator. + * + * @author Jimmy Shih + */ + private static class FixedSizeLocationIterator implements LocationIterator { + private final long startId; + private final Location[] locations; + private final Set splitIndexSet = new HashSet(); + private int currentIndex = -1; + + public FixedSizeLocationIterator(long startId, int size) { + this(startId, size, null); + } + + public FixedSizeLocationIterator(long startId, int size, int... splitIndexes) { + this.startId = startId; + this.locations = new Location[size]; + + for (int i = 0; i < size; i++) { + Location location = new Location("gps"); + location.setLatitude(-15.0 + i / 1000.0); + location.setLongitude(37 + i / 1000.0); + location.setAltitude(i); + locations[i] = location; + } + + if (splitIndexes != null) { + for (int splitIndex : splitIndexes) { + splitIndexSet.add(splitIndex); + + Location splitLocation = locations[splitIndex]; + splitLocation.setLatitude(100.0); + splitLocation.setLongitude(200.0); + } + } + } + + public void expectLocationsDelivered(TrackDataListener listener) { + for (int i = 0; i < locations.length; i++) { + if (splitIndexSet.contains(i)) { + listener.onSegmentSplit(locations[i]); + } else { + listener.onSampledInTrackPoint(locations[i]); + } + } + } + + public void expectSampledLocationsDelivered( + TrackDataListener listener, int sampleFrequency, boolean includeSampledOut) { + boolean includeNext = false; + for (int i = 0; i < locations.length; i++) { + if (splitIndexSet.contains(i)) { + listener.onSegmentSplit(locations[i]); + includeNext = true; + } else if (includeNext || (i % sampleFrequency == 0)) { + listener.onSampledInTrackPoint(locations[i]); + includeNext = false; + } else if (includeSampledOut) { + listener.onSampledOutTrackPoint(locations[i]); + } + } + } + + @Override + public boolean hasNext() { + return currentIndex < locations.length - 1; + } + + @Override + public Location next() { + currentIndex++; + return locations[currentIndex]; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public long getLocationId() { + return startId + currentIndex; + } + + @Override + public void close() { + // Do nothing } - } } - - @Override - public boolean hasNext() { - return currentIndex < locations.length - 1; - } - - @Override - public Location next() { - currentIndex++; - return locations[currentIndex]; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - @Override - public long getLocationId() { - return startId + currentIndex; - } - - @Override - public void close() { - // Do nothing - } - } - - /** - * Tests the method {@link TrackDataHub#start()}. - */ - public void testRegisterTracksTableListener() { - Capture observerCapture = new Capture(); - dataSource.registerContentObserver(eq(TracksColumns.CONTENT_URI), capture(observerCapture)); - Track track = TrackStubUtils.createTrack(1); - expect(contentProviderUtils.getTrack(capture(new Capture()))).andReturn(track); - // Make the track id is unique. - PreferencesUtils.setLong(context, R.string.recording_track_id_key, System.currentTimeMillis()); - trackDataListener1.onTrackUpdated(track); - replay(); - trackDataHub.start(); - trackDataHub.registerTrackDataListener( - trackDataListener1, EnumSet.of(TrackDataType.TRACKS_TABLE)); - verifyAndReset(); - } - - /** - * Tests the method {@link TrackDataHub#start()}. - */ - public void testRegisterWaypointsTableListener() { - Capture observerCapture = new Capture(); - dataSource.registerContentObserver(eq(WaypointsColumns.CONTENT_URI), capture(observerCapture)); - expect(contentProviderUtils.getWaypointCursor( - capture(new Capture()), capture(new Capture()), - capture(new Capture()))).andReturn(null); - trackDataListener1.clearWaypoints(); - trackDataListener1.onNewWaypointsDone(); - replay(); - trackDataHub.start(); - trackDataHub.registerTrackDataListener( - trackDataListener1, EnumSet.of(TrackDataType.WAYPOINTS_TABLE)); - verifyAndReset(); - } - - /** - * Tests the method {@link TrackDataHub#notifyPreferenceChanged(String)} when - * the key is R.string.min_required_accuracy_key. - */ - public void testNotifyPreferenceChanged_minRequiredAccuracy() { - int value = 1; - PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, value); - trackDataHub.notifyPreferenceChanged(PreferencesUtils - .getKey(context, R.string.recording_gps_accuracy_key)); - assertEquals(value, trackDataHub.getRecordingGpsAccuracy()); - PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, value + 1); - trackDataHub.notifyPreferenceChanged(PreferencesUtils - .getKey(context, R.string.recording_gps_accuracy_key)); - assertEquals(value + 1, trackDataHub.getRecordingGpsAccuracy()); - } - - /** - * Tests the method {@link TrackDataHub#notifyPreferenceChanged(String)} when - * the key is R.string.metric_units_key. - */ - public void testNotifyPreferenceChanged_metricUnitsNoNotify() { - String imperialUnits = context.getString(R.string.stats_units_imperial); - - PreferencesUtils.setString(context, R.string.stats_units_key, imperialUnits); - trackDataHub.notifyPreferenceChanged( - PreferencesUtils.getKey(context, R.string.stats_units_key)); - assertEquals(false, trackDataHub.isMetricUnits()); - PreferencesUtils.setString( - context, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT); - trackDataHub.notifyPreferenceChanged( - PreferencesUtils.getKey(context, R.string.stats_units_key)); - assertEquals(true, trackDataHub.isMetricUnits()); - } - - - /** - * Tests the method {@link TrackDataHub#notifyPreferenceChanged(String)} when - * the key is R.string.metric_units_key. - */ - public void testNotifyPreferenceChanged_reportSpeedNoNotify() { - String value = context.getString(R.string.stats_rate_pace); - PreferencesUtils.setString(context, R.string.stats_rate_key, value); - trackDataHub.notifyPreferenceChanged(PreferencesUtils - .getKey(context, R.string.stats_rate_key)); - assertEquals(false, trackDataHub.isReportSpeed()); - - value = context.getString(R.string.stats_rate_speed); - PreferencesUtils.setString(context, R.string.stats_rate_key, value); - trackDataHub.notifyPreferenceChanged(PreferencesUtils - .getKey(context, R.string.stats_rate_key)); - assertEquals(true, trackDataHub.isReportSpeed()); - } } diff --git a/src/androidTest/java/com/google/android/apps/mytracks/content/WaypointCreationRequestTest.java b/src/androidTest/java/com/google/android/apps/mytracks/content/WaypointCreationRequestTest.java index a4510fe20..ae0cad1b0 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/content/WaypointCreationRequestTest.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/content/WaypointCreationRequestTest.java @@ -1,12 +1,12 @@ /* * Copyright 2010 Google Inc. - * + * * 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 @@ -33,31 +33,31 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class WaypointCreationRequestTest { - public void testTypeParceling() { - WaypointCreationRequest original = WaypointCreationRequest.DEFAULT_WAYPOINT; - Parcel p = Parcel.obtain(); - original.writeToParcel(p, 0); - p.setDataPosition(0); - WaypointCreationRequest copy = WaypointCreationRequest.CREATOR.createFromParcel(p); - Assert.assertEquals(original.getType(), copy.getType()); - Assert.assertFalse(copy.isTrackStatistics()); - Assert.assertNull(copy.getName()); - Assert.assertNull(copy.getDescription()); - Assert.assertNull(copy.getIconUrl()); - } + public void testTypeParceling() { + WaypointCreationRequest original = WaypointCreationRequest.DEFAULT_WAYPOINT; + Parcel p = Parcel.obtain(); + original.writeToParcel(p, 0); + p.setDataPosition(0); + WaypointCreationRequest copy = WaypointCreationRequest.CREATOR.createFromParcel(p); + Assert.assertEquals(original.getType(), copy.getType()); + Assert.assertFalse(copy.isTrackStatistics()); + Assert.assertNull(copy.getName()); + Assert.assertNull(copy.getDescription()); + Assert.assertNull(copy.getIconUrl()); + } - public void testAllAttributesParceling() { - WaypointCreationRequest original = new WaypointCreationRequest( - WaypointType.WAYPOINT, false, "name", "category", "description", "img.png", null); - Parcel p = Parcel.obtain(); - original.writeToParcel(p, 0); - p.setDataPosition(0); - WaypointCreationRequest copy = WaypointCreationRequest.CREATOR.createFromParcel(p); - Assert.assertEquals(original.getType(), copy.getType()); - Assert.assertFalse(copy.isTrackStatistics()); - Assert.assertEquals("name", copy.getName()); - Assert.assertEquals("category", copy.getCategory()); - Assert.assertEquals("description", copy.getDescription()); - Assert.assertEquals("img.png", copy.getIconUrl()); - } + public void testAllAttributesParceling() { + WaypointCreationRequest original = new WaypointCreationRequest( + WaypointType.WAYPOINT, false, "name", "category", "description", "img.png", null); + Parcel p = Parcel.obtain(); + original.writeToParcel(p, 0); + p.setDataPosition(0); + WaypointCreationRequest copy = WaypointCreationRequest.CREATOR.createFromParcel(p); + Assert.assertEquals(original.getType(), copy.getType()); + Assert.assertFalse(copy.isTrackStatistics()); + Assert.assertEquals("name", copy.getName()); + Assert.assertEquals("category", copy.getCategory()); + Assert.assertEquals("description", copy.getDescription()); + Assert.assertEquals("img.png", copy.getIconUrl()); + } } diff --git a/src/androidTest/java/com/google/android/apps/mytracks/fragments/ChartFragmentTest.java b/src/androidTest/java/com/google/android/apps/mytracks/fragments/ChartFragmentTest.java index 924dc769f..b8fd08768 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/fragments/ChartFragmentTest.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/fragments/ChartFragmentTest.java @@ -34,324 +34,324 @@ import org.junit.runner.RunWith; /** * Tests {@link ChartFragment}. - * + * * @author Youtao Liu */ @RunWith(AndroidJUnit4.class) public class ChartFragmentTest { - private static final double HOURS_PER_UNIT = 60.0; + private static final double HOURS_PER_UNIT = 60.0; - private ChartFragment chartFragment; + private ChartFragment chartFragment; - @Before - protected void setUp() { - chartFragment = new ChartFragment(); - chartFragment.setChartView(new ChartView(InstrumentationRegistry.getInstrumentation().getContext())); - chartFragment.setTripStatisticsUpdater(TrackStubUtils.INITIAL_TIME); - } + @Before + protected void setUp() { + chartFragment = new ChartFragment(); + chartFragment.setChartView(new ChartView(InstrumentationRegistry.getInstrumentation().getContext())); + chartFragment.setTripStatisticsUpdater(TrackStubUtils.INITIAL_TIME); + } - /** - * Tests the logic to get the incorrect values of sensor in {@link ChartFragment#fillDataPoint(Location, - * double[])} - */ - public void testFillDataPoint_sensorIncorrect() { - SensorDataSetLocation sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation(); - - // No input. - double[] point = fillDataPointTestHelper(sensorDataSetLocation); - Assert.assertEquals(Double.NaN, point[ChartView.HEART_RATE_SERIES + 1]); - Assert.assertEquals(Double.NaN, point[ChartView.CADENCE_SERIES + 1]); - Assert.assertEquals(Double.NaN, point[ChartView.POWER_SERIES + 1]); - - // Input incorrect state. - // Creates SensorData. - Sensor.SensorData.Builder heartRateData = Sensor.SensorData.newBuilder() - .setValue(100).setState(Sensor.SensorState.NONE); - Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder() - .setValue(101).setState(Sensor.SensorState.NONE); - Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder() - .setValue(102).setState(Sensor.SensorState.NONE); - - // Creates SensorDataSet. - SensorDataSet sensorDataSet = sensorDataSetLocation.getSensorDataSet(); - sensorDataSet = sensorDataSet.toBuilder() - .setHeartRate(heartRateData).setCadence(cadenceData).setPower(powerData).build(); - sensorDataSetLocation.setSensorDataSet(sensorDataSet); - // Test. - point = fillDataPointTestHelper(sensorDataSetLocation); - Assert.assertEquals(Double.NaN, point[ChartView.HEART_RATE_SERIES + 1]); - Assert.assertEquals(Double.NaN, point[ChartView.CADENCE_SERIES + 1]); - Assert.assertEquals(Double.NaN, point[ChartView.POWER_SERIES + 1]); - } - - /** - * Tests the logic to get the correct values of sensor in {@link ChartFragment#fillDataPoint(Location, - * double[])}. - */ - public void testFillDataPoint_sensorCorrect() { - SensorDataSetLocation sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation(); - // No input. - double[] point = fillDataPointTestHelper(sensorDataSetLocation); - Assert.assertEquals(Double.NaN, point[ChartView.HEART_RATE_SERIES + 1]); - Assert.assertEquals(Double.NaN, point[ChartView.CADENCE_SERIES + 1]); - Assert.assertEquals(Double.NaN, point[ChartView.POWER_SERIES + 1]); - - // Creates SensorData. - Sensor.SensorData.Builder heartRateData = Sensor.SensorData.newBuilder() - .setValue(100).setState(Sensor.SensorState.SENDING); - Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder() - .setValue(101).setState(Sensor.SensorState.SENDING); - Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder() - .setValue(102).setState(Sensor.SensorState.SENDING); - - // Creates SensorDataSet. - SensorDataSet sensorDataSet = sensorDataSetLocation.getSensorDataSet(); - sensorDataSet = sensorDataSet.toBuilder() - .setHeartRate(heartRateData).setCadence(cadenceData).setPower(powerData).build(); - sensorDataSetLocation.setSensorDataSet(sensorDataSet); - // Test. - point = fillDataPointTestHelper(sensorDataSetLocation); - Assert.assertEquals(100.0, point[ChartView.HEART_RATE_SERIES + 1]); - Assert.assertEquals(101.0, point[ChartView.CADENCE_SERIES + 1]); - Assert.assertEquals(102.0, point[ChartView.POWER_SERIES + 1]); - } - - /** - * Tests the logic to get the value of metric Distance in {@link ChartFragment#fillDataPoint(Location, - * double[])}. - */ - public void testFillDataPoint_distanceMetric() { - // By distance. - chartFragment.setChartByDistance(true); - // Resets last location and writes first location. - SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation(); - double[] point = fillDataPointTestHelper(sensorDataSetLocation1); - Assert.assertEquals(0.0, point[0]); - - // The second is a same location, just different time. - SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation(); - point = fillDataPointTestHelper(sensorDataSetLocation2); - Assert.assertEquals(0.0, point[0]); - - // The third location is a new location, and use metric. - SensorDataSetLocation sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation(); - sensorDataSetLocation3.setLatitude(23); - point = fillDataPointTestHelper(sensorDataSetLocation3); - - // Computes the distance between Latitude 22 and 23. - float[] results = new float[4]; - Location.distanceBetween(sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(), - sensorDataSetLocation3.getLatitude(), sensorDataSetLocation3.getLongitude(), results); - double distance1 = results[0] * UnitConversions.M_TO_KM; - Assert.assertEquals(distance1, point[0]); - - // The fourth location is a new location, and use metric. - SensorDataSetLocation sensorDataSetLocation4 = TrackStubUtils.createSensorDataSetLocation(); - sensorDataSetLocation4.setLatitude(24); - point = fillDataPointTestHelper(sensorDataSetLocation4); - - // Computes the distance between Latitude 23 and 24. - Location.distanceBetween(sensorDataSetLocation3.getLatitude(), sensorDataSetLocation3.getLongitude(), - sensorDataSetLocation4.getLatitude(), sensorDataSetLocation4.getLongitude(), results); - double distance2 = results[0] * UnitConversions.M_TO_KM; - Assert.assertEquals((distance1 + distance2), point[0]); - } - - /** - * Tests the logic to get the value of imperial Distance in {@link ChartFragment#fillDataPoint(Location, - * double[])}. - */ - public void testFillDataPoint_distanceImperial() { - // By distance. - chartFragment.setChartByDistance(true); - // Setups to use imperial. - chartFragment.setMetricUnits(false); - - // The first is a same location, just different time. - SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation(); - double[] point = fillDataPointTestHelper(sensorDataSetLocation1); - Assert.assertEquals(0.0, point[0]); - - // The second location is a new location, and use imperial. - SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation(); - sensorDataSetLocation2.setLatitude(23); - point = fillDataPointTestHelper(sensorDataSetLocation2); - - /* - * Computes the distance between Latitude 22 and 23. And for we set using - * imperial, the distance should be multiplied by UnitConversions.KM_TO_MI. + /** + * Tests the logic to get the incorrect values of sensor in {@link ChartFragment#fillDataPoint(Location, + * double[])} */ - float[] results = new float[4]; - Location.distanceBetween(sensorDataSetLocation1.getLatitude(), sensorDataSetLocation1.getLongitude(), - sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(), results); - double distance1 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI; - Assert.assertEquals(distance1, point[0]); + public void testFillDataPoint_sensorIncorrect() { + SensorDataSetLocation sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation(); - // The third location is a new location, and use imperial. - SensorDataSetLocation sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation(); - sensorDataSetLocation3.setLatitude(24); - point = fillDataPointTestHelper(sensorDataSetLocation3); + // No input. + double[] point = fillDataPointTestHelper(sensorDataSetLocation); + Assert.assertEquals(Double.NaN, point[ChartView.HEART_RATE_SERIES + 1]); + Assert.assertEquals(Double.NaN, point[ChartView.CADENCE_SERIES + 1]); + Assert.assertEquals(Double.NaN, point[ChartView.POWER_SERIES + 1]); - /* - * Computes the distance between Latitude 23 and 24. And for we set using - * imperial, the distance should be multiplied by UnitConversions.KM_TO_MI. + // Input incorrect state. + // Creates SensorData. + Sensor.SensorData.Builder heartRateData = Sensor.SensorData.newBuilder() + .setValue(100).setState(Sensor.SensorState.NONE); + Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder() + .setValue(101).setState(Sensor.SensorState.NONE); + Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder() + .setValue(102).setState(Sensor.SensorState.NONE); + + // Creates SensorDataSet. + SensorDataSet sensorDataSet = sensorDataSetLocation.getSensorDataSet(); + sensorDataSet = sensorDataSet.toBuilder() + .setHeartRate(heartRateData).setCadence(cadenceData).setPower(powerData).build(); + sensorDataSetLocation.setSensorDataSet(sensorDataSet); + // Test. + point = fillDataPointTestHelper(sensorDataSetLocation); + Assert.assertEquals(Double.NaN, point[ChartView.HEART_RATE_SERIES + 1]); + Assert.assertEquals(Double.NaN, point[ChartView.CADENCE_SERIES + 1]); + Assert.assertEquals(Double.NaN, point[ChartView.POWER_SERIES + 1]); + } + + /** + * Tests the logic to get the correct values of sensor in {@link ChartFragment#fillDataPoint(Location, + * double[])}. */ - Location.distanceBetween(sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(), - sensorDataSetLocation3.getLatitude(), sensorDataSetLocation3.getLongitude(), results); - double distance2 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI; - Assert.assertEquals(distance1 + distance2, point[0]); - } + public void testFillDataPoint_sensorCorrect() { + SensorDataSetLocation sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation(); + // No input. + double[] point = fillDataPointTestHelper(sensorDataSetLocation); + Assert.assertEquals(Double.NaN, point[ChartView.HEART_RATE_SERIES + 1]); + Assert.assertEquals(Double.NaN, point[ChartView.CADENCE_SERIES + 1]); + Assert.assertEquals(Double.NaN, point[ChartView.POWER_SERIES + 1]); - /** - * Tests the logic to get the values of time in {@link ChartFragment#fillDataPoint(Location, - * double[])}. - */ - public void testFillDataPoint_time() { - // By time - chartFragment.setChartByDistance(false); - SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation(); - double[] point = fillDataPointTestHelper(sensorDataSetLocation1); - Assert.assertEquals(0.0, point[0]); - long timeSpan = 222; - SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation(); - sensorDataSetLocation2.setTime(sensorDataSetLocation1.getTime() + timeSpan); - point = fillDataPointTestHelper(sensorDataSetLocation2); - Assert.assertEquals((double) timeSpan, point[0]); - } + // Creates SensorData. + Sensor.SensorData.Builder heartRateData = Sensor.SensorData.newBuilder() + .setValue(100).setState(Sensor.SensorState.SENDING); + Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder() + .setValue(101).setState(Sensor.SensorState.SENDING); + Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder() + .setValue(102).setState(Sensor.SensorState.SENDING); - /** - * Tests the logic to get the value of elevation in {@link ChartFragment#fillDataPoint(Location, - * double[])} by one and two points. - */ - public void testFillDataPoint_elevation() { - SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation(); + // Creates SensorDataSet. + SensorDataSet sensorDataSet = sensorDataSetLocation.getSensorDataSet(); + sensorDataSet = sensorDataSet.toBuilder() + .setHeartRate(heartRateData).setCadence(cadenceData).setPower(powerData).build(); + sensorDataSetLocation.setSensorDataSet(sensorDataSet); + // Test. + point = fillDataPointTestHelper(sensorDataSetLocation); + Assert.assertEquals(100.0, point[ChartView.HEART_RATE_SERIES + 1]); + Assert.assertEquals(101.0, point[ChartView.CADENCE_SERIES + 1]); + Assert.assertEquals(102.0, point[ChartView.POWER_SERIES + 1]); + } - /* - * At first, clear old points of elevation, so give true to the second - * parameter. Then only one value INITIALLONGTITUDE in buffer. + /** + * Tests the logic to get the value of metric Distance in {@link ChartFragment#fillDataPoint(Location, + * double[])}. */ - double[] point = fillDataPointTestHelper(sensorDataSetLocation1); - Assert.assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point[ChartView.ELEVATION_SERIES + 1]); + public void testFillDataPoint_distanceMetric() { + // By distance. + chartFragment.setChartByDistance(true); + // Resets last location and writes first location. + SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation(); + double[] point = fillDataPointTestHelper(sensorDataSetLocation1); + Assert.assertEquals(0.0, point[0]); - /* - * Send another value to buffer, now there are two values, INITIALALTITUDE - * and INITIALALTITUDE * 2. + // The second is a same location, just different time. + SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation(); + point = fillDataPointTestHelper(sensorDataSetLocation2); + Assert.assertEquals(0.0, point[0]); + + // The third location is a new location, and use metric. + SensorDataSetLocation sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation(); + sensorDataSetLocation3.setLatitude(23); + point = fillDataPointTestHelper(sensorDataSetLocation3); + + // Computes the distance between Latitude 22 and 23. + float[] results = new float[4]; + Location.distanceBetween(sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(), + sensorDataSetLocation3.getLatitude(), sensorDataSetLocation3.getLongitude(), results); + double distance1 = results[0] * UnitConversions.M_TO_KM; + Assert.assertEquals(distance1, point[0]); + + // The fourth location is a new location, and use metric. + SensorDataSetLocation sensorDataSetLocation4 = TrackStubUtils.createSensorDataSetLocation(); + sensorDataSetLocation4.setLatitude(24); + point = fillDataPointTestHelper(sensorDataSetLocation4); + + // Computes the distance between Latitude 23 and 24. + Location.distanceBetween(sensorDataSetLocation3.getLatitude(), sensorDataSetLocation3.getLongitude(), + sensorDataSetLocation4.getLatitude(), sensorDataSetLocation4.getLongitude(), results); + double distance2 = results[0] * UnitConversions.M_TO_KM; + Assert.assertEquals((distance1 + distance2), point[0]); + } + + /** + * Tests the logic to get the value of imperial Distance in {@link ChartFragment#fillDataPoint(Location, + * double[])}. */ - SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation(); - sensorDataSetLocation2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2); - point = fillDataPointTestHelper(sensorDataSetLocation2); - Assert.assertEquals((TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0, - point[ChartView.ELEVATION_SERIES + 1]); - } + public void testFillDataPoint_distanceImperial() { + // By distance. + chartFragment.setChartByDistance(true); + // Setups to use imperial. + chartFragment.setMetricUnits(false); - /** - * Tests the logic to get the value of speed in {@link ChartFragment#fillDataPoint(Location, - * double[])}. In this test, firstly remove all - * points in memory, and then fill in two points one by one. The speed values - * of these points are 129, 130. - */ - public void testFillDataPoint_speed() { - /* - * At first, clear old points of speed, so give true to the second - * parameter. It will not be filled in to the speed buffer. + // The first is a same location, just different time. + SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation(); + double[] point = fillDataPointTestHelper(sensorDataSetLocation1); + Assert.assertEquals(0.0, point[0]); + + // The second location is a new location, and use imperial. + SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation(); + sensorDataSetLocation2.setLatitude(23); + point = fillDataPointTestHelper(sensorDataSetLocation2); + + /* + * Computes the distance between Latitude 22 and 23. And for we set using + * imperial, the distance should be multiplied by UnitConversions.KM_TO_MI. + */ + float[] results = new float[4]; + Location.distanceBetween(sensorDataSetLocation1.getLatitude(), sensorDataSetLocation1.getLongitude(), + sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(), results); + double distance1 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI; + Assert.assertEquals(distance1, point[0]); + + // The third location is a new location, and use imperial. + SensorDataSetLocation sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation(); + sensorDataSetLocation3.setLatitude(24); + point = fillDataPointTestHelper(sensorDataSetLocation3); + + /* + * Computes the distance between Latitude 23 and 24. And for we set using + * imperial, the distance should be multiplied by UnitConversions.KM_TO_MI. + */ + Location.distanceBetween(sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(), + sensorDataSetLocation3.getLatitude(), sensorDataSetLocation3.getLongitude(), results); + double distance2 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI; + Assert.assertEquals(distance1 + distance2, point[0]); + } + + /** + * Tests the logic to get the values of time in {@link ChartFragment#fillDataPoint(Location, + * double[])}. */ - SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation(); - sensorDataSetLocation1.setSpeed(128.5f); - double[] point = fillDataPointTestHelper(sensorDataSetLocation1); - Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1]); + public void testFillDataPoint_time() { + // By time + chartFragment.setChartByDistance(false); + SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation(); + double[] point = fillDataPointTestHelper(sensorDataSetLocation1); + Assert.assertEquals(0.0, point[0]); + long timeSpan = 222; + SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation(); + sensorDataSetLocation2.setTime(sensorDataSetLocation1.getTime() + timeSpan); + point = fillDataPointTestHelper(sensorDataSetLocation2); + Assert.assertEquals((double) timeSpan, point[0]); + } - /* - * Tests the logic when both metricUnits and reportSpeed are true.This - * location will be filled into speed buffer. + /** + * Tests the logic to get the value of elevation in {@link ChartFragment#fillDataPoint(Location, + * double[])} by one and two points. */ - SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation(); + public void testFillDataPoint_elevation() { + SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation(); - /* - * Add a time span here to make sure the second point is valid, the value - * 222 here is doesn't matter. + /* + * At first, clear old points of elevation, so give true to the second + * parameter. Then only one value INITIALLONGTITUDE in buffer. + */ + double[] point = fillDataPointTestHelper(sensorDataSetLocation1); + Assert.assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point[ChartView.ELEVATION_SERIES + 1]); + + /* + * Send another value to buffer, now there are two values, INITIALALTITUDE + * and INITIALALTITUDE * 2. + */ + SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation(); + sensorDataSetLocation2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2); + point = fillDataPointTestHelper(sensorDataSetLocation2); + Assert.assertEquals((TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0, + point[ChartView.ELEVATION_SERIES + 1]); + } + + /** + * Tests the logic to get the value of speed in {@link ChartFragment#fillDataPoint(Location, + * double[])}. In this test, firstly remove all + * points in memory, and then fill in two points one by one. The speed values + * of these points are 129, 130. */ - sensorDataSetLocation2.setTime(sensorDataSetLocation1.getTime() + 222); - sensorDataSetLocation2.setSpeed(130); - point = fillDataPointTestHelper(sensorDataSetLocation2); - Assert.assertEquals(130.0 * UnitConversions.MS_TO_KMH, point[ChartView.SPEED_SERIES + 1]); - } + public void testFillDataPoint_speed() { + /* + * At first, clear old points of speed, so give true to the second + * parameter. It will not be filled in to the speed buffer. + */ + SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation(); + sensorDataSetLocation1.setSpeed(128.5f); + double[] point = fillDataPointTestHelper(sensorDataSetLocation1); + Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1]); - /** - * Tests the logic to compute speed when use Imperial. - */ - public void testFillDataPoint_speedImperial() { - // Setups to use imperial. - chartFragment.setMetricUnits(false); + /* + * Tests the logic when both metricUnits and reportSpeed are true.This + * location will be filled into speed buffer. + */ + SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation(); - // First data point is not added to the speed buffer - SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation(); - sensorDataSetLocation1.setSpeed(100.0f); - double[] point = fillDataPointTestHelper(sensorDataSetLocation1); - Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1]); + /* + * Add a time span here to make sure the second point is valid, the value + * 222 here is doesn't matter. + */ + sensorDataSetLocation2.setTime(sensorDataSetLocation1.getTime() + 222); + sensorDataSetLocation2.setSpeed(130); + point = fillDataPointTestHelper(sensorDataSetLocation2); + Assert.assertEquals(130.0 * UnitConversions.MS_TO_KMH, point[ChartView.SPEED_SERIES + 1]); + } - SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation(); - - /* - * Add a time span here to make sure the second point is valid and the speed - * is valid. Speed is valid if: speedDifference > Constants.MAX_ACCELERATION - * * timeDifference speedDifference = 102 -100 timeDifference = 222 + /** + * Tests the logic to compute speed when use Imperial. */ - sensorDataSetLocation2.setTime(sensorDataSetLocation2.getTime() + 222); - sensorDataSetLocation2.setSpeed(102); - point = fillDataPointTestHelper(sensorDataSetLocation2); - Assert.assertEquals(102.0 * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI, point[ChartView.SPEED_SERIES + 1]); - } + public void testFillDataPoint_speedImperial() { + // Setups to use imperial. + chartFragment.setMetricUnits(false); - /** - * Tests the logic to get pace value when reportSpeed is false. - */ - public void testFillDataPoint_pace_nonZeroSpeed() { - // Setups reportSpeed to false. - chartFragment.setReportSpeed(false); + // First data point is not added to the speed buffer + SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation(); + sensorDataSetLocation1.setSpeed(100.0f); + double[] point = fillDataPointTestHelper(sensorDataSetLocation1); + Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1]); - // First data point is not added to the speed buffer - SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation(); - sensorDataSetLocation1.setSpeed(100.0f); - double[] point = fillDataPointTestHelper(sensorDataSetLocation1); - Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1]); + SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation(); - SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation(); + /* + * Add a time span here to make sure the second point is valid and the speed + * is valid. Speed is valid if: speedDifference > Constants.MAX_ACCELERATION + * * timeDifference speedDifference = 102 -100 timeDifference = 222 + */ + sensorDataSetLocation2.setTime(sensorDataSetLocation2.getTime() + 222); + sensorDataSetLocation2.setSpeed(102); + point = fillDataPointTestHelper(sensorDataSetLocation2); + Assert.assertEquals(102.0 * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI, point[ChartView.SPEED_SERIES + 1]); + } - /* - * Add a time span here to make sure the second point is valid and the speed - * is valid. Speed is valid if: speedDifference > Constants.MAX_ACCELERATION - * * timeDifference speedDifference = 102 -100 timeDifference = 222 + /** + * Tests the logic to get pace value when reportSpeed is false. */ - sensorDataSetLocation2.setTime(sensorDataSetLocation2.getTime() + 222); - sensorDataSetLocation2.setSpeed(102); - point = fillDataPointTestHelper(sensorDataSetLocation2); - Assert.assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MS_TO_KMH), point[ChartView.PACE_SERIES + 1]); - } + public void testFillDataPoint_pace_nonZeroSpeed() { + // Setups reportSpeed to false. + chartFragment.setReportSpeed(false); - /** - * Tests the logic to get pace value when reportSpeed is false and average - * speed is zero. - */ - public void testFillDataPoint_pace_zeroSpeed() { - // Setups reportSpeed to false. - chartFragment.setReportSpeed(false); - SensorDataSetLocation sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation(); - sensorDataSetLocation.setSpeed(0); - double[] point = fillDataPointTestHelper(sensorDataSetLocation); - Assert.assertEquals(0.0, point[ChartView.PACE_SERIES + 1]); - } + // First data point is not added to the speed buffer + SensorDataSetLocation sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation(); + sensorDataSetLocation1.setSpeed(100.0f); + double[] point = fillDataPointTestHelper(sensorDataSetLocation1); + Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1]); - /** - * Helper method to test fillDataPoint. - * - * @param location location to fill - * @return data of this location - */ - private double[] fillDataPointTestHelper(Location location) { - double[] point = new double[ChartView.NUM_SERIES + 1]; - chartFragment.fillDataPoint(location, point); - return point; - } + SensorDataSetLocation sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation(); + + /* + * Add a time span here to make sure the second point is valid and the speed + * is valid. Speed is valid if: speedDifference > Constants.MAX_ACCELERATION + * * timeDifference speedDifference = 102 -100 timeDifference = 222 + */ + sensorDataSetLocation2.setTime(sensorDataSetLocation2.getTime() + 222); + sensorDataSetLocation2.setSpeed(102); + point = fillDataPointTestHelper(sensorDataSetLocation2); + Assert.assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MS_TO_KMH), point[ChartView.PACE_SERIES + 1]); + } + + /** + * Tests the logic to get pace value when reportSpeed is false and average + * speed is zero. + */ + public void testFillDataPoint_pace_zeroSpeed() { + // Setups reportSpeed to false. + chartFragment.setReportSpeed(false); + SensorDataSetLocation sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation(); + sensorDataSetLocation.setSpeed(0); + double[] point = fillDataPointTestHelper(sensorDataSetLocation); + Assert.assertEquals(0.0, point[ChartView.PACE_SERIES + 1]); + } + + /** + * Helper method to test fillDataPoint. + * + * @param location location to fill + * @return data of this location + */ + private double[] fillDataPointTestHelper(Location location) { + double[] point = new double[ChartView.NUM_SERIES + 1]; + chartFragment.fillDataPoint(location, point); + return point; + } } diff --git a/src/androidTest/java/com/google/android/apps/mytracks/io/file/importer/AbstractTestFileTrackImporter.java b/src/androidTest/java/com/google/android/apps/mytracks/io/file/importer/AbstractTestFileTrackImporter.java index a972461cf..02cac3707 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/io/file/importer/AbstractTestFileTrackImporter.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/io/file/importer/AbstractTestFileTrackImporter.java @@ -16,8 +16,11 @@ package com.google.android.apps.mytracks.io.file.importer; -import static com.google.android.testing.mocking.AndroidMock.eq; -import static com.google.android.testing.mocking.AndroidMock.expect; +import android.content.ContentUris; +import android.location.Location; +import android.location.LocationManager; +import android.net.Uri; +import android.test.AndroidTestCase; import com.google.android.apps.mytracks.content.ContentProviderUtils; import com.google.android.apps.mytracks.content.ContentProviderUtils.Factory; @@ -29,136 +32,131 @@ import com.google.android.apps.mytracks.testing.TestingProviderUtilsFactory; import com.google.android.testing.mocking.AndroidMock; import com.google.android.testing.mocking.UsesMocks; -import android.content.ContentUris; -import android.location.Location; -import android.location.LocationManager; -import android.net.Uri; -import android.test.AndroidTestCase; +import org.easymock.Capture; import java.text.SimpleDateFormat; import java.util.Locale; import java.util.SimpleTimeZone; -import org.easymock.Capture; +import static com.google.android.testing.mocking.AndroidMock.eq; +import static com.google.android.testing.mocking.AndroidMock.expect; /** * Abstract class for testing file track importers. - * + * * @author Jimmy Shih. */ public class AbstractTestFileTrackImporter extends AndroidTestCase { - protected static final String TRACK_NAME_0 = "blablub"; - protected static final String TRACK_DESCRIPTION_0 = "s'Laebe isch koi Schlotzer"; + protected static final String TRACK_NAME_0 = "blablub"; + protected static final String TRACK_DESCRIPTION_0 = "s'Laebe isch koi Schlotzer"; - protected static final String TRACK_NAME_1 = "another track"; - protected static final String TRACK_DESCRIPTION_1 = "another description"; + protected static final String TRACK_NAME_1 = "another track"; + protected static final String TRACK_DESCRIPTION_1 = "another description"; - protected static final double TRACK_LATITUDE = 48.768364; - protected static final double TRACK_LONGITUDE = 9.177886; - protected static final double TRACK_ELEVATION = 324.0; + protected static final double TRACK_LATITUDE = 48.768364; + protected static final double TRACK_LONGITUDE = 9.177886; + protected static final double TRACK_ELEVATION = 324.0; - protected static final String TRACK_TIME_0 = "2010-04-22T18:21:00Z"; - protected static final String TRACK_TIME_1 = "2010-04-22T18:21:50.123"; - protected static final String TRACK_TIME_2 = "2010-04-22T18:23:00.123"; - protected static final String TRACK_TIME_3 = "2010-04-22T18:24:50.123"; + protected static final String TRACK_TIME_0 = "2010-04-22T18:21:00Z"; + protected static final String TRACK_TIME_1 = "2010-04-22T18:21:50.123"; + protected static final String TRACK_TIME_2 = "2010-04-22T18:23:00.123"; + protected static final String TRACK_TIME_3 = "2010-04-22T18:24:50.123"; - protected static final SimpleDateFormat DATE_FORMAT_0 = new SimpleDateFormat( - "yyyy-MM-dd'T'hh:mm:ss'Z'", Locale.US); - protected static final SimpleDateFormat DATE_FORMAT_1 = new SimpleDateFormat( - "yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US); + protected static final SimpleDateFormat DATE_FORMAT_0 = new SimpleDateFormat( + "yyyy-MM-dd'T'hh:mm:ss'Z'", Locale.US); + protected static final SimpleDateFormat DATE_FORMAT_1 = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US); + protected static final long TRACK_ID_0 = 1; + protected static final long TRACK_ID_1 = 2; + protected static final long TRACK_POINT_ID_0 = 1; + protected static final long TRACK_POINT_ID_1 = 2; + protected static final long TRACK_POINT_ID_3 = 4; + protected static final long WAYPOINT_ID_0 = 1; + protected static final Uri TRACK_ID_0_URI = ContentUris.appendId( + TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID_0).build(); + protected static final Uri TRACK_ID_1_URI = ContentUris.appendId( + TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID_1).build(); + protected static final Uri WAYPOINT_ID_O_URI = ContentUris.appendId( + WaypointsColumns.CONTENT_URI.buildUpon(), WAYPOINT_ID_0).build(); - static { - /* - * We can't omit the timezones in the test, otherwise it'll use the local - * timezone and fail depending on where the test runner is. + static { + /* + * We can't omit the timezones in the test, otherwise it'll use the local + * timezone and fail depending on where the test runner is. + */ + SimpleTimeZone utc = new SimpleTimeZone(0, "UTC"); + DATE_FORMAT_0.setTimeZone(utc); + DATE_FORMAT_1.setTimeZone(utc); + } + + protected ContentProviderUtils contentProviderUtils; + + private Factory oldMyTracksProviderUtilsFactory; + + @UsesMocks(ContentProviderUtils.class) + @Override + protected void setUp() throws Exception { + super.setUp(); + contentProviderUtils = AndroidMock.createMock(ContentProviderUtils.class); + oldMyTracksProviderUtilsFactory = TestingProviderUtilsFactory.installWithInstance( + contentProviderUtils); + } + + @Override + protected void tearDown() throws Exception { + TestingProviderUtilsFactory.restoreOldFactory(oldMyTracksProviderUtilsFactory); + super.tearDown(); + } + + protected Location createLocation(int index, long time) { + Location location = new Location(LocationManager.GPS_PROVIDER); + location.setLatitude(TRACK_LATITUDE + index); + location.setLongitude(TRACK_LONGITUDE + index); + location.setAltitude(TRACK_ELEVATION + index); + location.setTime(time); + return location; + } + + /** + * Expects the first track point to be added. + * + * @param location the location + * @param trackId the track id + * @param trackPointId the track point id */ - SimpleTimeZone utc = new SimpleTimeZone(0, "UTC"); - DATE_FORMAT_0.setTimeZone(utc); - DATE_FORMAT_1.setTimeZone(utc); - } - - protected static final long TRACK_ID_0 = 1; - protected static final long TRACK_ID_1 = 2; - protected static final long TRACK_POINT_ID_0 = 1; - protected static final long TRACK_POINT_ID_1 = 2; - protected static final long TRACK_POINT_ID_3 = 4; - protected static final long WAYPOINT_ID_0 = 1; - - protected static final Uri TRACK_ID_0_URI = ContentUris.appendId( - TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID_0).build(); - protected static final Uri TRACK_ID_1_URI = ContentUris.appendId( - TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID_1).build(); - protected static final Uri WAYPOINT_ID_O_URI = ContentUris.appendId( - WaypointsColumns.CONTENT_URI.buildUpon(), WAYPOINT_ID_0).build(); - - protected ContentProviderUtils contentProviderUtils; - - private Factory oldMyTracksProviderUtilsFactory; - - @UsesMocks(ContentProviderUtils.class) - @Override - protected void setUp() throws Exception { - super.setUp(); - contentProviderUtils = AndroidMock.createMock(ContentProviderUtils.class); - oldMyTracksProviderUtilsFactory = TestingProviderUtilsFactory.installWithInstance( - contentProviderUtils); - } - - @Override - protected void tearDown() throws Exception { - TestingProviderUtilsFactory.restoreOldFactory(oldMyTracksProviderUtilsFactory); - super.tearDown(); - } - - protected Location createLocation(int index, long time) { - Location location = new Location(LocationManager.GPS_PROVIDER); - location.setLatitude(TRACK_LATITUDE + index); - location.setLongitude(TRACK_LONGITUDE + index); - location.setAltitude(TRACK_ELEVATION + index); - location.setTime(time); - return location; - } - - /** - * Expects the first track point to be added. - * - * @param location the location - * @param trackId the track id - * @param trackPointId the track point id - */ - protected void expectFirstTrackPoint(Location location, long trackId, long trackPointId) { - expect(contentProviderUtils.bulkInsertTrackPoint( - location != null ? LocationsMatcher.eqLoc(location) : (Location[]) AndroidMock.anyObject(), - eq(1), eq(trackId))).andReturn(1); - expect(contentProviderUtils.getFirstTrackPointId(trackId)).andReturn(trackPointId); - expect(contentProviderUtils.getLastTrackPointId(trackId)).andReturn(trackPointId); - } - - /** - * Expects the track to be updated. - * - * @param track the track - * @param lastTrack true if it is the last track in the gpx - * @param trackId the track id - */ - protected void expectUpdateTrack(Capture track, boolean lastTrack, long trackId) { - contentProviderUtils.updateTrack(AndroidMock.capture(track)); - expect(contentProviderUtils.insertWaypoint((Waypoint) AndroidMock.anyObject())) - .andReturn(WAYPOINT_ID_O_URI); - if (lastTrack) { - // Return null to not add waypoints - expect(contentProviderUtils.getTrack(trackId)).andReturn(null); + protected void expectFirstTrackPoint(Location location, long trackId, long trackPointId) { + expect(contentProviderUtils.bulkInsertTrackPoint( + location != null ? LocationsMatcher.eqLoc(location) : (Location[]) AndroidMock.anyObject(), + eq(1), eq(trackId))).andReturn(1); + expect(contentProviderUtils.getFirstTrackPointId(trackId)).andReturn(trackPointId); + expect(contentProviderUtils.getLastTrackPointId(trackId)).andReturn(trackPointId); } - } - protected void verifyTrack(Track track, String name, String description, long time) { - assertEquals(name, track.getName()); - assertEquals(description, track.getDescription()); - if (time != -1L) { - assertEquals(time, track.getTripStatistics().getStartTime()); + /** + * Expects the track to be updated. + * + * @param track the track + * @param lastTrack true if it is the last track in the gpx + * @param trackId the track id + */ + protected void expectUpdateTrack(Capture track, boolean lastTrack, long trackId) { + contentProviderUtils.updateTrack(AndroidMock.capture(track)); + expect(contentProviderUtils.insertWaypoint((Waypoint) AndroidMock.anyObject())) + .andReturn(WAYPOINT_ID_O_URI); + if (lastTrack) { + // Return null to not add waypoints + expect(contentProviderUtils.getTrack(trackId)).andReturn(null); + } + } + + protected void verifyTrack(Track track, String name, String description, long time) { + assertEquals(name, track.getName()); + assertEquals(description, track.getDescription()); + if (time != -1L) { + assertEquals(time, track.getTripStatistics().getStartTime()); + } + assertNotSame(-1, track.getStartId()); + assertNotSame(-1, track.getStopId()); } - assertNotSame(-1, track.getStartId()); - assertNotSame(-1, track.getStopId()); - } } diff --git a/src/androidTest/java/com/google/android/apps/mytracks/io/file/importer/GpxFileTrackImporterTest.java b/src/androidTest/java/com/google/android/apps/mytracks/io/file/importer/GpxFileTrackImporterTest.java index 9f8be6aa9..812081636 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/io/file/importer/GpxFileTrackImporterTest.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/io/file/importer/GpxFileTrackImporterTest.java @@ -16,242 +16,241 @@ package com.google.android.apps.mytracks.io.file.importer; -import static com.google.android.testing.mocking.AndroidMock.eq; -import static com.google.android.testing.mocking.AndroidMock.expect; +import android.location.Location; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.util.PreferencesUtils; import com.google.android.maps.mytracks.R; import com.google.android.testing.mocking.AndroidMock; -import android.location.Location; +import org.easymock.Capture; import java.io.ByteArrayInputStream; import java.io.InputStream; -import org.easymock.Capture; +import static com.google.android.testing.mocking.AndroidMock.eq; +import static com.google.android.testing.mocking.AndroidMock.expect; /** * Tests for {@link GpxFileTrackImporter}. - * + * * @author Steffen Horlacher */ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter { - private static String getNameAndDescription(String name, String description) { - return "" + ""; - } + private static final String VALID_ONE_TRACK_ONE_SEGMENT_GPX = "" + + getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "" + + getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1) + ""; + private static final String VALID_ONE_TRACK_TWO_SEGMENTS_GPX = "" + + getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "" + + getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1) + "" + + getTrackPoint(2, TRACK_TIME_2) + getTrackPoint(3, TRACK_TIME_3) + ""; + private static final String VALID_ONE_TRACK_TWO_SEGMENTS_NO_TIME_GPX = "" + + getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "" + + getTrackPoint(0, null) + getTrackPoint(1, null) + "" + + getTrackPoint(2, null) + getTrackPoint(3, null) + ""; + private static final String INVALID_XML_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.substring( + 0, VALID_ONE_TRACK_ONE_SEGMENT_GPX.length() - 50); + private static final String INVALID_LOCATION_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll( + Double.toString(TRACK_LATITUDE), "1000.0"); + private static final String INVALID_TIME_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll( + TRACK_TIME_0, "invalid"); + private static final String INVALID_ALTITUDE_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll( + Double.toString(TRACK_ELEVATION), "invalid"); + private static final String INVALID_LATITUDE_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll( + Double.toString(TRACK_LATITUDE), "invalid"); + private static final String INVALID_LONGITUDE_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll( + Double.toString(TRACK_LONGITUDE), "invalid"); - private static String getTrackPoint(int index, String time) { - String latitude = Double.toString(TRACK_LATITUDE + index); - String longitude = Double.toString(TRACK_LONGITUDE + index); - String elevation = Double.toString(TRACK_ELEVATION + index); - StringBuffer buffer = new StringBuffer(); - buffer.append( - "" + elevation + ""); - if (time != null) { - buffer.append(""); + private static String getNameAndDescription(String name, String description) { + return "" + ""; } - buffer.append(""); - return buffer.toString(); - } - private static final String VALID_ONE_TRACK_ONE_SEGMENT_GPX = "" - + getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "" - + getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1) + ""; - private static final String VALID_ONE_TRACK_TWO_SEGMENTS_GPX = "" - + getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "" - + getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1) + "" - + getTrackPoint(2, TRACK_TIME_2) + getTrackPoint(3, TRACK_TIME_3) + ""; - private static final String VALID_ONE_TRACK_TWO_SEGMENTS_NO_TIME_GPX = "" - + getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "" - + getTrackPoint(0, null) + getTrackPoint(1, null) + "" - + getTrackPoint(2, null) + getTrackPoint(3, null) + ""; + private static String getTrackPoint(int index, String time) { + String latitude = Double.toString(TRACK_LATITUDE + index); + String longitude = Double.toString(TRACK_LONGITUDE + index); + String elevation = Double.toString(TRACK_ELEVATION + index); + StringBuffer buffer = new StringBuffer(); + buffer.append( + "" + elevation + ""); + if (time != null) { + buffer.append(""); + } + buffer.append(""); + return buffer.toString(); + } - private static final String INVALID_XML_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.substring( - 0, VALID_ONE_TRACK_ONE_SEGMENT_GPX.length() - 50); - private static final String INVALID_LOCATION_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll( - Double.toString(TRACK_LATITUDE), "1000.0"); - private static final String INVALID_TIME_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll( - TRACK_TIME_0, "invalid"); - private static final String INVALID_ALTITUDE_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll( - Double.toString(TRACK_ELEVATION), "invalid"); - private static final String INVALID_LATITUDE_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll( - Double.toString(TRACK_LATITUDE), "invalid"); - private static final String INVALID_LONGITUDE_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll( - Double.toString(TRACK_LONGITUDE), "invalid"); + /** + * Tests one track with one segment. + */ + public void testOneTrackOneSegment() throws Exception { + Capture track = new Capture(); - /** - * Tests one track with one segment. - */ - public void testOneTrackOneSegment() throws Exception { - Capture track = new Capture(); + Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime()); + Location location1 = createLocation(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime()); - Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime()); - Location location1 = createLocation(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime()); + expect(contentProviderUtils.insertTrack((Track) AndroidMock.anyObject())) + .andReturn(TRACK_ID_0_URI); + expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0); - expect(contentProviderUtils.insertTrack((Track) AndroidMock.anyObject())) - .andReturn(TRACK_ID_0_URI); - expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0); + // A flush happens at the end + expect(contentProviderUtils.bulkInsertTrackPoint( + LocationsMatcher.eqLoc(location1), eq(1), eq(TRACK_ID_0))).andReturn(1); + expect(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).andReturn(TRACK_POINT_ID_1); + expect( + contentProviderUtils.getTrack(PreferencesUtils.getLong(getContext(), + R.string.recording_track_id_key))).andStubReturn(null); + expectUpdateTrack(track, true, TRACK_ID_0); + AndroidMock.replay(contentProviderUtils); - // A flush happens at the end - expect(contentProviderUtils.bulkInsertTrackPoint( - LocationsMatcher.eqLoc(location1), eq(1), eq(TRACK_ID_0))).andReturn(1); - expect(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).andReturn(TRACK_POINT_ID_1); - expect( - contentProviderUtils.getTrack(PreferencesUtils.getLong(getContext(), - R.string.recording_track_id_key))).andStubReturn(null); - expectUpdateTrack(track, true, TRACK_ID_0); - AndroidMock.replay(contentProviderUtils); + InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes()); + GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter( + getContext(), contentProviderUtils); + long trackId = gpxFileTrackImporter.importFile(inputStream); + assertEquals(TRACK_ID_0, trackId); - InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes()); - GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter( - getContext(), contentProviderUtils); - long trackId = gpxFileTrackImporter.importFile(inputStream); - assertEquals(TRACK_ID_0, trackId); + long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime(); + long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime(); + assertEquals(time1 - time0, track.getValue().getTripStatistics().getTotalTime()); + AndroidMock.verify(contentProviderUtils); + verifyTrack(track.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0); + } - long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime(); - long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime(); - assertEquals(time1 - time0, track.getValue().getTripStatistics().getTotalTime()); - AndroidMock.verify(contentProviderUtils); - verifyTrack(track.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0); - } + /** + * Tests one track with two segments. + */ + public void testOneTrackTwoSegments() throws Exception { + Capture track = new Capture(); - /** - * Tests one track with two segments. - */ - public void testOneTrackTwoSegments() throws Exception { - Capture track = new Capture(); + Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime()); - Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime()); + expect(contentProviderUtils.insertTrack((Track) AndroidMock.anyObject())) + .andReturn(TRACK_ID_0_URI); + expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0); + // A flush happens at the end + expect(contentProviderUtils.bulkInsertTrackPoint( + (Location[]) AndroidMock.anyObject(), eq(5), eq(TRACK_ID_0))).andStubReturn(5); + expect(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).andReturn(TRACK_POINT_ID_3); + expect( + contentProviderUtils.getTrack(PreferencesUtils.getLong(getContext(), + R.string.recording_track_id_key))).andStubReturn(null); + expectUpdateTrack(track, true, TRACK_ID_0); + AndroidMock.replay(contentProviderUtils); - expect(contentProviderUtils.insertTrack((Track) AndroidMock.anyObject())) - .andReturn(TRACK_ID_0_URI); - expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0); - // A flush happens at the end - expect(contentProviderUtils.bulkInsertTrackPoint( - (Location[]) AndroidMock.anyObject(), eq(5), eq(TRACK_ID_0))).andStubReturn(5); - expect(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).andReturn(TRACK_POINT_ID_3); - expect( - contentProviderUtils.getTrack(PreferencesUtils.getLong(getContext(), - R.string.recording_track_id_key))).andStubReturn(null); - expectUpdateTrack(track, true, TRACK_ID_0); - AndroidMock.replay(contentProviderUtils); + InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes()); + GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter( + getContext(), contentProviderUtils); + long trackId = gpxFileTrackImporter.importFile(inputStream); + assertEquals(TRACK_ID_0, trackId); - InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes()); - GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter( - getContext(), contentProviderUtils); - long trackId = gpxFileTrackImporter.importFile(inputStream); - assertEquals(TRACK_ID_0, trackId); + long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime(); + long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime(); + long time2 = DATE_FORMAT_1.parse(TRACK_TIME_2).getTime(); + long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime(); + assertEquals( + time1 - time0 + time3 - time2, track.getValue().getTripStatistics().getTotalTime()); - long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime(); - long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime(); - long time2 = DATE_FORMAT_1.parse(TRACK_TIME_2).getTime(); - long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime(); - assertEquals( - time1 - time0 + time3 - time2, track.getValue().getTripStatistics().getTotalTime()); + AndroidMock.verify(contentProviderUtils); + verifyTrack(track.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, + DATE_FORMAT_0.parse(TRACK_TIME_0).getTime()); + } - AndroidMock.verify(contentProviderUtils); - verifyTrack(track.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, - DATE_FORMAT_0.parse(TRACK_TIME_0).getTime()); - } + /** + * Tests one track with two segments, but no time in the track points. + */ + public void testOneTrackTwoSegmentsNoTime() throws Exception { + Capture track = new Capture(); - /** - * Tests one track with two segments, but no time in the track points. - */ - public void testOneTrackTwoSegmentsNoTime() throws Exception { - Capture track = new Capture(); + expect(contentProviderUtils.insertTrack((Track) AndroidMock.anyObject())) + .andReturn(TRACK_ID_0_URI); + expectFirstTrackPoint(null, TRACK_ID_0, TRACK_POINT_ID_0); - expect(contentProviderUtils.insertTrack((Track) AndroidMock.anyObject())) - .andReturn(TRACK_ID_0_URI); - expectFirstTrackPoint(null, TRACK_ID_0, TRACK_POINT_ID_0); + // A flush happens at the end + expect(contentProviderUtils.bulkInsertTrackPoint( + (Location[]) AndroidMock.anyObject(), eq(5), eq(TRACK_ID_0))).andStubReturn(5); + expect(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).andReturn(TRACK_POINT_ID_3); + expect( + contentProviderUtils.getTrack(PreferencesUtils.getLong(getContext(), + R.string.recording_track_id_key))).andStubReturn(null); + expectUpdateTrack(track, true, TRACK_ID_0); + AndroidMock.replay(contentProviderUtils); - // A flush happens at the end - expect(contentProviderUtils.bulkInsertTrackPoint( - (Location[]) AndroidMock.anyObject(), eq(5), eq(TRACK_ID_0))).andStubReturn(5); - expect(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).andReturn(TRACK_POINT_ID_3); - expect( - contentProviderUtils.getTrack(PreferencesUtils.getLong(getContext(), - R.string.recording_track_id_key))).andStubReturn(null); - expectUpdateTrack(track, true, TRACK_ID_0); - AndroidMock.replay(contentProviderUtils); + InputStream inputStream = new ByteArrayInputStream( + VALID_ONE_TRACK_TWO_SEGMENTS_NO_TIME_GPX.getBytes()); + GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter( + getContext(), contentProviderUtils); + long trackId = gpxFileTrackImporter.importFile(inputStream); + assertEquals(TRACK_ID_0, trackId); + assertEquals(0, track.getValue().getTripStatistics().getTotalTime()); - InputStream inputStream = new ByteArrayInputStream( - VALID_ONE_TRACK_TWO_SEGMENTS_NO_TIME_GPX.getBytes()); - GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter( - getContext(), contentProviderUtils); - long trackId = gpxFileTrackImporter.importFile(inputStream); - assertEquals(TRACK_ID_0, trackId); - assertEquals(0, track.getValue().getTripStatistics().getTotalTime()); + AndroidMock.verify(contentProviderUtils); + verifyTrack(track.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, -1L); + } - AndroidMock.verify(contentProviderUtils); - verifyTrack(track.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, -1L); - } + /** + * Test an invalid xml input. + */ + public void testInvalidXml() throws Exception { + testInvalidGpx(INVALID_XML_GPX); + } - /** - * Test an invalid xml input. - */ - public void testInvalidXml() throws Exception { - testInvalidGpx(INVALID_XML_GPX); - } + /** + * Test an invalid location. + */ + public void testInvalidLocation() throws Exception { + testInvalidGpx(INVALID_LOCATION_GPX); + } - /** - * Test an invalid location. - */ - public void testInvalidLocation() throws Exception { - testInvalidGpx(INVALID_LOCATION_GPX); - } + /** + * Test an invalid time. + */ + public void testInvalidTime() throws Exception { + testInvalidGpx(INVALID_TIME_GPX); + } - /** - * Test an invalid time. - */ - public void testInvalidTime() throws Exception { - testInvalidGpx(INVALID_TIME_GPX); - } + /** + * Test an invalid altitude. + */ + public void testInvalidAltitude() throws Exception { + testInvalidGpx(INVALID_ALTITUDE_GPX); + } - /** - * Test an invalid altitude. - */ - public void testInvalidAltitude() throws Exception { - testInvalidGpx(INVALID_ALTITUDE_GPX); - } + /** + * Test an invalid latitude. + */ + public void testInvalidLatitude() throws Exception { + testInvalidGpx(INVALID_LATITUDE_GPX); + } - /** - * Test an invalid latitude. - */ - public void testInvalidLatitude() throws Exception { - testInvalidGpx(INVALID_LATITUDE_GPX); - } + /** + * Test an invalid longitude. + */ + public void testInvalidLongitude() throws Exception { + testInvalidGpx(INVALID_LONGITUDE_GPX); + } - /** - * Test an invalid longitude. - */ - public void testInvalidLongitude() throws Exception { - testInvalidGpx(INVALID_LONGITUDE_GPX); - } + private void testInvalidGpx(String xml) throws Exception { + expect(contentProviderUtils.insertTrack((Track) AndroidMock.anyObject())) + .andReturn(TRACK_ID_0_URI); - private void testInvalidGpx(String xml) throws Exception { - expect(contentProviderUtils.insertTrack((Track) AndroidMock.anyObject())) - .andReturn(TRACK_ID_0_URI); + // For the following, use StubReturn since we don't care whether they are + // invoked or not. + expect(contentProviderUtils.bulkInsertTrackPoint( + (Location[]) AndroidMock.anyObject(), AndroidMock.anyInt(), AndroidMock.anyLong())) + .andStubReturn(1); + expect(contentProviderUtils.getFirstTrackPointId(TRACK_ID_0)).andStubReturn(TRACK_POINT_ID_0); + expect(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).andStubReturn(TRACK_POINT_ID_0); + expect( + contentProviderUtils.getTrack(PreferencesUtils.getLong(getContext(), + R.string.recording_track_id_key))).andStubReturn(null); + contentProviderUtils.deleteTrack(getContext(), TRACK_ID_0); + AndroidMock.replay(contentProviderUtils); - // For the following, use StubReturn since we don't care whether they are - // invoked or not. - expect(contentProviderUtils.bulkInsertTrackPoint( - (Location[]) AndroidMock.anyObject(), AndroidMock.anyInt(), AndroidMock.anyLong())) - .andStubReturn(1); - expect(contentProviderUtils.getFirstTrackPointId(TRACK_ID_0)).andStubReturn(TRACK_POINT_ID_0); - expect(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).andStubReturn(TRACK_POINT_ID_0); - expect( - contentProviderUtils.getTrack(PreferencesUtils.getLong(getContext(), - R.string.recording_track_id_key))).andStubReturn(null); - contentProviderUtils.deleteTrack(getContext(), TRACK_ID_0); - AndroidMock.replay(contentProviderUtils); - - InputStream inputStream = new ByteArrayInputStream(xml.getBytes()); - GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter( - getContext(), contentProviderUtils); - long trackId = gpxFileTrackImporter.importFile(inputStream); - assertEquals(-1L, trackId); - AndroidMock.verify(contentProviderUtils); - } + InputStream inputStream = new ByteArrayInputStream(xml.getBytes()); + GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter( + getContext(), contentProviderUtils); + long trackId = gpxFileTrackImporter.importFile(inputStream); + assertEquals(-1L, trackId); + AndroidMock.verify(contentProviderUtils); + } } diff --git a/src/androidTest/java/com/google/android/apps/mytracks/io/file/importer/KmlFileTrackImporterTest.java b/src/androidTest/java/com/google/android/apps/mytracks/io/file/importer/KmlFileTrackImporterTest.java index ed5b5074f..70a343100 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/io/file/importer/KmlFileTrackImporterTest.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/io/file/importer/KmlFileTrackImporterTest.java @@ -16,126 +16,126 @@ package com.google.android.apps.mytracks.io.file.importer; -import static com.google.android.testing.mocking.AndroidMock.eq; -import static com.google.android.testing.mocking.AndroidMock.expect; +import android.location.Location; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.util.PreferencesUtils; import com.google.android.maps.mytracks.R; import com.google.android.testing.mocking.AndroidMock; -import android.location.Location; +import org.easymock.Capture; import java.io.ByteArrayInputStream; import java.io.InputStream; -import org.easymock.Capture; +import static com.google.android.testing.mocking.AndroidMock.eq; +import static com.google.android.testing.mocking.AndroidMock.expect; /** * Tests for {@link KmlFileTrackImporter}. - * + * * @author Jimmy Shih */ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter { - private static String getNameAndDescription(String name, String description) { - return ""; - } + private static final String VALID_ONE_TRACK_ONE_SEGMENT_GPX = + "" + + getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "" + + getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1) + + ""; + private static final String VALID_ONE_TRACK_TWO_SEGMENTS_GPX = + "" + + getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "" + + getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1) + "" + + getTrackPoint(2, TRACK_TIME_2) + getTrackPoint(3, TRACK_TIME_3) + + ""; - private static String getTrackPoint(int index, String time) { - String latitude = Double.toString(TRACK_LATITUDE + index); - String longitude = Double.toString(TRACK_LONGITUDE + index); - String altitude = Double.toString(TRACK_ELEVATION + index); - StringBuffer buffer = new StringBuffer(); - buffer.append("" + time + "" + "" + longitude + " " + latitude + " " - + altitude + ""); - return buffer.toString(); - } + private static String getNameAndDescription(String name, String description) { + return ""; + } - private static final String VALID_ONE_TRACK_ONE_SEGMENT_GPX = - "" - + getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "" - + getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1) - + ""; - private static final String VALID_ONE_TRACK_TWO_SEGMENTS_GPX = - "" - + getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "" - + getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1) + "" - + getTrackPoint(2, TRACK_TIME_2) + getTrackPoint(3, TRACK_TIME_3) - + ""; + private static String getTrackPoint(int index, String time) { + String latitude = Double.toString(TRACK_LATITUDE + index); + String longitude = Double.toString(TRACK_LONGITUDE + index); + String altitude = Double.toString(TRACK_ELEVATION + index); + StringBuffer buffer = new StringBuffer(); + buffer.append("" + time + "" + "" + longitude + " " + latitude + " " + + altitude + ""); + return buffer.toString(); + } - /** - * Tests one track with one segment. - */ - public void testOneTrackOneSegment() throws Exception { - Capture track = new Capture(); + /** + * Tests one track with one segment. + */ + public void testOneTrackOneSegment() throws Exception { + Capture track = new Capture(); - Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime()); - Location location1 = createLocation(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime()); + Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime()); + Location location1 = createLocation(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime()); - contentProviderUtils.clearTrack(getContext(), TRACK_ID_0); - expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0); + contentProviderUtils.clearTrack(getContext(), TRACK_ID_0); + expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0); - // A flush happens at the end - expect(contentProviderUtils.bulkInsertTrackPoint( - LocationsMatcher.eqLoc(location1), eq(1), eq(TRACK_ID_0))).andReturn(1); - expect(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).andReturn(TRACK_POINT_ID_1); - expect( - contentProviderUtils.getTrack(PreferencesUtils.getLong(getContext(), - R.string.recording_track_id_key))).andStubReturn(null); - expectUpdateTrack(track, true, TRACK_ID_0); - AndroidMock.replay(contentProviderUtils); + // A flush happens at the end + expect(contentProviderUtils.bulkInsertTrackPoint( + LocationsMatcher.eqLoc(location1), eq(1), eq(TRACK_ID_0))).andReturn(1); + expect(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).andReturn(TRACK_POINT_ID_1); + expect( + contentProviderUtils.getTrack(PreferencesUtils.getLong(getContext(), + R.string.recording_track_id_key))).andStubReturn(null); + expectUpdateTrack(track, true, TRACK_ID_0); + AndroidMock.replay(contentProviderUtils); - InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes()); - KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter( - getContext(), TRACK_ID_0, contentProviderUtils); - long trackId = kmlFileTrackImporter.importFile(inputStream); - assertEquals(TRACK_ID_0, trackId); + InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes()); + KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter( + getContext(), TRACK_ID_0, contentProviderUtils); + long trackId = kmlFileTrackImporter.importFile(inputStream); + assertEquals(TRACK_ID_0, trackId); - long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime(); - long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime(); - assertEquals(time1 - time0, track.getValue().getTripStatistics().getTotalTime()); - AndroidMock.verify(contentProviderUtils); - verifyTrack(track.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0); - } + long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime(); + long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime(); + assertEquals(time1 - time0, track.getValue().getTripStatistics().getTotalTime()); + AndroidMock.verify(contentProviderUtils); + verifyTrack(track.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0); + } - /** - * Tests one track with two segments. - */ - public void testOneTrackTwoSegments() throws Exception { - Capture track = new Capture(); + /** + * Tests one track with two segments. + */ + public void testOneTrackTwoSegments() throws Exception { + Capture track = new Capture(); - Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime()); + Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime()); - contentProviderUtils.clearTrack(getContext(), TRACK_ID_0); - expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0); + contentProviderUtils.clearTrack(getContext(), TRACK_ID_0); + expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0); - // A flush happens at the end - expect(contentProviderUtils.bulkInsertTrackPoint( - (Location[]) AndroidMock.anyObject(), eq(5), eq(TRACK_ID_0))).andStubReturn(5); - expect(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).andReturn(TRACK_POINT_ID_3); - expect( - contentProviderUtils.getTrack(PreferencesUtils.getLong(getContext(), - R.string.recording_track_id_key))).andStubReturn(null); - expectUpdateTrack(track, true, TRACK_ID_0); - AndroidMock.replay(contentProviderUtils); + // A flush happens at the end + expect(contentProviderUtils.bulkInsertTrackPoint( + (Location[]) AndroidMock.anyObject(), eq(5), eq(TRACK_ID_0))).andStubReturn(5); + expect(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).andReturn(TRACK_POINT_ID_3); + expect( + contentProviderUtils.getTrack(PreferencesUtils.getLong(getContext(), + R.string.recording_track_id_key))).andStubReturn(null); + expectUpdateTrack(track, true, TRACK_ID_0); + AndroidMock.replay(contentProviderUtils); - InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes()); - KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter( - getContext(), TRACK_ID_0, contentProviderUtils); - long trackId = kmlFileTrackImporter.importFile(inputStream); - assertEquals(TRACK_ID_0, trackId); + InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes()); + KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter( + getContext(), TRACK_ID_0, contentProviderUtils); + long trackId = kmlFileTrackImporter.importFile(inputStream); + assertEquals(TRACK_ID_0, trackId); - long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime(); - long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime(); - long time2 = DATE_FORMAT_1.parse(TRACK_TIME_2).getTime(); - long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime(); - assertEquals( - time1 - time0 + time3 - time2, track.getValue().getTripStatistics().getTotalTime()); + long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime(); + long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime(); + long time2 = DATE_FORMAT_1.parse(TRACK_TIME_2).getTime(); + long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime(); + assertEquals( + time1 - time0 + time3 - time2, track.getValue().getTripStatistics().getTotalTime()); - AndroidMock.verify(contentProviderUtils); - verifyTrack(track.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, - DATE_FORMAT_0.parse(TRACK_TIME_0).getTime()); - } + AndroidMock.verify(contentProviderUtils); + verifyTrack(track.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, + DATE_FORMAT_0.parse(TRACK_TIME_0).getTime()); + } } diff --git a/src/androidTest/java/com/google/android/apps/mytracks/io/file/importer/LocationsMatcher.java b/src/androidTest/java/com/google/android/apps/mytracks/io/file/importer/LocationsMatcher.java index b54979259..a91ed960f 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/io/file/importer/LocationsMatcher.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/io/file/importer/LocationsMatcher.java @@ -16,66 +16,66 @@ package com.google.android.apps.mytracks.io.file.importer; -import com.google.android.testing.mocking.AndroidMock; - import android.location.Location; -import java.util.Arrays; +import com.google.android.testing.mocking.AndroidMock; import org.easymock.IArgumentMatcher; +import java.util.Arrays; + /** * Locations matcher. Workaround because of capture bug 2617107 in easymock: * http://sourceforge.net/tracker/?func=detail&aid=2617107&group_id=82958&atid=567837 - * + * * @author Jimmy Shih */ public class LocationsMatcher implements IArgumentMatcher { - public static Location[] eqLoc(Location expected) { - return eqLoc(new Location[] { expected }); - } + private final Location[] expectedLocations; - public static Location[] eqLoc(Location[] expected) { - IArgumentMatcher matcher = new LocationsMatcher(expected); - AndroidMock.reportMatcher(matcher); - return null; - } - - private final Location[] expectedLocations; - - private LocationsMatcher(Location[] expected) { - this.expectedLocations = expected; - } - - @Override - public void appendTo(StringBuffer buf) { - buf.append("eqLoc(").append(Arrays.toString(expectedLocations)).append(")"); - } - - @Override - public boolean matches(Object obj) { - if (!(obj instanceof Location[])) { - return false; - } - Location[] locations = (Location[]) obj; - if (locations.length < expectedLocations.length) { - return false; + private LocationsMatcher(Location[] expected) { + this.expectedLocations = expected; } - // Only check the first elements (those that will be taken into account) - for (int i = 0; i < expectedLocations.length; i++) { - if (!matchLocation(locations[i], expectedLocations[i])) { - return false; - } + public static Location[] eqLoc(Location expected) { + return eqLoc(new Location[]{expected}); } - return true; - } - private boolean matchLocation(Location location1, Location location2) { - return (location1.getTime() == location2.getTime()) - && (location1.getLatitude() == location2.getLatitude()) - && (location1.getLongitude() == location2.getLongitude()) - && (location1.getAltitude() == location2.getAltitude()); - } + public static Location[] eqLoc(Location[] expected) { + IArgumentMatcher matcher = new LocationsMatcher(expected); + AndroidMock.reportMatcher(matcher); + return null; + } + + @Override + public void appendTo(StringBuffer buf) { + buf.append("eqLoc(").append(Arrays.toString(expectedLocations)).append(")"); + } + + @Override + public boolean matches(Object obj) { + if (!(obj instanceof Location[])) { + return false; + } + Location[] locations = (Location[]) obj; + if (locations.length < expectedLocations.length) { + return false; + } + + // Only check the first elements (those that will be taken into account) + for (int i = 0; i < expectedLocations.length; i++) { + if (!matchLocation(locations[i], expectedLocations[i])) { + return false; + } + } + return true; + } + + private boolean matchLocation(Location location1, Location location2) { + return (location1.getTime() == location2.getTime()) + && (location1.getLatitude() == location2.getLatitude()) + && (location1.getLongitude() == location2.getLongitude()) + && (location1.getAltitude() == location2.getAltitude()); + } } diff --git a/src/androidTest/java/com/google/android/apps/mytracks/services/AdaptiveLocationListenerPolicyTest.java b/src/androidTest/java/com/google/android/apps/mytracks/services/AdaptiveLocationListenerPolicyTest.java index 22741cad5..4e91d4d70 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/services/AdaptiveLocationListenerPolicyTest.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/services/AdaptiveLocationListenerPolicyTest.java @@ -1,12 +1,12 @@ /* * Copyright 2012 Google Inc. - * + * * 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 @@ -19,58 +19,58 @@ import junit.framework.TestCase; /** * Tests the {@link AdaptiveLocationListenerPolicy}. - * + * * @author youtaol */ public class AdaptiveLocationListenerPolicyTest extends TestCase { - private AdaptiveLocationListenerPolicy adocationListenerPolicy; - private static final long MIN = 1000; - private static final long MAX = 3000; - private static final int MIN_DISTANCE = 10; - private static final long NEW_IDLE_TIME_BIG = 10000; - private static final long NEW_IDLE_TIME_NORMAL = 5000; - private static final long NEW_IDLE_TIME_SMALL = 2000; - private static final long NEW_IDLE_TIME_LESS_THAN_MIN = 500; + private static final long MIN = 1000; + private static final long MAX = 3000; + private static final int MIN_DISTANCE = 10; + private static final long NEW_IDLE_TIME_BIG = 10000; + private static final long NEW_IDLE_TIME_NORMAL = 5000; + private static final long NEW_IDLE_TIME_SMALL = 2000; + private static final long NEW_IDLE_TIME_LESS_THAN_MIN = 500; + private AdaptiveLocationListenerPolicy adocationListenerPolicy; - @Override - protected void setUp() throws Exception { - super.setUp(); - adocationListenerPolicy = new AdaptiveLocationListenerPolicy(MIN, MAX, MIN_DISTANCE); - } + @Override + protected void setUp() throws Exception { + super.setUp(); + adocationListenerPolicy = new AdaptiveLocationListenerPolicy(MIN, MAX, MIN_DISTANCE); + } - /** - * Tests the - * {@link AdaptiveLocationListenerPolicy#getDesiredPollingInterval()} in four - * situations. - *

    - *
  • The newIdleTime is bigger than max interval.
  • - *
  • The newIdleTime is between min and max interval.
  • - *
  • The newIdleTime is smaller than max interval.
  • - *
  • The newIdleTime is smaller than the smallest interval unit.
  • - *
- */ - public void testGetDesiredPollingInterval() { - adocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_BIG); - assertEquals(MAX, adocationListenerPolicy.getDesiredPollingInterval()); + /** + * Tests the + * {@link AdaptiveLocationListenerPolicy#getDesiredPollingInterval()} in four + * situations. + *
    + *
  • The newIdleTime is bigger than max interval.
  • + *
  • The newIdleTime is between min and max interval.
  • + *
  • The newIdleTime is smaller than max interval.
  • + *
  • The newIdleTime is smaller than the smallest interval unit.
  • + *
+ */ + public void testGetDesiredPollingInterval() { + adocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_BIG); + assertEquals(MAX, adocationListenerPolicy.getDesiredPollingInterval()); - adocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_NORMAL); - // First get the half of NEW_IDLE_TIME_NORMAL, and then round it to the - // nearest second. - assertEquals((NEW_IDLE_TIME_NORMAL / 2 / 1000) * 1000, - adocationListenerPolicy.getDesiredPollingInterval()); + adocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_NORMAL); + // First get the half of NEW_IDLE_TIME_NORMAL, and then round it to the + // nearest second. + assertEquals((NEW_IDLE_TIME_NORMAL / 2 / 1000) * 1000, + adocationListenerPolicy.getDesiredPollingInterval()); - adocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_SMALL); - assertEquals(MIN, adocationListenerPolicy.getDesiredPollingInterval()); + adocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_SMALL); + assertEquals(MIN, adocationListenerPolicy.getDesiredPollingInterval()); - adocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_LESS_THAN_MIN); - assertEquals(MIN, adocationListenerPolicy.getDesiredPollingInterval()); - } + adocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_LESS_THAN_MIN); + assertEquals(MIN, adocationListenerPolicy.getDesiredPollingInterval()); + } - /** - * Tests the method {@link AdaptiveLocationListenerPolicy#getMinDistance()}. - */ - public void testGetMinDistance() { - assertEquals(MIN_DISTANCE, adocationListenerPolicy.getMinDistance()); - } + /** + * Tests the method {@link AdaptiveLocationListenerPolicy#getMinDistance()}. + */ + public void testGetMinDistance() { + assertEquals(MIN_DISTANCE, adocationListenerPolicy.getMinDistance()); + } } diff --git a/src/androidTest/java/com/google/android/apps/mytracks/services/ControlRecordingServiceTest.java b/src/androidTest/java/com/google/android/apps/mytracks/services/ControlRecordingServiceTest.java index e7c887a5f..6d1faab98 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/services/ControlRecordingServiceTest.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/services/ControlRecordingServiceTest.java @@ -15,91 +15,91 @@ */ package com.google.android.apps.mytracks.services; -import com.google.android.maps.mytracks.R; -import com.google.android.testing.mocking.UsesMocks; - import android.content.Context; import android.content.Intent; import android.os.RemoteException; import android.test.ServiceTestCase; +import com.google.android.maps.mytracks.R; +import com.google.android.testing.mocking.UsesMocks; + import org.easymock.EasyMock; /** * Tests {@link ControlRecordingService}. - * + * * @author Youtao Liu */ public class ControlRecordingServiceTest extends ServiceTestCase { - private Context context; - private ControlRecordingService controlRecordingService; + private Context context; + private ControlRecordingService controlRecordingService; - public ControlRecordingServiceTest() { - super(ControlRecordingService.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - context = getContext(); - } - - /** - * Tests the start of {@link ControlRecordingService} and tests the method - * {@link ControlRecordingService#onHandleIntent(Intent, ITrackRecordingService)} - * to start a track recording. - */ - @UsesMocks(ITrackRecordingService.class) - public void testStartRecording() { - assertNull(controlRecordingService); - Intent intent = startControlRecordingService(context.getString(R.string.track_action_start)); - assertNotNull(controlRecordingService); - - ITrackRecordingService iTrackRecordingServiceMock = EasyMock - .createStrictMock(ITrackRecordingService.class); - try { - EasyMock.expect(iTrackRecordingServiceMock.startNewTrack()).andReturn(1L); - EasyMock.replay(iTrackRecordingServiceMock); - controlRecordingService.onHandleIntent(intent, iTrackRecordingServiceMock); - EasyMock.verify(iTrackRecordingServiceMock); - } catch (RemoteException e) { - fail(); + public ControlRecordingServiceTest() { + super(ControlRecordingService.class); } - } - /** - * Tests the method - * {@link ControlRecordingService#onHandleIntent(Intent, ITrackRecordingService)} - * to stop a track recording. - */ - @UsesMocks(ITrackRecordingService.class) - public void testStopRecording() { - Intent intent = startControlRecordingService(context.getString(R.string.track_action_end)); - - ITrackRecordingService iTrackRecordingServiceMock = EasyMock - .createStrictMock(ITrackRecordingService.class); - try { - iTrackRecordingServiceMock.endCurrentTrack(); - EasyMock.replay(iTrackRecordingServiceMock); - controlRecordingService.onHandleIntent(intent, iTrackRecordingServiceMock); - EasyMock.verify(iTrackRecordingServiceMock); - } catch (RemoteException e) { - fail(); + @Override + protected void setUp() throws Exception { + super.setUp(); + context = getContext(); + } + + /** + * Tests the start of {@link ControlRecordingService} and tests the method + * {@link ControlRecordingService#onHandleIntent(Intent, ITrackRecordingService)} + * to start a track recording. + */ + @UsesMocks(ITrackRecordingService.class) + public void testStartRecording() { + assertNull(controlRecordingService); + Intent intent = startControlRecordingService(context.getString(R.string.track_action_start)); + assertNotNull(controlRecordingService); + + ITrackRecordingService iTrackRecordingServiceMock = EasyMock + .createStrictMock(ITrackRecordingService.class); + try { + EasyMock.expect(iTrackRecordingServiceMock.startNewTrack()).andReturn(1L); + EasyMock.replay(iTrackRecordingServiceMock); + controlRecordingService.onHandleIntent(intent, iTrackRecordingServiceMock); + EasyMock.verify(iTrackRecordingServiceMock); + } catch (RemoteException e) { + fail(); + } + } + + /** + * Tests the method + * {@link ControlRecordingService#onHandleIntent(Intent, ITrackRecordingService)} + * to stop a track recording. + */ + @UsesMocks(ITrackRecordingService.class) + public void testStopRecording() { + Intent intent = startControlRecordingService(context.getString(R.string.track_action_end)); + + ITrackRecordingService iTrackRecordingServiceMock = EasyMock + .createStrictMock(ITrackRecordingService.class); + try { + iTrackRecordingServiceMock.endCurrentTrack(); + EasyMock.replay(iTrackRecordingServiceMock); + controlRecordingService.onHandleIntent(intent, iTrackRecordingServiceMock); + EasyMock.verify(iTrackRecordingServiceMock); + } catch (RemoteException e) { + fail(); + } + } + + /** + * Starts a ControlRecordingService with a specified action. + * + * @param action the action string in the start intent + */ + private Intent startControlRecordingService(String action) { + Intent intent = new Intent(context, ControlRecordingService.class); + intent.setAction(action); + startService(intent); + controlRecordingService = getService(); + return intent; } - } - - /** - * Starts a ControlRecordingService with a specified action. - * - * @param action the action string in the start intent - */ - private Intent startControlRecordingService(String action) { - Intent intent = new Intent(context, ControlRecordingService.class); - intent.setAction(action); - startService(intent); - controlRecordingService = getService(); - return intent; - } } diff --git a/src/androidTest/java/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java b/src/androidTest/java/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java index 455b66556..3a9800031 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java @@ -38,8 +38,8 @@ import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; import com.google.android.apps.mytracks.Constants; -import com.google.android.apps.mytracks.content.CustomContentProvider; import com.google.android.apps.mytracks.content.ContentProviderUtils; +import com.google.android.apps.mytracks.content.CustomContentProvider; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.content.Waypoint.WaypointType; @@ -58,703 +58,703 @@ import java.util.List; * not possible to properly shutdown the service, unless tearDown() is called, * which prevents from testing multiple scenarios in a single test (see * runFunctionTest for more details). - * + * * @author Bartlomiej Niechwiej */ public class TrackRecordingServiceTest extends ServiceTestCase { - private Context context; - private ContentProviderUtils providerUtils; - - /* - * In order to support starting and binding to the service in the same unit - * test, we provide a workaround, as the original class doesn't allow to bind - * after the service has been previously started. - */ - - private boolean bound; - private Intent serviceIntent; - - public TrackRecordingServiceTest() { - super(TestRecordingService.class); - } - - /** - * A context wrapper with the user provided {@link ContentResolver}. TODO: - * Move to test utils package. - */ - public static class MockContext extends ContextWrapper { - private final ContentResolver contentResolver; - - public MockContext(ContentResolver contentResolver, Context base) { - super(base); - this.contentResolver = contentResolver; - } - - @Override - public ContentResolver getContentResolver() { - return contentResolver; - } - } - - @Override - protected IBinder bindService(Intent intent) { - if (getService() != null) { - if (bound) { - throw new IllegalStateException("Service: " + getService() + " is already bound"); - } - bound = true; - serviceIntent = intent.cloneFilter(); - return getService().onBind(intent); - } else { - return super.bindService(intent); - } - } - - @Override - protected void shutdownService() { - if (bound) { - assertNotNull(getService()); - getService().onUnbind(serviceIntent); - bound = false; - } - super.shutdownService(); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); + private Context context; + private ContentProviderUtils providerUtils; /* - * Create a mock context that uses a mock content resolver and a renaming - * delegating context. + * In order to support starting and binding to the service in the same unit + * test, we provide a workaround, as the original class doesn't allow to bind + * after the service has been previously started. */ - MockContentResolver mockContentResolver = new MockContentResolver(); - RenamingDelegatingContext renamingDelegatingContext = new RenamingDelegatingContext( - getContext(), getContext(), "test."); - context = new MockContext(mockContentResolver, renamingDelegatingContext); - // Set up the mock content resolver - CustomContentProvider customContentProvider = new CustomContentProvider(); - customContentProvider.attachInfo(context, null); - mockContentResolver.addProvider(ContentProviderUtils.AUTHORITY, customContentProvider); + private boolean bound; + private Intent serviceIntent; - MockContentProvider settingsProvider = new MockContentProvider(context) { - @Override - public Bundle call(String method, String arg, Bundle extras) { - return null; - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - return null; - } - }; - mockContentResolver.addProvider(Settings.AUTHORITY, settingsProvider); - - // Set the context - setContext(context); - - providerUtils = ContentProviderUtils.Factory.get(context); - - SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); - // Let's use default values. - sharedPreferences.edit().clear().apply(); - - // Disable auto resume by default. - updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, 0); - // No recording track. - PreferencesUtils.setLong( - context, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT); - } - - @SmallTest - public void testStartable() { - startService(createStartIntent()); - assertNotNull(getService()); - } - - @MediumTest - public void testBindable() { - IBinder service = bindService(createStartIntent()); - assertNotNull(service); - } - - @MediumTest - public void testResumeAfterReboot_shouldResume() throws Exception { - // Insert a dummy track and mark it as recording track. - createDummyTrack(123L, System.currentTimeMillis(), true); - - // Clear the number of attempts and set the timeout to 10 min. - updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, - PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT); - - // Start the service in "resume" mode (simulates the on-reboot action). - Intent startIntent = createStartIntent(); - startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true); - startService(startIntent); - assertNotNull(getService()); - - // We expect to resume the previous track. - assertTrue(getService().isRecording()); - ITrackRecordingService service = bindAndGetService(createStartIntent()); - assertEquals(123L, service.getRecordingTrackId()); - } - - /* - * TODO: shutdownService() has a bug and doesn't set mServiceCreated to false, - * thus preventing from a second call to onCreate(). Report the bug to Android - * team. Until then, the following tests and checks must be commented out. - * - * TODO: If fixed, remove "disabled" prefix from the test name. - */ - @MediumTest - public void disabledTestResumeAfterReboot_simulateReboot() throws Exception { - updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, - PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT); - ITrackRecordingService service = bindAndGetService(createStartIntent()); - assertFalse(service.isRecording()); - - // Simulate recording a track. - long id = service.startNewTrack(); - assertTrue(service.isRecording()); - assertEquals(id, service.getRecordingTrackId()); - shutdownService(); - assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key)); - - // Start the service in "resume" mode (simulates the on-reboot action). - Intent startIntent = createStartIntent(); - startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true); - startService(startIntent); - assertNotNull(getService()); - - assertTrue(getService().isRecording()); - } - - @MediumTest - public void testResumeAfterReboot_noRecordingTrack() throws Exception { - // Insert a dummy track and mark it as recording track. - createDummyTrack(123L, System.currentTimeMillis(), false); - - // Clear the number of attempts and set the timeout to 10 min. - updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, - PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT); - - // Start the service in "resume" mode (simulates the on-reboot action). - Intent startIntent = createStartIntent(); - startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true); - startService(startIntent); - assertNotNull(getService()); - - // We don't expect to resume the previous track, because it was stopped. - assertFalse(getService().isRecording()); - ITrackRecordingService service = bindAndGetService(createStartIntent()); - assertEquals(-1L, service.getRecordingTrackId()); - } - - @MediumTest - public void testResumeAfterReboot_expiredTrack() throws Exception { - // Insert a dummy track last updated 20 min ago. - createDummyTrack(123L, System.currentTimeMillis() - 20 * 60 * 1000, true); - - // Clear the number of attempts and set the timeout to 10 min. - updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, - PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT); - - // Start the service in "resume" mode (simulates the on-reboot action). - Intent startIntent = createStartIntent(); - startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true); - startService(startIntent); - assertNotNull(getService()); - - // We don't expect to resume the previous track, because it has expired. - assertFalse(getService().isRecording()); - ITrackRecordingService service = bindAndGetService(createStartIntent()); - assertEquals(-1L, service.getRecordingTrackId()); - } - - @MediumTest - public void testResumeAfterReboot_tooManyAttempts() throws Exception { - // Insert a dummy track. - createDummyTrack(123L, System.currentTimeMillis(), true); - - // Set the number of attempts to max. - updateAutoResumePrefs(TrackRecordingService.MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS, - PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT); - - // Start the service in "resume" mode (simulates the on-reboot action). - Intent startIntent = createStartIntent(); - startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true); - startService(startIntent); - assertNotNull(getService()); - - // We don't expect to resume the previous track, because there were already - // too many attempts. - assertFalse(getService().isRecording()); - ITrackRecordingService service = bindAndGetService(createStartIntent()); - assertEquals(-1L, service.getRecordingTrackId()); - } - - @MediumTest - public void testRecording_noTracks() throws Exception { - List tracks = providerUtils.getAllTracks(); - assertTrue(tracks.isEmpty()); - - ITrackRecordingService service = bindAndGetService(createStartIntent()); - // Test if we start in no-recording mode by default. - assertFalse(service.isRecording()); - assertEquals(-1L, service.getRecordingTrackId()); - } - - @MediumTest - public void testRecording_oldTracks() throws Exception { - createDummyTrack(123L, -1L, false); - - ITrackRecordingService service = bindAndGetService(createStartIntent()); - assertFalse(service.isRecording()); - assertEquals(-1L, service.getRecordingTrackId()); - } - - @MediumTest - public void testRecording_orphanedRecordingTrack() throws Exception { - // Just set recording track to a bogus value. - PreferencesUtils.setLong(context, R.string.recording_track_id_key, 256L); - - // Make sure that the service will not start recording and will clear - // the bogus track. - ITrackRecordingService service = bindAndGetService(createStartIntent()); - assertFalse(service.isRecording()); - assertEquals(-1L, service.getRecordingTrackId()); - } - - /** - * Synchronous/waitable broadcast receiver to be used in testing. - */ - private class BlockingBroadcastReceiver extends BroadcastReceiver { - private static final long MAX_WAIT_TIME_MS = 10000; - private List receivedIntents = new ArrayList(); - - public List getReceivedIntents() { - return receivedIntents; + public TrackRecordingServiceTest() { + super(TestRecordingService.class); } @Override - public void onReceive(Context ctx, Intent intent) { - Log.d("Test", "Got broadcast: " + intent); - synchronized (receivedIntents) { - receivedIntents.add(intent); - receivedIntents.notifyAll(); - } - } - - public boolean waitUntilReceived(int receiveCount) { - long deadline = System.currentTimeMillis() + MAX_WAIT_TIME_MS; - synchronized (receivedIntents) { - while (receivedIntents.size() < receiveCount) { - try { - // Wait releases synchronized lock until it returns - receivedIntents.wait(500); - } catch (InterruptedException e) { - // Do nothing - } - - if (System.currentTimeMillis() > deadline) { - return false; - } + protected IBinder bindService(Intent intent) { + if (getService() != null) { + if (bound) { + throw new IllegalStateException("Service: " + getService() + " is already bound"); + } + bound = true; + serviceIntent = intent.cloneFilter(); + return getService().onBind(intent); + } else { + return super.bindService(intent); } - } - - return true; } - } - @MediumTest - public void testStartNewTrack_noRecording() throws Exception { - // NOTICE: due to the way Android permissions work, if this fails, - // uninstall the test apk then retry - the test must be installed *after* the app (go figure). - // Reference: http://code.google.com/p/android/issues/detail?id=5521 - BlockingBroadcastReceiver startReceiver = new BlockingBroadcastReceiver(); - String startAction = context.getString(R.string.track_started_broadcast_action); - context.registerReceiver(startReceiver, new IntentFilter(startAction)); + @Override + protected void shutdownService() { + if (bound) { + assertNotNull(getService()); + getService().onUnbind(serviceIntent); + bound = false; + } + super.shutdownService(); + } - List tracks = providerUtils.getAllTracks(); - assertTrue(tracks.isEmpty()); + @Override + protected void setUp() throws Exception { + super.setUp(); - ITrackRecordingService service = bindAndGetService(createStartIntent()); - assertFalse(service.isRecording()); + /* + * Create a mock context that uses a mock content resolver and a renaming + * delegating context. + */ + MockContentResolver mockContentResolver = new MockContentResolver(); + RenamingDelegatingContext renamingDelegatingContext = new RenamingDelegatingContext( + getContext(), getContext(), "test."); + context = new MockContext(mockContentResolver, renamingDelegatingContext); - long id = service.startNewTrack(); - assertTrue(id >= 0); - assertTrue(service.isRecording()); - Track track = providerUtils.getTrack(id); - assertNotNull(track); - assertEquals(id, track.getId()); - assertEquals(PreferencesUtils.getString( - context, R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT), - track.getCategory()); - assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key)); - assertEquals(id, service.getRecordingTrackId()); + // Set up the mock content resolver + CustomContentProvider customContentProvider = new CustomContentProvider(); + customContentProvider.attachInfo(context, null); + mockContentResolver.addProvider(ContentProviderUtils.AUTHORITY, customContentProvider); - // Verify that the start broadcast was received. - assertTrue(startReceiver.waitUntilReceived(1)); - List receivedIntents = startReceiver.getReceivedIntents(); - assertEquals(1, receivedIntents.size()); - Intent broadcastIntent = receivedIntents.get(0); - assertEquals(startAction, broadcastIntent.getAction()); - assertEquals(id, - broadcastIntent.getLongExtra(context.getString(R.string.track_id_broadcast_extra), -1L)); + MockContentProvider settingsProvider = new MockContentProvider(context) { + @Override + public Bundle call(String method, String arg, Bundle extras) { + return null; + } - context.unregisterReceiver(startReceiver); - } + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + return null; + } + }; + mockContentResolver.addProvider(Settings.AUTHORITY, settingsProvider); - @MediumTest - public void testStartNewTrack_alreadyRecording() throws Exception { - createDummyTrack(123L, -1L, true); + // Set the context + setContext(context); - ITrackRecordingService service = bindAndGetService(createStartIntent()); - assertTrue(service.isRecording()); + providerUtils = ContentProviderUtils.Factory.get(context); - // Starting a new track when there is a recording should just return -1L. - long newTrack = service.startNewTrack(); - assertEquals(-1L, newTrack); + SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); + // Let's use default values. + sharedPreferences.edit().clear().apply(); - assertEquals(123L, PreferencesUtils.getLong(context, R.string.recording_track_id_key)); - assertEquals(123L, service.getRecordingTrackId()); - } + // Disable auto resume by default. + updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, 0); + // No recording track. + PreferencesUtils.setLong( + context, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT); + } - @MediumTest - public void testEndCurrentTrack_alreadyRecording() throws Exception { - // See comment above if this fails randomly. - BlockingBroadcastReceiver stopReceiver = new BlockingBroadcastReceiver(); - String stopAction = context.getString(R.string.track_stopped_broadcast_action); - context.registerReceiver(stopReceiver, new IntentFilter(stopAction)); + @SmallTest + public void testStartable() { + startService(createStartIntent()); + assertNotNull(getService()); + } - createDummyTrack(123L, -1L, true); + @MediumTest + public void testBindable() { + IBinder service = bindService(createStartIntent()); + assertNotNull(service); + } - ITrackRecordingService service = bindAndGetService(createStartIntent()); - assertTrue(service.isRecording()); + @MediumTest + public void testResumeAfterReboot_shouldResume() throws Exception { + // Insert a dummy track and mark it as recording track. + createDummyTrack(123L, System.currentTimeMillis(), true); - // End the current track. - service.endCurrentTrack(); - assertFalse(service.isRecording()); - assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, - PreferencesUtils.getLong(context, R.string.recording_track_id_key)); - assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId()); + // Clear the number of attempts and set the timeout to 10 min. + updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, + PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT); - // Verify that the stop broadcast was received. - assertTrue(stopReceiver.waitUntilReceived(1)); - List receivedIntents = stopReceiver.getReceivedIntents(); - assertEquals(1, receivedIntents.size()); - Intent broadcastIntent = receivedIntents.get(0); - assertEquals(stopAction, broadcastIntent.getAction()); - assertEquals(123L, - broadcastIntent.getLongExtra(context.getString(R.string.track_id_broadcast_extra), -1L)); + // Start the service in "resume" mode (simulates the on-reboot action). + Intent startIntent = createStartIntent(); + startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true); + startService(startIntent); + assertNotNull(getService()); - context.unregisterReceiver(stopReceiver); - } - - @MediumTest - public void testEndCurrentTrack_noRecording() throws Exception { - ITrackRecordingService service = bindAndGetService(createStartIntent()); - assertFalse(service.isRecording()); + // We expect to resume the previous track. + assertTrue(getService().isRecording()); + ITrackRecordingService service = bindAndGetService(createStartIntent()); + assertEquals(123L, service.getRecordingTrackId()); + } /* - * Ending the current track when there is no recording should not result in - * any error. + * TODO: shutdownService() has a bug and doesn't set mServiceCreated to false, + * thus preventing from a second call to onCreate(). Report the bug to Android + * team. Until then, the following tests and checks must be commented out. + * + * TODO: If fixed, remove "disabled" prefix from the test name. */ - service.endCurrentTrack(); + @MediumTest + public void disabledTestResumeAfterReboot_simulateReboot() throws Exception { + updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, + PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT); + ITrackRecordingService service = bindAndGetService(createStartIntent()); + assertFalse(service.isRecording()); - assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, - PreferencesUtils.getLong(context, R.string.recording_track_id_key)); - assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId()); - } + // Simulate recording a track. + long id = service.startNewTrack(); + assertTrue(service.isRecording()); + assertEquals(id, service.getRecordingTrackId()); + shutdownService(); + assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key)); - @MediumTest - public void testIntegration_completeRecordingSession() throws Exception { - List tracks = providerUtils.getAllTracks(); - assertTrue(tracks.isEmpty()); - fullRecordingSession(); - } + // Start the service in "resume" mode (simulates the on-reboot action). + Intent startIntent = createStartIntent(); + startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true); + startService(startIntent); + assertNotNull(getService()); - @MediumTest - public void testInsertStatisticsMarker_noRecordingTrack() throws Exception { - ITrackRecordingService service = bindAndGetService(createStartIntent()); - assertFalse(service.isRecording()); - - long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS); - assertEquals(-1L, waypointId); - } - - @MediumTest - public void testInsertStatisticsMarker_validLocation() throws Exception { - createDummyTrack(123L, -1L, true); - - ITrackRecordingService service = bindAndGetService(createStartIntent()); - assertTrue(service.isRecording()); - assertFalse(service.isPaused()); - insertLocation(service); - - assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS)); - assertEquals(2, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS)); - - Waypoint wpt = providerUtils.getWaypoint(2); - assertEquals(getContext().getString(R.string.marker_statistics_icon_url), wpt.getIcon()); - assertEquals(getContext().getString(R.string.marker_split_name_format, 1), wpt.getName()); - assertEquals(WaypointType.STATISTICS, wpt.getType()); - assertEquals(123L, wpt.getTrackId()); - assertEquals(0.0, wpt.getLength()); - assertNotNull(wpt.getLocation()); - assertNotNull(wpt.getTripStatistics()); - // TODO check the rest of the params. - - // TODO: Check waypoint 2. - } - - @MediumTest - public void testInsertWaypointMarker_noRecordingTrack() throws Exception { - ITrackRecordingService service = bindAndGetService(createStartIntent()); - assertFalse(service.isRecording()); - - long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT); - assertEquals(-1L, waypointId); - } - - @MediumTest - public void testInsertWaypointMarker_validWaypoint() throws Exception { - createDummyTrack(123L, -1L, true); - - ITrackRecordingService service = bindAndGetService(createStartIntent()); - assertTrue(service.isRecording()); - insertLocation(service); - - assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT)); - Waypoint wpt = providerUtils.getWaypoint(1); - assertEquals(getContext().getString(R.string.marker_waypoint_icon_url), wpt.getIcon()); - assertEquals(getContext().getString(R.string.marker_name_format, 1), wpt.getName()); - assertEquals(WaypointType.WAYPOINT, wpt.getType()); - assertEquals(123L, wpt.getTrackId()); - assertEquals(0.0, wpt.getLength()); - assertNotNull(wpt.getLocation()); - assertNull(wpt.getTripStatistics()); - } - - @MediumTest - public void testWithProperties_voiceFrequencyDefault() throws Exception { - PreferencesUtils.setInt( - context, R.string.voice_frequency_key, PreferencesUtils.VOICE_FREQUENCY_DEFAULT); - fullRecordingSession(); - } - - @MediumTest - public void testWithProperties_voiceFrequencyByDistance() throws Exception { - PreferencesUtils.setInt(context, R.string.voice_frequency_key, -1); - fullRecordingSession(); - } - - @MediumTest - public void testWithProperties_voiceFrequencyByTime() throws Exception { - PreferencesUtils.setInt(context, R.string.voice_frequency_key, 1); - fullRecordingSession(); - } - - @MediumTest - public void testWithProperties_maxRecordingDistanceDefault() throws Exception { - PreferencesUtils.setInt(context, R.string.max_recording_distance_key, - PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT); - fullRecordingSession(); - } - - @MediumTest - public void testWithProperties_maxRecordingDistance() throws Exception { - PreferencesUtils.setInt(context, R.string.max_recording_distance_key, 50); - fullRecordingSession(); - } - - @MediumTest - public void testWithProperties_minRecordingDistanceDefault() throws Exception { - PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, - PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT); - fullRecordingSession(); - } - - @MediumTest - public void testWithProperties_minRecordingDistance() throws Exception { - PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, 2); - fullRecordingSession(); - } - - @MediumTest - public void testWithProperties_splitFrequencyDefault() throws Exception { - PreferencesUtils.setInt( - context, R.string.split_frequency_key, PreferencesUtils.SPLIT_FREQUENCY_DEFAULT); - fullRecordingSession(); - } - - @MediumTest - public void testWithProperties_splitFrequencyByDistance() throws Exception { - PreferencesUtils.setInt(context, R.string.split_frequency_key, -1); - fullRecordingSession(); - } - - @MediumTest - public void testWithProperties_splitFrequencyByTime() throws Exception { - PreferencesUtils.setInt(context, R.string.split_frequency_key, 1); - fullRecordingSession(); - } - - @MediumTest - public void testWithProperties_metricUnitsDefault() throws Exception { - PreferencesUtils.setString( - context, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT); - fullRecordingSession(); - } - - @MediumTest - public void testWithProperties_metricUnitsDisabled() throws Exception { - PreferencesUtils.setString( - context, R.string.stats_units_key, context.getString(R.string.stats_units_imperial)); - fullRecordingSession(); - } - - @MediumTest - public void testWithProperties_minRecordingIntervalDefault() throws Exception { - PreferencesUtils.setInt(context, R.string.min_recording_interval_key, - PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT); - fullRecordingSession(); - } - - @MediumTest - public void testWithProperties_minRecordingInterval() throws Exception { - PreferencesUtils.setInt(context, R.string.min_recording_interval_key, 2); - fullRecordingSession(); - } - - @MediumTest - public void testWithProperties_minRequiredAccuracyDefault() throws Exception { - PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, - PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT); - fullRecordingSession(); - } - - @MediumTest - public void testWithProperties_minRequiredAccuracy() throws Exception { - PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, 500); - fullRecordingSession(); - } - - @MediumTest - public void testWithProperties_sensorTypeDefault() throws Exception { - PreferencesUtils.setString( - context, R.string.sensor_type_key, PreferencesUtils.SENSOR_TYPE_DEFAULT); - fullRecordingSession(); - } - - @MediumTest - public void testWithProperties_sensorTypeZephyr() throws Exception { - PreferencesUtils.setString( - context, R.string.sensor_type_key, context.getString(R.string.sensor_type_value_zephyr)); - fullRecordingSession(); - } - - private ITrackRecordingService bindAndGetService(Intent intent) { - ITrackRecordingService service = ITrackRecordingService.Stub.asInterface(bindService(intent)); - assertNotNull(service); - return service; - } - - private Track createDummyTrack(long id, long stopTime, boolean isRecording) { - Track dummyTrack = new Track(); - dummyTrack.setId(id); - dummyTrack.setName("Dummy Track"); - TripStatistics tripStatistics = new TripStatistics(); - tripStatistics.setStopTime(stopTime); - dummyTrack.setTripStatistics(tripStatistics); - addTrack(dummyTrack, isRecording); - return dummyTrack; - } - - private void updateAutoResumePrefs(int attempts, int timeoutMins) { - PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, attempts); - PreferencesUtils.setInt(context, R.string.auto_resume_track_timeout_key, timeoutMins); - } - - private Intent createStartIntent() { - Intent startIntent = new Intent(); - startIntent.setClass(context, TrackRecordingService.class); - return startIntent; - } - - private void addTrack(Track track, boolean isRecording) { - assertTrue(track.getId() >= 0); - providerUtils.insertTrack(track); - assertEquals(track.getId(), providerUtils.getTrack(track.getId()).getId()); - PreferencesUtils.setLong(context, R.string.recording_track_id_key, - isRecording ? track.getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT); - PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, !isRecording); - } - - private void fullRecordingSession() throws Exception { - ITrackRecordingService service = bindAndGetService(createStartIntent()); - assertFalse(service.isRecording()); - - // Start a track. - long id = service.startNewTrack(); - assertTrue(id >= 0); - assertTrue(service.isRecording()); - Track track = providerUtils.getTrack(id); - assertNotNull(track); - assertEquals(id, track.getId()); - assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key)); - assertEquals(id, service.getRecordingTrackId()); - - // Insert a few points, markers and statistics. - long startTime = System.currentTimeMillis(); - for (int i = 0; i < 30; i++) { - Location loc = new Location("gps"); - loc.setLongitude(35.0f + i / 10.0f); - loc.setLatitude(45.0f - i / 5.0f); - loc.setAccuracy(5); - loc.setSpeed(10); - loc.setTime(startTime + i * 10000); - loc.setBearing(3.0f); - service.insertTrackPoint(loc); - - if (i % 10 == 0) { - service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS); - } else if (i % 7 == 0) { - service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT); - } + assertTrue(getService().isRecording()); } - // Stop the track. Validate if it has correct data. - service.endCurrentTrack(); - assertFalse(service.isRecording()); - assertEquals(-1L, service.getRecordingTrackId()); - track = providerUtils.getTrack(id); - assertNotNull(track); - assertEquals(id, track.getId()); - TripStatistics tripStatistics = track.getTripStatistics(); - assertNotNull(tripStatistics); - assertTrue(tripStatistics.getStartTime() > 0); - assertTrue(tripStatistics.getStopTime() >= tripStatistics.getStartTime()); - } + @MediumTest + public void testResumeAfterReboot_noRecordingTrack() throws Exception { + // Insert a dummy track and mark it as recording track. + createDummyTrack(123L, System.currentTimeMillis(), false); - /** - * Inserts a location and waits for 100ms. - * - * @param trackRecordingService the track recording service - */ - private void insertLocation(ITrackRecordingService trackRecordingService) - throws RemoteException, InterruptedException { - Location location = new Location("gps"); - location.setLongitude(35.0f); - location.setLatitude(45.0f); - location.setAccuracy(5); - location.setSpeed(10); - location.setTime(System.currentTimeMillis()); - location.setBearing(3.0f); - trackRecordingService.insertTrackPoint(location); - Thread.sleep(200); - } + // Clear the number of attempts and set the timeout to 10 min. + updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, + PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT); + + // Start the service in "resume" mode (simulates the on-reboot action). + Intent startIntent = createStartIntent(); + startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true); + startService(startIntent); + assertNotNull(getService()); + + // We don't expect to resume the previous track, because it was stopped. + assertFalse(getService().isRecording()); + ITrackRecordingService service = bindAndGetService(createStartIntent()); + assertEquals(-1L, service.getRecordingTrackId()); + } + + @MediumTest + public void testResumeAfterReboot_expiredTrack() throws Exception { + // Insert a dummy track last updated 20 min ago. + createDummyTrack(123L, System.currentTimeMillis() - 20 * 60 * 1000, true); + + // Clear the number of attempts and set the timeout to 10 min. + updateAutoResumePrefs(PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, + PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT); + + // Start the service in "resume" mode (simulates the on-reboot action). + Intent startIntent = createStartIntent(); + startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true); + startService(startIntent); + assertNotNull(getService()); + + // We don't expect to resume the previous track, because it has expired. + assertFalse(getService().isRecording()); + ITrackRecordingService service = bindAndGetService(createStartIntent()); + assertEquals(-1L, service.getRecordingTrackId()); + } + + @MediumTest + public void testResumeAfterReboot_tooManyAttempts() throws Exception { + // Insert a dummy track. + createDummyTrack(123L, System.currentTimeMillis(), true); + + // Set the number of attempts to max. + updateAutoResumePrefs(TrackRecordingService.MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS, + PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT); + + // Start the service in "resume" mode (simulates the on-reboot action). + Intent startIntent = createStartIntent(); + startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true); + startService(startIntent); + assertNotNull(getService()); + + // We don't expect to resume the previous track, because there were already + // too many attempts. + assertFalse(getService().isRecording()); + ITrackRecordingService service = bindAndGetService(createStartIntent()); + assertEquals(-1L, service.getRecordingTrackId()); + } + + @MediumTest + public void testRecording_noTracks() throws Exception { + List tracks = providerUtils.getAllTracks(); + assertTrue(tracks.isEmpty()); + + ITrackRecordingService service = bindAndGetService(createStartIntent()); + // Test if we start in no-recording mode by default. + assertFalse(service.isRecording()); + assertEquals(-1L, service.getRecordingTrackId()); + } + + @MediumTest + public void testRecording_oldTracks() throws Exception { + createDummyTrack(123L, -1L, false); + + ITrackRecordingService service = bindAndGetService(createStartIntent()); + assertFalse(service.isRecording()); + assertEquals(-1L, service.getRecordingTrackId()); + } + + @MediumTest + public void testRecording_orphanedRecordingTrack() throws Exception { + // Just set recording track to a bogus value. + PreferencesUtils.setLong(context, R.string.recording_track_id_key, 256L); + + // Make sure that the service will not start recording and will clear + // the bogus track. + ITrackRecordingService service = bindAndGetService(createStartIntent()); + assertFalse(service.isRecording()); + assertEquals(-1L, service.getRecordingTrackId()); + } + + @MediumTest + public void testStartNewTrack_noRecording() throws Exception { + // NOTICE: due to the way Android permissions work, if this fails, + // uninstall the test apk then retry - the test must be installed *after* the app (go figure). + // Reference: http://code.google.com/p/android/issues/detail?id=5521 + BlockingBroadcastReceiver startReceiver = new BlockingBroadcastReceiver(); + String startAction = context.getString(R.string.track_started_broadcast_action); + context.registerReceiver(startReceiver, new IntentFilter(startAction)); + + List tracks = providerUtils.getAllTracks(); + assertTrue(tracks.isEmpty()); + + ITrackRecordingService service = bindAndGetService(createStartIntent()); + assertFalse(service.isRecording()); + + long id = service.startNewTrack(); + assertTrue(id >= 0); + assertTrue(service.isRecording()); + Track track = providerUtils.getTrack(id); + assertNotNull(track); + assertEquals(id, track.getId()); + assertEquals(PreferencesUtils.getString( + context, R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT), + track.getCategory()); + assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key)); + assertEquals(id, service.getRecordingTrackId()); + + // Verify that the start broadcast was received. + assertTrue(startReceiver.waitUntilReceived(1)); + List receivedIntents = startReceiver.getReceivedIntents(); + assertEquals(1, receivedIntents.size()); + Intent broadcastIntent = receivedIntents.get(0); + assertEquals(startAction, broadcastIntent.getAction()); + assertEquals(id, + broadcastIntent.getLongExtra(context.getString(R.string.track_id_broadcast_extra), -1L)); + + context.unregisterReceiver(startReceiver); + } + + @MediumTest + public void testStartNewTrack_alreadyRecording() throws Exception { + createDummyTrack(123L, -1L, true); + + ITrackRecordingService service = bindAndGetService(createStartIntent()); + assertTrue(service.isRecording()); + + // Starting a new track when there is a recording should just return -1L. + long newTrack = service.startNewTrack(); + assertEquals(-1L, newTrack); + + assertEquals(123L, PreferencesUtils.getLong(context, R.string.recording_track_id_key)); + assertEquals(123L, service.getRecordingTrackId()); + } + + @MediumTest + public void testEndCurrentTrack_alreadyRecording() throws Exception { + // See comment above if this fails randomly. + BlockingBroadcastReceiver stopReceiver = new BlockingBroadcastReceiver(); + String stopAction = context.getString(R.string.track_stopped_broadcast_action); + context.registerReceiver(stopReceiver, new IntentFilter(stopAction)); + + createDummyTrack(123L, -1L, true); + + ITrackRecordingService service = bindAndGetService(createStartIntent()); + assertTrue(service.isRecording()); + + // End the current track. + service.endCurrentTrack(); + assertFalse(service.isRecording()); + assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, + PreferencesUtils.getLong(context, R.string.recording_track_id_key)); + assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId()); + + // Verify that the stop broadcast was received. + assertTrue(stopReceiver.waitUntilReceived(1)); + List receivedIntents = stopReceiver.getReceivedIntents(); + assertEquals(1, receivedIntents.size()); + Intent broadcastIntent = receivedIntents.get(0); + assertEquals(stopAction, broadcastIntent.getAction()); + assertEquals(123L, + broadcastIntent.getLongExtra(context.getString(R.string.track_id_broadcast_extra), -1L)); + + context.unregisterReceiver(stopReceiver); + } + + @MediumTest + public void testEndCurrentTrack_noRecording() throws Exception { + ITrackRecordingService service = bindAndGetService(createStartIntent()); + assertFalse(service.isRecording()); + + /* + * Ending the current track when there is no recording should not result in + * any error. + */ + service.endCurrentTrack(); + + assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, + PreferencesUtils.getLong(context, R.string.recording_track_id_key)); + assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId()); + } + + @MediumTest + public void testIntegration_completeRecordingSession() throws Exception { + List tracks = providerUtils.getAllTracks(); + assertTrue(tracks.isEmpty()); + fullRecordingSession(); + } + + @MediumTest + public void testInsertStatisticsMarker_noRecordingTrack() throws Exception { + ITrackRecordingService service = bindAndGetService(createStartIntent()); + assertFalse(service.isRecording()); + + long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS); + assertEquals(-1L, waypointId); + } + + @MediumTest + public void testInsertStatisticsMarker_validLocation() throws Exception { + createDummyTrack(123L, -1L, true); + + ITrackRecordingService service = bindAndGetService(createStartIntent()); + assertTrue(service.isRecording()); + assertFalse(service.isPaused()); + insertLocation(service); + + assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS)); + assertEquals(2, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS)); + + Waypoint wpt = providerUtils.getWaypoint(2); + assertEquals(getContext().getString(R.string.marker_statistics_icon_url), wpt.getIcon()); + assertEquals(getContext().getString(R.string.marker_split_name_format, 1), wpt.getName()); + assertEquals(WaypointType.STATISTICS, wpt.getType()); + assertEquals(123L, wpt.getTrackId()); + assertEquals(0.0, wpt.getLength()); + assertNotNull(wpt.getLocation()); + assertNotNull(wpt.getTripStatistics()); + // TODO check the rest of the params. + + // TODO: Check waypoint 2. + } + + @MediumTest + public void testInsertWaypointMarker_noRecordingTrack() throws Exception { + ITrackRecordingService service = bindAndGetService(createStartIntent()); + assertFalse(service.isRecording()); + + long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT); + assertEquals(-1L, waypointId); + } + + @MediumTest + public void testInsertWaypointMarker_validWaypoint() throws Exception { + createDummyTrack(123L, -1L, true); + + ITrackRecordingService service = bindAndGetService(createStartIntent()); + assertTrue(service.isRecording()); + insertLocation(service); + + assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT)); + Waypoint wpt = providerUtils.getWaypoint(1); + assertEquals(getContext().getString(R.string.marker_waypoint_icon_url), wpt.getIcon()); + assertEquals(getContext().getString(R.string.marker_name_format, 1), wpt.getName()); + assertEquals(WaypointType.WAYPOINT, wpt.getType()); + assertEquals(123L, wpt.getTrackId()); + assertEquals(0.0, wpt.getLength()); + assertNotNull(wpt.getLocation()); + assertNull(wpt.getTripStatistics()); + } + + @MediumTest + public void testWithProperties_voiceFrequencyDefault() throws Exception { + PreferencesUtils.setInt( + context, R.string.voice_frequency_key, PreferencesUtils.VOICE_FREQUENCY_DEFAULT); + fullRecordingSession(); + } + + @MediumTest + public void testWithProperties_voiceFrequencyByDistance() throws Exception { + PreferencesUtils.setInt(context, R.string.voice_frequency_key, -1); + fullRecordingSession(); + } + + @MediumTest + public void testWithProperties_voiceFrequencyByTime() throws Exception { + PreferencesUtils.setInt(context, R.string.voice_frequency_key, 1); + fullRecordingSession(); + } + + @MediumTest + public void testWithProperties_maxRecordingDistanceDefault() throws Exception { + PreferencesUtils.setInt(context, R.string.max_recording_distance_key, + PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT); + fullRecordingSession(); + } + + @MediumTest + public void testWithProperties_maxRecordingDistance() throws Exception { + PreferencesUtils.setInt(context, R.string.max_recording_distance_key, 50); + fullRecordingSession(); + } + + @MediumTest + public void testWithProperties_minRecordingDistanceDefault() throws Exception { + PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, + PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT); + fullRecordingSession(); + } + + @MediumTest + public void testWithProperties_minRecordingDistance() throws Exception { + PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, 2); + fullRecordingSession(); + } + + @MediumTest + public void testWithProperties_splitFrequencyDefault() throws Exception { + PreferencesUtils.setInt( + context, R.string.split_frequency_key, PreferencesUtils.SPLIT_FREQUENCY_DEFAULT); + fullRecordingSession(); + } + + @MediumTest + public void testWithProperties_splitFrequencyByDistance() throws Exception { + PreferencesUtils.setInt(context, R.string.split_frequency_key, -1); + fullRecordingSession(); + } + + @MediumTest + public void testWithProperties_splitFrequencyByTime() throws Exception { + PreferencesUtils.setInt(context, R.string.split_frequency_key, 1); + fullRecordingSession(); + } + + @MediumTest + public void testWithProperties_metricUnitsDefault() throws Exception { + PreferencesUtils.setString( + context, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT); + fullRecordingSession(); + } + + @MediumTest + public void testWithProperties_metricUnitsDisabled() throws Exception { + PreferencesUtils.setString( + context, R.string.stats_units_key, context.getString(R.string.stats_units_imperial)); + fullRecordingSession(); + } + + @MediumTest + public void testWithProperties_minRecordingIntervalDefault() throws Exception { + PreferencesUtils.setInt(context, R.string.min_recording_interval_key, + PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT); + fullRecordingSession(); + } + + @MediumTest + public void testWithProperties_minRecordingInterval() throws Exception { + PreferencesUtils.setInt(context, R.string.min_recording_interval_key, 2); + fullRecordingSession(); + } + + @MediumTest + public void testWithProperties_minRequiredAccuracyDefault() throws Exception { + PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, + PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT); + fullRecordingSession(); + } + + @MediumTest + public void testWithProperties_minRequiredAccuracy() throws Exception { + PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, 500); + fullRecordingSession(); + } + + @MediumTest + public void testWithProperties_sensorTypeDefault() throws Exception { + PreferencesUtils.setString( + context, R.string.sensor_type_key, PreferencesUtils.SENSOR_TYPE_DEFAULT); + fullRecordingSession(); + } + + @MediumTest + public void testWithProperties_sensorTypeZephyr() throws Exception { + PreferencesUtils.setString( + context, R.string.sensor_type_key, context.getString(R.string.sensor_type_value_zephyr)); + fullRecordingSession(); + } + + private ITrackRecordingService bindAndGetService(Intent intent) { + ITrackRecordingService service = ITrackRecordingService.Stub.asInterface(bindService(intent)); + assertNotNull(service); + return service; + } + + private Track createDummyTrack(long id, long stopTime, boolean isRecording) { + Track dummyTrack = new Track(); + dummyTrack.setId(id); + dummyTrack.setName("Dummy Track"); + TripStatistics tripStatistics = new TripStatistics(); + tripStatistics.setStopTime(stopTime); + dummyTrack.setTripStatistics(tripStatistics); + addTrack(dummyTrack, isRecording); + return dummyTrack; + } + + private void updateAutoResumePrefs(int attempts, int timeoutMins) { + PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, attempts); + PreferencesUtils.setInt(context, R.string.auto_resume_track_timeout_key, timeoutMins); + } + + private Intent createStartIntent() { + Intent startIntent = new Intent(); + startIntent.setClass(context, TrackRecordingService.class); + return startIntent; + } + + private void addTrack(Track track, boolean isRecording) { + assertTrue(track.getId() >= 0); + providerUtils.insertTrack(track); + assertEquals(track.getId(), providerUtils.getTrack(track.getId()).getId()); + PreferencesUtils.setLong(context, R.string.recording_track_id_key, + isRecording ? track.getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT); + PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, !isRecording); + } + + private void fullRecordingSession() throws Exception { + ITrackRecordingService service = bindAndGetService(createStartIntent()); + assertFalse(service.isRecording()); + + // Start a track. + long id = service.startNewTrack(); + assertTrue(id >= 0); + assertTrue(service.isRecording()); + Track track = providerUtils.getTrack(id); + assertNotNull(track); + assertEquals(id, track.getId()); + assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key)); + assertEquals(id, service.getRecordingTrackId()); + + // Insert a few points, markers and statistics. + long startTime = System.currentTimeMillis(); + for (int i = 0; i < 30; i++) { + Location loc = new Location("gps"); + loc.setLongitude(35.0f + i / 10.0f); + loc.setLatitude(45.0f - i / 5.0f); + loc.setAccuracy(5); + loc.setSpeed(10); + loc.setTime(startTime + i * 10000); + loc.setBearing(3.0f); + service.insertTrackPoint(loc); + + if (i % 10 == 0) { + service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS); + } else if (i % 7 == 0) { + service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT); + } + } + + // Stop the track. Validate if it has correct data. + service.endCurrentTrack(); + assertFalse(service.isRecording()); + assertEquals(-1L, service.getRecordingTrackId()); + track = providerUtils.getTrack(id); + assertNotNull(track); + assertEquals(id, track.getId()); + TripStatistics tripStatistics = track.getTripStatistics(); + assertNotNull(tripStatistics); + assertTrue(tripStatistics.getStartTime() > 0); + assertTrue(tripStatistics.getStopTime() >= tripStatistics.getStartTime()); + } + + /** + * Inserts a location and waits for 100ms. + * + * @param trackRecordingService the track recording service + */ + private void insertLocation(ITrackRecordingService trackRecordingService) + throws RemoteException, InterruptedException { + Location location = new Location("gps"); + location.setLongitude(35.0f); + location.setLatitude(45.0f); + location.setAccuracy(5); + location.setSpeed(10); + location.setTime(System.currentTimeMillis()); + location.setBearing(3.0f); + trackRecordingService.insertTrackPoint(location); + Thread.sleep(200); + } + + /** + * A context wrapper with the user provided {@link ContentResolver}. TODO: + * Move to test utils package. + */ + public static class MockContext extends ContextWrapper { + private final ContentResolver contentResolver; + + public MockContext(ContentResolver contentResolver, Context base) { + super(base); + this.contentResolver = contentResolver; + } + + @Override + public ContentResolver getContentResolver() { + return contentResolver; + } + } + + /** + * Synchronous/waitable broadcast receiver to be used in testing. + */ + private class BlockingBroadcastReceiver extends BroadcastReceiver { + private static final long MAX_WAIT_TIME_MS = 10000; + private List receivedIntents = new ArrayList(); + + public List getReceivedIntents() { + return receivedIntents; + } + + @Override + public void onReceive(Context ctx, Intent intent) { + Log.d("Test", "Got broadcast: " + intent); + synchronized (receivedIntents) { + receivedIntents.add(intent); + receivedIntents.notifyAll(); + } + } + + public boolean waitUntilReceived(int receiveCount) { + long deadline = System.currentTimeMillis() + MAX_WAIT_TIME_MS; + synchronized (receivedIntents) { + while (receivedIntents.size() < receiveCount) { + try { + // Wait releases synchronized lock until it returns + receivedIntents.wait(500); + } catch (InterruptedException e) { + // Do nothing + } + + if (System.currentTimeMillis() > deadline) { + return false; + } + } + } + + return true; + } + } } diff --git a/src/androidTest/java/com/google/android/apps/mytracks/services/tasks/AnnouncementPeriodicTaskFactoryTest.java b/src/androidTest/java/com/google/android/apps/mytracks/services/tasks/AnnouncementPeriodicTaskFactoryTest.java index ebab80f72..f18b0ad4a 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/services/tasks/AnnouncementPeriodicTaskFactoryTest.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/services/tasks/AnnouncementPeriodicTaskFactoryTest.java @@ -30,9 +30,9 @@ import org.junit.runner.RunWith; */ @RunWith(AndroidJUnit4.class) public class AnnouncementPeriodicTaskFactoryTest { - public void testCreate() { - PeriodicTaskFactory factory = new AnnouncementPeriodicTaskFactory(); - PeriodicTask task = factory.create(InstrumentationRegistry.getInstrumentation().getContext()); - Assert.assertTrue(task instanceof AnnouncementPeriodicTask); - } + public void testCreate() { + PeriodicTaskFactory factory = new AnnouncementPeriodicTaskFactory(); + PeriodicTask task = factory.create(InstrumentationRegistry.getInstrumentation().getContext()); + Assert.assertTrue(task instanceof AnnouncementPeriodicTask); + } } diff --git a/src/androidTest/java/com/google/android/apps/mytracks/services/tasks/AnnouncementPeriodicTaskTest.java b/src/androidTest/java/com/google/android/apps/mytracks/services/tasks/AnnouncementPeriodicTaskTest.java index 82b4fd40c..8bdfa1317 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/services/tasks/AnnouncementPeriodicTaskTest.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/services/tasks/AnnouncementPeriodicTaskTest.java @@ -15,16 +15,6 @@ */ package com.google.android.apps.mytracks.services.tasks; -import static com.google.android.testing.mocking.AndroidMock.capture; -import static com.google.android.testing.mocking.AndroidMock.eq; -import static com.google.android.testing.mocking.AndroidMock.expect; -import static com.google.android.testing.mocking.AndroidMock.same; - -import com.google.android.apps.mytracks.stats.TripStatistics; -import com.google.android.apps.mytracks.util.StringUtils; -import com.google.android.testing.mocking.AndroidMock; -import com.google.android.testing.mocking.UsesMocks; - import android.content.Context; import android.speech.tts.TextToSpeech; import android.speech.tts.TextToSpeech.OnInitListener; @@ -33,501 +23,530 @@ import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.test.AndroidTestCase; -import java.util.HashMap; -import java.util.Locale; -import java.util.concurrent.atomic.AtomicBoolean; +import com.google.android.apps.mytracks.stats.TripStatistics; +import com.google.android.apps.mytracks.util.StringUtils; +import com.google.android.testing.mocking.AndroidMock; +import com.google.android.testing.mocking.UsesMocks; import org.easymock.Capture; import org.easymock.EasyMock; +import java.util.HashMap; +import java.util.Locale; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.google.android.testing.mocking.AndroidMock.capture; +import static com.google.android.testing.mocking.AndroidMock.eq; +import static com.google.android.testing.mocking.AndroidMock.expect; +import static com.google.android.testing.mocking.AndroidMock.same; + /** * Tests for {@link AnnouncementPeriodicTask}. * WARNING: I'm not responsible if your eyes start bleeding while reading this - * code. You have been warned. It's still better than no test, though. + * code. You have been warned. It's still better than no test, though. * * @author Rodrigo Damazio */ public class AnnouncementPeriodicTaskTest extends AndroidTestCase { - // Use something other than our hardcoded value - private static final Locale DEFAULT_LOCALE = Locale.KOREAN; - private static final String ANNOUNCEMENT = "I can haz cheeseburger?"; - private Locale oldDefaultLocale; + // Use something other than our hardcoded value + private static final Locale DEFAULT_LOCALE = Locale.KOREAN; + private static final String ANNOUNCEMENT = "I can haz cheeseburger?"; + private Locale oldDefaultLocale; - private AnnouncementPeriodicTask task; - private AnnouncementPeriodicTask mockTask; - private Capture initListenerCapture; - private Capture phoneListenerCapture; - private TextToSpeechDelegate ttsDelegate; - private TextToSpeechInterface tts; - - /** - * Mockable interface that we delegate TTS calls to. - */ - interface TextToSpeechInterface { - int addEarcon(String earcon, String packagename, int resourceId); - int addEarcon(String earcon, String filename); - int addSpeech(String text, String packagename, int resourceId); - int addSpeech(String text, String filename); - boolean areDefaultsEnforced(); - String getDefaultEngine(); - Locale getLanguage(); - int isLanguageAvailable(Locale loc); - boolean isSpeaking(); - int playEarcon(String earcon, int queueMode, - HashMap params); - int playSilence(long durationInMs, int queueMode, - HashMap params); - int setEngineByPackageName(String enginePackageName); - int setLanguage(Locale loc); - int setOnUtteranceCompletedListener(OnUtteranceCompletedListener listener); - int setPitch(float pitch); - int setSpeechRate(float speechRate); - void shutdown(); - int speak(String text, int queueMode, HashMap params); - int stop(); - int synthesizeToFile(String text, HashMap params, - String filename); - } - - /** - * Subclass of {@link TextToSpeech} which delegates calls to the interface - * above. - * The logic here is stupid and the author is ashamed of having to write it - * like this, but basically the issue is that TextToSpeech cannot be mocked - * without running its constructor, its constructor runs async operations - * which call other methods (and then if the methods are part of a mock we'd - * have to set a behavior, but we can't 'cause the object hasn't been fully - * built yet). - * The logic is that calls made during the constructor (when tts is not yet - * set) will go up to the original class, but after tts is set we'll forward - * them all to the mock. - */ - private class TextToSpeechDelegate - extends TextToSpeech implements TextToSpeechInterface { - public TextToSpeechDelegate(Context context, OnInitListener listener) { - super(context, listener); - } + private AnnouncementPeriodicTask task; + private AnnouncementPeriodicTask mockTask; + private Capture initListenerCapture; + private Capture phoneListenerCapture; + private TextToSpeechDelegate ttsDelegate; + private TextToSpeechInterface tts; + @UsesMocks({ + AnnouncementPeriodicTask.class, + StringUtils.class, + }) @Override - public int addEarcon(String earcon, String packagename, int resourceId) { - if (tts == null) { - return super.addEarcon(earcon, packagename, resourceId); - } - return tts.addEarcon(earcon, packagename, resourceId); - } + protected void setUp() throws Exception { + super.setUp(); - @Override - public int addEarcon(String earcon, String filename) { - if (tts == null) { - return super.addEarcon(earcon, filename); - } - return tts.addEarcon(earcon, filename); - } + oldDefaultLocale = Locale.getDefault(); + Locale.setDefault(DEFAULT_LOCALE); - @Override - public int addSpeech(String text, String packagename, int resourceId) { - if (tts == null) { - return super.addSpeech(text, packagename, resourceId); - } - return tts.addSpeech(text, packagename, resourceId); - } + // Eww, the effort required just to mock TextToSpeech is insane + final AtomicBoolean listenerCalled = new AtomicBoolean(); + OnInitListener blockingListener = new OnInitListener() { + @Override + public void onInit(int status) { + synchronized (this) { + listenerCalled.set(true); + notify(); + } + } + }; - @Override - public int addSpeech(String text, String filename) { - if (tts == null) { - return super.addSpeech(text, filename); - } - return tts.addSpeech(text, filename); - } + ttsDelegate = new TextToSpeechDelegate(getContext(), blockingListener); - @Override - public Locale getLanguage() { - if (tts == null) { - return super.getLanguage(); - } - return tts.getLanguage(); - } - - @Override - public int isLanguageAvailable(Locale loc) { - if (tts == null) { - return super.isLanguageAvailable(loc); - } - return tts.isLanguageAvailable(loc); - } - - @Override - public boolean isSpeaking() { - if (tts == null) { - return super.isSpeaking(); - } - return tts.isSpeaking(); - } - - @Override - public int playEarcon(String earcon, int queueMode, - HashMap params) { - if (tts == null) { - return super.playEarcon(earcon, queueMode, params); - } - return tts.playEarcon(earcon, queueMode, params); - } - - @Override - public int playSilence(long durationInMs, int queueMode, - HashMap params) { - if (tts == null) { - return super.playSilence(durationInMs, queueMode, params); - } - return tts.playSilence(durationInMs, queueMode, params); - } - - @Override - public int setLanguage(Locale loc) { - if (tts == null) { - return super.setLanguage(loc); - } - return tts.setLanguage(loc); - } - - @Override - public int setOnUtteranceCompletedListener( - OnUtteranceCompletedListener listener) { - if (tts == null) { - return super.setOnUtteranceCompletedListener(listener); - } - return tts.setOnUtteranceCompletedListener(listener); - } - - @Override - public int setPitch(float pitch) { - if (tts == null) { - return super.setPitch(pitch); - } - return tts.setPitch(pitch); - } - - @Override - public int setSpeechRate(float speechRate) { - if (tts == null) { - return super.setSpeechRate(speechRate); - } - return tts.setSpeechRate(speechRate); - } - - @Override - public void shutdown() { - if (tts == null) { - super.shutdown(); - return; - } - tts.shutdown(); - } - - @Override - public int speak( - String text, int queueMode, HashMap params) { - if (tts == null) { - return super.speak(text, queueMode, params); - } - return tts.speak(text, queueMode, params); - } - - @Override - public int stop() { - if (tts == null) { - return super.stop(); - } - return tts.stop(); - } - - @Override - public int synthesizeToFile(String text, HashMap params, - String filename) { - if (tts == null) { - return super.synthesizeToFile(text, params, filename); - } - return tts.synthesizeToFile(text, params, filename); - } - } - - @UsesMocks({ - AnnouncementPeriodicTask.class, - StringUtils.class, - }) - @Override - protected void setUp() throws Exception { - super.setUp(); - - oldDefaultLocale = Locale.getDefault(); - Locale.setDefault(DEFAULT_LOCALE); - - // Eww, the effort required just to mock TextToSpeech is insane - final AtomicBoolean listenerCalled = new AtomicBoolean(); - OnInitListener blockingListener = new OnInitListener() { - @Override - public void onInit(int status) { - synchronized (this) { - listenerCalled.set(true); - notify(); + // Wait for all async operations done in the constructor to finish. + synchronized (blockingListener) { + while (!listenerCalled.get()) { + // Releases the synchronized lock until we're woken up. + blockingListener.wait(); + } } - } - }; - ttsDelegate = new TextToSpeechDelegate(getContext(), blockingListener); + // Phew, done, now we can start forwarding calls + tts = AndroidMock.createMock(TextToSpeechInterface.class); - // Wait for all async operations done in the constructor to finish. - synchronized (blockingListener) { - while (!listenerCalled.get()) { - // Releases the synchronized lock until we're woken up. - blockingListener.wait(); - } + initListenerCapture = new Capture(); + phoneListenerCapture = new Capture(); + + // Create a partial forwarding mock + mockTask = AndroidMock.createMock(AnnouncementPeriodicTask.class, getContext()); + task = new AnnouncementPeriodicTask(getContext()) { + @Override + protected TextToSpeech newTextToSpeech(Context ctx, + OnInitListener onInitListener) { + return mockTask.newTextToSpeech(ctx, onInitListener); + } + + @Override + protected String getAnnouncement(TripStatistics stats) { + return mockTask.getAnnouncement(stats); + } + + @Override + protected void listenToPhoneState( + PhoneStateListener listener, int events) { + mockTask.listenToPhoneState(listener, events); + } + }; } - // Phew, done, now we can start forwarding calls - tts = AndroidMock.createMock(TextToSpeechInterface.class); + @Override + protected void tearDown() { + Locale.setDefault(oldDefaultLocale); + } - initListenerCapture = new Capture(); - phoneListenerCapture = new Capture(); + public void testStart() { + doStart(); + OnInitListener ttsInitListener = initListenerCapture.getValue(); + assertNotNull(ttsInitListener); - // Create a partial forwarding mock - mockTask = AndroidMock.createMock(AnnouncementPeriodicTask.class, getContext()); - task = new AnnouncementPeriodicTask(getContext()) { - @Override - protected TextToSpeech newTextToSpeech(Context ctx, - OnInitListener onInitListener) { - return mockTask.newTextToSpeech(ctx, onInitListener); - } + AndroidMock.replay(tts); - @Override - protected String getAnnouncement(TripStatistics stats) { - return mockTask.getAnnouncement(stats); - } + ttsInitListener.onInit(TextToSpeech.SUCCESS); - @Override - protected void listenToPhoneState( - PhoneStateListener listener, int events) { - mockTask.listenToPhoneState(listener, events); - } - }; - } + AndroidMock.verify(mockTask, tts); + } - @Override - protected void tearDown() { - Locale.setDefault(oldDefaultLocale); - } + public void testStart_notReady() { + doStart(); + OnInitListener ttsInitListener = initListenerCapture.getValue(); + assertNotNull(ttsInitListener); - public void testStart() { - doStart(); - OnInitListener ttsInitListener = initListenerCapture.getValue(); - assertNotNull(ttsInitListener); + AndroidMock.replay(tts); - AndroidMock.replay(tts); + ttsInitListener.onInit(TextToSpeech.ERROR); - ttsInitListener.onInit(TextToSpeech.SUCCESS); + AndroidMock.verify(mockTask, tts); + } - AndroidMock.verify(mockTask, tts); - } + public void testShutdown() { + // First, start + doStart(); + AndroidMock.verify(mockTask); + AndroidMock.reset(mockTask); - public void testStart_notReady() { - doStart(); - OnInitListener ttsInitListener = initListenerCapture.getValue(); - assertNotNull(ttsInitListener); + // Then, shut down + PhoneStateListener phoneListener = phoneListenerCapture.getValue(); + mockTask.listenToPhoneState( + same(phoneListener), eq(PhoneStateListener.LISTEN_NONE)); + tts.shutdown(); + AndroidMock.replay(mockTask, tts); + task.shutdown(); + AndroidMock.verify(mockTask, tts); + } - AndroidMock.replay(tts); + public void testRun() throws Exception { + // Expect service data calls + TripStatistics stats = new TripStatistics(); - ttsInitListener.onInit(TextToSpeech.ERROR); + // Expect announcement building call + expect(mockTask.getAnnouncement(same(stats))).andStubReturn(ANNOUNCEMENT); - AndroidMock.verify(mockTask, tts); - } + // Put task in "ready" state + startTask(TextToSpeech.SUCCESS); - public void testShutdown() { - // First, start - doStart(); - AndroidMock.verify(mockTask); - AndroidMock.reset(mockTask); + expect(tts.isLanguageAvailable(DEFAULT_LOCALE)).andStubReturn(TextToSpeech.LANG_AVAILABLE); + expect(tts.setLanguage(DEFAULT_LOCALE)).andReturn(TextToSpeech.LANG_AVAILABLE); + expect(tts.setSpeechRate(AnnouncementPeriodicTask.TTS_SPEECH_RATE)).andReturn( + TextToSpeech.SUCCESS); + expect(tts.setOnUtteranceCompletedListener((OnUtteranceCompletedListener) EasyMock.anyObject())) + .andReturn(0); - // Then, shut down - PhoneStateListener phoneListener = phoneListenerCapture.getValue(); - mockTask.listenToPhoneState( - same(phoneListener), eq(PhoneStateListener.LISTEN_NONE)); - tts.shutdown(); - AndroidMock.replay(mockTask, tts); - task.shutdown(); - AndroidMock.verify(mockTask, tts); - } + // Expect actual announcement call + expect( + tts.speak(eq(ANNOUNCEMENT), eq(TextToSpeech.QUEUE_FLUSH), + eq(AnnouncementPeriodicTask.SPEECH_PARAMS))).andReturn(0); - public void testRun() throws Exception { - // Expect service data calls - TripStatistics stats = new TripStatistics(); + // Run the announcement + AndroidMock.replay(tts); + task.announce(stats); + AndroidMock.verify(mockTask, tts); + } - // Expect announcement building call - expect(mockTask.getAnnouncement(same(stats))).andStubReturn(ANNOUNCEMENT); + public void testRun_notReady() throws Exception { + // Put task in "not ready" state + startTask(TextToSpeech.ERROR); - // Put task in "ready" state - startTask(TextToSpeech.SUCCESS); + // Run the announcement + AndroidMock.replay(tts); + task.run(null); + AndroidMock.verify(mockTask, tts); + } - expect(tts.isLanguageAvailable(DEFAULT_LOCALE)).andStubReturn(TextToSpeech.LANG_AVAILABLE); - expect(tts.setLanguage(DEFAULT_LOCALE)).andReturn(TextToSpeech.LANG_AVAILABLE); - expect(tts.setSpeechRate(AnnouncementPeriodicTask.TTS_SPEECH_RATE)).andReturn( - TextToSpeech.SUCCESS); - expect(tts.setOnUtteranceCompletedListener((OnUtteranceCompletedListener) EasyMock.anyObject())) - .andReturn(0); + public void testRun_duringCall() throws Exception { + startTask(TextToSpeech.SUCCESS); - // Expect actual announcement call - expect( - tts.speak(eq(ANNOUNCEMENT), eq(TextToSpeech.QUEUE_FLUSH), - eq(AnnouncementPeriodicTask.SPEECH_PARAMS))).andReturn(0); + expect(tts.isSpeaking()).andStubReturn(false); - // Run the announcement - AndroidMock.replay(tts); - task.announce(stats); - AndroidMock.verify(mockTask, tts); - } + // Run the announcement + AndroidMock.replay(tts); + PhoneStateListener phoneListener = phoneListenerCapture.getValue(); + phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK, null); + task.run(null); + AndroidMock.verify(mockTask, tts); + } - public void testRun_notReady() throws Exception { - // Put task in "not ready" state - startTask(TextToSpeech.ERROR); + public void testRun_ringWhileSpeaking() throws Exception { + startTask(TextToSpeech.SUCCESS); - // Run the announcement - AndroidMock.replay(tts); - task.run(null); - AndroidMock.verify(mockTask, tts); - } + expect(tts.isSpeaking()).andStubReturn(true); + expect(tts.stop()).andReturn(TextToSpeech.SUCCESS); - public void testRun_duringCall() throws Exception { - startTask(TextToSpeech.SUCCESS); + AndroidMock.replay(tts); - expect(tts.isSpeaking()).andStubReturn(false); + // Update the state to ringing - this should stop the current announcement. + PhoneStateListener phoneListener = phoneListenerCapture.getValue(); + phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING, null); - // Run the announcement - AndroidMock.replay(tts); - PhoneStateListener phoneListener = phoneListenerCapture.getValue(); - phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK, null); - task.run(null); - AndroidMock.verify(mockTask, tts); - } + // Run the announcement - this should do nothing. + task.run(null); - public void testRun_ringWhileSpeaking() throws Exception { - startTask(TextToSpeech.SUCCESS); + AndroidMock.verify(mockTask, tts); + } - expect(tts.isSpeaking()).andStubReturn(true); - expect(tts.stop()).andReturn(TextToSpeech.SUCCESS); + public void testRun_whileRinging() throws Exception { + startTask(TextToSpeech.SUCCESS); - AndroidMock.replay(tts); + expect(tts.isSpeaking()).andStubReturn(false); - // Update the state to ringing - this should stop the current announcement. - PhoneStateListener phoneListener = phoneListenerCapture.getValue(); - phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING, null); + // Run the announcement + AndroidMock.replay(tts); + PhoneStateListener phoneListener = phoneListenerCapture.getValue(); + phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING, null); + task.run(null); + AndroidMock.verify(mockTask, tts); + } - // Run the announcement - this should do nothing. - task.run(null); + public void testRun_noService() throws Exception { + startTask(TextToSpeech.SUCCESS); - AndroidMock.verify(mockTask, tts); - } + // Run the announcement + AndroidMock.replay(tts); + task.run(null); + AndroidMock.verify(mockTask, tts); + } - public void testRun_whileRinging() throws Exception { - startTask(TextToSpeech.SUCCESS); + public void testRun_noStats() throws Exception { + // Expect service data calls - expect(tts.isSpeaking()).andStubReturn(false); + startTask(TextToSpeech.SUCCESS); - // Run the announcement - AndroidMock.replay(tts); - PhoneStateListener phoneListener = phoneListenerCapture.getValue(); - phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING, null); - task.run(null); - AndroidMock.verify(mockTask, tts); - } + // Run the announcement + AndroidMock.replay(tts); + task.run(null); + AndroidMock.verify(mockTask, tts); + } - public void testRun_noService() throws Exception { - startTask(TextToSpeech.SUCCESS); + /** + * Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with time zero. + */ + public void testGetAnnounceTime_time_zero() { + long time = 0; // 0 seconds + assertEquals("0 minutes 0 seconds", task.getAnnounceTime(time)); + } - // Run the announcement - AndroidMock.replay(tts); - task.run(null); - AndroidMock.verify(mockTask, tts); - } + /** + * Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with time one. + */ + public void testGetAnnounceTime_time_one() { + long time = 1 * 1000; // 1 second + assertEquals("0 minutes 1 second", task.getAnnounceTime(time)); + } - public void testRun_noStats() throws Exception { - // Expect service data calls + /** + * Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with singular + * numbers with the hour unit. + */ + public void testGetAnnounceTime_singular_has_hour() { + long time = (1 * 60 * 60 * 1000) + (1 * 60 * 1000) + (1 * 1000); // 1 hour 1 minute 1 second + assertEquals("1 hour 1 minute 1 second", task.getAnnounceTime(time)); + } - startTask(TextToSpeech.SUCCESS); + /** + * Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with plural numbers + * with the hour unit. + */ + public void testGetAnnounceTime_plural_has_hour() { + long time = (2 * 60 * 60 * 1000) + (2 * 60 * 1000) + (2 * 1000); // 2 hours 2 minutes 2 seconds + assertEquals("2 hours 2 minutes 2 seconds", task.getAnnounceTime(time)); + } - // Run the announcement - AndroidMock.replay(tts); - task.run(null); - AndroidMock.verify(mockTask, tts); - } - - /** - * Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with time zero. - */ - public void testGetAnnounceTime_time_zero() { - long time = 0; // 0 seconds - assertEquals("0 minutes 0 seconds", task.getAnnounceTime(time)); - } + /** + * Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with singular + * numbers without the hour unit. + */ + public void testGetAnnounceTime_singular_no_hour() { + long time = (1 * 60 * 1000) + (1 * 1000); // 1 minute 1 second + assertEquals("1 minute 1 second", task.getAnnounceTime(time)); + } - /** - * Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with time one. - */ - public void testGetAnnounceTime_time_one() { - long time = 1 * 1000; // 1 second - assertEquals("0 minutes 1 second", task.getAnnounceTime(time)); - } + /** + * Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with plural numbers + * without the hour unit. + */ + public void testGetAnnounceTime_plural_no_hour() { + long time = (2 * 60 * 1000) + (2 * 1000); // 2 minutes 2 seconds + assertEquals("2 minutes 2 seconds", task.getAnnounceTime(time)); + } - /** - * Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with singular - * numbers with the hour unit. - */ - public void testGetAnnounceTime_singular_has_hour() { - long time = (1 * 60 * 60 * 1000) + (1 * 60 * 1000) + (1 * 1000); // 1 hour 1 minute 1 second - assertEquals("1 hour 1 minute 1 second", task.getAnnounceTime(time)); - } + private void startTask(int state) { + AndroidMock.resetToNice(tts); + AndroidMock.replay(tts); + doStart(); + OnInitListener ttsInitListener = initListenerCapture.getValue(); + ttsInitListener.onInit(state); + AndroidMock.resetToDefault(tts); + } - /** - * Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with plural numbers - * with the hour unit. - */ - public void testGetAnnounceTime_plural_has_hour() { - long time = (2 * 60 * 60 * 1000) + (2 * 60 * 1000) + (2 * 1000); // 2 hours 2 minutes 2 seconds - assertEquals("2 hours 2 minutes 2 seconds", task.getAnnounceTime(time)); - } - - /** - * Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with singular - * numbers without the hour unit. - */ - public void testGetAnnounceTime_singular_no_hour() { - long time = (1 * 60 * 1000) + (1 * 1000); // 1 minute 1 second - assertEquals("1 minute 1 second", task.getAnnounceTime(time)); - } + private void doStart() { + mockTask.listenToPhoneState(capture(phoneListenerCapture), + eq(PhoneStateListener.LISTEN_CALL_STATE)); + expect(mockTask.newTextToSpeech( + same(getContext()), capture(initListenerCapture))) + .andStubReturn(ttsDelegate); + AndroidMock.replay(mockTask); + task.start(); + } - /** - * Tests {@link AnnouncementPeriodicTask#getAnnounceTime(long)} with plural numbers - * without the hour unit. - */ - public void testGetAnnounceTime_plural_no_hour() { - long time = (2 * 60 * 1000) + (2 * 1000); // 2 minutes 2 seconds - assertEquals("2 minutes 2 seconds", task.getAnnounceTime(time)); - } - - private void startTask(int state) { - AndroidMock.resetToNice(tts); - AndroidMock.replay(tts); - doStart(); - OnInitListener ttsInitListener = initListenerCapture.getValue(); - ttsInitListener.onInit(state); - AndroidMock.resetToDefault(tts); - } + /** + * Mockable interface that we delegate TTS calls to. + */ + interface TextToSpeechInterface { + int addEarcon(String earcon, String packagename, int resourceId); - private void doStart() { - mockTask.listenToPhoneState(capture(phoneListenerCapture), - eq(PhoneStateListener.LISTEN_CALL_STATE)); - expect(mockTask.newTextToSpeech( - same(getContext()), capture(initListenerCapture))) - .andStubReturn(ttsDelegate); - AndroidMock.replay(mockTask); - task.start(); - } + int addEarcon(String earcon, String filename); + + int addSpeech(String text, String packagename, int resourceId); + + int addSpeech(String text, String filename); + + boolean areDefaultsEnforced(); + + String getDefaultEngine(); + + Locale getLanguage(); + + int isLanguageAvailable(Locale loc); + + boolean isSpeaking(); + + int playEarcon(String earcon, int queueMode, + HashMap params); + + int playSilence(long durationInMs, int queueMode, + HashMap params); + + int setEngineByPackageName(String enginePackageName); + + int setLanguage(Locale loc); + + int setOnUtteranceCompletedListener(OnUtteranceCompletedListener listener); + + int setPitch(float pitch); + + int setSpeechRate(float speechRate); + + void shutdown(); + + int speak(String text, int queueMode, HashMap params); + + int stop(); + + int synthesizeToFile(String text, HashMap params, + String filename); + } + + /** + * Subclass of {@link TextToSpeech} which delegates calls to the interface + * above. + * The logic here is stupid and the author is ashamed of having to write it + * like this, but basically the issue is that TextToSpeech cannot be mocked + * without running its constructor, its constructor runs async operations + * which call other methods (and then if the methods are part of a mock we'd + * have to set a behavior, but we can't 'cause the object hasn't been fully + * built yet). + * The logic is that calls made during the constructor (when tts is not yet + * set) will go up to the original class, but after tts is set we'll forward + * them all to the mock. + */ + private class TextToSpeechDelegate + extends TextToSpeech implements TextToSpeechInterface { + public TextToSpeechDelegate(Context context, OnInitListener listener) { + super(context, listener); + } + + @Override + public int addEarcon(String earcon, String packagename, int resourceId) { + if (tts == null) { + return super.addEarcon(earcon, packagename, resourceId); + } + return tts.addEarcon(earcon, packagename, resourceId); + } + + @Override + public int addEarcon(String earcon, String filename) { + if (tts == null) { + return super.addEarcon(earcon, filename); + } + return tts.addEarcon(earcon, filename); + } + + @Override + public int addSpeech(String text, String packagename, int resourceId) { + if (tts == null) { + return super.addSpeech(text, packagename, resourceId); + } + return tts.addSpeech(text, packagename, resourceId); + } + + @Override + public int addSpeech(String text, String filename) { + if (tts == null) { + return super.addSpeech(text, filename); + } + return tts.addSpeech(text, filename); + } + + @Override + public Locale getLanguage() { + if (tts == null) { + return super.getLanguage(); + } + return tts.getLanguage(); + } + + @Override + public int isLanguageAvailable(Locale loc) { + if (tts == null) { + return super.isLanguageAvailable(loc); + } + return tts.isLanguageAvailable(loc); + } + + @Override + public boolean isSpeaking() { + if (tts == null) { + return super.isSpeaking(); + } + return tts.isSpeaking(); + } + + @Override + public int playEarcon(String earcon, int queueMode, + HashMap params) { + if (tts == null) { + return super.playEarcon(earcon, queueMode, params); + } + return tts.playEarcon(earcon, queueMode, params); + } + + @Override + public int playSilence(long durationInMs, int queueMode, + HashMap params) { + if (tts == null) { + return super.playSilence(durationInMs, queueMode, params); + } + return tts.playSilence(durationInMs, queueMode, params); + } + + @Override + public int setLanguage(Locale loc) { + if (tts == null) { + return super.setLanguage(loc); + } + return tts.setLanguage(loc); + } + + @Override + public int setOnUtteranceCompletedListener( + OnUtteranceCompletedListener listener) { + if (tts == null) { + return super.setOnUtteranceCompletedListener(listener); + } + return tts.setOnUtteranceCompletedListener(listener); + } + + @Override + public int setPitch(float pitch) { + if (tts == null) { + return super.setPitch(pitch); + } + return tts.setPitch(pitch); + } + + @Override + public int setSpeechRate(float speechRate) { + if (tts == null) { + return super.setSpeechRate(speechRate); + } + return tts.setSpeechRate(speechRate); + } + + @Override + public void shutdown() { + if (tts == null) { + super.shutdown(); + return; + } + tts.shutdown(); + } + + @Override + public int speak( + String text, int queueMode, HashMap params) { + if (tts == null) { + return super.speak(text, queueMode, params); + } + return tts.speak(text, queueMode, params); + } + + @Override + public int stop() { + if (tts == null) { + return super.stop(); + } + return tts.stop(); + } + + @Override + public int synthesizeToFile(String text, HashMap params, + String filename) { + if (tts == null) { + return super.synthesizeToFile(text, params, filename); + } + return tts.synthesizeToFile(text, params, filename); + } + } } diff --git a/src/androidTest/java/com/google/android/apps/mytracks/stats/TripStatisticsTest.java b/src/androidTest/java/com/google/android/apps/mytracks/stats/TripStatisticsTest.java index f7d90947c..73c6e529e 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/stats/TripStatisticsTest.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/stats/TripStatisticsTest.java @@ -1,12 +1,12 @@ /* * Copyright 2010 Google Inc. - * + * * 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 @@ -25,81 +25,81 @@ import junit.framework.TestCase; */ public class TripStatisticsTest extends TestCase { - private TripStatistics statistics; + private TripStatistics statistics; - @Override - protected void setUp() throws Exception { - super.setUp(); + @Override + protected void setUp() throws Exception { + super.setUp(); - statistics = new TripStatistics(); - } + statistics = new TripStatistics(); + } - public void testSetBounds() { - // This is not a trivial setter, conversion happens in it - statistics.setBounds(12345, -34567, 56789, -98765); - assertEquals(12345, statistics.getLeft()); - assertEquals(-34567, statistics.getTop()); - assertEquals(56789, statistics.getRight()); - assertEquals(-98765, statistics.getBottom()); - } + public void testSetBounds() { + // This is not a trivial setter, conversion happens in it + statistics.setBounds(12345, -34567, 56789, -98765); + assertEquals(12345, statistics.getLeft()); + assertEquals(-34567, statistics.getTop()); + assertEquals(56789, statistics.getRight()); + assertEquals(-98765, statistics.getBottom()); + } - public void testMerge() { - TripStatistics statistics2 = new TripStatistics(); - statistics.setStartTime(1000L); // Resulting start time - statistics.setStopTime(2500L); - statistics2.setStartTime(3000L); - statistics2.setStopTime(4000L); // Resulting stop time - statistics.setTotalTime(1500L); - statistics2.setTotalTime(1000L); // Result: 1500+1000 - statistics.setMovingTime(700L); - statistics2.setMovingTime(600L); // Result: 700+600 - statistics.setTotalDistance(750.0); - statistics2.setTotalDistance(350.0); // Result: 750+350 - statistics.setTotalElevationGain(50.0); - statistics2.setTotalElevationGain(850.0); // Result: 850+50 - statistics.setMaxSpeed(60.0); // Resulting max speed - statistics2.setMaxSpeed(30.0); - statistics.setMaxElevation(1250.0); - statistics.setMinElevation(1200.0); // Resulting min elevation - statistics2.setMaxElevation(3575.0); // Resulting max elevation - statistics2.setMinElevation(2800.0); - statistics.setMaxGrade(15.0); - statistics.setMinGrade(-25.0); // Resulting min grade - statistics2.setMaxGrade(35.0); // Resulting max grade - statistics2.setMinGrade(0.0); + public void testMerge() { + TripStatistics statistics2 = new TripStatistics(); + statistics.setStartTime(1000L); // Resulting start time + statistics.setStopTime(2500L); + statistics2.setStartTime(3000L); + statistics2.setStopTime(4000L); // Resulting stop time + statistics.setTotalTime(1500L); + statistics2.setTotalTime(1000L); // Result: 1500+1000 + statistics.setMovingTime(700L); + statistics2.setMovingTime(600L); // Result: 700+600 + statistics.setTotalDistance(750.0); + statistics2.setTotalDistance(350.0); // Result: 750+350 + statistics.setTotalElevationGain(50.0); + statistics2.setTotalElevationGain(850.0); // Result: 850+50 + statistics.setMaxSpeed(60.0); // Resulting max speed + statistics2.setMaxSpeed(30.0); + statistics.setMaxElevation(1250.0); + statistics.setMinElevation(1200.0); // Resulting min elevation + statistics2.setMaxElevation(3575.0); // Resulting max elevation + statistics2.setMinElevation(2800.0); + statistics.setMaxGrade(15.0); + statistics.setMinGrade(-25.0); // Resulting min grade + statistics2.setMaxGrade(35.0); // Resulting max grade + statistics2.setMinGrade(0.0); - // Resulting bounds: -10000, 35000, 30000, -40000 - statistics.setBounds(-10000, 20000, 30000, -40000); - statistics2.setBounds(-5000, 35000, 0, 20000); + // Resulting bounds: -10000, 35000, 30000, -40000 + statistics.setBounds(-10000, 20000, 30000, -40000); + statistics2.setBounds(-5000, 35000, 0, 20000); - statistics.merge(statistics2); + statistics.merge(statistics2); - assertEquals(1000L, statistics.getStartTime()); - assertEquals(4000L, statistics.getStopTime()); - assertEquals(2500L, statistics.getTotalTime()); - assertEquals(1300L, statistics.getMovingTime()); - assertEquals(1100.0, statistics.getTotalDistance()); - assertEquals(900.0, statistics.getTotalElevationGain()); - assertEquals(statistics.getTotalDistance()/ (statistics.getMovingTime() / 1000.0), statistics.getMaxSpeed()); - assertEquals(-10000, statistics.getLeft()); - assertEquals(30000, statistics.getRight()); - assertEquals(35000, statistics.getTop()); - assertEquals(-40000, statistics.getBottom()); - assertEquals(1200.0, statistics.getMinElevation()); - assertEquals(3575.0, statistics.getMaxElevation()); - assertEquals(-25.0, statistics.getMinGrade()); - assertEquals(35.0, statistics.getMaxGrade()); - } + assertEquals(1000L, statistics.getStartTime()); + assertEquals(4000L, statistics.getStopTime()); + assertEquals(2500L, statistics.getTotalTime()); + assertEquals(1300L, statistics.getMovingTime()); + assertEquals(1100.0, statistics.getTotalDistance()); + assertEquals(900.0, statistics.getTotalElevationGain()); + assertEquals(statistics.getTotalDistance() / (statistics.getMovingTime() / 1000.0), statistics.getMaxSpeed()); + assertEquals(-10000, statistics.getLeft()); + assertEquals(30000, statistics.getRight()); + assertEquals(35000, statistics.getTop()); + assertEquals(-40000, statistics.getBottom()); + assertEquals(1200.0, statistics.getMinElevation()); + assertEquals(3575.0, statistics.getMaxElevation()); + assertEquals(-25.0, statistics.getMinGrade()); + assertEquals(35.0, statistics.getMaxGrade()); + } - public void testGetAverageSpeed() { - statistics.setTotalDistance(1000.0); - statistics.setTotalTime(50000); // in milliseconds - assertEquals(20.0, statistics.getAverageSpeed()); - } + public void testGetAverageSpeed() { + statistics.setTotalDistance(1000.0); + statistics.setTotalTime(50000); // in milliseconds + assertEquals(20.0, statistics.getAverageSpeed()); + } - public void testGetAverageMovingSpeed() { - statistics.setTotalDistance(1000.0); - statistics.setMovingTime(20000); // in milliseconds - assertEquals(50.0, statistics.getAverageMovingSpeed()); - } + public void testGetAverageMovingSpeed() { + statistics.setTotalDistance(1000.0); + statistics.setMovingTime(20000); // in milliseconds + assertEquals(50.0, statistics.getAverageMovingSpeed()); + } } diff --git a/src/androidTest/java/com/google/android/apps/mytracks/util/FileUtilsTest.java b/src/androidTest/java/com/google/android/apps/mytracks/util/FileUtilsTest.java index e0bdc3fbe..a0391f961 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/util/FileUtilsTest.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/util/FileUtilsTest.java @@ -17,105 +17,105 @@ package com.google.android.apps.mytracks.util; import android.os.Environment; -import java.io.File; - import junit.framework.TestCase; +import java.io.File; + /** * Tests for {@link FileUtils}. - * + * * @author Rodrigo Damazio */ public class FileUtilsTest extends TestCase { - /** - * Tests {@link FileUtils#getPath(String...)}. - */ - public void testBuildExternalDirectoryPath() { - String expectedName = Environment.getExternalStorageDirectory() + File.separator - + FileUtils.SDCARD_TOP_DIR + File.separator + "a" + File.separator + "b" + File.separator - + "c"; - String dirName = FileUtils.getPath("a", "b", "c"); - assertEquals(expectedName, dirName); - } - - /** - * Tests {@link FileUtils#buildUniqueFileName(File, String, String)} when the - * file is new. - */ - public void testBuildUniqueFileName_new() { - String filename = FileUtils.buildUniqueFileName(new File("/dir"), "Filename", "ext"); - assertEquals("Filename.ext", filename); - } - - /** - * Tests {@link FileUtils#buildUniqueFileName(File, String, String)} when the - * file exists already. - */ - public void testBuildUniqueFileName_exist() { - // Expect "/default.prop" to exist on the phone/emulator - String filename = FileUtils.buildUniqueFileName(new File("/"), "default", "prop"); - assertEquals("default(1).prop", filename); - } - - /** - * Tests {@link FileUtils#sanitizeFileName(String)} with special characters. - * Verifies that they are sanitized. - */ - public void testSanitizeFileName() { - String name = "Swim\10ming-^across:/the/ pacific (ocean)."; - String expected = "Swim_ming-^across_the_ pacific (ocean)_"; - assertEquals(expected, FileUtils.sanitizeFileName(name)); - } - - /** - * Tests {@link FileUtils#sanitizeFileName(String)} with i18n characters (in - * Chinese and Russian). Verifies that they are allowed. - */ - public void testSanitizeFileName_i18n() { - String name = "您好-привет"; - String expected = "您好-привет"; - assertEquals(expected, FileUtils.sanitizeFileName(name)); - } - - /** - * Tests {@link FileUtils#sanitizeFileName(String)} with special FAT32 - * characters. Verifies that they are allowed. - */ - public void testSanitizeFileName_special_characters() { - String name = "$%'-_@~`!(){}^#&+,;=[] "; - String expected = "$%'-_@~`!(){}^#&+,;=[] "; - assertEquals(expected, FileUtils.sanitizeFileName(name)); - } - - /** - * Tests {@link FileUtils#sanitizeFileName(String)} with multiple escaped - * characters in a row. Verifies that they are collapsed into one underscore. - */ - public void testSanitizeFileName_collapse() { - String name = "hello//there"; - String expected = "hello_there"; - assertEquals(expected, FileUtils.sanitizeFileName(name)); - } - - /** - * Tests {@link FileUtils#truncateFileName(File, String, String)}. Verifies - * the a long file name is truncated. - */ - public void testTruncateFileName() { - File directory = new File("/dir1/dir2/"); - String suffix = ".gpx"; - char[] name = new char[FileUtils.MAX_FAT32_PATH_LENGTH]; - for (int i = 0; i < name.length; i++) { - name[i] = 'a'; + /** + * Tests {@link FileUtils#getPath(String...)}. + */ + public void testBuildExternalDirectoryPath() { + String expectedName = Environment.getExternalStorageDirectory() + File.separator + + FileUtils.SDCARD_TOP_DIR + File.separator + "a" + File.separator + "b" + File.separator + + "c"; + String dirName = FileUtils.getPath("a", "b", "c"); + assertEquals(expectedName, dirName); } - String nameString = new String(name); - String truncated = FileUtils.truncateFileName(directory, nameString, suffix); - for (int i = 0; i < truncated.length(); i++) { - assertEquals('a', truncated.charAt(i)); + /** + * Tests {@link FileUtils#buildUniqueFileName(File, String, String)} when the + * file is new. + */ + public void testBuildUniqueFileName_new() { + String filename = FileUtils.buildUniqueFileName(new File("/dir"), "Filename", "ext"); + assertEquals("Filename.ext", filename); + } + + /** + * Tests {@link FileUtils#buildUniqueFileName(File, String, String)} when the + * file exists already. + */ + public void testBuildUniqueFileName_exist() { + // Expect "/default.prop" to exist on the phone/emulator + String filename = FileUtils.buildUniqueFileName(new File("/"), "default", "prop"); + assertEquals("default(1).prop", filename); + } + + /** + * Tests {@link FileUtils#sanitizeFileName(String)} with special characters. + * Verifies that they are sanitized. + */ + public void testSanitizeFileName() { + String name = "Swim\10ming-^across:/the/ pacific (ocean)."; + String expected = "Swim_ming-^across_the_ pacific (ocean)_"; + assertEquals(expected, FileUtils.sanitizeFileName(name)); + } + + /** + * Tests {@link FileUtils#sanitizeFileName(String)} with i18n characters (in + * Chinese and Russian). Verifies that they are allowed. + */ + public void testSanitizeFileName_i18n() { + String name = "您好-привет"; + String expected = "您好-привет"; + assertEquals(expected, FileUtils.sanitizeFileName(name)); + } + + /** + * Tests {@link FileUtils#sanitizeFileName(String)} with special FAT32 + * characters. Verifies that they are allowed. + */ + public void testSanitizeFileName_special_characters() { + String name = "$%'-_@~`!(){}^#&+,;=[] "; + String expected = "$%'-_@~`!(){}^#&+,;=[] "; + assertEquals(expected, FileUtils.sanitizeFileName(name)); + } + + /** + * Tests {@link FileUtils#sanitizeFileName(String)} with multiple escaped + * characters in a row. Verifies that they are collapsed into one underscore. + */ + public void testSanitizeFileName_collapse() { + String name = "hello//there"; + String expected = "hello_there"; + assertEquals(expected, FileUtils.sanitizeFileName(name)); + } + + /** + * Tests {@link FileUtils#truncateFileName(File, String, String)}. Verifies + * the a long file name is truncated. + */ + public void testTruncateFileName() { + File directory = new File("/dir1/dir2/"); + String suffix = ".gpx"; + char[] name = new char[FileUtils.MAX_FAT32_PATH_LENGTH]; + for (int i = 0; i < name.length; i++) { + name[i] = 'a'; + } + String nameString = new String(name); + String truncated = FileUtils.truncateFileName(directory, nameString, suffix); + + for (int i = 0; i < truncated.length(); i++) { + assertEquals('a', truncated.charAt(i)); + } + assertEquals(FileUtils.MAX_FAT32_PATH_LENGTH, + new File(directory, truncated + suffix).getPath().length()); } - assertEquals(FileUtils.MAX_FAT32_PATH_LENGTH, - new File(directory, truncated + suffix).getPath().length()); - } } diff --git a/src/androidTest/java/com/google/android/apps/mytracks/util/StringUtilsTest.java b/src/androidTest/java/com/google/android/apps/mytracks/util/StringUtilsTest.java index 7498dfab1..152f665b1 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/util/StringUtilsTest.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/util/StringUtilsTest.java @@ -1,12 +1,12 @@ /* * Copyright 2010 Google Inc. - * + * * 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 @@ -33,156 +33,156 @@ import java.util.TimeZone; @RunWith(AndroidJUnit4.class) public class StringUtilsTest { - /** - * Tests {@link StringUtils#formatDateTimeIso8601(long)}. - */ - public void testFormatDateTimeIso8601() { - Assert.assertEquals("1970-01-01T00:00:12.345Z", StringUtils.formatDateTimeIso8601(12345)); - } + /** + * Tests {@link StringUtils#formatDateTimeIso8601(long)}. + */ + public void testFormatDateTimeIso8601() { + Assert.assertEquals("1970-01-01T00:00:12.345Z", StringUtils.formatDateTimeIso8601(12345)); + } - /** - * Tests {@link StringUtils#formatElapsedTime(long)}. - */ - public void testformatElapsedTime() { - // 1 second - Assert.assertEquals("00:01", StringUtils.formatElapsedTime(1000)); - // 10 seconds - Assert.assertEquals("00:10", StringUtils.formatElapsedTime(10000)); - // 1 minute - Assert.assertEquals("01:00", StringUtils.formatElapsedTime(60000)); - // 10 minutes - Assert.assertEquals("10:00", StringUtils.formatElapsedTime(600000)); - // 1 hour - Assert.assertEquals("1:00:00", StringUtils.formatElapsedTime(3600000)); - // 10 hours - Assert.assertEquals("10:00:00", StringUtils.formatElapsedTime(36000000)); - // 100 hours - Assert.assertEquals("100:00:00", StringUtils.formatElapsedTime(360000000)); - } + /** + * Tests {@link StringUtils#formatElapsedTime(long)}. + */ + public void testformatElapsedTime() { + // 1 second + Assert.assertEquals("00:01", StringUtils.formatElapsedTime(1000)); + // 10 seconds + Assert.assertEquals("00:10", StringUtils.formatElapsedTime(10000)); + // 1 minute + Assert.assertEquals("01:00", StringUtils.formatElapsedTime(60000)); + // 10 minutes + Assert.assertEquals("10:00", StringUtils.formatElapsedTime(600000)); + // 1 hour + Assert.assertEquals("1:00:00", StringUtils.formatElapsedTime(3600000)); + // 10 hours + Assert.assertEquals("10:00:00", StringUtils.formatElapsedTime(36000000)); + // 100 hours + Assert.assertEquals("100:00:00", StringUtils.formatElapsedTime(360000000)); + } - /** - * Tests {@link StringUtils#formatElapsedTimeWithHour(long)}. - */ - public void testformatElapsedTimeWithHour() { - // 1 second - Assert.assertEquals("0:00:01", StringUtils.formatElapsedTimeWithHour(1000)); - // 10 seconds - Assert.assertEquals("0:00:10", StringUtils.formatElapsedTimeWithHour(10000)); - // 1 minute - Assert.assertEquals("0:01:00", StringUtils.formatElapsedTimeWithHour(60000)); - // 10 minutes - Assert.assertEquals("0:10:00", StringUtils.formatElapsedTimeWithHour(600000)); - // 1 hour - Assert.assertEquals("1:00:00", StringUtils.formatElapsedTimeWithHour(3600000)); - // 10 hours - Assert.assertEquals("10:00:00", StringUtils.formatElapsedTimeWithHour(36000000)); - // 100 hours - Assert.assertEquals("100:00:00", StringUtils.formatElapsedTimeWithHour(360000000)); - } + /** + * Tests {@link StringUtils#formatElapsedTimeWithHour(long)}. + */ + public void testformatElapsedTimeWithHour() { + // 1 second + Assert.assertEquals("0:00:01", StringUtils.formatElapsedTimeWithHour(1000)); + // 10 seconds + Assert.assertEquals("0:00:10", StringUtils.formatElapsedTimeWithHour(10000)); + // 1 minute + Assert.assertEquals("0:01:00", StringUtils.formatElapsedTimeWithHour(60000)); + // 10 minutes + Assert.assertEquals("0:10:00", StringUtils.formatElapsedTimeWithHour(600000)); + // 1 hour + Assert.assertEquals("1:00:00", StringUtils.formatElapsedTimeWithHour(3600000)); + // 10 hours + Assert.assertEquals("10:00:00", StringUtils.formatElapsedTimeWithHour(36000000)); + // 100 hours + Assert.assertEquals("100:00:00", StringUtils.formatElapsedTimeWithHour(360000000)); + } - /** - * Tests {@link StringUtils#formatDistance(android.content.Context, double, - * boolean)}. - */ - public void testFormatDistance() { - // A large number in metric - Assert.assertEquals("5.00 km", StringUtils.formatDistance(InstrumentationRegistry.getInstrumentation().getContext(), 5000, true)); - // A large number in imperial - Assert.assertEquals("3.11 mi", StringUtils.formatDistance(InstrumentationRegistry.getInstrumentation().getContext(), 5000, false)); - // A small number in metric - Assert.assertEquals("100.00 m", StringUtils.formatDistance(InstrumentationRegistry.getInstrumentation().getContext(), 100, true)); - // A small number in imperial - Assert.assertEquals("328.08 ft", StringUtils.formatDistance(InstrumentationRegistry.getInstrumentation().getContext(), 100, false)); - } + /** + * Tests {@link StringUtils#formatDistance(android.content.Context, double, + * boolean)}. + */ + public void testFormatDistance() { + // A large number in metric + Assert.assertEquals("5.00 km", StringUtils.formatDistance(InstrumentationRegistry.getInstrumentation().getContext(), 5000, true)); + // A large number in imperial + Assert.assertEquals("3.11 mi", StringUtils.formatDistance(InstrumentationRegistry.getInstrumentation().getContext(), 5000, false)); + // A small number in metric + Assert.assertEquals("100.00 m", StringUtils.formatDistance(InstrumentationRegistry.getInstrumentation().getContext(), 100, true)); + // A small number in imperial + Assert.assertEquals("328.08 ft", StringUtils.formatDistance(InstrumentationRegistry.getInstrumentation().getContext(), 100, false)); + } - /** - * Tests {@link StringUtils#formatCData(String)}. - */ - public void testFormatCData() { - Assert.assertEquals("", StringUtils.formatCData("hello")); - Assert.assertEquals("there]]>", StringUtils.formatCData("hello]]>there")); - } - - /** - * Tests {@link StringUtils#getTime(String)}. - */ - public void testGetTime() { - assertGetTime("2010-05-04T03:02:01", 2010, 5, 4, 3, 2, 1, 0); - assertGetTime("2010-05-04T03:02:01Z", 2010, 5, 4, 3, 2, 1, 0); - } + /** + * Tests {@link StringUtils#formatCData(String)}. + */ + public void testFormatCData() { + Assert.assertEquals("", StringUtils.formatCData("hello")); + Assert.assertEquals("there]]>", StringUtils.formatCData("hello]]>there")); + } - /** - * Tests {@link StringUtils#getTime(String)} with fractional seconds. - */ - public void testGetTime_fractional() { - assertGetTime("2010-05-04T03:02:01.3", 2010, 5, 4, 3, 2, 1, 300); - assertGetTime("2010-05-04T03:02:01.35", 2010, 5, 4, 3, 2, 1, 350); - assertGetTime("2010-05-04T03:02:01.352Z", 2010, 5, 4, 3, 2, 1, 352); - assertGetTime("2010-05-04T03:02:01.3525Z", 2010, 5, 4, 3, 2, 1, 352); - } + /** + * Tests {@link StringUtils#getTime(String)}. + */ + public void testGetTime() { + assertGetTime("2010-05-04T03:02:01", 2010, 5, 4, 3, 2, 1, 0); + assertGetTime("2010-05-04T03:02:01Z", 2010, 5, 4, 3, 2, 1, 0); + } - /** - * Tests {@link StringUtils#getTime(String)} with time zone. - */ - public void testGetTime_timezone() { - assertGetTime("2010-05-04T03:02:01Z", 2010, 5, 4, 3, 2, 1, 0); - assertGetTime("2010-05-04T03:02:01+00:00", 2010, 5, 4, 3, 2, 1, 0); - assertGetTime("2010-05-04T03:02:01-00:00", 2010, 5, 4, 3, 2, 1, 0); - assertGetTime("2010-05-04T03:02:01+01:00", 2010, 5, 4, 2, 2, 1, 0); - assertGetTime("2010-05-04T03:02:01+10:30", 2010, 5, 3, 16, 32, 1, 0); - assertGetTime("2010-05-04T03:02:01-09:30", 2010, 5, 4, 12, 32, 1, 0); - assertGetTime("2010-05-04T03:02:01-05:00", 2010, 5, 4, 8, 2, 1, 0); - } + /** + * Tests {@link StringUtils#getTime(String)} with fractional seconds. + */ + public void testGetTime_fractional() { + assertGetTime("2010-05-04T03:02:01.3", 2010, 5, 4, 3, 2, 1, 300); + assertGetTime("2010-05-04T03:02:01.35", 2010, 5, 4, 3, 2, 1, 350); + assertGetTime("2010-05-04T03:02:01.352Z", 2010, 5, 4, 3, 2, 1, 352); + assertGetTime("2010-05-04T03:02:01.3525Z", 2010, 5, 4, 3, 2, 1, 352); + } - /** - * Tests {@link StringUtils#getTime(String)} with fractional seconds and time - * zone. - */ - public void testGetTime_fractionalAndTimezone() { - assertGetTime("2010-05-04T03:02:01.352Z", 2010, 5, 4, 3, 2, 1, 352); - assertGetTime("2010-05-04T03:02:01.47+00:00", 2010, 5, 4, 3, 2, 1, 470); - assertGetTime("2010-05-04T03:02:01.5791+03:00", 2010, 5, 4, 0, 2, 1, 579); - assertGetTime("2010-05-04T03:02:01.8-05:30", 2010, 5, 4, 8, 32, 1, 800); - } + /** + * Tests {@link StringUtils#getTime(String)} with time zone. + */ + public void testGetTime_timezone() { + assertGetTime("2010-05-04T03:02:01Z", 2010, 5, 4, 3, 2, 1, 0); + assertGetTime("2010-05-04T03:02:01+00:00", 2010, 5, 4, 3, 2, 1, 0); + assertGetTime("2010-05-04T03:02:01-00:00", 2010, 5, 4, 3, 2, 1, 0); + assertGetTime("2010-05-04T03:02:01+01:00", 2010, 5, 4, 2, 2, 1, 0); + assertGetTime("2010-05-04T03:02:01+10:30", 2010, 5, 3, 16, 32, 1, 0); + assertGetTime("2010-05-04T03:02:01-09:30", 2010, 5, 4, 12, 32, 1, 0); + assertGetTime("2010-05-04T03:02:01-05:00", 2010, 5, 4, 8, 2, 1, 0); + } - /** - * Asserts the {@link StringUtils#getTime(String)} returns the expected - * values. - * - * @param xmlDateTime the xml date time string - * @param year the expected year - * @param month the expected month - * @param day the expected day - * @param hour the expected hour - * @param minute the expected minute - * @param second the expected second - * @param millisecond the expected milliseconds - */ - private void assertGetTime(String xmlDateTime, int year, int month, int day, int hour, int minute, - int second, int millisecond) { - GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC")); - calendar.set(year, month - 1, day, hour, minute, second); - calendar.set(GregorianCalendar.MILLISECOND, millisecond); - Assert.assertEquals(calendar.getTimeInMillis(), StringUtils.getTime(xmlDateTime)); - } + /** + * Tests {@link StringUtils#getTime(String)} with fractional seconds and time + * zone. + */ + public void testGetTime_fractionalAndTimezone() { + assertGetTime("2010-05-04T03:02:01.352Z", 2010, 5, 4, 3, 2, 1, 352); + assertGetTime("2010-05-04T03:02:01.47+00:00", 2010, 5, 4, 3, 2, 1, 470); + assertGetTime("2010-05-04T03:02:01.5791+03:00", 2010, 5, 4, 0, 2, 1, 579); + assertGetTime("2010-05-04T03:02:01.8-05:30", 2010, 5, 4, 8, 32, 1, 800); + } - /** - * Tests {@link StringUtils#getTimeParts(long)} with a positive number. - */ - public void testGetTimeParts_postive() { - int[] parts = StringUtils.getTimeParts(61000); - Assert.assertEquals(1, parts[0]); - Assert.assertEquals(1, parts[1]); - Assert.assertEquals(0, parts[2]); - } + /** + * Asserts the {@link StringUtils#getTime(String)} returns the expected + * values. + * + * @param xmlDateTime the xml date time string + * @param year the expected year + * @param month the expected month + * @param day the expected day + * @param hour the expected hour + * @param minute the expected minute + * @param second the expected second + * @param millisecond the expected milliseconds + */ + private void assertGetTime(String xmlDateTime, int year, int month, int day, int hour, int minute, + int second, int millisecond) { + GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + calendar.set(year, month - 1, day, hour, minute, second); + calendar.set(GregorianCalendar.MILLISECOND, millisecond); + Assert.assertEquals(calendar.getTimeInMillis(), StringUtils.getTime(xmlDateTime)); + } - /** - * Tests {@link StringUtils#getTimeParts(long)} with a negative number. - */ - public void testGetTimeParts_negative() { - int[] parts = StringUtils.getTimeParts(-61000); - Assert.assertEquals(-1, parts[0]); - Assert.assertEquals(-1, parts[1]); - Assert.assertEquals(0, parts[2]); - } + /** + * Tests {@link StringUtils#getTimeParts(long)} with a positive number. + */ + public void testGetTimeParts_postive() { + int[] parts = StringUtils.getTimeParts(61000); + Assert.assertEquals(1, parts[0]); + Assert.assertEquals(1, parts[1]); + Assert.assertEquals(0, parts[2]); + } + + /** + * Tests {@link StringUtils#getTimeParts(long)} with a negative number. + */ + public void testGetTimeParts_negative() { + int[] parts = StringUtils.getTimeParts(-61000); + Assert.assertEquals(-1, parts[0]); + Assert.assertEquals(-1, parts[1]); + Assert.assertEquals(0, parts[2]); + } } diff --git a/src/androidTest/java/com/google/android/apps/mytracks/util/TrackNameUtilsTest.java b/src/androidTest/java/com/google/android/apps/mytracks/util/TrackNameUtilsTest.java index 12769113c..7506a5fff 100644 --- a/src/androidTest/java/com/google/android/apps/mytracks/util/TrackNameUtilsTest.java +++ b/src/androidTest/java/com/google/android/apps/mytracks/util/TrackNameUtilsTest.java @@ -32,46 +32,46 @@ import java.util.Locale; /** * Tests {@link TrackNameUtils}. - * + * * @author Matthew Simmons */ @RunWith(AndroidJUnit4.class) public class TrackNameUtilsTest { - private static final long TRACK_ID = 1L; - private static final long START_TIME = 1288213406000L; + private static final long TRACK_ID = 1L; + private static final long START_TIME = 1288213406000L; - private static final Context CONTEXT = InstrumentationRegistry.getInstrumentation().getContext(); + private static final Context CONTEXT = InstrumentationRegistry.getInstrumentation().getContext(); - /** - * Tests when the track_name_key is - * settings_recording_track_name_date_local_value. - */ - public void testTrackName_date_local() { - PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value)); - Assert.assertEquals(StringUtils.formatDateTime(CONTEXT, START_TIME), - TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME, null)); - } + /** + * Tests when the track_name_key is + * settings_recording_track_name_date_local_value. + */ + public void testTrackName_date_local() { + PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value)); + Assert.assertEquals(StringUtils.formatDateTime(CONTEXT, START_TIME), + TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME, null)); + } - /** - * Tests when the track_name_key is - * settings_recording_track_name_date_iso_8601_value. - */ - public void testTrackName_date_iso_8601() { - PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_iso_8601_value)); - SimpleDateFormat simpleDateFormat = new SimpleDateFormat( - TrackNameUtils.ISO_8601_FORMAT, Locale.US); - Assert.assertEquals(simpleDateFormat.format(new Date(START_TIME)), - TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME, null)); - } + /** + * Tests when the track_name_key is + * settings_recording_track_name_date_iso_8601_value. + */ + public void testTrackName_date_iso_8601() { + PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_iso_8601_value)); + SimpleDateFormat simpleDateFormat = new SimpleDateFormat( + TrackNameUtils.ISO_8601_FORMAT, Locale.US); + Assert.assertEquals(simpleDateFormat.format(new Date(START_TIME)), + TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME, null)); + } - /** - * Tests when the track_name_key is - * settings_recording_track_name_number_value. - */ - public void testTrackName_number() { - PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value)); - Assert.assertEquals( - "Track " + TRACK_ID, TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME, null)); - } + /** + * Tests when the track_name_key is + * settings_recording_track_name_number_value. + */ + public void testTrackName_number() { + PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value)); + Assert.assertEquals( + "Track " + TRACK_ID, TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME, null)); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/AbstractActivity.java b/src/main/java/com/google/android/apps/mytracks/AbstractActivity.java index 42e81eaaf..4286a0a57 100644 --- a/src/main/java/com/google/android/apps/mytracks/AbstractActivity.java +++ b/src/main/java/com/google/android/apps/mytracks/AbstractActivity.java @@ -1,12 +1,12 @@ /* * Copyright 2012 Google Inc. - * + * * 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 @@ -27,62 +27,61 @@ import androidx.fragment.app.FragmentActivity; import com.google.android.apps.mytracks.util.TrackIconUtils; /** - * * @author Jimmy Shih */ public abstract class AbstractActivity extends FragmentActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - // Set volume control stream for text to speech - setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM); + // Set volume control stream for text to speech + setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM); - setContentView(getLayoutResId()); + setContentView(getLayoutResId()); - // Configure action bar must be after setContentView - if (configureActionBarHomeAsUp()) { - ActionBar actionBar = this.getActionBar(); - if (actionBar != null) { - actionBar.setHomeButtonEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(true); - } + // Configure action bar must be after setContentView + if (configureActionBarHomeAsUp()) { + ActionBar actionBar = this.getActionBar(); + if (actionBar != null) { + actionBar.setHomeButtonEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(true); + } + } } - } - /** - * Gets the layout resource id. - */ - protected abstract int getLayoutResId(); + /** + * Gets the layout resource id. + */ + protected abstract int getLayoutResId(); - /** - * Returns true to configure the action bar home button as the up button (go to previous activity). - */ - protected boolean configureActionBarHomeAsUp() { - return true; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - TrackIconUtils.setMenuIconColor(menu); - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - // Handles actionBar's home menu item (arrow top left corner) and triggers, up navigation to previous activity. - if (item.getItemId() == android.R.id.home) { - onHomeSelected(); - return true; + /** + * Returns true to configure the action bar home button as the up button (go to previous activity). + */ + protected boolean configureActionBarHomeAsUp() { + return true; } - return super.onOptionsItemSelected(item); - } - /** - * Callback when the home menu item is selected. E.g., setup the back stack when home is selected. - */ - protected void onHomeSelected() { - finish(); - } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + TrackIconUtils.setMenuIconColor(menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handles actionBar's home menu item (arrow top left corner) and triggers, up navigation to previous activity. + if (item.getItemId() == android.R.id.home) { + onHomeSelected(); + return true; + } + return super.onOptionsItemSelected(item); + } + + /** + * Callback when the home menu item is selected. E.g., setup the back stack when home is selected. + */ + protected void onHomeSelected() { + finish(); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/AbstractTrackActivity.java b/src/main/java/com/google/android/apps/mytracks/AbstractTrackActivity.java index 57df10b19..01fa4c0f1 100644 --- a/src/main/java/com/google/android/apps/mytracks/AbstractTrackActivity.java +++ b/src/main/java/com/google/android/apps/mytracks/AbstractTrackActivity.java @@ -1,12 +1,12 @@ /* * Copyright 2012 Google Inc. - * + * * 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 @@ -51,74 +51,65 @@ import java.util.ArrayList; */ public abstract class AbstractTrackActivity extends AbstractActivity implements ConfirmDeleteCaller { - private static final int DELETE_REQUEST_CODE = 3; - protected static final int GPS_REQUEST_CODE = 6; + protected static final int GPS_REQUEST_CODE = 6; + private static final int DELETE_REQUEST_CODE = 3; - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == DELETE_REQUEST_CODE) { - onDeleted(); - } else { - super.onActivityResult(requestCode, resultCode, data); - } - } + public static void configureListViewContextualMenu(final ListView listView, final ContextualActionModeCallback contextualActionModeCallback) { + listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); + listView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() { - public static void configureListViewContextualMenu(final ListView listView, final ContextualActionModeCallback contextualActionModeCallback) { - listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); - listView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() { + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + mode.getMenuInflater().inflate(R.menu.list_context_menu, menu); + return true; + } - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - mode.getMenuInflater().inflate(R.menu.list_context_menu, menu); - return true; - } + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + contextualActionModeCallback.onPrepare(menu, getCheckedPositions(listView), listView.getCheckedItemIds(), true); + return true; + } - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - contextualActionModeCallback.onPrepare(menu, getCheckedPositions(listView), listView.getCheckedItemIds(), true); - return true; - } + @Override + public void onDestroyActionMode(ActionMode mode) { + // Do nothing + } - @Override - public void onDestroyActionMode(ActionMode mode) { - // Do nothing - } + @Override + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { + mode.invalidate(); + } - @Override - public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { - mode.invalidate(); - } + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + if (contextualActionModeCallback.onClick(item.getItemId(), getCheckedPositions(listView), listView.getCheckedItemIds())) { + mode.finish(); + } + return true; + } - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - if (contextualActionModeCallback.onClick(item.getItemId(), getCheckedPositions(listView), listView.getCheckedItemIds())) { - mode.finish(); - } - return true; - } - - /** - * Gets the checked positions in a list view. - * - * @param list the list view - */ - private int[] getCheckedPositions(ListView list) { - SparseBooleanArray positions = list.getCheckedItemPositions(); - ArrayList arrayList = new ArrayList<>(); - for (int i = 0; i < positions.size(); i++) { - int key = positions.keyAt(i); - if (positions.valueAt(i)) { - arrayList.add(key); - } - } - int[] result = new int[arrayList.size()]; - for (int i = 0; i < arrayList.size(); i++) { - result[i] = arrayList.get(i); - } - return result; - } - }); - } + /** + * Gets the checked positions in a list view. + * + * @param list the list view + */ + private int[] getCheckedPositions(ListView list) { + SparseBooleanArray positions = list.getCheckedItemPositions(); + ArrayList arrayList = new ArrayList<>(); + for (int i = 0; i < positions.size(); i++) { + int key = positions.keyAt(i); + if (positions.valueAt(i)) { + arrayList.add(key); + } + } + int[] result = new int[arrayList.size()]; + for (int i = 0; i < arrayList.size(); i++) { + result[i] = arrayList.get(i); + } + return result; + } + }); + } public static void configureSearchWidget(Activity activity, final MenuItem menuItem, final TrackController trackController) { SearchManager searchManager = (SearchManager) activity.getSystemService(Context.SEARCH_SERVICE); @@ -166,46 +157,55 @@ public abstract class AbstractTrackActivity extends AbstractActivity implements }); } - /** - * Delete tracks. - * - * @param trackIds the track ids - */ - protected void deleteTracks(long[] trackIds) { - ConfirmDeleteDialogFragment.newInstance(trackIds) - .show(getSupportFragmentManager(), ConfirmDeleteDialogFragment.CONFIRM_DELETE_DIALOG_TAG); - } - - @Override - public void onConfirmDeleteDone(long[] trackIds) { - boolean stopRecording = false; - if (trackIds.length == 1 && trackIds[0] == -1L) { - stopRecording = true; - } else { - long recordingTrackId = PreferencesUtils.getLong(this, R.string.recording_track_id_key); - for (long trackId : trackIds) { - if (trackId == recordingTrackId) { - stopRecording = true; - break; + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == DELETE_REQUEST_CODE) { + onDeleted(); + } else { + super.onActivityResult(requestCode, resultCode, data); } - } } - if (stopRecording) { - TrackRecordingServiceConnectionUtils.stopRecording(this, getTrackRecordingServiceConnection(), false); + + /** + * Delete tracks. + * + * @param trackIds the track ids + */ + protected void deleteTracks(long[] trackIds) { + ConfirmDeleteDialogFragment.newInstance(trackIds) + .show(getSupportFragmentManager(), ConfirmDeleteDialogFragment.CONFIRM_DELETE_DIALOG_TAG); } - Intent intent = IntentUtils.newIntent(this, DeleteActivity.class); - intent.putExtra(DeleteActivity.EXTRA_TRACK_IDS, trackIds); - startActivityForResult(intent, DELETE_REQUEST_CODE); - } - /** - * Gets the track recording service connection. For stopping the current - * recording if need to delete the current recording track. - */ - abstract protected TrackRecordingServiceConnection getTrackRecordingServiceConnection(); + @Override + public void onConfirmDeleteDone(long[] trackIds) { + boolean stopRecording = false; + if (trackIds.length == 1 && trackIds[0] == -1L) { + stopRecording = true; + } else { + long recordingTrackId = PreferencesUtils.getLong(this, R.string.recording_track_id_key); + for (long trackId : trackIds) { + if (trackId == recordingTrackId) { + stopRecording = true; + break; + } + } + } + if (stopRecording) { + TrackRecordingServiceConnectionUtils.stopRecording(this, getTrackRecordingServiceConnection(), false); + } + Intent intent = IntentUtils.newIntent(this, DeleteActivity.class); + intent.putExtra(DeleteActivity.EXTRA_TRACK_IDS, trackIds); + startActivityForResult(intent, DELETE_REQUEST_CODE); + } - /** - * Called after {@link DeleteActivity} returns its result. - */ - abstract protected void onDeleted(); + /** + * Gets the track recording service connection. For stopping the current + * recording if need to delete the current recording track. + */ + abstract protected TrackRecordingServiceConnection getTrackRecordingServiceConnection(); + + /** + * Called after {@link DeleteActivity} returns its result. + */ + abstract protected void onDeleted(); } diff --git a/src/main/java/com/google/android/apps/mytracks/AggregatedStatsActivity.java b/src/main/java/com/google/android/apps/mytracks/AggregatedStatsActivity.java index 0782d2f29..c2b8cdf27 100644 --- a/src/main/java/com/google/android/apps/mytracks/AggregatedStatsActivity.java +++ b/src/main/java/com/google/android/apps/mytracks/AggregatedStatsActivity.java @@ -16,14 +16,14 @@ package com.google.android.apps.mytracks; +import android.os.Bundle; + import com.google.android.apps.mytracks.content.ContentProviderUtils; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.stats.TripStatistics; import com.google.android.apps.mytracks.util.StatsUtils; import com.google.android.maps.mytracks.R; -import android.os.Bundle; - import java.util.List; /** @@ -33,31 +33,31 @@ import java.util.List; */ public class AggregatedStatsActivity extends AbstractActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - StatsUtils.setTripStatisticsValues(this, this, null, getTripStatistics(), null); - StatsUtils.setLocationValues(this, this, null, null, false); - } - - @Override - protected int getLayoutResId() { - return R.layout.stats; - } - - /** - * Gets the aggregated trip statistics for all the recorded tracks or null if - * there is no track. - */ - private TripStatistics getTripStatistics() { - List tracks = ContentProviderUtils.Factory.get(this).getAllTracks(); - TripStatistics tripStatistics = null; - if (!tracks.isEmpty()) { - tripStatistics = new TripStatistics(tracks.iterator().next().getTripStatistics()); - for (int i = 1; i < tracks.size(); i++) { - tripStatistics.merge(tracks.get(i).getTripStatistics()); - } + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + StatsUtils.setTripStatisticsValues(this, this, null, getTripStatistics(), null); + StatsUtils.setLocationValues(this, this, null, null, false); + } + + @Override + protected int getLayoutResId() { + return R.layout.stats; + } + + /** + * Gets the aggregated trip statistics for all the recorded tracks or null if + * there is no track. + */ + private TripStatistics getTripStatistics() { + List tracks = ContentProviderUtils.Factory.get(this).getAllTracks(); + TripStatistics tripStatistics = null; + if (!tracks.isEmpty()) { + tripStatistics = new TripStatistics(tracks.iterator().next().getTripStatistics()); + for (int i = 1; i < tracks.size(); i++) { + tripStatistics.merge(tracks.get(i).getTripStatistics()); + } + } + return tripStatistics; } - return tripStatistics; - } } diff --git a/src/main/java/com/google/android/apps/mytracks/BootReceiver.java b/src/main/java/com/google/android/apps/mytracks/BootReceiver.java index 861beed2c..eded4b317 100644 --- a/src/main/java/com/google/android/apps/mytracks/BootReceiver.java +++ b/src/main/java/com/google/android/apps/mytracks/BootReceiver.java @@ -1,12 +1,12 @@ /* * Copyright 2010 Google Inc. - * + * * 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 @@ -15,43 +15,43 @@ */ package com.google.android.apps.mytracks; -import static android.content.Intent.ACTION_BOOT_COMPLETED; - -import com.google.android.apps.mytracks.services.TrackRecordingService; - import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; +import com.google.android.apps.mytracks.services.TrackRecordingService; + +import static android.content.Intent.ACTION_BOOT_COMPLETED; + /** * This class react to the BOOT_COMPLETED broadcast. - * + *

* One example of a broadcast message that this class is interested in, * is notification about the phone boot. We may want to resume a previously * started tracking session if the phone crashed (hopefully not), or the user * decided to swap the battery or some external event occurred which forced * a phone reboot. - * + *

* This class simply delegates to {@link TrackRecordingService} to make a * decision whether to continue with the previous track (if any), or just * abandon it. - * + * * @author Bartlomiej Niechwiej */ public class BootReceiver extends BroadcastReceiver { - private static final String TAG = BootReceiver.class.getSimpleName(); - - @Override - public void onReceive(Context context, Intent intent) { - Log.d(TAG, "BootReceiver.onReceive: " + intent.getAction()); - if (ACTION_BOOT_COMPLETED.equals(intent.getAction())) { - Intent startIntent = new Intent(context, TrackRecordingService.class) - .putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true); - context.startService(startIntent); - } else { - Log.w(TAG, "BootReceiver: unsupported action"); + private static final String TAG = BootReceiver.class.getSimpleName(); + + @Override + public void onReceive(Context context, Intent intent) { + Log.d(TAG, "BootReceiver.onReceive: " + intent.getAction()); + if (ACTION_BOOT_COMPLETED.equals(intent.getAction())) { + Intent startIntent = new Intent(context, TrackRecordingService.class) + .putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true); + context.startService(startIntent); + } else { + Log.w(TAG, "BootReceiver: unsupported action"); + } } - } } diff --git a/src/main/java/com/google/android/apps/mytracks/ChartValueSeries.java b/src/main/java/com/google/android/apps/mytracks/ChartValueSeries.java index 45ff9e73f..212c09a0c 100644 --- a/src/main/java/com/google/android/apps/mytracks/ChartValueSeries.java +++ b/src/main/java/com/google/android/apps/mytracks/ChartValueSeries.java @@ -22,6 +22,7 @@ import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Paint.Style; import android.graphics.Path; + import androidx.annotation.VisibleForTesting; import com.google.android.apps.mytracks.stats.ExtremityMonitor; @@ -30,234 +31,234 @@ import java.text.NumberFormat; /** * This class encapsulates the meta data for one series of the chart values. - * + * * @author Sandor Dornbush */ public class ChartValueSeries { - private static final float STROKE_WIDTH = 2f; + private static final float STROKE_WIDTH = 2f; - private final int absoluteMin; - private final int absoluteMax; - private final int[] intervalValues; - private final int metricTitleId; - private final int imperialTitleId; - private final Paint fillPaint; - private final Paint strokePaint; - private final Paint titlePaint; - private final Paint markerPaint; - private final ExtremityMonitor extremityMonitor; - private final NumberFormat numberFormat; - private final Path path; - - private int interval = 1; - private int minMarkerValue = 0; - private int maxMarkerValue = interval * ChartView.Y_AXIS_INTERVALS; - private boolean enabled = true; + private final int absoluteMin; + private final int absoluteMax; + private final int[] intervalValues; + private final int metricTitleId; + private final int imperialTitleId; + private final Paint fillPaint; + private final Paint strokePaint; + private final Paint titlePaint; + private final Paint markerPaint; + private final ExtremityMonitor extremityMonitor; + private final NumberFormat numberFormat; + private final Path path; - /** - * Constructor. - * - * @param context the context - * @param absoluteMin the absolute min value - * @param absoluteMax the absolute max value - * @param intervalValues the list of interval values - * @param metricTitleId the metric title id - * @param imperialTitleId the imperial title id - * @param fillColor the fill color - * @param strokeColor the stroke color - */ - public ChartValueSeries(Context context, int absoluteMin, int absoluteMax, int[] intervalValues, - int metricTitleId, int imperialTitleId, int fillColor, int strokeColor) { - this.absoluteMin = absoluteMin; - this.absoluteMax = absoluteMax; - this.intervalValues = intervalValues; - this.metricTitleId = metricTitleId; - this.imperialTitleId = imperialTitleId; + private int interval = 1; + private int minMarkerValue = 0; + private int maxMarkerValue = interval * ChartView.Y_AXIS_INTERVALS; + private boolean enabled = true; - fillPaint = new Paint(); - fillPaint.setStyle(Style.FILL); - fillPaint.setColor(context.getResources().getColor(fillColor)); - fillPaint.setAntiAlias(true); + /** + * Constructor. + * + * @param context the context + * @param absoluteMin the absolute min value + * @param absoluteMax the absolute max value + * @param intervalValues the list of interval values + * @param metricTitleId the metric title id + * @param imperialTitleId the imperial title id + * @param fillColor the fill color + * @param strokeColor the stroke color + */ + public ChartValueSeries(Context context, int absoluteMin, int absoluteMax, int[] intervalValues, + int metricTitleId, int imperialTitleId, int fillColor, int strokeColor) { + this.absoluteMin = absoluteMin; + this.absoluteMax = absoluteMax; + this.intervalValues = intervalValues; + this.metricTitleId = metricTitleId; + this.imperialTitleId = imperialTitleId; - strokePaint = new Paint(); - strokePaint.setStyle(Style.STROKE); - strokePaint.setColor(context.getResources().getColor(strokeColor)); - strokePaint.setAntiAlias(true); + fillPaint = new Paint(); + fillPaint.setStyle(Style.FILL); + fillPaint.setColor(context.getResources().getColor(fillColor)); + fillPaint.setAntiAlias(true); - float scale = context.getResources().getDisplayMetrics().density; - - // Make copies of the stroke paint with the default thickness - titlePaint = new Paint(strokePaint); - titlePaint.setTextSize(ChartView.MEDIUM_TEXT_SIZE * scale); - titlePaint.setTextAlign(Align.CENTER); + strokePaint = new Paint(); + strokePaint.setStyle(Style.STROKE); + strokePaint.setColor(context.getResources().getColor(strokeColor)); + strokePaint.setAntiAlias(true); - markerPaint = new Paint(strokePaint); - markerPaint.setTextSize(ChartView.SMALL_TEXT_SIZE * scale); - markerPaint.setTextAlign(Align.RIGHT); + float scale = context.getResources().getDisplayMetrics().density; - // Set stroke paint thickness - strokePaint.setStrokeWidth(STROKE_WIDTH); + // Make copies of the stroke paint with the default thickness + titlePaint = new Paint(strokePaint); + titlePaint.setTextSize(ChartView.MEDIUM_TEXT_SIZE * scale); + titlePaint.setTextAlign(Align.CENTER); - extremityMonitor = new ExtremityMonitor(); - numberFormat = NumberFormat.getIntegerInstance(); - path = new Path(); - } + markerPaint = new Paint(strokePaint); + markerPaint.setTextSize(ChartView.SMALL_TEXT_SIZE * scale); + markerPaint.setTextAlign(Align.RIGHT); - /** - * Sets the series enabled value. - * - * @param enabled true to enable - */ - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } + // Set stroke paint thickness + strokePaint.setStrokeWidth(STROKE_WIDTH); - /** - * Returns true if the series is enabled. - */ - public boolean isEnabled() { - return enabled; - } - - /** - * Returns true if the series has data. - */ - public boolean hasData() { - return extremityMonitor.hasData(); - } - - /** - * Updates the series with a new value. - * - * @param value the new value - */ - public void update(double value) { - extremityMonitor.update(value); - } - - /** - * Gets the path. - */ - public Path getPath() { - return path; - } - - /** - * Draws the path on canvas. - * - * @param canvas the canvas - */ - public void drawPath(Canvas canvas) { - canvas.drawPath(path, fillPaint); - canvas.drawPath(path, strokePaint); - } - - /** - * Updates the y axis dimension. - */ - public void updateDimension() { - double min = hasData() ? extremityMonitor.getMin() : 0.0; - double max = hasData() ? extremityMonitor.getMax() : 1.0; - min = Math.max(min, absoluteMin); - max = Math.min(max, absoluteMax); - interval = getInterval(min, max); - minMarkerValue = getMinMarkerValue(min, interval); - maxMarkerValue = minMarkerValue + interval * ChartView.Y_AXIS_INTERVALS; - } - - /** - * Gets the interval value. - * - * @param min the min value - * @param max the max value - */ - private int getInterval(double min, double max) { - for (int intervalValue : intervalValues) { - int minValue = getMinMarkerValue(min, intervalValue); - double targetInterval = (max - minValue) / ChartView.Y_AXIS_INTERVALS; - if (intervalValue >= targetInterval) { - return intervalValue; - } + extremityMonitor = new ExtremityMonitor(); + numberFormat = NumberFormat.getIntegerInstance(); + path = new Path(); } - // Return the largest interval - return intervalValues[intervalValues.length - 1]; - } - /** - * Gets the min marker value. - * - * @param min the min series value - * @param intervalValue the interval value - */ - private int getMinMarkerValue(double min, int intervalValue) { - // Round down to the nearest intervalValue - int value = ((int) (min / intervalValue)) * intervalValue; - // value > min if min is negative - if (value > min) { - return value - intervalValue; + /** + * Returns true if the series is enabled. + */ + public boolean isEnabled() { + return enabled; } - return value; - } - /** - * Gets the interval value. - */ - public int getInterval() { - return interval; - } + /** + * Sets the series enabled value. + * + * @param enabled true to enable + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } - /** - * Gets the min marker value. - */ - public int getMinMarkerValue() { - return minMarkerValue; - } + /** + * Returns true if the series has data. + */ + public boolean hasData() { + return extremityMonitor.hasData(); + } - /** - * Gets the max marker value. - */ - @VisibleForTesting - int getMaxMarkerValue() { - return maxMarkerValue; - } + /** + * Updates the series with a new value. + * + * @param value the new value + */ + public void update(double value) { + extremityMonitor.update(value); + } - /** - * Gets the title id. - */ - public int getTitleId(boolean metricUnits) { - return metricUnits ? metricTitleId : imperialTitleId; - } + /** + * Gets the path. + */ + public Path getPath() { + return path; + } - /** - * Gets the title paint. - */ - public Paint getTitlePaint() { - return titlePaint; - } + /** + * Draws the path on canvas. + * + * @param canvas the canvas + */ + public void drawPath(Canvas canvas) { + canvas.drawPath(path, fillPaint); + canvas.drawPath(path, strokePaint); + } - /** - * Gets the marker paint. - */ - public Paint getMarkerPaint() { - return markerPaint; - } + /** + * Updates the y axis dimension. + */ + public void updateDimension() { + double min = hasData() ? extremityMonitor.getMin() : 0.0; + double max = hasData() ? extremityMonitor.getMax() : 1.0; + min = Math.max(min, absoluteMin); + max = Math.min(max, absoluteMax); + interval = getInterval(min, max); + minMarkerValue = getMinMarkerValue(min, interval); + maxMarkerValue = minMarkerValue + interval * ChartView.Y_AXIS_INTERVALS; + } - /** - * Gets the largest marker. - */ - public String getLargestMarker() { - String minMarker = numberFormat.format(getMinMarkerValue()); - String maxMarker = numberFormat.format(getMaxMarkerValue()); - return minMarker.length() >= maxMarker.length() ? minMarker : maxMarker; - } + /** + * Gets the interval value. + * + * @param min the min value + * @param max the max value + */ + private int getInterval(double min, double max) { + for (int intervalValue : intervalValues) { + int minValue = getMinMarkerValue(min, intervalValue); + double targetInterval = (max - minValue) / ChartView.Y_AXIS_INTERVALS; + if (intervalValue >= targetInterval) { + return intervalValue; + } + } + // Return the largest interval + return intervalValues[intervalValues.length - 1]; + } - /** - * Formats a marker value. - * - * @param value the value - */ - public String formatMarker(int value) { - return numberFormat.format(value); - } + /** + * Gets the min marker value. + * + * @param min the min series value + * @param intervalValue the interval value + */ + private int getMinMarkerValue(double min, int intervalValue) { + // Round down to the nearest intervalValue + int value = ((int) (min / intervalValue)) * intervalValue; + // value > min if min is negative + if (value > min) { + return value - intervalValue; + } + return value; + } + + /** + * Gets the interval value. + */ + public int getInterval() { + return interval; + } + + /** + * Gets the min marker value. + */ + public int getMinMarkerValue() { + return minMarkerValue; + } + + /** + * Gets the max marker value. + */ + @VisibleForTesting + int getMaxMarkerValue() { + return maxMarkerValue; + } + + /** + * Gets the title id. + */ + public int getTitleId(boolean metricUnits) { + return metricUnits ? metricTitleId : imperialTitleId; + } + + /** + * Gets the title paint. + */ + public Paint getTitlePaint() { + return titlePaint; + } + + /** + * Gets the marker paint. + */ + public Paint getMarkerPaint() { + return markerPaint; + } + + /** + * Gets the largest marker. + */ + public String getLargestMarker() { + String minMarker = numberFormat.format(getMinMarkerValue()); + String maxMarker = numberFormat.format(getMaxMarkerValue()); + return minMarker.length() >= maxMarker.length() ? minMarker : maxMarker; + } + + /** + * Formats a marker value. + * + * @param value the value + */ + public String formatMarker(int value) { + return numberFormat.format(value); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/ChartView.java b/src/main/java/com/google/android/apps/mytracks/ChartView.java index 29ccfe755..8683d410c 100644 --- a/src/main/java/com/google/android/apps/mytracks/ChartView.java +++ b/src/main/java/com/google/android/apps/mytracks/ChartView.java @@ -45,935 +45,932 @@ import java.util.ArrayList; /** * Visualization of the chart. - * + * * @author Sandor Dornbush * @author Leif Hendrik Wilden */ public class ChartView extends View { - public static final float MEDIUM_TEXT_SIZE = 18f; - public static final float SMALL_TEXT_SIZE = 12f; + public static final float MEDIUM_TEXT_SIZE = 18f; + public static final float SMALL_TEXT_SIZE = 12f; - public static final int Y_AXIS_INTERVALS = 5; + public static final int Y_AXIS_INTERVALS = 5; - public static final int NUM_SERIES = 6; - public static final int ELEVATION_SERIES = 0; - public static final int SPEED_SERIES = 1; - public static final int PACE_SERIES = 2; - public static final int HEART_RATE_SERIES = 3; - public static final int CADENCE_SERIES = 4; - public static final int POWER_SERIES = 5; + public static final int NUM_SERIES = 6; + public static final int ELEVATION_SERIES = 0; + public static final int SPEED_SERIES = 1; + public static final int PACE_SERIES = 2; + public static final int HEART_RATE_SERIES = 3; + public static final int CADENCE_SERIES = 4; + public static final int POWER_SERIES = 5; - private static final int TARGET_X_AXIS_INTERVALS = 4; + private static final int TARGET_X_AXIS_INTERVALS = 4; - private static final int MIN_ZOOM_LEVEL = 1; - private static final int MAX_ZOOM_LEVEL = 10; + private static final int MIN_ZOOM_LEVEL = 1; + private static final int MAX_ZOOM_LEVEL = 10; - private static final NumberFormat X_NUMBER_FORMAT = NumberFormat.getIntegerInstance(); - private static final NumberFormat X_FRACTION_FORMAT = NumberFormat.getNumberInstance(); - static { - X_FRACTION_FORMAT.setMaximumFractionDigits(1); - X_FRACTION_FORMAT.setMinimumFractionDigits(1); - } + private static final NumberFormat X_NUMBER_FORMAT = NumberFormat.getIntegerInstance(); + private static final NumberFormat X_FRACTION_FORMAT = NumberFormat.getNumberInstance(); + private static final int BORDER = 8; + private static final int SPACER = 4; + private static final int Y_AXIS_OFFSET = 16; - private static final int BORDER = 8; - private static final int SPACER = 4; - private static final int Y_AXIS_OFFSET = 16; - - private final ChartValueSeries[] series = new ChartValueSeries[NUM_SERIES]; - private final ArrayList chartData = new ArrayList<>(); - private final ArrayList waypoints = new ArrayList<>(); - private final ExtremityMonitor xExtremityMonitor = new ExtremityMonitor(); - private double maxX = 1.0; - - private final Paint axisPaint; - private final Paint xAxisMarkerPaint; - private final Paint gridPaint; - - private final Drawable pointer; - private final int markerWidth; - private final int markerHeight; - - private final Scroller scroller; - private VelocityTracker velocityTracker = null; - private float lastMotionEventX = -1; - private int zoomLevel = 1; - - private int leftBorder = BORDER; - private int topBorder = BORDER; - private int bottomBorder = BORDER; - private int rightBorder = BORDER; - private int spacer = SPACER; - private int yAxisOffset = Y_AXIS_OFFSET; - - private int width = 0; - private int height = 0; - private int effectiveWidth = 0; - private int effectiveHeight = 0; - - private boolean chartByDistance = true; - private boolean metricUnits = true; - private boolean reportSpeed = true; - private boolean showPointer = false; - - /** - * Constructor. - * - * @param context the context - */ - public ChartView(Context context) { - super(context); - - series[ELEVATION_SERIES] = new ChartValueSeries(context, - Integer.MIN_VALUE, - Integer.MAX_VALUE, - new int[] { 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000 }, - R.string.description_elevation_metric, - R.string.description_elevation_imperial, - R.color.chart_elevation_fill, - R.color.chart_elevation_border); - series[SPEED_SERIES] = new ChartValueSeries(context, - 0, - Integer.MAX_VALUE, - new int[] {1, 5, 10, 20, 50, 100 }, - R.string.description_speed_metric, - R.string.description_speed_imperial, - R.color.chart_speed_fill, - R.color.chart_speed_border); - series[PACE_SERIES] = new ChartValueSeries(context, - 0, - Integer.MAX_VALUE, - new int[] {1, 2, 5, 10, 15, 20, 30, 60, 120 }, - R.string.description_pace_metric, - R.string.description_pace_imperial, - R.color.chart_pace_fill, - R.color.chart_pace_border); - series[HEART_RATE_SERIES] = new ChartValueSeries(context, - 0, - Integer.MAX_VALUE, - new int[] {25, 50 }, - R.string.description_sensor_heart_rate, - R.string.description_sensor_heart_rate, - R.color.chart_heart_rate_fill, - R.color.chart_heart_rate_border); - series[CADENCE_SERIES] = new ChartValueSeries(context, - 0, - Integer.MAX_VALUE, - new int[] {5, 10, 25, 50 }, - R.string.description_sensor_cadence, - R.string.description_sensor_cadence, - R.color.chart_cadence_fill, - R.color.chart_cadence_border); - series[POWER_SERIES] = new ChartValueSeries(context, - 0, - 1000, - new int[] { 5, 50, 100, 200 }, - R.string.description_sensor_power, - R.string.description_sensor_power, - R.color.chart_power_fill, - R.color.chart_power_border); - - float scale = context.getResources().getDisplayMetrics().density; - - axisPaint = new Paint(); - axisPaint.setStyle(Style.STROKE); - axisPaint.setColor(context.getResources().getColor(android.R.color.black)); - axisPaint.setAntiAlias(true); - axisPaint.setTextSize(SMALL_TEXT_SIZE * scale); - - xAxisMarkerPaint = new Paint(axisPaint); - xAxisMarkerPaint.setTextAlign(Align.CENTER); - - gridPaint = new Paint(); - gridPaint.setStyle(Style.STROKE); - gridPaint.setColor(context.getResources().getColor(android.R.color.darker_gray)); - gridPaint.setAntiAlias(false); - gridPaint.setPathEffect(new DashPathEffect(new float[] { 3, 2 }, 0)); - - Paint markerPaint = new Paint(); - markerPaint.setStyle(Style.STROKE); - markerPaint.setColor(context.getResources().getColor(android.R.color.darker_gray)); - markerPaint.setAntiAlias(false); - - pointer = context.getResources().getDrawable(R.drawable.ic_arrow_180); - pointer.setBounds(0, 0, pointer.getIntrinsicWidth(), pointer.getIntrinsicHeight()); - - Drawable statisticsMarker = getResources().getDrawable(R.drawable.ic_marker_yellow_pushpin); - markerWidth = statisticsMarker.getIntrinsicWidth(); - markerHeight = statisticsMarker.getIntrinsicHeight(); - statisticsMarker.setBounds(0, 0, markerWidth, markerHeight); - - Drawable waypointMarker = getResources().getDrawable(R.drawable.ic_marker_blue_pushpin); - waypointMarker.setBounds(0, 0, markerWidth, markerHeight); - - scroller = new Scroller(context); - setFocusable(true); - setClickable(true); - updateDimensions(); - } - - @Override - public boolean canScrollHorizontally(int direction) { - return true; - } - - /** - * Sets the enabled value for a chart value series. - * - * @param index the chart value series index - */ - public void setChartValueSeriesEnabled(int index, boolean enabled) { - series[index].setEnabled(enabled); - } - - /** - * Sets chart by distance. It is expected that after changing this value, data - * will be reloaded. - * - * @param value true for by distance, false for by time - */ - public void setChartByDistance(boolean value) { - chartByDistance = value; - } - - /** - * Sets metric units. - * - * @param value true to use metric units - */ - public void setMetricUnits(boolean value) { - metricUnits = value; - } - - /** - * Sets report speed. - * - * @param value true to report speed - */ - public void setReportSpeed(boolean value) { - reportSpeed = value; - } - - /** - * Sets show pointer. - * - * @param value true to show pointer - */ - public void setShowPointer(boolean value) { - showPointer = value; - } - - /** - * Adds data points. - * - * @param dataPoints an array of data points to be added - */ - public void addDataPoints(ArrayList dataPoints) { - synchronized (chartData) { - chartData.addAll(dataPoints); - for (int i = 0; i < dataPoints.size(); i++) { - double[] dataPoint = dataPoints.get(i); - xExtremityMonitor.update(dataPoint[0]); - for (int j = 0; j < series.length; j++) { - if (!Double.isNaN(dataPoint[j + 1])) { - series[j].update(dataPoint[j + 1]); - } - } - } - updateDimensions(); - updatePaths(); - } - } - - /** - * Clears all data. - */ - public void reset() { - synchronized (chartData) { - chartData.clear(); - xExtremityMonitor.reset(); - zoomLevel = 1; - updateDimensions(); - } - } - - /** - * Resets scroll. To be called on the UI thread. - */ - public void resetScroll() { - scrollTo(0, 0); - } - - /** - * Adds a waypoint. - * - * @param waypoint the waypoint - */ - public void addWaypoint(Waypoint waypoint) { - synchronized (waypoints) { - waypoints.add(waypoint); - } - } - - /** - * Clears the waypoints. - */ - public void clearWaypoints() { - synchronized (waypoints) { - waypoints.clear(); - } - } - - /** - * Returns true if can zoom in. - */ - public boolean canZoomIn() { - return zoomLevel < MAX_ZOOM_LEVEL; - } - - /** - * Returns true if can zoom out. - */ - public boolean canZoomOut() { - return zoomLevel > MIN_ZOOM_LEVEL; - } - - /** - * Zooms in one level. - */ - public void zoomIn() { - if (canZoomIn()) { - zoomLevel++; - updatePaths(); - invalidate(); - } - } - - /** - * Zooms out one level. - */ - public void zoomOut() { - if (canZoomOut()) { - zoomLevel--; - scroller.abortAnimation(); - int scrollX = getScrollX(); - int maxWidth = effectiveWidth * (zoomLevel - 1); - if (scrollX > maxWidth) { - scrollX = maxWidth; - scrollTo(scrollX, 0); - } - updatePaths(); - invalidate(); - } - } - - /** - * Initiates flinging. - * - * @param velocityX velocity of fling in pixels per second - */ - public void fling(int velocityX) { - int maxWidth = effectiveWidth * (zoomLevel - 1); - scroller.fling(getScrollX(), 0, velocityX, 0, 0, maxWidth, 0, 0); - invalidate(); - } - - /** - * Scrolls the view horizontally by a given amount. - * - * @param deltaX the number of pixels to scroll - */ - public void scrollBy(int deltaX) { - int scrollX = getScrollX() + deltaX; - if (scrollX < 0) { - scrollX = 0; - } - int maxWidth = effectiveWidth * (zoomLevel - 1); - if (scrollX > maxWidth) { - scrollX = maxWidth; - } - scrollTo(scrollX, 0); - } - - /** - * Called by the parent to indicate that the mScrollX/Y values need to be - * updated. Triggers a redraw during flinging. - */ - @Override - public void computeScroll() { - if (scroller.computeScrollOffset()) { - int oldX = getScrollX(); - int x = scroller.getCurrX(); - scrollTo(x, 0); - if (oldX != x) { - onScrollChanged(x, 0, oldX, 0); - postInvalidate(); - } - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (velocityTracker == null) { - velocityTracker = VelocityTracker.obtain(); - } - velocityTracker.addMovement(event); - float x = event.getX(); - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - // Stop the fling - if (!scroller.isFinished()) { - scroller.abortAnimation(); - } - lastMotionEventX = x; - break; - case MotionEvent.ACTION_MOVE: - if (lastMotionEventX == -1) { - break; - } - // Scroll to follow the motion event - int deltaX = (int) (lastMotionEventX - x); - lastMotionEventX = x; - if (deltaX < 0) { - if (getScrollX() > 0) { - scrollBy(deltaX); - } - } else if (deltaX > 0) { - int availableToScroll = effectiveWidth * (zoomLevel - 1) - getScrollX(); - if (availableToScroll > 0) { - scrollBy(Math.min(availableToScroll, deltaX)); - } - } - break; - case MotionEvent.ACTION_UP: - // Check if the y event is within markerHeight of the marker center - if (Math.abs(event.getY() - topBorder - spacer - markerHeight / 2) < markerHeight) { - int minDistance = Integer.MAX_VALUE; - Waypoint nearestWaypoint = null; - synchronized (waypoints) { - for (int i = 0; i < waypoints.size(); i++) { - Waypoint waypoint = waypoints.get(i); - int distance = Math.abs( - getX(getWaypointXValue(waypoint)) - (int) event.getX() - getScrollX()); - if (distance < minDistance) { - minDistance = distance; - nearestWaypoint = waypoint; - } - } - } - if (nearestWaypoint != null && minDistance < markerWidth) { - Intent intent = IntentUtils.newIntent(getContext(), MarkerDetailActivity.class) - .putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, nearestWaypoint.getId()); - getContext().startActivity(intent); - return true; - } - } - - VelocityTracker myVelocityTracker = velocityTracker; - myVelocityTracker.computeCurrentVelocity(1000); - int initialVelocity = (int) myVelocityTracker.getXVelocity(); - if (Math.abs(initialVelocity) > ViewConfiguration.getMinimumFlingVelocity()) { - fling(-initialVelocity); - } - if (velocityTracker != null) { - velocityTracker.recycle(); - velocityTracker = null; - } - break; - } - return true; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - updateEffectiveDimensionsIfChanged( - View.MeasureSpec.getSize(widthMeasureSpec), View.MeasureSpec.getSize(heightMeasureSpec)); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - @Override - protected void onDraw(Canvas canvas) { - synchronized (chartData) { - - canvas.save(); - - canvas.drawColor(Color.WHITE); - - canvas.save(); - - clipToGraphArea(canvas); - drawDataSeries(canvas); - drawGrid(canvas); - - canvas.restore(); - - drawSeriesTitles(canvas); - drawXAxis(canvas); - drawYAxis(canvas); - - canvas.restore(); - - if (showPointer) { - drawPointer(canvas); - } - } - } - - /** - * Clips a canvas to the graph area. - * - * @param canvas the canvas - */ - private void clipToGraphArea(Canvas canvas) { - int x = getScrollX() + leftBorder; - int y = topBorder; - canvas.clipRect(x, y, x + effectiveWidth, y + effectiveHeight); - } - - /** - * Draws the data series. - * - * @param canvas the canvas - */ - private void drawDataSeries(Canvas canvas) { - for (ChartValueSeries chartValueSeries : series) { - if (chartValueSeries.isEnabled() && chartValueSeries.hasData()) { - chartValueSeries.drawPath(canvas); - } - } - } - - /** - * Draws the grid. - * - * @param canvas the canvas - */ - private void drawGrid(Canvas canvas) { - // X axis grid - ArrayList xAxisMarkerPositions = getXAxisMarkerPositions(getXAxisInterval()); - for (int i = 0; i < xAxisMarkerPositions.size(); i++) { - int x = getX(xAxisMarkerPositions.get(i)); - canvas.drawLine(x, topBorder, x, topBorder + effectiveHeight, gridPaint); - } - // Y axis grid - float rightEdge = getX(maxX); - for (int i = 0; i <= Y_AXIS_INTERVALS; i++) { - double percentage = (double) i / Y_AXIS_INTERVALS; - int range = effectiveHeight - 2 * yAxisOffset; - int y = topBorder + yAxisOffset + (int) (percentage * range); - canvas.drawLine(leftBorder, y, rightEdge, y, gridPaint); - } - } - - /** - * Draws series titles. - * - * @param canvas the canvas - */ - private void drawSeriesTitles(Canvas canvas) { - int[] titleDimensions = getTitleDimenions(); - int lines = titleDimensions[0]; - int lineHeight = titleDimensions[1]; - int count = 0; - for (int i = 0; i < series.length; i++) { - ChartValueSeries chartValueSeries = series[i]; - if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(i)) { - count++; - String title = getContext().getString(chartValueSeries.getTitleId(metricUnits)); - Paint paint = chartValueSeries.getTitlePaint(); - int x = (int) (0.5 * width) + getScrollX(); - int y = topBorder - spacer - (lines - count) * (lineHeight + spacer); - canvas.drawText(title, x, y, paint); - } - } - } - - /** - * Gets the title dimensions. Returns an array of 2 integers, first element is - * the number of lines and the second element is the line height. - */ - private int[] getTitleDimenions() { - int lines = 0; - int lineHeight = 0; - for (int i = 0; i < series.length; i++) { - ChartValueSeries chartValueSeries = series[i]; - if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(i)) { - lines++; - String title = getContext().getString(chartValueSeries.getTitleId(metricUnits)); - Rect rect = getRect(chartValueSeries.getTitlePaint(), title); - if (rect.height() > lineHeight) { - lineHeight = rect.height(); - } - } - } - return new int[] { lines, lineHeight }; - } - - /** - * Draws the x axis. - * - * @param canvas the canvas - */ - private void drawXAxis(Canvas canvas) { - int x = getScrollX() + leftBorder; - int y = topBorder + effectiveHeight; - canvas.drawLine(x, y, x + effectiveWidth, y, axisPaint); - String label = getXAxisLabel(); - Rect rect = getRect(axisPaint, label); - int yOffset = rect.height() / 2; - canvas.drawText(label, x + effectiveWidth + spacer, y + yOffset, axisPaint); - - double interval = getXAxisInterval(); - ArrayList markerPositions = getXAxisMarkerPositions(interval); - NumberFormat numberFormat = interval < 1 ? X_FRACTION_FORMAT : X_NUMBER_FORMAT; - for (int i = 0; i < markerPositions.size(); i++) { - drawXAxisMarker(canvas, markerPositions.get(i), numberFormat, spacer + yOffset); - } - } - - /** - * Gets the x axis label. - */ - private String getXAxisLabel() { - Context context = getContext(); - if (chartByDistance) { - return metricUnits ? context.getString(R.string.unit_kilometer) - : context.getString(R.string.unit_mile); - } else { - return context.getString(R.string.description_time); - } - } - - /** - * Draws a x axis marker. - * - * @param canvas canvas - * @param value value - * @param numberFormat the number format - * @param spacing the spacing between x axis and marker - */ - private void drawXAxisMarker(Canvas canvas, double value, NumberFormat numberFormat, int spacing) { - String marker = chartByDistance ? numberFormat.format(value) : StringUtils.formatElapsedTime((long) value); - Rect rect = getRect(xAxisMarkerPaint, marker); - canvas.drawText(marker, getX(value), topBorder + effectiveHeight + spacing + rect.height(), xAxisMarkerPaint); - } - - /** - * Gets the x axis interval. - */ - private double getXAxisInterval() { - double interval = maxX / zoomLevel / TARGET_X_AXIS_INTERVALS; - if (interval < 1) { - interval = .5; - } else if (interval < 5) { - interval = 2; - } else if (interval < 10) { - interval = 5; - } else { - interval = (interval / 10) * 10; - } - return interval; - } - - /** - * Gets the x axis marker positions. - */ - private ArrayList getXAxisMarkerPositions(double interval) { - ArrayList markers = new ArrayList<>(); - markers.add(0d); - for (int i = 1; i * interval < maxX; i++) { - markers.add(i * interval); - } - // At least 2 markers - if (markers.size() < 2) { - markers.add(maxX); - } - return markers; - } - - /** - * Draws the y axis. - * - * @param canvas the canvas - */ - private void drawYAxis(Canvas canvas) { - int x = getScrollX() + leftBorder; - int y = topBorder; - canvas.drawLine(x, y, x, y + effectiveHeight, axisPaint); - - int markerXPosition = x - spacer; - for (int i = 0; i < series.length; i++) { - int index = series.length - 1 - i; - ChartValueSeries chartValueSeries = series[index]; - if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(index)) { - markerXPosition -= drawYAxisMarkers(chartValueSeries, canvas, markerXPosition) + spacer; - } - } - } - - /** - * Draws the y axis markers for a chart value series. - * - * @param chartValueSeries the chart value series - * @param canvas the canvas - * @param xPosition the right most x position - * @return the maximum marker width. - */ - private float drawYAxisMarkers(ChartValueSeries chartValueSeries, Canvas canvas, int xPosition) { - int interval = chartValueSeries.getInterval(); - float maxMarkerWidth = 0; - for (int i = 0; i <= Y_AXIS_INTERVALS; i++) { - maxMarkerWidth = Math.max(maxMarkerWidth, drawYAxisMarker(chartValueSeries, canvas, xPosition, - i * interval + chartValueSeries.getMinMarkerValue())); - } - return maxMarkerWidth; - } - - /** - * Draws a y axis marker. - * - * @param chartValueSeries the chart value series - * @param canvas the canvas - * @param xPosition the right most x position - * @param yValue the y value - * @return the marker width. - */ - private float drawYAxisMarker( - ChartValueSeries chartValueSeries, Canvas canvas, int xPosition, int yValue) { - String marker = chartValueSeries.formatMarker(yValue); - Paint paint = chartValueSeries.getMarkerPaint(); - Rect rect = getRect(paint, marker); - int yPosition = getY(chartValueSeries, yValue) + (rect.height() / 2); - canvas.drawText(marker, xPosition, yPosition, paint); - return paint.measureText(marker); - } - - /** - * Draws the current pointer. - * - * @param canvas the canvas - */ - private void drawPointer(Canvas canvas) { - int index = -1; - for (int i = 0; i < series.length; i++) { - ChartValueSeries chartValueSeries = series[i]; - if (chartValueSeries.isEnabled() && chartValueSeries.hasData()) { - index = i; - break; - } - } - if (index != -1 && chartData.size() > 0) { - int dx = getX(maxX) - pointer.getIntrinsicWidth() / 2; - int dy = getY(series[index], chartData.get(chartData.size() - 1)[index + 1]) - - pointer.getIntrinsicHeight(); - canvas.translate(dx, dy); - pointer.draw(canvas); - } - } - - /** - * Updates paths. The path needs to be updated any time after the data or the - * dimensions change. - */ - private void updatePaths() { - synchronized (chartData) { - for (ChartValueSeries chartValueSeries : series) { - chartValueSeries.getPath().reset(); - } - drawPaths(); - closePaths(); - } - } - - /** - * Draws all paths. - */ - private void drawPaths() { - boolean[] hasMoved = new boolean[series.length]; - - for (int i = 0; i < chartData.size(); i++) { - double[] dataPoint = chartData.get(i); - for (int j = 0; j < series.length; j++) { - double value = dataPoint[j + 1]; - if (Double.isNaN(value)) { - continue; - } - ChartValueSeries chartValueSeries = series[j]; - Path path = chartValueSeries.getPath(); - int x = getX(dataPoint[0]); - int y = getY(chartValueSeries, value); - if (!hasMoved[j]) { - hasMoved[j] = true; - path.moveTo(x, y); - } else { - path.lineTo(x, y); - } - } - } - } - - /** - * Closes all paths. - */ - private void closePaths() { - for (int i = 0; i < series.length; i++) { - int first = getFirstPopulatedChartDataIndex(i); - - if (first != -1) { - int xCorner = getX(chartData.get(first)[0]); - int yCorner = topBorder + effectiveHeight; - ChartValueSeries chartValueSeries = series[i]; - Path path = chartValueSeries.getPath(); - // Bottom right corner - path.lineTo(getX(chartData.get(chartData.size() - 1)[0]), yCorner); - // Bottom left corner - path.lineTo(xCorner, yCorner); - // Top right corner - path.lineTo(xCorner, getY(chartValueSeries, chartData.get(first)[i + 1])); - } - } - } - - /** - * Finds the index of the first data point containing data for a series. - * Returns -1 if no data point contains data for the series. - * - * @param seriesIndex the series's index - */ - private int getFirstPopulatedChartDataIndex(int seriesIndex) { - for (int i = 0; i < chartData.size(); i++) { - if (!Double.isNaN(chartData.get(i)[seriesIndex + 1])) { - return i; - } - } - return -1; - } - - /** - * Updates the chart dimensions. - */ - private void updateDimensions() { - maxX = xExtremityMonitor.hasData() ? xExtremityMonitor.getMax() : 1.0; - for (ChartValueSeries chartValueSeries : series) { - chartValueSeries.updateDimension(); - } - float density = getContext().getResources().getDisplayMetrics().density; - spacer = (int) (density * SPACER); - yAxisOffset = (int) (density * Y_AXIS_OFFSET); - - int markerLength = 0; - for (int i = 0; i < series.length; i ++) { - ChartValueSeries chartValueSeries = series[i]; - if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(i)) { - Rect rect = getRect(chartValueSeries.getMarkerPaint(), chartValueSeries.getLargestMarker()); - markerLength += rect.width() + spacer; - } + static { + X_FRACTION_FORMAT.setMaximumFractionDigits(1); + X_FRACTION_FORMAT.setMinimumFractionDigits(1); } - leftBorder = (int) (density * BORDER + markerLength); - int[] titleDimensions = getTitleDimenions(); - topBorder = (int) (density * BORDER + titleDimensions[0] * (titleDimensions[1] + spacer)); - Rect xAxisLabelRect = getRect(axisPaint, getXAxisLabel()); - // border + x axis marker + spacer + .5 x axis label - bottomBorder = (int) (density * BORDER + getRect(xAxisMarkerPaint, "1").height() + spacer - + (xAxisLabelRect.height() / 2)); - rightBorder = (int) (density * BORDER + xAxisLabelRect.width() + spacer); - updateEffectiveDimensions(); - } + private final ChartValueSeries[] series = new ChartValueSeries[NUM_SERIES]; + private final ArrayList chartData = new ArrayList<>(); + private final ArrayList waypoints = new ArrayList<>(); + private final ExtremityMonitor xExtremityMonitor = new ExtremityMonitor(); + private final Paint axisPaint; + private final Paint xAxisMarkerPaint; + private final Paint gridPaint; + private final Drawable pointer; + private final int markerWidth; + private final int markerHeight; + private final Scroller scroller; + private double maxX = 1.0; + private VelocityTracker velocityTracker = null; + private float lastMotionEventX = -1; + private int zoomLevel = 1; - /** - * Updates the effective dimensions. - */ - private void updateEffectiveDimensions() { - effectiveWidth = Math.max(0, width - leftBorder - rightBorder); - effectiveHeight = Math.max(0, height - topBorder - bottomBorder); - } + private int leftBorder = BORDER; + private int topBorder = BORDER; + private int bottomBorder = BORDER; + private int rightBorder = BORDER; + private int spacer = SPACER; + private int yAxisOffset = Y_AXIS_OFFSET; - /** - * Updates the effective dimensions if changed. - * - * @param newWidth the new width - * @param newHeight the new height - */ - private void updateEffectiveDimensionsIfChanged(int newWidth, int newHeight) { - if (width != newWidth || height != newHeight) { - width = newWidth; - height = newHeight; - updateEffectiveDimensions(); - updatePaths(); + private int width = 0; + private int height = 0; + private int effectiveWidth = 0; + private int effectiveHeight = 0; + + private boolean chartByDistance = true; + private boolean metricUnits = true; + private boolean reportSpeed = true; + private boolean showPointer = false; + + /** + * Constructor. + * + * @param context the context + */ + public ChartView(Context context) { + super(context); + + series[ELEVATION_SERIES] = new ChartValueSeries(context, + Integer.MIN_VALUE, + Integer.MAX_VALUE, + new int[]{5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000}, + R.string.description_elevation_metric, + R.string.description_elevation_imperial, + R.color.chart_elevation_fill, + R.color.chart_elevation_border); + series[SPEED_SERIES] = new ChartValueSeries(context, + 0, + Integer.MAX_VALUE, + new int[]{1, 5, 10, 20, 50, 100}, + R.string.description_speed_metric, + R.string.description_speed_imperial, + R.color.chart_speed_fill, + R.color.chart_speed_border); + series[PACE_SERIES] = new ChartValueSeries(context, + 0, + Integer.MAX_VALUE, + new int[]{1, 2, 5, 10, 15, 20, 30, 60, 120}, + R.string.description_pace_metric, + R.string.description_pace_imperial, + R.color.chart_pace_fill, + R.color.chart_pace_border); + series[HEART_RATE_SERIES] = new ChartValueSeries(context, + 0, + Integer.MAX_VALUE, + new int[]{25, 50}, + R.string.description_sensor_heart_rate, + R.string.description_sensor_heart_rate, + R.color.chart_heart_rate_fill, + R.color.chart_heart_rate_border); + series[CADENCE_SERIES] = new ChartValueSeries(context, + 0, + Integer.MAX_VALUE, + new int[]{5, 10, 25, 50}, + R.string.description_sensor_cadence, + R.string.description_sensor_cadence, + R.color.chart_cadence_fill, + R.color.chart_cadence_border); + series[POWER_SERIES] = new ChartValueSeries(context, + 0, + 1000, + new int[]{5, 50, 100, 200}, + R.string.description_sensor_power, + R.string.description_sensor_power, + R.color.chart_power_fill, + R.color.chart_power_border); + + float scale = context.getResources().getDisplayMetrics().density; + + axisPaint = new Paint(); + axisPaint.setStyle(Style.STROKE); + axisPaint.setColor(context.getResources().getColor(android.R.color.black)); + axisPaint.setAntiAlias(true); + axisPaint.setTextSize(SMALL_TEXT_SIZE * scale); + + xAxisMarkerPaint = new Paint(axisPaint); + xAxisMarkerPaint.setTextAlign(Align.CENTER); + + gridPaint = new Paint(); + gridPaint.setStyle(Style.STROKE); + gridPaint.setColor(context.getResources().getColor(android.R.color.darker_gray)); + gridPaint.setAntiAlias(false); + gridPaint.setPathEffect(new DashPathEffect(new float[]{3, 2}, 0)); + + Paint markerPaint = new Paint(); + markerPaint.setStyle(Style.STROKE); + markerPaint.setColor(context.getResources().getColor(android.R.color.darker_gray)); + markerPaint.setAntiAlias(false); + + pointer = context.getResources().getDrawable(R.drawable.ic_arrow_180); + pointer.setBounds(0, 0, pointer.getIntrinsicWidth(), pointer.getIntrinsicHeight()); + + Drawable statisticsMarker = getResources().getDrawable(R.drawable.ic_marker_yellow_pushpin); + markerWidth = statisticsMarker.getIntrinsicWidth(); + markerHeight = statisticsMarker.getIntrinsicHeight(); + statisticsMarker.setBounds(0, 0, markerWidth, markerHeight); + + Drawable waypointMarker = getResources().getDrawable(R.drawable.ic_marker_blue_pushpin); + waypointMarker.setBounds(0, 0, markerWidth, markerHeight); + + scroller = new Scroller(context); + setFocusable(true); + setClickable(true); + updateDimensions(); } - } - /** - * Gets the x position for a value. - * - * @param value the value - */ - private int getX(double value) { - if (value > maxX) { - value = maxX; - } - double percentage = value / maxX; - return leftBorder + (int) (percentage * effectiveWidth * zoomLevel); - } - - /** - * Gets the y position for a value in a chart value series - * - * @param chartValueSeries the chart value series - * @param value the value - */ - private int getY(ChartValueSeries chartValueSeries, double value) { - int effectiveSpread = chartValueSeries.getInterval() * Y_AXIS_INTERVALS; - double percentage = (value - chartValueSeries.getMinMarkerValue()) / effectiveSpread; - int rangeHeight = effectiveHeight - 2 * yAxisOffset; - return topBorder + yAxisOffset + (int) ((1 - percentage) * rangeHeight); - } - - /** - * Gets a waypoint's x value. - * - * @param waypoint the waypoint - */ - private double getWaypointXValue(Waypoint waypoint) { - if (chartByDistance) { - double lenghtInKm = waypoint.getLength() * UnitConversions.M_TO_KM; - return metricUnits ? lenghtInKm : lenghtInKm * UnitConversions.KM_TO_MI; - } else { - return waypoint.getDuration(); - } - } - - /** - * Gets a paint's Rect for a string. - * - * @param paint the paint - * @param string the string - */ - private Rect getRect(Paint paint, String string) { - Rect rect = new Rect(); - paint.getTextBounds(string, 0, string.length(), rect); - return rect; - } - - /** - * Returns true if the index is allowed when the chartData is empty. - * - * @param index the index - */ - private boolean allowIfEmpty(int index) { - if (!chartData.isEmpty()) { - return false; - } - switch (index) { - case ELEVATION_SERIES: + @Override + public boolean canScrollHorizontally(int direction) { return true; - case SPEED_SERIES: - return reportSpeed; - case PACE_SERIES: - return !reportSpeed; - default: - return false; } - } + + /** + * Sets the enabled value for a chart value series. + * + * @param index the chart value series index + */ + public void setChartValueSeriesEnabled(int index, boolean enabled) { + series[index].setEnabled(enabled); + } + + /** + * Sets chart by distance. It is expected that after changing this value, data + * will be reloaded. + * + * @param value true for by distance, false for by time + */ + public void setChartByDistance(boolean value) { + chartByDistance = value; + } + + /** + * Sets metric units. + * + * @param value true to use metric units + */ + public void setMetricUnits(boolean value) { + metricUnits = value; + } + + /** + * Sets report speed. + * + * @param value true to report speed + */ + public void setReportSpeed(boolean value) { + reportSpeed = value; + } + + /** + * Sets show pointer. + * + * @param value true to show pointer + */ + public void setShowPointer(boolean value) { + showPointer = value; + } + + /** + * Adds data points. + * + * @param dataPoints an array of data points to be added + */ + public void addDataPoints(ArrayList dataPoints) { + synchronized (chartData) { + chartData.addAll(dataPoints); + for (int i = 0; i < dataPoints.size(); i++) { + double[] dataPoint = dataPoints.get(i); + xExtremityMonitor.update(dataPoint[0]); + for (int j = 0; j < series.length; j++) { + if (!Double.isNaN(dataPoint[j + 1])) { + series[j].update(dataPoint[j + 1]); + } + } + } + updateDimensions(); + updatePaths(); + } + } + + /** + * Clears all data. + */ + public void reset() { + synchronized (chartData) { + chartData.clear(); + xExtremityMonitor.reset(); + zoomLevel = 1; + updateDimensions(); + } + } + + /** + * Resets scroll. To be called on the UI thread. + */ + public void resetScroll() { + scrollTo(0, 0); + } + + /** + * Adds a waypoint. + * + * @param waypoint the waypoint + */ + public void addWaypoint(Waypoint waypoint) { + synchronized (waypoints) { + waypoints.add(waypoint); + } + } + + /** + * Clears the waypoints. + */ + public void clearWaypoints() { + synchronized (waypoints) { + waypoints.clear(); + } + } + + /** + * Returns true if can zoom in. + */ + public boolean canZoomIn() { + return zoomLevel < MAX_ZOOM_LEVEL; + } + + /** + * Returns true if can zoom out. + */ + public boolean canZoomOut() { + return zoomLevel > MIN_ZOOM_LEVEL; + } + + /** + * Zooms in one level. + */ + public void zoomIn() { + if (canZoomIn()) { + zoomLevel++; + updatePaths(); + invalidate(); + } + } + + /** + * Zooms out one level. + */ + public void zoomOut() { + if (canZoomOut()) { + zoomLevel--; + scroller.abortAnimation(); + int scrollX = getScrollX(); + int maxWidth = effectiveWidth * (zoomLevel - 1); + if (scrollX > maxWidth) { + scrollX = maxWidth; + scrollTo(scrollX, 0); + } + updatePaths(); + invalidate(); + } + } + + /** + * Initiates flinging. + * + * @param velocityX velocity of fling in pixels per second + */ + public void fling(int velocityX) { + int maxWidth = effectiveWidth * (zoomLevel - 1); + scroller.fling(getScrollX(), 0, velocityX, 0, 0, maxWidth, 0, 0); + invalidate(); + } + + /** + * Scrolls the view horizontally by a given amount. + * + * @param deltaX the number of pixels to scroll + */ + public void scrollBy(int deltaX) { + int scrollX = getScrollX() + deltaX; + if (scrollX < 0) { + scrollX = 0; + } + int maxWidth = effectiveWidth * (zoomLevel - 1); + if (scrollX > maxWidth) { + scrollX = maxWidth; + } + scrollTo(scrollX, 0); + } + + /** + * Called by the parent to indicate that the mScrollX/Y values need to be + * updated. Triggers a redraw during flinging. + */ + @Override + public void computeScroll() { + if (scroller.computeScrollOffset()) { + int oldX = getScrollX(); + int x = scroller.getCurrX(); + scrollTo(x, 0); + if (oldX != x) { + onScrollChanged(x, 0, oldX, 0); + postInvalidate(); + } + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (velocityTracker == null) { + velocityTracker = VelocityTracker.obtain(); + } + velocityTracker.addMovement(event); + float x = event.getX(); + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + // Stop the fling + if (!scroller.isFinished()) { + scroller.abortAnimation(); + } + lastMotionEventX = x; + break; + case MotionEvent.ACTION_MOVE: + if (lastMotionEventX == -1) { + break; + } + // Scroll to follow the motion event + int deltaX = (int) (lastMotionEventX - x); + lastMotionEventX = x; + if (deltaX < 0) { + if (getScrollX() > 0) { + scrollBy(deltaX); + } + } else if (deltaX > 0) { + int availableToScroll = effectiveWidth * (zoomLevel - 1) - getScrollX(); + if (availableToScroll > 0) { + scrollBy(Math.min(availableToScroll, deltaX)); + } + } + break; + case MotionEvent.ACTION_UP: + // Check if the y event is within markerHeight of the marker center + if (Math.abs(event.getY() - topBorder - spacer - markerHeight / 2) < markerHeight) { + int minDistance = Integer.MAX_VALUE; + Waypoint nearestWaypoint = null; + synchronized (waypoints) { + for (int i = 0; i < waypoints.size(); i++) { + Waypoint waypoint = waypoints.get(i); + int distance = Math.abs( + getX(getWaypointXValue(waypoint)) - (int) event.getX() - getScrollX()); + if (distance < minDistance) { + minDistance = distance; + nearestWaypoint = waypoint; + } + } + } + if (nearestWaypoint != null && minDistance < markerWidth) { + Intent intent = IntentUtils.newIntent(getContext(), MarkerDetailActivity.class) + .putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, nearestWaypoint.getId()); + getContext().startActivity(intent); + return true; + } + } + + VelocityTracker myVelocityTracker = velocityTracker; + myVelocityTracker.computeCurrentVelocity(1000); + int initialVelocity = (int) myVelocityTracker.getXVelocity(); + if (Math.abs(initialVelocity) > ViewConfiguration.getMinimumFlingVelocity()) { + fling(-initialVelocity); + } + if (velocityTracker != null) { + velocityTracker.recycle(); + velocityTracker = null; + } + break; + } + return true; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + updateEffectiveDimensionsIfChanged( + View.MeasureSpec.getSize(widthMeasureSpec), View.MeasureSpec.getSize(heightMeasureSpec)); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + protected void onDraw(Canvas canvas) { + synchronized (chartData) { + + canvas.save(); + + canvas.drawColor(Color.WHITE); + + canvas.save(); + + clipToGraphArea(canvas); + drawDataSeries(canvas); + drawGrid(canvas); + + canvas.restore(); + + drawSeriesTitles(canvas); + drawXAxis(canvas); + drawYAxis(canvas); + + canvas.restore(); + + if (showPointer) { + drawPointer(canvas); + } + } + } + + /** + * Clips a canvas to the graph area. + * + * @param canvas the canvas + */ + private void clipToGraphArea(Canvas canvas) { + int x = getScrollX() + leftBorder; + int y = topBorder; + canvas.clipRect(x, y, x + effectiveWidth, y + effectiveHeight); + } + + /** + * Draws the data series. + * + * @param canvas the canvas + */ + private void drawDataSeries(Canvas canvas) { + for (ChartValueSeries chartValueSeries : series) { + if (chartValueSeries.isEnabled() && chartValueSeries.hasData()) { + chartValueSeries.drawPath(canvas); + } + } + } + + /** + * Draws the grid. + * + * @param canvas the canvas + */ + private void drawGrid(Canvas canvas) { + // X axis grid + ArrayList xAxisMarkerPositions = getXAxisMarkerPositions(getXAxisInterval()); + for (int i = 0; i < xAxisMarkerPositions.size(); i++) { + int x = getX(xAxisMarkerPositions.get(i)); + canvas.drawLine(x, topBorder, x, topBorder + effectiveHeight, gridPaint); + } + // Y axis grid + float rightEdge = getX(maxX); + for (int i = 0; i <= Y_AXIS_INTERVALS; i++) { + double percentage = (double) i / Y_AXIS_INTERVALS; + int range = effectiveHeight - 2 * yAxisOffset; + int y = topBorder + yAxisOffset + (int) (percentage * range); + canvas.drawLine(leftBorder, y, rightEdge, y, gridPaint); + } + } + + /** + * Draws series titles. + * + * @param canvas the canvas + */ + private void drawSeriesTitles(Canvas canvas) { + int[] titleDimensions = getTitleDimenions(); + int lines = titleDimensions[0]; + int lineHeight = titleDimensions[1]; + int count = 0; + for (int i = 0; i < series.length; i++) { + ChartValueSeries chartValueSeries = series[i]; + if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(i)) { + count++; + String title = getContext().getString(chartValueSeries.getTitleId(metricUnits)); + Paint paint = chartValueSeries.getTitlePaint(); + int x = (int) (0.5 * width) + getScrollX(); + int y = topBorder - spacer - (lines - count) * (lineHeight + spacer); + canvas.drawText(title, x, y, paint); + } + } + } + + /** + * Gets the title dimensions. Returns an array of 2 integers, first element is + * the number of lines and the second element is the line height. + */ + private int[] getTitleDimenions() { + int lines = 0; + int lineHeight = 0; + for (int i = 0; i < series.length; i++) { + ChartValueSeries chartValueSeries = series[i]; + if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(i)) { + lines++; + String title = getContext().getString(chartValueSeries.getTitleId(metricUnits)); + Rect rect = getRect(chartValueSeries.getTitlePaint(), title); + if (rect.height() > lineHeight) { + lineHeight = rect.height(); + } + } + } + return new int[]{lines, lineHeight}; + } + + /** + * Draws the x axis. + * + * @param canvas the canvas + */ + private void drawXAxis(Canvas canvas) { + int x = getScrollX() + leftBorder; + int y = topBorder + effectiveHeight; + canvas.drawLine(x, y, x + effectiveWidth, y, axisPaint); + String label = getXAxisLabel(); + Rect rect = getRect(axisPaint, label); + int yOffset = rect.height() / 2; + canvas.drawText(label, x + effectiveWidth + spacer, y + yOffset, axisPaint); + + double interval = getXAxisInterval(); + ArrayList markerPositions = getXAxisMarkerPositions(interval); + NumberFormat numberFormat = interval < 1 ? X_FRACTION_FORMAT : X_NUMBER_FORMAT; + for (int i = 0; i < markerPositions.size(); i++) { + drawXAxisMarker(canvas, markerPositions.get(i), numberFormat, spacer + yOffset); + } + } + + /** + * Gets the x axis label. + */ + private String getXAxisLabel() { + Context context = getContext(); + if (chartByDistance) { + return metricUnits ? context.getString(R.string.unit_kilometer) + : context.getString(R.string.unit_mile); + } else { + return context.getString(R.string.description_time); + } + } + + /** + * Draws a x axis marker. + * + * @param canvas canvas + * @param value value + * @param numberFormat the number format + * @param spacing the spacing between x axis and marker + */ + private void drawXAxisMarker(Canvas canvas, double value, NumberFormat numberFormat, int spacing) { + String marker = chartByDistance ? numberFormat.format(value) : StringUtils.formatElapsedTime((long) value); + Rect rect = getRect(xAxisMarkerPaint, marker); + canvas.drawText(marker, getX(value), topBorder + effectiveHeight + spacing + rect.height(), xAxisMarkerPaint); + } + + /** + * Gets the x axis interval. + */ + private double getXAxisInterval() { + double interval = maxX / zoomLevel / TARGET_X_AXIS_INTERVALS; + if (interval < 1) { + interval = .5; + } else if (interval < 5) { + interval = 2; + } else if (interval < 10) { + interval = 5; + } else { + interval = (interval / 10) * 10; + } + return interval; + } + + /** + * Gets the x axis marker positions. + */ + private ArrayList getXAxisMarkerPositions(double interval) { + ArrayList markers = new ArrayList<>(); + markers.add(0d); + for (int i = 1; i * interval < maxX; i++) { + markers.add(i * interval); + } + // At least 2 markers + if (markers.size() < 2) { + markers.add(maxX); + } + return markers; + } + + /** + * Draws the y axis. + * + * @param canvas the canvas + */ + private void drawYAxis(Canvas canvas) { + int x = getScrollX() + leftBorder; + int y = topBorder; + canvas.drawLine(x, y, x, y + effectiveHeight, axisPaint); + + int markerXPosition = x - spacer; + for (int i = 0; i < series.length; i++) { + int index = series.length - 1 - i; + ChartValueSeries chartValueSeries = series[index]; + if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(index)) { + markerXPosition -= drawYAxisMarkers(chartValueSeries, canvas, markerXPosition) + spacer; + } + } + } + + /** + * Draws the y axis markers for a chart value series. + * + * @param chartValueSeries the chart value series + * @param canvas the canvas + * @param xPosition the right most x position + * @return the maximum marker width. + */ + private float drawYAxisMarkers(ChartValueSeries chartValueSeries, Canvas canvas, int xPosition) { + int interval = chartValueSeries.getInterval(); + float maxMarkerWidth = 0; + for (int i = 0; i <= Y_AXIS_INTERVALS; i++) { + maxMarkerWidth = Math.max(maxMarkerWidth, drawYAxisMarker(chartValueSeries, canvas, xPosition, + i * interval + chartValueSeries.getMinMarkerValue())); + } + return maxMarkerWidth; + } + + /** + * Draws a y axis marker. + * + * @param chartValueSeries the chart value series + * @param canvas the canvas + * @param xPosition the right most x position + * @param yValue the y value + * @return the marker width. + */ + private float drawYAxisMarker( + ChartValueSeries chartValueSeries, Canvas canvas, int xPosition, int yValue) { + String marker = chartValueSeries.formatMarker(yValue); + Paint paint = chartValueSeries.getMarkerPaint(); + Rect rect = getRect(paint, marker); + int yPosition = getY(chartValueSeries, yValue) + (rect.height() / 2); + canvas.drawText(marker, xPosition, yPosition, paint); + return paint.measureText(marker); + } + + /** + * Draws the current pointer. + * + * @param canvas the canvas + */ + private void drawPointer(Canvas canvas) { + int index = -1; + for (int i = 0; i < series.length; i++) { + ChartValueSeries chartValueSeries = series[i]; + if (chartValueSeries.isEnabled() && chartValueSeries.hasData()) { + index = i; + break; + } + } + if (index != -1 && chartData.size() > 0) { + int dx = getX(maxX) - pointer.getIntrinsicWidth() / 2; + int dy = getY(series[index], chartData.get(chartData.size() - 1)[index + 1]) + - pointer.getIntrinsicHeight(); + canvas.translate(dx, dy); + pointer.draw(canvas); + } + } + + /** + * Updates paths. The path needs to be updated any time after the data or the + * dimensions change. + */ + private void updatePaths() { + synchronized (chartData) { + for (ChartValueSeries chartValueSeries : series) { + chartValueSeries.getPath().reset(); + } + drawPaths(); + closePaths(); + } + } + + /** + * Draws all paths. + */ + private void drawPaths() { + boolean[] hasMoved = new boolean[series.length]; + + for (int i = 0; i < chartData.size(); i++) { + double[] dataPoint = chartData.get(i); + for (int j = 0; j < series.length; j++) { + double value = dataPoint[j + 1]; + if (Double.isNaN(value)) { + continue; + } + ChartValueSeries chartValueSeries = series[j]; + Path path = chartValueSeries.getPath(); + int x = getX(dataPoint[0]); + int y = getY(chartValueSeries, value); + if (!hasMoved[j]) { + hasMoved[j] = true; + path.moveTo(x, y); + } else { + path.lineTo(x, y); + } + } + } + } + + /** + * Closes all paths. + */ + private void closePaths() { + for (int i = 0; i < series.length; i++) { + int first = getFirstPopulatedChartDataIndex(i); + + if (first != -1) { + int xCorner = getX(chartData.get(first)[0]); + int yCorner = topBorder + effectiveHeight; + ChartValueSeries chartValueSeries = series[i]; + Path path = chartValueSeries.getPath(); + // Bottom right corner + path.lineTo(getX(chartData.get(chartData.size() - 1)[0]), yCorner); + // Bottom left corner + path.lineTo(xCorner, yCorner); + // Top right corner + path.lineTo(xCorner, getY(chartValueSeries, chartData.get(first)[i + 1])); + } + } + } + + /** + * Finds the index of the first data point containing data for a series. + * Returns -1 if no data point contains data for the series. + * + * @param seriesIndex the series's index + */ + private int getFirstPopulatedChartDataIndex(int seriesIndex) { + for (int i = 0; i < chartData.size(); i++) { + if (!Double.isNaN(chartData.get(i)[seriesIndex + 1])) { + return i; + } + } + return -1; + } + + /** + * Updates the chart dimensions. + */ + private void updateDimensions() { + maxX = xExtremityMonitor.hasData() ? xExtremityMonitor.getMax() : 1.0; + for (ChartValueSeries chartValueSeries : series) { + chartValueSeries.updateDimension(); + } + float density = getContext().getResources().getDisplayMetrics().density; + spacer = (int) (density * SPACER); + yAxisOffset = (int) (density * Y_AXIS_OFFSET); + + int markerLength = 0; + for (int i = 0; i < series.length; i++) { + ChartValueSeries chartValueSeries = series[i]; + if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(i)) { + Rect rect = getRect(chartValueSeries.getMarkerPaint(), chartValueSeries.getLargestMarker()); + markerLength += rect.width() + spacer; + } + } + + leftBorder = (int) (density * BORDER + markerLength); + int[] titleDimensions = getTitleDimenions(); + topBorder = (int) (density * BORDER + titleDimensions[0] * (titleDimensions[1] + spacer)); + Rect xAxisLabelRect = getRect(axisPaint, getXAxisLabel()); + // border + x axis marker + spacer + .5 x axis label + bottomBorder = (int) (density * BORDER + getRect(xAxisMarkerPaint, "1").height() + spacer + + (xAxisLabelRect.height() / 2)); + rightBorder = (int) (density * BORDER + xAxisLabelRect.width() + spacer); + updateEffectiveDimensions(); + } + + /** + * Updates the effective dimensions. + */ + private void updateEffectiveDimensions() { + effectiveWidth = Math.max(0, width - leftBorder - rightBorder); + effectiveHeight = Math.max(0, height - topBorder - bottomBorder); + } + + /** + * Updates the effective dimensions if changed. + * + * @param newWidth the new width + * @param newHeight the new height + */ + private void updateEffectiveDimensionsIfChanged(int newWidth, int newHeight) { + if (width != newWidth || height != newHeight) { + width = newWidth; + height = newHeight; + updateEffectiveDimensions(); + updatePaths(); + } + } + + /** + * Gets the x position for a value. + * + * @param value the value + */ + private int getX(double value) { + if (value > maxX) { + value = maxX; + } + double percentage = value / maxX; + return leftBorder + (int) (percentage * effectiveWidth * zoomLevel); + } + + /** + * Gets the y position for a value in a chart value series + * + * @param chartValueSeries the chart value series + * @param value the value + */ + private int getY(ChartValueSeries chartValueSeries, double value) { + int effectiveSpread = chartValueSeries.getInterval() * Y_AXIS_INTERVALS; + double percentage = (value - chartValueSeries.getMinMarkerValue()) / effectiveSpread; + int rangeHeight = effectiveHeight - 2 * yAxisOffset; + return topBorder + yAxisOffset + (int) ((1 - percentage) * rangeHeight); + } + + /** + * Gets a waypoint's x value. + * + * @param waypoint the waypoint + */ + private double getWaypointXValue(Waypoint waypoint) { + if (chartByDistance) { + double lenghtInKm = waypoint.getLength() * UnitConversions.M_TO_KM; + return metricUnits ? lenghtInKm : lenghtInKm * UnitConversions.KM_TO_MI; + } else { + return waypoint.getDuration(); + } + } + + /** + * Gets a paint's Rect for a string. + * + * @param paint the paint + * @param string the string + */ + private Rect getRect(Paint paint, String string) { + Rect rect = new Rect(); + paint.getTextBounds(string, 0, string.length(), rect); + return rect; + } + + /** + * Returns true if the index is allowed when the chartData is empty. + * + * @param index the index + */ + private boolean allowIfEmpty(int index) { + if (!chartData.isEmpty()) { + return false; + } + switch (index) { + case ELEVATION_SERIES: + return true; + case SPEED_SERIES: + return reportSpeed; + case PACE_SERIES: + return !reportSpeed; + default: + return false; + } + } } diff --git a/src/main/java/com/google/android/apps/mytracks/Constants.java b/src/main/java/com/google/android/apps/mytracks/Constants.java index 774523033..f8725cb25 100644 --- a/src/main/java/com/google/android/apps/mytracks/Constants.java +++ b/src/main/java/com/google/android/apps/mytracks/Constants.java @@ -18,20 +18,20 @@ package com.google.android.apps.mytracks; /** * Common constants. - * + * * @author Leif Hendrik Wilden */ public class Constants { - private Constants() {} + /** + * Maximum number of waypoints that will be loaded at one time. + */ + public static final int MAX_LOADED_WAYPOINTS_POINTS = 10000; + /** + * The settings file name. + */ + public static final String SETTINGS_NAME = "SettingsActivity"; - /** - * Maximum number of waypoints that will be loaded at one time. - */ - public static final int MAX_LOADED_WAYPOINTS_POINTS = 10000; - - /** - * The settings file name. - */ - public static final String SETTINGS_NAME = "SettingsActivity"; + private Constants() { + } } diff --git a/src/main/java/com/google/android/apps/mytracks/ContextualActionModeCallback.java b/src/main/java/com/google/android/apps/mytracks/ContextualActionModeCallback.java index 568ea89d2..b86e03b5d 100644 --- a/src/main/java/com/google/android/apps/mytracks/ContextualActionModeCallback.java +++ b/src/main/java/com/google/android/apps/mytracks/ContextualActionModeCallback.java @@ -20,27 +20,27 @@ import android.view.Menu; /** * Callback when items in the contextual action mode are selected. - * + * * @author Jimmy Shih */ public interface ContextualActionModeCallback { - /** - * Invoked to prepare the menu for the selected items. - * - * @param menu the menu - * @param positions the selected items' positions - * @param ids the selected items' ids, if available - * @param showSelectAll true to show select all - */ - void onPrepare(Menu menu, int[] positions, long[] ids, boolean showSelectAll); + /** + * Invoked to prepare the menu for the selected items. + * + * @param menu the menu + * @param positions the selected items' positions + * @param ids the selected items' ids, if available + * @param showSelectAll true to show select all + */ + void onPrepare(Menu menu, int[] positions, long[] ids, boolean showSelectAll); - /** - * Invoked when items are selected. - * - * @param itemId the context menu item id - * @param positions the selected items' positions - * @param ids the selected items' ids, if available - */ - boolean onClick(int itemId, int[] positions, long[] ids); + /** + * Invoked when items are selected. + * + * @param itemId the context menu item id + * @param positions the selected items' positions + * @param ids the selected items' ids, if available + */ + boolean onClick(int itemId, int[] positions, long[] ids); } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/DeleteActivity.java b/src/main/java/com/google/android/apps/mytracks/DeleteActivity.java index 3f47b0bdc..0f241245e 100644 --- a/src/main/java/com/google/android/apps/mytracks/DeleteActivity.java +++ b/src/main/java/com/google/android/apps/mytracks/DeleteActivity.java @@ -16,82 +16,82 @@ package com.google.android.apps.mytracks; -import com.google.android.apps.mytracks.util.DialogUtils; -import com.google.android.maps.mytracks.R; - import android.app.Activity; import android.app.Dialog; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; +import com.google.android.apps.mytracks.util.DialogUtils; +import com.google.android.maps.mytracks.R; + /** * An activity for delete tracks. - * + * * @author Jimmy Shih */ public class DeleteActivity extends Activity { - public static final String EXTRA_TRACK_IDS = "track_ids"; + public static final String EXTRA_TRACK_IDS = "track_ids"; - private static final int DIALOG_PROGRESS_ID = 0; + private static final int DIALOG_PROGRESS_ID = 0; - private DeleteAsyncTask deleteAsyncTask; + private DeleteAsyncTask deleteAsyncTask; - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setResult(RESULT_CANCELED); - - Intent intent = getIntent(); - long[] trackIds = intent.getLongArrayExtra(EXTRA_TRACK_IDS); + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - Object retained = getLastNonConfigurationInstance(); - if (retained instanceof DeleteAsyncTask) { - deleteAsyncTask = (DeleteAsyncTask) retained; - deleteAsyncTask.setActivity(this); - } else { - deleteAsyncTask = new DeleteAsyncTask(this, trackIds); - deleteAsyncTask.execute(); + setResult(RESULT_CANCELED); + + Intent intent = getIntent(); + long[] trackIds = intent.getLongArrayExtra(EXTRA_TRACK_IDS); + + Object retained = getLastNonConfigurationInstance(); + if (retained instanceof DeleteAsyncTask) { + deleteAsyncTask = (DeleteAsyncTask) retained; + deleteAsyncTask.setActivity(this); + } else { + deleteAsyncTask = new DeleteAsyncTask(this, trackIds); + deleteAsyncTask.execute(); + } } - } - @Override - public Object onRetainNonConfigurationInstance() { - deleteAsyncTask.setActivity(null); - return deleteAsyncTask; - } - - @Override - protected Dialog onCreateDialog(int id) { - if (id != DIALOG_PROGRESS_ID) { - return null; + @Override + public Object onRetainNonConfigurationInstance() { + deleteAsyncTask.setActivity(null); + return deleteAsyncTask; } - return DialogUtils.createSpinnerProgressDialog( - this, R.string.track_delete_progress_message, new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - deleteAsyncTask.cancel(true); - dialog.dismiss(); - finish(); - } - }); - } - /** - * Invokes when the associated AsyncTask completes. - */ - public void onAsyncTaskCompleted() { - removeDialog(DIALOG_PROGRESS_ID); - setResult(RESULT_OK); - finish(); - } + @Override + protected Dialog onCreateDialog(int id) { + if (id != DIALOG_PROGRESS_ID) { + return null; + } + return DialogUtils.createSpinnerProgressDialog( + this, R.string.track_delete_progress_message, new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + deleteAsyncTask.cancel(true); + dialog.dismiss(); + finish(); + } + }); + } - /** - * Shows the progress dialog. - */ - public void showProgressDialog() { - showDialog(DIALOG_PROGRESS_ID); - } + /** + * Invokes when the associated AsyncTask completes. + */ + public void onAsyncTaskCompleted() { + removeDialog(DIALOG_PROGRESS_ID); + setResult(RESULT_OK); + finish(); + } + + /** + * Shows the progress dialog. + */ + public void showProgressDialog() { + showDialog(DIALOG_PROGRESS_ID); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/DeleteAsyncTask.java b/src/main/java/com/google/android/apps/mytracks/DeleteAsyncTask.java index 308b503dd..6343a1c67 100644 --- a/src/main/java/com/google/android/apps/mytracks/DeleteAsyncTask.java +++ b/src/main/java/com/google/android/apps/mytracks/DeleteAsyncTask.java @@ -23,73 +23,72 @@ import com.google.android.apps.mytracks.content.ContentProviderUtils; /** * Async Task to delete tracks. - * + * * @author Jimmy Shih */ public class DeleteAsyncTask extends AsyncTask { - private DeleteActivity deleteActivity; - private final long[] trackIds; - private final Context context; + private final long[] trackIds; + private final Context context; + private DeleteActivity deleteActivity; + // true if the AsyncTask has completed + private boolean completed; - // true if the AsyncTask has completed - private boolean completed; - - /** - * Creates an AsyncTask. - * - * @param deleteActivity the activity currently associated with this task - * @param trackIds the track ids to delete. To delete all, set to size 1 with - * trackIds[0] == -1L - */ - public DeleteAsyncTask(DeleteActivity deleteActivity, long[] trackIds) { - this.deleteActivity = deleteActivity; - this.trackIds = trackIds; - context = deleteActivity.getApplicationContext(); - completed = false; - } - - /** - * Sets the current activity associated with this AyncTask. - * - * @param deleteActivity the current activity, can be null - */ - public void setActivity(DeleteActivity deleteActivity) { - this.deleteActivity = deleteActivity; - if (completed && deleteActivity != null) { - deleteActivity.onAsyncTaskCompleted(); + /** + * Creates an AsyncTask. + * + * @param deleteActivity the activity currently associated with this task + * @param trackIds the track ids to delete. To delete all, set to size 1 with + * trackIds[0] == -1L + */ + public DeleteAsyncTask(DeleteActivity deleteActivity, long[] trackIds) { + this.deleteActivity = deleteActivity; + this.trackIds = trackIds; + context = deleteActivity.getApplicationContext(); + completed = false; } - } - @Override - protected void onPreExecute() { - if (deleteActivity != null) { - deleteActivity.showProgressDialog(); - } - } - - @Override - protected Boolean doInBackground(Void... params) { - ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context); - - if (trackIds.length == 1 && trackIds[0] == -1L) { - contentProviderUtils.deleteAllTracks(context); - } else { - for (long id : trackIds) { - if (isCancelled()) { - return false; + /** + * Sets the current activity associated with this AyncTask. + * + * @param deleteActivity the current activity, can be null + */ + public void setActivity(DeleteActivity deleteActivity) { + this.deleteActivity = deleteActivity; + if (completed && deleteActivity != null) { + deleteActivity.onAsyncTaskCompleted(); } - contentProviderUtils.deleteTrack(context, id); - } } - return true; - } - @Override - protected void onPostExecute(Boolean result) { - completed = true; - if (deleteActivity != null) { - deleteActivity.onAsyncTaskCompleted(); + @Override + protected void onPreExecute() { + if (deleteActivity != null) { + deleteActivity.showProgressDialog(); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context); + + if (trackIds.length == 1 && trackIds[0] == -1L) { + contentProviderUtils.deleteAllTracks(context); + } else { + for (long id : trackIds) { + if (isCancelled()) { + return false; + } + contentProviderUtils.deleteTrack(context, id); + } + } + return true; + } + + @Override + protected void onPostExecute(Boolean result) { + completed = true; + if (deleteActivity != null) { + deleteActivity.onAsyncTaskCompleted(); + } } - } } diff --git a/src/main/java/com/google/android/apps/mytracks/MarkerDetailActivity.java b/src/main/java/com/google/android/apps/mytracks/MarkerDetailActivity.java index a1934c659..a1e95e8bc 100644 --- a/src/main/java/com/google/android/apps/mytracks/MarkerDetailActivity.java +++ b/src/main/java/com/google/android/apps/mytracks/MarkerDetailActivity.java @@ -1,12 +1,12 @@ /* * Copyright 2009 Google Inc. - * + * * 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 @@ -35,92 +35,92 @@ import java.util.ArrayList; /** * An activity to display marker detail info. - * + * * @author Leif Hendrik Wilden */ public class MarkerDetailActivity extends AbstractActivity implements DeleteMarkerCaller { - public static final String EXTRA_MARKER_ID = "marker_id"; - private static final String TAG = MarkerDetailActivity.class.getSimpleName(); + public static final String EXTRA_MARKER_ID = "marker_id"; + private static final String TAG = MarkerDetailActivity.class.getSimpleName(); - private ArrayList markerIds; + private ArrayList markerIds; - @Override - protected void onCreate(Bundle bundle) { - super.onCreate(bundle); + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); - long markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L); - if (markerId == -1L) { - Log.d(TAG, "invalid marker id"); - finish(); - return; + long markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L); + if (markerId == -1L) { + Log.d(TAG, "invalid marker id"); + finish(); + return; + } + + ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(this); + Waypoint waypoint = contentProviderUtils.getWaypoint(markerId); + + markerIds = new ArrayList<>(); + int markerIndex = -1; + + try (Cursor cursor = contentProviderUtils.getWaypointCursor(waypoint.getTrackId(), -1L, -1)) { + if (cursor != null && cursor.moveToFirst()) { + /* + * Yes, this will skip the first waypoint and that is intentional as the + * first waypoint holds the stats for the track. + */ + while (cursor.moveToNext()) { + Waypoint current = contentProviderUtils.createWaypoint(cursor); + + markerIds.add(current.getId()); + if (current.getId() == markerId) { + markerIndex = markerIds.size() - 1; + } + } + } + } + + ViewPager viewPager = findViewById(R.id.maker_detail_activity_view_pager); + viewPager.setAdapter(new MarkerDetailPagerAdapter(getSupportFragmentManager())); + if (markerIndex != -1) { + viewPager.setCurrentItem(markerIndex); + } } - ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(this); - Waypoint waypoint = contentProviderUtils.getWaypoint(markerId); - - markerIds = new ArrayList<>(); - int markerIndex = -1; - - try (Cursor cursor = contentProviderUtils.getWaypointCursor(waypoint.getTrackId(), -1L, -1)) { - if (cursor != null && cursor.moveToFirst()) { - /* - * Yes, this will skip the first waypoint and that is intentional as the - * first waypoint holds the stats for the track. - */ - while (cursor.moveToNext()) { - Waypoint current = contentProviderUtils.createWaypoint(cursor); - - markerIds.add(current.getId()); - if (current.getId() == markerId) { - markerIndex = markerIds.size() - 1; - } - } - } - } - - ViewPager viewPager = findViewById(R.id.maker_detail_activity_view_pager); - viewPager.setAdapter(new MarkerDetailPagerAdapter(getSupportFragmentManager())); - if (markerIndex != -1) { - viewPager.setCurrentItem(markerIndex); + @Override + protected int getLayoutResId() { + return R.layout.marker_detail_activity; } - } - @Override - protected int getLayoutResId() { - return R.layout.marker_detail_activity; - } + @Override + public void onDeleteMarkerDone() { + runOnUiThread(new Runnable() { + @Override + public void run() { + finish(); + } + }); + } + + /** + * Marker detail pager adapter. + * + * @author Jimmy Shih + */ + private class MarkerDetailPagerAdapter extends FragmentStatePagerAdapter { + + public MarkerDetailPagerAdapter(FragmentManager fragmentManager) { + super(fragmentManager); + } - @Override - public void onDeleteMarkerDone() { - runOnUiThread(new Runnable() { @Override - public void run() { - finish(); - } - }); - } + public Fragment getItem(int position) { + String title = getString(R.string.marker_title, position + 1, getCount()); + return MarkerDetailFragment.newInstance(markerIds.get(position), title); + } - /** - * Marker detail pager adapter. - * - * @author Jimmy Shih - */ - private class MarkerDetailPagerAdapter extends FragmentStatePagerAdapter { - - public MarkerDetailPagerAdapter(FragmentManager fragmentManager) { - super(fragmentManager); + @Override + public int getCount() { + return markerIds.size(); + } } - - @Override - public Fragment getItem(int position) { - String title = getString(R.string.marker_title, position + 1, getCount()); - return MarkerDetailFragment.newInstance(markerIds.get(position), title); - } - - @Override - public int getCount() { - return markerIds.size(); - } - } } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/MarkerEditActivity.java b/src/main/java/com/google/android/apps/mytracks/MarkerEditActivity.java index 99fb843c2..ff3f01bbc 100644 --- a/src/main/java/com/google/android/apps/mytracks/MarkerEditActivity.java +++ b/src/main/java/com/google/android/apps/mytracks/MarkerEditActivity.java @@ -1,12 +1,12 @@ /* * Copyright 2012 Google Inc. - * + * * 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 @@ -16,14 +16,6 @@ package com.google.android.apps.mytracks; -import com.google.android.apps.mytracks.content.ContentProviderUtils; -import com.google.android.apps.mytracks.content.Waypoint; -import com.google.android.apps.mytracks.content.Waypoint.WaypointType; -import com.google.android.apps.mytracks.content.WaypointCreationRequest; -import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection; -import com.google.android.apps.mytracks.util.TrackRecordingServiceConnectionUtils; -import com.google.android.maps.mytracks.R; - import android.os.Bundle; import android.util.Log; import android.view.View; @@ -33,6 +25,14 @@ import android.widget.AutoCompleteTextView; import android.widget.Button; import android.widget.EditText; +import com.google.android.apps.mytracks.content.ContentProviderUtils; +import com.google.android.apps.mytracks.content.Waypoint; +import com.google.android.apps.mytracks.content.Waypoint.WaypointType; +import com.google.android.apps.mytracks.content.WaypointCreationRequest; +import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection; +import com.google.android.apps.mytracks.util.TrackRecordingServiceConnectionUtils; +import com.google.android.maps.mytracks.R; + /** * An activity to add/edit a marker. * @@ -40,152 +40,150 @@ import android.widget.EditText; */ public class MarkerEditActivity extends AbstractActivity { - private static final String TAG = MarkerEditActivity.class.getSimpleName(); + public static final String EXTRA_TRACK_ID = "track_id"; + public static final String EXTRA_MARKER_ID = "marker_id"; + private static final String TAG = MarkerEditActivity.class.getSimpleName(); + private long trackId; + private long markerId; + private TrackRecordingServiceConnection trackRecordingServiceConnection; + private Waypoint waypoint; - public static final String EXTRA_TRACK_ID = "track_id"; - public static final String EXTRA_MARKER_ID = "marker_id"; + // UI elements + private View statisticsSection; + private EditText statisticsName; + private View waypointSection; + private EditText waypointName; + private AutoCompleteTextView waypointMarkerType; + private EditText waypointDescription; + private Button done; - private long trackId; - private long markerId; - private TrackRecordingServiceConnection trackRecordingServiceConnection; - private Waypoint waypoint; + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); - // UI elements - private View statisticsSection; - private EditText statisticsName; - private View waypointSection; - private EditText waypointName; - private AutoCompleteTextView waypointMarkerType; - private EditText waypointDescription; - private Button done; + trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L); + markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L); + trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null); - @Override - protected void onCreate(Bundle bundle) { - super.onCreate(bundle); + // Setup UI elements + statisticsSection = findViewById(R.id.marker_edit_statistics_section); + statisticsName = findViewById(R.id.marker_edit_statistics_name); - trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L); - markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L); - trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null); - - // Setup UI elements - statisticsSection = findViewById(R.id.marker_edit_statistics_section); - statisticsName = findViewById(R.id.marker_edit_statistics_name); - - waypointSection = findViewById(R.id.marker_edit_waypoint_section); - waypointName = findViewById(R.id.marker_edit_waypoint_name); - waypointMarkerType = findViewById(R.id.marker_edit_waypoint_marker_type); - ArrayAdapter adapter = ArrayAdapter.createFromResource( - this, R.array.waypoint_types, android.R.layout.simple_dropdown_item_1line); - waypointMarkerType.setAdapter(adapter); - waypointDescription = findViewById(R.id.marker_edit_waypoint_description); + waypointSection = findViewById(R.id.marker_edit_waypoint_section); + waypointName = findViewById(R.id.marker_edit_waypoint_name); + waypointMarkerType = findViewById(R.id.marker_edit_waypoint_marker_type); + ArrayAdapter adapter = ArrayAdapter.createFromResource( + this, R.array.waypoint_types, android.R.layout.simple_dropdown_item_1line); + waypointMarkerType.setAdapter(adapter); + waypointDescription = findViewById(R.id.marker_edit_waypoint_description); - Button cancel = findViewById(R.id.marker_edit_cancel); - cancel.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - finish(); - } - }); - done = findViewById(R.id.marker_edit_done); - updateUiByMarkerId(); - } + Button cancel = findViewById(R.id.marker_edit_cancel); + cancel.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + done = findViewById(R.id.marker_edit_done); + updateUiByMarkerId(); + } - @Override - protected void onStart() { - super.onStart(); - TrackRecordingServiceConnectionUtils.startConnection(this, trackRecordingServiceConnection); - } - - @Override - protected void onStop() { - super.onStop(); - trackRecordingServiceConnection.unbind(); - } + @Override + protected void onStart() { + super.onStart(); + TrackRecordingServiceConnectionUtils.startConnection(this, trackRecordingServiceConnection); + } - @Override - protected int getLayoutResId() { - return R.layout.marker_edit; - } + @Override + protected void onStop() { + super.onStop(); + trackRecordingServiceConnection.unbind(); + } + + @Override + protected int getLayoutResId() { + return R.layout.marker_edit; + } + + /** + * Updates the UI based on the marker id. + */ + private void updateUiByMarkerId() { + final boolean newMarker = markerId == -1L; + + setTitle(newMarker ? R.string.menu_insert_marker : R.string.menu_edit); + done.setText(newMarker ? R.string.generic_add : R.string.generic_save); + done.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (newMarker) { + addMarker(); + } else { + saveMarker(); + } + } + }); - /** - * Updates the UI based on the marker id. - */ - private void updateUiByMarkerId() { - final boolean newMarker = markerId == -1L; - - setTitle(newMarker ? R.string.menu_insert_marker : R.string.menu_edit); - done.setText(newMarker ? R.string.generic_add : R.string.generic_save); - done.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { if (newMarker) { - addMarker(); + statisticsSection.setVisibility(View.GONE); + waypointSection.setVisibility(View.VISIBLE); + int nextWaypointNumber = trackId == -1L ? -1 + : ContentProviderUtils.Factory.get(this).getNextWaypointNumber(trackId, WaypointType.WAYPOINT); + if (nextWaypointNumber == -1) { + nextWaypointNumber = 0; + } + waypointName.setText(getString(R.string.marker_name_format, nextWaypointNumber)); + waypointName.selectAll(); + waypointMarkerType.setText(""); + waypointDescription.setText(""); } else { - saveMarker(); + waypoint = ContentProviderUtils.Factory.get(this).getWaypoint(markerId); + if (waypoint == null) { + Log.d(TAG, "waypoint is null"); + finish(); + return; + } + boolean statistics = waypoint.getType() == WaypointType.STATISTICS; + statisticsSection.setVisibility(statistics ? View.VISIBLE : View.GONE); + waypointSection.setVisibility(statistics ? View.GONE : View.VISIBLE); + if (statistics) { + statisticsName.setText(waypoint.getName()); + } else { + waypointName.setText(waypoint.getName()); + waypointMarkerType.setText(waypoint.getCategory()); + waypointDescription.setText(waypoint.getDescription()); + } } - } - }); - - if (newMarker) { - statisticsSection.setVisibility(View.GONE); - waypointSection.setVisibility(View.VISIBLE); - int nextWaypointNumber = trackId == -1L ? -1 - : ContentProviderUtils.Factory.get(this).getNextWaypointNumber(trackId, WaypointType.WAYPOINT); - if (nextWaypointNumber == -1) { - nextWaypointNumber = 0; - } - waypointName.setText(getString(R.string.marker_name_format, nextWaypointNumber)); - waypointName.selectAll(); - waypointMarkerType.setText(""); - waypointDescription.setText(""); - } else { - waypoint = ContentProviderUtils.Factory.get(this).getWaypoint(markerId); - if (waypoint == null) { - Log.d(TAG, "waypoint is null"); - finish(); - return; - } - boolean statistics = waypoint.getType() == WaypointType.STATISTICS; - statisticsSection.setVisibility(statistics ? View.VISIBLE : View.GONE); - waypointSection.setVisibility(statistics ? View.GONE : View.VISIBLE); - if (statistics) { - statisticsName.setText(waypoint.getName()); - } else { - waypointName.setText(waypoint.getName()); - waypointMarkerType.setText(waypoint.getCategory()); - waypointDescription.setText(waypoint.getDescription()); - } } - } - /** - * Adds a marker. - */ - private void addMarker() { - WaypointCreationRequest waypointCreationRequest = new WaypointCreationRequest( - WaypointType.WAYPOINT, - false, - waypointName.getText().toString(), - waypointMarkerType.getText().toString(), - waypointDescription.getText().toString(), - null, - null); - TrackRecordingServiceConnectionUtils.addMarker( - this, trackRecordingServiceConnection, waypointCreationRequest); - } - - /** - * Saves a marker. - */ - private void saveMarker() { - boolean statistics = waypoint.getType() == WaypointType.STATISTICS; - if (statistics) { - waypoint.setName(statisticsName.getText().toString()); - } else { - waypoint.setName(waypointName.getText().toString()); - waypoint.setCategory(waypointMarkerType.getText().toString()); - waypoint.setDescription(waypointDescription.getText().toString()); + /** + * Adds a marker. + */ + private void addMarker() { + WaypointCreationRequest waypointCreationRequest = new WaypointCreationRequest( + WaypointType.WAYPOINT, + false, + waypointName.getText().toString(), + waypointMarkerType.getText().toString(), + waypointDescription.getText().toString(), + null, + null); + TrackRecordingServiceConnectionUtils.addMarker( + this, trackRecordingServiceConnection, waypointCreationRequest); + } + + /** + * Saves a marker. + */ + private void saveMarker() { + boolean statistics = waypoint.getType() == WaypointType.STATISTICS; + if (statistics) { + waypoint.setName(statisticsName.getText().toString()); + } else { + waypoint.setName(waypointName.getText().toString()); + waypoint.setCategory(waypointMarkerType.getText().toString()); + waypoint.setDescription(waypointDescription.getText().toString()); + } + ContentProviderUtils.Factory.get(this).updateWaypoint(waypoint); } - ContentProviderUtils.Factory.get(this).updateWaypoint(waypoint); - } } diff --git a/src/main/java/com/google/android/apps/mytracks/MarkerListActivity.java b/src/main/java/com/google/android/apps/mytracks/MarkerListActivity.java index bfbee87d6..ceb78cc71 100644 --- a/src/main/java/com/google/android/apps/mytracks/MarkerListActivity.java +++ b/src/main/java/com/google/android/apps/mytracks/MarkerListActivity.java @@ -53,284 +53,278 @@ import com.google.android.maps.mytracks.R; /** * Activity to show a list of markers in a track. - * + * * @author Leif Hendrik Wilden */ public class MarkerListActivity extends AbstractActivity implements DeleteMarkerCaller { - public static final String EXTRA_TRACK_ID = "track_id"; + public static final String EXTRA_TRACK_ID = "track_id"; - private static final String TAG = MarkerListActivity.class.getSimpleName(); + private static final String TAG = MarkerListActivity.class.getSimpleName(); - private static final String[] PROJECTION = new String[] { WaypointsColumns._ID, - WaypointsColumns.NAME, WaypointsColumns.DESCRIPTION, WaypointsColumns.CATEGORY, - WaypointsColumns.TYPE, WaypointsColumns.TIME, WaypointsColumns.PHOTOURL, - WaypointsColumns.LATITUDE, WaypointsColumns.LONGITUDE}; - - // Callback when an item is selected in the contextual action mode - private ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() { + private static final String[] PROJECTION = new String[]{WaypointsColumns._ID, + WaypointsColumns.NAME, WaypointsColumns.DESCRIPTION, WaypointsColumns.CATEGORY, + WaypointsColumns.TYPE, WaypointsColumns.TIME, WaypointsColumns.PHOTOURL, + WaypointsColumns.LATITUDE, WaypointsColumns.LONGITUDE}; + private ContentProviderUtils contentProviderUtils; + private SharedPreferences sharedPreferences; + private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; + private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT; + /* + * Note that sharedPreferenceChangeListener cannot be an anonymous inner + * class. Anonymous inner class will get garbage collected. + */ + private final OnSharedPreferenceChangeListener + sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { + @Override + public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { + // Note that the key can be null + if (key == null || key.equals(PreferencesUtils.getKey(MarkerListActivity.this, R.string.recording_track_id_key))) { + recordingTrackId = PreferencesUtils.getLong(MarkerListActivity.this, R.string.recording_track_id_key); + } + if (key == null || key.equals(PreferencesUtils.getKey(MarkerListActivity.this, R.string.recording_track_paused_key))) { + recordingTrackPaused = PreferencesUtils.getBoolean(MarkerListActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); + } + if (key != null) { + runOnUiThread(new Runnable() { + @Override + public void run() { + MarkerListActivity.this.invalidateOptionsMenu(); + } + }); + } + } + }; + private Track track; + private ResourceCursorAdapter resourceCursorAdapter; + // UI elements + private ListView listView; + // Callback when an item is selected in the contextual action mode + private ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() { @Override public void onPrepare(Menu menu, int[] positions, long[] ids, boolean showSelectAll) { - boolean isSingleSelection = ids.length == 1; + boolean isSingleSelection = ids.length == 1; - menu.findItem(R.id.list_context_menu_share).setVisible(false); - menu.findItem(R.id.list_context_menu_show_on_map).setVisible(isSingleSelection); - menu.findItem(R.id.list_context_menu_edit).setVisible(isSingleSelection); - menu.findItem(R.id.list_context_menu_delete).setVisible(true); - /* - * Set select all to the same visibility as delete since delete is the - * only action that can be applied to multiple markers. - */ - menu.findItem(R.id.list_context_menu_select_all).setVisible(showSelectAll); + menu.findItem(R.id.list_context_menu_share).setVisible(false); + menu.findItem(R.id.list_context_menu_show_on_map).setVisible(isSingleSelection); + menu.findItem(R.id.list_context_menu_edit).setVisible(isSingleSelection); + menu.findItem(R.id.list_context_menu_delete).setVisible(true); + /* + * Set select all to the same visibility as delete since delete is the + * only action that can be applied to multiple markers. + */ + menu.findItem(R.id.list_context_menu_select_all).setVisible(showSelectAll); } - @Override + @Override public boolean onClick(int itemId, int[] positions, long[] ids) { - return handleContextItem(itemId, ids); + return handleContextItem(itemId, ids); } - }; - - /* - * Note that sharedPreferenceChangeListener cannot be an anonymous inner - * class. Anonymous inner class will get garbage collected. - */ - private final OnSharedPreferenceChangeListener - sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { - @Override - public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { - // Note that the key can be null - if (key == null || key.equals(PreferencesUtils.getKey(MarkerListActivity.this, R.string.recording_track_id_key))) { - recordingTrackId = PreferencesUtils.getLong(MarkerListActivity.this, R.string.recording_track_id_key); - } - if (key == null || key.equals(PreferencesUtils.getKey(MarkerListActivity.this, R.string.recording_track_paused_key))) { - recordingTrackPaused = PreferencesUtils.getBoolean(MarkerListActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); - } - if (key != null) { - runOnUiThread(new Runnable() { - @Override - public void run() { - MarkerListActivity.this.invalidateOptionsMenu(); - } - }); - } - } - }; - - private ContentProviderUtils contentProviderUtils; - private SharedPreferences sharedPreferences; - - private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; - private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT; - - private Track track; - private ResourceCursorAdapter resourceCursorAdapter; - - // UI elements - private ListView listView; - private MenuItem insertMarkerMenuItem; - private MenuItem searchMenuItem; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - contentProviderUtils = ContentProviderUtils.Factory.get(this); - sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); - - long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L); - track = trackId != -1L ? contentProviderUtils.getTrack(trackId) : null; - final long trackFirstWaypointId = trackId != -1 ? contentProviderUtils.getFirstWaypointId(trackId) : -1; - - setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); - - listView = findViewById(R.id.marker_list); - listView.setEmptyView(findViewById(R.id.marker_list_empty)); - listView.setOnItemClickListener(new OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - Intent intent = IntentUtils.newIntent(MarkerListActivity.this, MarkerDetailActivity.class) - .putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, id); - startActivity(intent); - } - }); - resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) { - @Override - public void bindView(View view, Context context, Cursor cursor) { - int typeIndex = cursor.getColumnIndex(WaypointsColumns.TYPE); - int nameIndex = cursor.getColumnIndex(WaypointsColumns.NAME); - int timeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME); - int categoryIndex = cursor.getColumnIndex(WaypointsColumns.CATEGORY); - int descriptionIndex = cursor.getColumnIndex(WaypointsColumns.DESCRIPTION); - int photoUrlIndex = cursor.getColumnIndex(WaypointsColumns.PHOTOURL); - int latitudeIndex = cursor.getColumnIndex(WaypointsColumns.LATITUDE); - int longitudeIndex = cursor.getColumnIndex(WaypointsColumns.LONGITUDE); - - boolean statistics = WaypointType.values()[cursor.getInt(typeIndex)] == WaypointType.STATISTICS; - int iconId = statistics ? R.drawable.ic_marker_yellow_pushpin : R.drawable.ic_marker_blue_pushpin; - String name = cursor.getString(nameIndex); - long time = cursor.getLong(timeIndex); - String category = statistics ? null : cursor.getString(categoryIndex); - String description = statistics ? null : cursor.getString(descriptionIndex); - String photoUrl = cursor.getString(photoUrlIndex); - double latitude = cursor.getDouble(latitudeIndex); - double longitude = cursor.getDouble(longitudeIndex); - - ListItemUtils.setListItem(MarkerListActivity.this, view, false, true, iconId, R.string.image_marker, name, null, null, 0, time, false, category, description, photoUrl); - } }; - listView.setAdapter(resourceCursorAdapter); - AbstractTrackActivity.configureListViewContextualMenu(listView, contextualActionModeCallback); + private MenuItem insertMarkerMenuItem; + private MenuItem searchMenuItem; - LoaderManager.getInstance(this).initLoader(0, null, new LoaderCallbacks() { - @NonNull - @Override - public Loader onCreateLoader(int arg0, Bundle arg1) { - if (track != null) { - return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION, - WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns._ID + "!=?", - new String[] { String.valueOf(track.getId()), String.valueOf(trackFirstWaypointId) }, null); - } else { - return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION, - WaypointsColumns.STARTTIME + " IS NULL",null, null); - } - } + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - @Override - public void onLoadFinished(@NonNull Loader loader, Cursor cursor) { - resourceCursorAdapter.swapCursor(cursor); - } + contentProviderUtils = ContentProviderUtils.Factory.get(this); + sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); - @Override - public void onLoaderReset(@NonNull Loader loader) { - resourceCursorAdapter.swapCursor(null); - } - }); - } + long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L); + track = trackId != -1L ? contentProviderUtils.getTrack(trackId) : null; + final long trackFirstWaypointId = trackId != -1 ? contentProviderUtils.getFirstWaypointId(trackId) : -1; - @Override - protected void onStart() { - super.onStart(); - sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); - sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null); - } + setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); - @Override - protected void onResume() { - super.onResume(); - this.invalidateOptionsMenu(); - } + listView = findViewById(R.id.marker_list); + listView.setEmptyView(findViewById(R.id.marker_list_empty)); + listView.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + Intent intent = IntentUtils.newIntent(MarkerListActivity.this, MarkerDetailActivity.class) + .putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, id); + startActivity(intent); + } + }); + resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) { + @Override + public void bindView(View view, Context context, Cursor cursor) { + int typeIndex = cursor.getColumnIndex(WaypointsColumns.TYPE); + int nameIndex = cursor.getColumnIndex(WaypointsColumns.NAME); + int timeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME); + int categoryIndex = cursor.getColumnIndex(WaypointsColumns.CATEGORY); + int descriptionIndex = cursor.getColumnIndex(WaypointsColumns.DESCRIPTION); + int photoUrlIndex = cursor.getColumnIndex(WaypointsColumns.PHOTOURL); + int latitudeIndex = cursor.getColumnIndex(WaypointsColumns.LATITUDE); + int longitudeIndex = cursor.getColumnIndex(WaypointsColumns.LONGITUDE); - @Override - protected void onStop() { - super.onStop(); - sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); - } + boolean statistics = WaypointType.values()[cursor.getInt(typeIndex)] == WaypointType.STATISTICS; + int iconId = statistics ? R.drawable.ic_marker_yellow_pushpin : R.drawable.ic_marker_blue_pushpin; + String name = cursor.getString(nameIndex); + long time = cursor.getLong(timeIndex); + String category = statistics ? null : cursor.getString(categoryIndex); + String description = statistics ? null : cursor.getString(descriptionIndex); + String photoUrl = cursor.getString(photoUrlIndex); + double latitude = cursor.getDouble(latitudeIndex); + double longitude = cursor.getDouble(longitudeIndex); - @Override - protected int getLayoutResId() { - return R.layout.marker_list; - } + ListItemUtils.setListItem(MarkerListActivity.this, view, false, true, iconId, R.string.image_marker, name, null, null, 0, time, false, category, description, photoUrl); + } + }; + listView.setAdapter(resourceCursorAdapter); + AbstractTrackActivity.configureListViewContextualMenu(listView, contextualActionModeCallback); - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.marker_list, menu); + LoaderManager.getInstance(this).initLoader(0, null, new LoaderCallbacks() { + @NonNull + @Override + public Loader onCreateLoader(int arg0, Bundle arg1) { + if (track != null) { + return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION, + WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns._ID + "!=?", + new String[]{String.valueOf(track.getId()), String.valueOf(trackFirstWaypointId)}, null); + } else { + return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION, + WaypointsColumns.STARTTIME + " IS NULL", null, null); + } + } - insertMarkerMenuItem = menu.findItem(R.id.marker_list_insert_marker); + @Override + public void onLoadFinished(@NonNull Loader loader, Cursor cursor) { + resourceCursorAdapter.swapCursor(cursor); + } - searchMenuItem = menu.findItem(R.id.marker_list_search); - AbstractTrackActivity.configureSearchWidget(this, searchMenuItem, null); - - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - insertMarkerMenuItem.setVisible(track != null && track.getId() == recordingTrackId && !recordingTrackPaused); - return super.onPrepareOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (track != null && item.getItemId() == R.id.marker_list_insert_marker) { - Intent intent = IntentUtils.newIntent(this, MarkerEditActivity.class) - .putExtra(MarkerEditActivity.EXTRA_TRACK_ID, track.getId()); - startActivity(intent); - return true; + @Override + public void onLoaderReset(@NonNull Loader loader) { + resourceCursorAdapter.swapCursor(null); + } + }); } - return super.onOptionsItemSelected(item); - } - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - getMenuInflater().inflate(R.menu.list_context_menu, menu); - - AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; - contextualActionModeCallback.onPrepare(menu, new int[] { info.position }, new long[] { info.id }, false); - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); - if (handleContextItem(item.getItemId(), new long[] {info.id})) { - return true; + @Override + protected void onStart() { + super.onStart(); + sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null); } - return super.onContextItemSelected(item); - } - /** - * Handles a context item selection. - * - * @param itemId the menu item id - * @param markerIds the marker ids - * @return true if handled. - */ - private boolean handleContextItem(int itemId, long[] markerIds) { - Intent intent; - switch (itemId) { - case R.id.list_context_menu_show_on_map: - if (markerIds.length == 1) { - intent = IntentUtils.newIntent(this, TrackDetailActivity.class) - .putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerIds[0]); - //TODO Use IntentUtils.newShowOnMapIntent() - startActivity(intent); - } - return true; - case R.id.list_context_menu_edit: - if (markerIds.length == 1) { - intent = IntentUtils.newIntent(this, MarkerEditActivity.class) - .putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerIds[0]); - startActivity(intent); - } - return true; - case R.id.list_context_menu_delete: - if (markerIds.length > 1 && markerIds.length == listView.getCount()) { - markerIds = new long[] { -1L }; - } - DeleteMarkerDialogFragment.newInstance(markerIds) - .show(getSupportFragmentManager(), DeleteMarkerDialogFragment.DELETE_MARKER_DIALOG_TAG); - return true; - case R.id.list_context_menu_select_all: - int size = listView.getCount(); - for (int i = 0; i < size; i++) { - listView.setItemChecked(i, true); - } - return false; - default: - return false; + @Override + protected void onResume() { + super.onResume(); + this.invalidateOptionsMenu(); } - } - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_SEARCH && searchMenuItem != null) { - return true; + @Override + protected void onStop() { + super.onStop(); + sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); } - return super.onKeyUp(keyCode, event); - } - @Override - public void onDeleteMarkerDone() { - // Do nothing - } + @Override + protected int getLayoutResId() { + return R.layout.marker_list; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.marker_list, menu); + + insertMarkerMenuItem = menu.findItem(R.id.marker_list_insert_marker); + + searchMenuItem = menu.findItem(R.id.marker_list_search); + AbstractTrackActivity.configureSearchWidget(this, searchMenuItem, null); + + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + insertMarkerMenuItem.setVisible(track != null && track.getId() == recordingTrackId && !recordingTrackPaused); + return super.onPrepareOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (track != null && item.getItemId() == R.id.marker_list_insert_marker) { + Intent intent = IntentUtils.newIntent(this, MarkerEditActivity.class) + .putExtra(MarkerEditActivity.EXTRA_TRACK_ID, track.getId()); + startActivity(intent); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + getMenuInflater().inflate(R.menu.list_context_menu, menu); + + AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; + contextualActionModeCallback.onPrepare(menu, new int[]{info.position}, new long[]{info.id}, false); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); + if (handleContextItem(item.getItemId(), new long[]{info.id})) { + return true; + } + return super.onContextItemSelected(item); + } + + /** + * Handles a context item selection. + * + * @param itemId the menu item id + * @param markerIds the marker ids + * @return true if handled. + */ + private boolean handleContextItem(int itemId, long[] markerIds) { + Intent intent; + switch (itemId) { + case R.id.list_context_menu_show_on_map: + if (markerIds.length == 1) { + intent = IntentUtils.newIntent(this, TrackDetailActivity.class) + .putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerIds[0]); + //TODO Use IntentUtils.newShowOnMapIntent() + startActivity(intent); + } + return true; + case R.id.list_context_menu_edit: + if (markerIds.length == 1) { + intent = IntentUtils.newIntent(this, MarkerEditActivity.class) + .putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerIds[0]); + startActivity(intent); + } + return true; + case R.id.list_context_menu_delete: + if (markerIds.length > 1 && markerIds.length == listView.getCount()) { + markerIds = new long[]{-1L}; + } + DeleteMarkerDialogFragment.newInstance(markerIds) + .show(getSupportFragmentManager(), DeleteMarkerDialogFragment.DELETE_MARKER_DIALOG_TAG); + return true; + case R.id.list_context_menu_select_all: + int size = listView.getCount(); + for (int i = 0; i < size; i++) { + listView.setItemChecked(i, true); + } + return false; + default: + return false; + } + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_SEARCH && searchMenuItem != null) { + return true; + } + return super.onKeyUp(keyCode, event); + } + + @Override + public void onDeleteMarkerDone() { + // Do nothing + } } diff --git a/src/main/java/com/google/android/apps/mytracks/SearchListActivity.java b/src/main/java/com/google/android/apps/mytracks/SearchListActivity.java index 4e3bd851d..7f012e77f 100644 --- a/src/main/java/com/google/android/apps/mytracks/SearchListActivity.java +++ b/src/main/java/com/google/android/apps/mytracks/SearchListActivity.java @@ -68,446 +68,443 @@ import java.util.SortedSet; /** * An activity to display a list of search results. - * - * @author Rodrigo Damazio * + * @author Rodrigo Damazio + *

* TODO: allow to refine search (present search in context menu) */ public class SearchListActivity extends AbstractTrackActivity implements DeleteMarkerCaller, ConfirmDeleteDialogFragment.ConfirmDeleteCaller { - private static final String TAG = SearchListActivity.class.getSimpleName(); + private static final String TAG = SearchListActivity.class.getSimpleName(); - private static final String IS_RECORDING_FIELD = "isRecording"; - private static final String IS_PAUSED_FIELD = "isPaused"; - private static final String ICON_ID_FIELD = "icon"; - private static final String ICON_CONTENT_DESCRIPTION_ID_FIELD = "iconContentDescription"; - private static final String NAME_FIELD = "name"; - private static final String TOTAL_TIME_FIELD = "totalTime"; - private static final String TOTAL_DISTANCE_FIELD = "totalDistance"; - private static final String MARKER_COUNT_FIELD = "markerCount"; - private static final String START_TIME_FIELD = "startTime"; - private static final String CATEGORY_FIELD = "category"; - private static final String DESCRIPTION_FIELD = "description"; - private static final String PHOTO_URL_FIELD = "photoUrl"; - private static final String MARKER_LATITUDE_FIELD = "latitude"; - private static final String MARKER_LONGITUDE_FIELD = "longitude"; + private static final String IS_RECORDING_FIELD = "isRecording"; + private static final String IS_PAUSED_FIELD = "isPaused"; + private static final String ICON_ID_FIELD = "icon"; + private static final String ICON_CONTENT_DESCRIPTION_ID_FIELD = "iconContentDescription"; + private static final String NAME_FIELD = "name"; + private static final String TOTAL_TIME_FIELD = "totalTime"; + private static final String TOTAL_DISTANCE_FIELD = "totalDistance"; + private static final String MARKER_COUNT_FIELD = "markerCount"; + private static final String START_TIME_FIELD = "startTime"; + private static final String CATEGORY_FIELD = "category"; + private static final String DESCRIPTION_FIELD = "description"; + private static final String PHOTO_URL_FIELD = "photoUrl"; + private static final String MARKER_LATITUDE_FIELD = "latitude"; + private static final String MARKER_LONGITUDE_FIELD = "longitude"; - private static final String TRACK_ID_FIELD = "trackId"; - private static final String MARKER_ID_FIELD = "markerId"; - - private final OnSharedPreferenceChangeListener - sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { - @Override - public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { - if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.stats_units_key))) { - metricUnits = PreferencesUtils.isMetricUnits(SearchListActivity.this); - } - if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.recording_track_id_key))) { - recordingTrackId = PreferencesUtils.getLong(SearchListActivity.this, R.string.recording_track_id_key); - } - if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.recording_track_paused_key))) { - recordingTrackPaused = PreferencesUtils.getBoolean(SearchListActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); - } - if (key != null) { - runOnUiThread(new Runnable() { - @Override - public void run() { - arrayAdapter.notifyDataSetChanged(); - } - }); - } - } - }; - - // Callback when an item is selected in the contextual action mode - private ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() { - @Override - public boolean onClick(int itemId, int[] positions, long[] ids) { - return handleContextItem(itemId, positions); - } - @Override - public void onPrepare(Menu menu, int[] positions, long[] ids, boolean showSelectAll) { - boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; - boolean isSingleSelection = positions.length == 1; - boolean isSingleSelectionTrack; - if (isSingleSelection) { - Map item = arrayAdapter.getItem(positions[0]); - Long trackId = (Long) item.get(TRACK_ID_FIELD); - Track track = contentProviderUtils.getTrack(trackId); - - isSingleSelectionTrack = item.get(MARKER_ID_FIELD) == null; - } else { - isSingleSelectionTrack = false; - } - // Not recording, one item, item is a track - menu.findItem(R.id.list_context_menu_share).setVisible(!isRecording && isSingleSelection && isSingleSelectionTrack); - //TODO Setup shareIntent. - - // One item, item is a marker - menu.findItem(R.id.list_context_menu_show_on_map).setVisible(isSingleSelection && !isSingleSelectionTrack); - // One item, can be a track or a marker - menu.findItem(R.id.list_context_menu_edit).setVisible(isSingleSelection); - // One item. If track, no restriction. - menu.findItem(R.id.list_context_menu_delete).setVisible(isSingleSelection && (isSingleSelectionTrack)); - // Disable select all, no action is available for multiple selection - menu.findItem(R.id.list_context_menu_select_all).setVisible(false); - } - }; - - private ContentProviderUtils contentProviderUtils; - private SharedPreferences sharedPreferences; - private TrackRecordingServiceConnection trackRecordingServiceConnection; - private SearchEngine searchEngine; - private SearchRecentSuggestions searchRecentSuggestions; - private ArrayAdapter> arrayAdapter; - - private boolean metricUnits = true; - private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; - private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); - - contentProviderUtils = ContentProviderUtils.Factory.get(this); - sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); - trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null); - searchEngine = new SearchEngine(contentProviderUtils); - searchRecentSuggestions = SearchEngineProvider.newHelper(this); - - arrayAdapter = new ArrayAdapter>(this, R.layout.list_item, R.id.list_item_name) { - @NonNull + private static final String TRACK_ID_FIELD = "trackId"; + private static final String MARKER_ID_FIELD = "markerId"; + private ContentProviderUtils contentProviderUtils; + private SharedPreferences sharedPreferences; + private TrackRecordingServiceConnection trackRecordingServiceConnection; + private SearchEngine searchEngine; + private SearchRecentSuggestions searchRecentSuggestions; + private ArrayAdapter> arrayAdapter; + private boolean metricUnits = true; + private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; + // Callback when an item is selected in the contextual action mode + private ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() { @Override - public View getView(int position, View convertView, @NonNull android.view.ViewGroup parent) { - View view; - if (convertView == null) { - view = getLayoutInflater().inflate(R.layout.list_item, parent, false); - } else { - view = convertView; + public boolean onClick(int itemId, int[] positions, long[] ids) { + return handleContextItem(itemId, positions); + } + + @Override + public void onPrepare(Menu menu, int[] positions, long[] ids, boolean showSelectAll) { + boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; + boolean isSingleSelection = positions.length == 1; + boolean isSingleSelectionTrack; + if (isSingleSelection) { + Map item = arrayAdapter.getItem(positions[0]); + Long trackId = (Long) item.get(TRACK_ID_FIELD); + Track track = contentProviderUtils.getTrack(trackId); + + isSingleSelectionTrack = item.get(MARKER_ID_FIELD) == null; + } else { + isSingleSelectionTrack = false; + } + // Not recording, one item, item is a track + menu.findItem(R.id.list_context_menu_share).setVisible(!isRecording && isSingleSelection && isSingleSelectionTrack); + //TODO Setup shareIntent. + + // One item, item is a marker + menu.findItem(R.id.list_context_menu_show_on_map).setVisible(isSingleSelection && !isSingleSelectionTrack); + // One item, can be a track or a marker + menu.findItem(R.id.list_context_menu_edit).setVisible(isSingleSelection); + // One item. If track, no restriction. + menu.findItem(R.id.list_context_menu_delete).setVisible(isSingleSelection && (isSingleSelectionTrack)); + // Disable select all, no action is available for multiple selection + menu.findItem(R.id.list_context_menu_select_all).setVisible(false); } - Map resultMap = getItem(position); - boolean isRecording = (boolean) resultMap.get(IS_RECORDING_FIELD); - boolean isPaused = (boolean) resultMap.get(IS_PAUSED_FIELD); - int iconId = (int) resultMap.get(ICON_ID_FIELD); - int iconContentDescriptionId = (int) resultMap.get(ICON_CONTENT_DESCRIPTION_ID_FIELD); - String name = (String) resultMap.get(NAME_FIELD); - String totalTime = (String) resultMap.get(TOTAL_TIME_FIELD); - String totalDistance = (String) resultMap.get(TOTAL_DISTANCE_FIELD); - int markerCount = (int) resultMap.get(MARKER_COUNT_FIELD); - long startTime = (long) resultMap.get(START_TIME_FIELD); - String category = (String) resultMap.get(CATEGORY_FIELD); - String description = (String) resultMap.get(DESCRIPTION_FIELD); - String photoUrl = (String) resultMap.get(PHOTO_URL_FIELD); - - ListItemUtils.setListItem(SearchListActivity.this, view, isRecording, isPaused, iconId, - iconContentDescriptionId, name, totalTime, totalDistance, markerCount, - startTime, false, category, description, photoUrl); - return view; - } }; - // UI elements - ListView listView = findViewById(R.id.search_list); - listView.setAdapter(arrayAdapter); - listView.setEmptyView(findViewById(R.id.search_list_empty)); - listView.setOnItemClickListener(new OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - Map item = arrayAdapter.getItem(position); + private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT; + private final OnSharedPreferenceChangeListener + sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { + @Override + public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { + if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.stats_units_key))) { + metricUnits = PreferencesUtils.isMetricUnits(SearchListActivity.this); + } + if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.recording_track_id_key))) { + recordingTrackId = PreferencesUtils.getLong(SearchListActivity.this, R.string.recording_track_id_key); + } + if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.recording_track_paused_key))) { + recordingTrackPaused = PreferencesUtils.getBoolean(SearchListActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); + } + if (key != null) { + runOnUiThread(new Runnable() { + @Override + public void run() { + arrayAdapter.notifyDataSetChanged(); + } + }); + } + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); + + contentProviderUtils = ContentProviderUtils.Factory.get(this); + sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); + trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null); + searchEngine = new SearchEngine(contentProviderUtils); + searchRecentSuggestions = SearchEngineProvider.newHelper(this); + + arrayAdapter = new ArrayAdapter>(this, R.layout.list_item, R.id.list_item_name) { + @NonNull + @Override + public View getView(int position, View convertView, @NonNull android.view.ViewGroup parent) { + View view; + if (convertView == null) { + view = getLayoutInflater().inflate(R.layout.list_item, parent, false); + } else { + view = convertView; + } + Map resultMap = getItem(position); + boolean isRecording = (boolean) resultMap.get(IS_RECORDING_FIELD); + boolean isPaused = (boolean) resultMap.get(IS_PAUSED_FIELD); + int iconId = (int) resultMap.get(ICON_ID_FIELD); + int iconContentDescriptionId = (int) resultMap.get(ICON_CONTENT_DESCRIPTION_ID_FIELD); + String name = (String) resultMap.get(NAME_FIELD); + String totalTime = (String) resultMap.get(TOTAL_TIME_FIELD); + String totalDistance = (String) resultMap.get(TOTAL_DISTANCE_FIELD); + int markerCount = (int) resultMap.get(MARKER_COUNT_FIELD); + long startTime = (long) resultMap.get(START_TIME_FIELD); + String category = (String) resultMap.get(CATEGORY_FIELD); + String description = (String) resultMap.get(DESCRIPTION_FIELD); + String photoUrl = (String) resultMap.get(PHOTO_URL_FIELD); + + ListItemUtils.setListItem(SearchListActivity.this, view, isRecording, isPaused, iconId, + iconContentDescriptionId, name, totalTime, totalDistance, markerCount, + startTime, false, category, description, photoUrl); + return view; + } + }; + // UI elements + ListView listView = findViewById(R.id.search_list); + listView.setAdapter(arrayAdapter); + listView.setEmptyView(findViewById(R.id.search_list_empty)); + listView.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + Map item = arrayAdapter.getItem(position); + Long trackId = (Long) item.get(TRACK_ID_FIELD); + Long markerId = (Long) item.get(MARKER_ID_FIELD); + Intent intent; + if (markerId != null) { + intent = IntentUtils.newIntent(SearchListActivity.this, TrackDetailActivity.class) + .putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerId); + } else { + intent = IntentUtils.newIntent(SearchListActivity.this, TrackDetailActivity.class) + .putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId); + } + startActivity(intent); + } + }); + AbstractTrackActivity.configureListViewContextualMenu(listView, contextualActionModeCallback); + handleIntent(getIntent()); + } + + @Override + protected void onStart() { + super.onStart(); + sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null); + TrackRecordingServiceConnectionUtils.startConnection(this, trackRecordingServiceConnection); + } + + @Override + protected void onResume() { + super.onResume(); + arrayAdapter.notifyDataSetChanged(); + } + + @Override + protected void onStop() { + super.onStop(); + sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + trackRecordingServiceConnection.unbind(); + } + + @Override + protected int getLayoutResId() { + return R.layout.search_list; + } + + @Override + public void onNewIntent(Intent intent) { + setIntent(intent); + handleIntent(intent); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.search_list, menu); + menu.findItem(R.id.search_list_search).setVisible(false); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.search_list_search) { + return false; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + getMenuInflater().inflate(R.menu.list_context_menu, menu); + + AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; + contextualActionModeCallback.onPrepare(menu, new int[]{info.position}, new long[]{info.id}, false); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); + if (handleContextItem(item.getItemId(), new int[]{info.position})) { + return true; + } + return super.onContextItemSelected(item); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_SEARCH) { + return true; + } + return super.onKeyUp(keyCode, event); + } + + /** + * Handles a context item selection. + * + * @param itemId the menu item id + * @param positions the positions of the selected rows + * @return true if handled. + */ + private boolean handleContextItem(int itemId, int[] positions) { + if (positions.length != 1) { + return false; + } + Map item = arrayAdapter.getItem(positions[0]); Long trackId = (Long) item.get(TRACK_ID_FIELD); Long markerId = (Long) item.get(MARKER_ID_FIELD); Intent intent; - if (markerId != null) { - intent = IntentUtils.newIntent(SearchListActivity.this, TrackDetailActivity.class) - .putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerId); - } else { - intent = IntentUtils.newIntent(SearchListActivity.this, TrackDetailActivity.class) - .putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId); + switch (itemId) { + case R.id.list_context_menu_share: + //TODO + Log.e(TAG, "Not implemented"); + return true; + case R.id.list_context_menu_show_on_map: + intent = IntentUtils.newShowOnMapIntent((double) item.get(MARKER_LATITUDE_FIELD), (double) item.get(MARKER_LONGITUDE_FIELD), item.get(NAME_FIELD) + ""); + startActivity(intent); + return true; + case R.id.list_context_menu_edit: + if (markerId != null) { + intent = IntentUtils.newIntent(this, MarkerEditActivity.class) + .putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerId); + } else { + intent = IntentUtils.newIntent(this, TrackEditActivity.class) + .putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId); + } + startActivity(intent); + + // Close the search result since its content can change after edit. + finish(); + return true; + case R.id.list_context_menu_delete: + if (markerId != null) { + DeleteMarkerDialogFragment.newInstance(new long[]{markerId}).show(getSupportFragmentManager(), DeleteMarkerDialogFragment.DELETE_MARKER_DIALOG_TAG); + } else { + deleteTracks(new long[]{trackId}); + } + return true; + default: + return false; } - startActivity(intent); - } - }); - AbstractTrackActivity.configureListViewContextualMenu(listView, contextualActionModeCallback); - handleIntent(getIntent()); - } - - @Override - protected void onStart() { - super.onStart(); - sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); - sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null); - TrackRecordingServiceConnectionUtils.startConnection(this, trackRecordingServiceConnection); - } - - @Override - protected void onResume() { - super.onResume(); - arrayAdapter.notifyDataSetChanged(); - } - - @Override - protected void onStop() { - super.onStop(); - sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); - trackRecordingServiceConnection.unbind(); - } - - @Override - protected int getLayoutResId() { - return R.layout.search_list; - } - - @Override - public void onNewIntent(Intent intent) { - setIntent(intent); - handleIntent(intent); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.search_list, menu); - menu.findItem(R.id.search_list_search).setVisible(false); - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.search_list_search) { - return false; - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - getMenuInflater().inflate(R.menu.list_context_menu, menu); - - AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; - contextualActionModeCallback.onPrepare(menu, new int[] { info.position }, new long[] { info.id }, false); - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); - if (handleContextItem(item.getItemId(), new int[] {info.position})) { - return true; - } - return super.onContextItemSelected(item); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_SEARCH) { - return true; - } - return super.onKeyUp(keyCode, event); - } - - /** - * Handles a context item selection. - * - * @param itemId the menu item id - * @param positions the positions of the selected rows - * @return true if handled. - */ - private boolean handleContextItem(int itemId, int[] positions) { - if (positions.length != 1) { - return false; - } - Map item = arrayAdapter.getItem(positions[0]); - Long trackId = (Long) item.get(TRACK_ID_FIELD); - Long markerId = (Long) item.get(MARKER_ID_FIELD); - Intent intent; - switch (itemId) { - case R.id.list_context_menu_share: - //TODO - Log.e(TAG, "Not implemented"); - return true; - case R.id.list_context_menu_show_on_map: - intent = IntentUtils.newShowOnMapIntent((double)item.get(MARKER_LATITUDE_FIELD), (double)item.get(MARKER_LONGITUDE_FIELD), item.get(NAME_FIELD) + ""); - startActivity(intent); - return true; - case R.id.list_context_menu_edit: - if (markerId != null) { - intent = IntentUtils.newIntent(this, MarkerEditActivity.class) - .putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerId); - } else { - intent = IntentUtils.newIntent(this, TrackEditActivity.class) - .putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId); - } - startActivity(intent); - - // Close the search result since its content can change after edit. - finish(); - return true; - case R.id.list_context_menu_delete: - if (markerId != null) { - DeleteMarkerDialogFragment.newInstance(new long[] { markerId }).show(getSupportFragmentManager(), DeleteMarkerDialogFragment.DELETE_MARKER_DIALOG_TAG); - } else { - deleteTracks(new long[] { trackId }); - } - return true; - default: - return false; - } - } - - /** - * Handles the intent. - * - * @param intent the intent - */ - private void handleIntent(Intent intent) { - if (!Intent.ACTION_SEARCH.equals(intent.getAction())) { - Log.e(TAG, "Invalid intent action: " + intent); - finish(); - return; } - final String textQuery = intent.getStringExtra(SearchManager.QUERY); - setTitle(textQuery); - - doSearch(textQuery); - } - - /** - * Do the search. - * - * @param textQuery the query - */ - private void doSearch(String textQuery) { - SearchQuery query = new SearchQuery(textQuery, null, -1L, System.currentTimeMillis()); - SortedSet scoredResults = searchEngine.search(query); - final List> displayResults = prepareResultsforDisplay(scoredResults); - - // Use the UI thread to display the results - runOnUiThread(new Runnable() { - @Override - public void run() { - arrayAdapter.clear(); - arrayAdapter.addAll(displayResults); - } - }); - - // Save the query as a suggestion for the future - searchRecentSuggestions.saveRecentQuery(query.textQuery, null); - } - - /** - * Prepares the result for display. - * - * @param scoredResults a list of score results - * @return a list of result maps - */ - private List> prepareResultsforDisplay(Collection scoredResults) { - //TODO Replace use of map, but rather provide Track or Waypoint directly. - ArrayList> output = new ArrayList<>(scoredResults.size()); - for (ScoredResult result : scoredResults) { - Map resultMap = new HashMap<>(); - if (result.track != null) { - prepareTrackForDisplay(result.track, resultMap); - } else { - prepareMarkerForDisplay(result.waypoint, resultMap); - } - output.add(resultMap); - } - return output; - } - - /** - * Prepares a marker for display by filling in a result map. - * - * @param waypoint the marker - * @param resultMap the result map - */ - @Deprecated - private void prepareMarkerForDisplay(Waypoint waypoint, Map resultMap) { - /* - * TODO: It may be more appropriate to obtain the track name as a join in - * the retrieval phase of the search. + /** + * Handles the intent. + * + * @param intent the intent */ - String trackName = null; - long trackId = waypoint.getTrackId(); - if (trackId != -1L) { - Track track = contentProviderUtils.getTrack(trackId); - if (track != null) { - trackName = track.getName(); - } + private void handleIntent(Intent intent) { + if (!Intent.ACTION_SEARCH.equals(intent.getAction())) { + Log.e(TAG, "Invalid intent action: " + intent); + finish(); + return; + } + + final String textQuery = intent.getStringExtra(SearchManager.QUERY); + setTitle(textQuery); + + doSearch(textQuery); } - boolean statistics = waypoint.getType() == WaypointType.STATISTICS; + /** + * Do the search. + * + * @param textQuery the query + */ + private void doSearch(String textQuery) { + SearchQuery query = new SearchQuery(textQuery, null, -1L, System.currentTimeMillis()); + SortedSet scoredResults = searchEngine.search(query); + final List> displayResults = prepareResultsforDisplay(scoredResults); - resultMap.put(IS_RECORDING_FIELD, false); - resultMap.put(IS_PAUSED_FIELD, true); - resultMap.put(ICON_ID_FIELD, statistics ? R.drawable.ic_marker_yellow_pushpin : R.drawable.ic_marker_blue_pushpin); - resultMap.put(ICON_CONTENT_DESCRIPTION_ID_FIELD, R.string.image_marker); - resultMap.put(NAME_FIELD, waypoint.getName()); - // Display the marker's track name in the total time field - resultMap.put(TOTAL_TIME_FIELD, trackName == null ? null : getString(R.string.search_list_marker_track_location, trackName)); - resultMap.put(TOTAL_DISTANCE_FIELD, null); - resultMap.put(MARKER_COUNT_FIELD, 0); - resultMap.put(START_TIME_FIELD, waypoint.getLocation().getTime()); - resultMap.put(CATEGORY_FIELD, statistics ? null : waypoint.getCategory()); - resultMap.put(DESCRIPTION_FIELD, statistics ? null : waypoint.getDescription()); - resultMap.put(PHOTO_URL_FIELD, waypoint.getPhotoUrl()); - resultMap.put(TRACK_ID_FIELD, waypoint.getTrackId()); - resultMap.put(MARKER_ID_FIELD, waypoint.getId()); + // Use the UI thread to display the results + runOnUiThread(new Runnable() { + @Override + public void run() { + arrayAdapter.clear(); + arrayAdapter.addAll(displayResults); + } + }); - resultMap.put(MARKER_LATITUDE_FIELD, waypoint.getLocation().getLatitude()); - resultMap.put(MARKER_LONGITUDE_FIELD, waypoint.getLocation().getLongitude()); - } + // Save the query as a suggestion for the future + searchRecentSuggestions.saveRecentQuery(query.textQuery, null); + } - /** - * Prepares a track for display by filling in a result map. - * - * @param track the track - * @param resultMap the result map - */ - @Deprecated - private void prepareTrackForDisplay(Track track, Map resultMap) { - TripStatistics tripStatitics = track.getTripStatistics(); - String icon = track.getIcon(); - String category = icon != null && !icon.equals("") ? null : track.getCategory(); + /** + * Prepares the result for display. + * + * @param scoredResults a list of score results + * @return a list of result maps + */ + private List> prepareResultsforDisplay(Collection scoredResults) { + //TODO Replace use of map, but rather provide Track or Waypoint directly. + ArrayList> output = new ArrayList<>(scoredResults.size()); + for (ScoredResult result : scoredResults) { + Map resultMap = new HashMap<>(); + if (result.track != null) { + prepareTrackForDisplay(result.track, resultMap); + } else { + prepareMarkerForDisplay(result.waypoint, resultMap); + } + output.add(resultMap); + } + return output; + } - resultMap.put(IS_RECORDING_FIELD, track.getId() == recordingTrackId); - resultMap.put(IS_PAUSED_FIELD, recordingTrackPaused); - resultMap.put(ICON_ID_FIELD, TrackIconUtils.getIconDrawable(icon)); - resultMap.put(ICON_CONTENT_DESCRIPTION_ID_FIELD, R.string.image_track); - resultMap.put(NAME_FIELD, track.getName()); - resultMap.put(TOTAL_TIME_FIELD, StringUtils.formatElapsedTime(tripStatitics.getTotalTime())); - resultMap.put(TOTAL_DISTANCE_FIELD, StringUtils.formatDistance(this, tripStatitics.getTotalDistance(), metricUnits)); - resultMap.put(MARKER_COUNT_FIELD, contentProviderUtils.getWaypointCount(track.getId())); - resultMap.put(START_TIME_FIELD, tripStatitics.getStartTime()); - resultMap.put(CATEGORY_FIELD, category); - resultMap.put(DESCRIPTION_FIELD, track.getDescription()); - resultMap.put(PHOTO_URL_FIELD, null); - resultMap.put(TRACK_ID_FIELD, track.getId()); - resultMap.put(MARKER_ID_FIELD, null); - } + /** + * Prepares a marker for display by filling in a result map. + * + * @param waypoint the marker + * @param resultMap the result map + */ + @Deprecated + private void prepareMarkerForDisplay(Waypoint waypoint, Map resultMap) { + /* + * TODO: It may be more appropriate to obtain the track name as a join in + * the retrieval phase of the search. + */ + String trackName = null; + long trackId = waypoint.getTrackId(); + if (trackId != -1L) { + Track track = contentProviderUtils.getTrack(trackId); + if (track != null) { + trackName = track.getName(); + } + } - @Override - public void onDeleteMarkerDone() { - runOnUiThread(new Runnable() { - @Override - public void run() { - handleIntent(getIntent()); - } - }); - } - - @Override - protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() { - return trackRecordingServiceConnection; - } + boolean statistics = waypoint.getType() == WaypointType.STATISTICS; - @Override - protected void onDeleted() { - runOnUiThread(new Runnable() { - @Override - public void run() { - handleIntent(getIntent()); - } - }); - } + resultMap.put(IS_RECORDING_FIELD, false); + resultMap.put(IS_PAUSED_FIELD, true); + resultMap.put(ICON_ID_FIELD, statistics ? R.drawable.ic_marker_yellow_pushpin : R.drawable.ic_marker_blue_pushpin); + resultMap.put(ICON_CONTENT_DESCRIPTION_ID_FIELD, R.string.image_marker); + resultMap.put(NAME_FIELD, waypoint.getName()); + // Display the marker's track name in the total time field + resultMap.put(TOTAL_TIME_FIELD, trackName == null ? null : getString(R.string.search_list_marker_track_location, trackName)); + resultMap.put(TOTAL_DISTANCE_FIELD, null); + resultMap.put(MARKER_COUNT_FIELD, 0); + resultMap.put(START_TIME_FIELD, waypoint.getLocation().getTime()); + resultMap.put(CATEGORY_FIELD, statistics ? null : waypoint.getCategory()); + resultMap.put(DESCRIPTION_FIELD, statistics ? null : waypoint.getDescription()); + resultMap.put(PHOTO_URL_FIELD, waypoint.getPhotoUrl()); + resultMap.put(TRACK_ID_FIELD, waypoint.getTrackId()); + resultMap.put(MARKER_ID_FIELD, waypoint.getId()); + + resultMap.put(MARKER_LATITUDE_FIELD, waypoint.getLocation().getLatitude()); + resultMap.put(MARKER_LONGITUDE_FIELD, waypoint.getLocation().getLongitude()); + } + + /** + * Prepares a track for display by filling in a result map. + * + * @param track the track + * @param resultMap the result map + */ + @Deprecated + private void prepareTrackForDisplay(Track track, Map resultMap) { + TripStatistics tripStatitics = track.getTripStatistics(); + String icon = track.getIcon(); + String category = icon != null && !icon.equals("") ? null : track.getCategory(); + + resultMap.put(IS_RECORDING_FIELD, track.getId() == recordingTrackId); + resultMap.put(IS_PAUSED_FIELD, recordingTrackPaused); + resultMap.put(ICON_ID_FIELD, TrackIconUtils.getIconDrawable(icon)); + resultMap.put(ICON_CONTENT_DESCRIPTION_ID_FIELD, R.string.image_track); + resultMap.put(NAME_FIELD, track.getName()); + resultMap.put(TOTAL_TIME_FIELD, StringUtils.formatElapsedTime(tripStatitics.getTotalTime())); + resultMap.put(TOTAL_DISTANCE_FIELD, StringUtils.formatDistance(this, tripStatitics.getTotalDistance(), metricUnits)); + resultMap.put(MARKER_COUNT_FIELD, contentProviderUtils.getWaypointCount(track.getId())); + resultMap.put(START_TIME_FIELD, tripStatitics.getStartTime()); + resultMap.put(CATEGORY_FIELD, category); + resultMap.put(DESCRIPTION_FIELD, track.getDescription()); + resultMap.put(PHOTO_URL_FIELD, null); + resultMap.put(TRACK_ID_FIELD, track.getId()); + resultMap.put(MARKER_ID_FIELD, null); + } + + @Override + public void onDeleteMarkerDone() { + runOnUiThread(new Runnable() { + @Override + public void run() { + handleIntent(getIntent()); + } + }); + } + + @Override + protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() { + return trackRecordingServiceConnection; + } + + @Override + protected void onDeleted() { + runOnUiThread(new Runnable() { + @Override + public void run() { + handleIntent(getIntent()); + } + }); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/TabsAdapter.java b/src/main/java/com/google/android/apps/mytracks/TabsAdapter.java index b27c8a3ab..69b78f628 100644 --- a/src/main/java/com/google/android/apps/mytracks/TabsAdapter.java +++ b/src/main/java/com/google/android/apps/mytracks/TabsAdapter.java @@ -18,16 +18,17 @@ package com.google.android.apps.mytracks; import android.content.Context; import android.os.Bundle; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentActivity; -import androidx.fragment.app.FragmentPagerAdapter; -import androidx.viewpager.widget.ViewPager; import android.view.View; import android.view.ViewGroup; import android.widget.TabHost; import android.widget.TabHost.TabContentFactory; import android.widget.TabWidget; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentPagerAdapter; +import androidx.viewpager.widget.ViewPager; + import java.util.ArrayList; /** @@ -41,110 +42,112 @@ import java.util.ArrayList; * the ViewPager whenever the selected tab changes. *

* Copied from the FragmentTabsPager sample in the support library. - * + * * @author Jimmy Shih */ //TODO Rework or better remove. public class TabsAdapter extends FragmentPagerAdapter implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener { - private final Context context; - private final TabHost tabHost; - private final ViewPager viewPager; - private final ArrayList tabInfos = new ArrayList<>(); - - /** - * An object to hold a tab's info. - * - * @author Jimmy Shih - */ - private static final class TabInfo { - - private final Class clss; - private final Bundle bundle; - - public TabInfo(Class clss, Bundle bunlde) { - this.clss = clss; - this.bundle = bunlde; - } - } - - /** - * A dummy {@link TabContentFactory} that creates an empty view to satisfy the - * {@link TabHost} API. - * - * @author Jimmy Shih - */ - private static class DummyTabFactory implements TabHost.TabContentFactory { - private final Context context; + private final TabHost tabHost; + private final ViewPager viewPager; + private final ArrayList tabInfos = new ArrayList<>(); - public DummyTabFactory(Context context) { - this.context = context; + public TabsAdapter(FragmentActivity activity, TabHost tabHost, ViewPager viewPager) { + super(activity.getSupportFragmentManager()); + this.context = activity; + this.tabHost = tabHost; + this.viewPager = viewPager; + this.tabHost.setOnTabChangedListener(this); + this.viewPager.setAdapter(this); + this.viewPager.setOnPageChangeListener(this); + } + + public void addTab(TabHost.TabSpec tabSpec, Class clss, Bundle bundle) { + tabSpec.setContent(new DummyTabFactory(context)); + + TabInfo info = new TabInfo(clss, bundle); + + tabInfos.add(info); + tabHost.addTab(tabSpec); + notifyDataSetChanged(); } @Override - public View createTabContent(String tag) { - View view = new View(context); - view.setMinimumWidth(0); - view.setMinimumHeight(0); - return view; + public int getCount() { + return tabInfos.size(); } - } - public TabsAdapter(FragmentActivity activity, TabHost tabHost, ViewPager viewPager) { - super(activity.getSupportFragmentManager()); - this.context = activity; - this.tabHost = tabHost; - this.viewPager = viewPager; - this.tabHost.setOnTabChangedListener(this); - this.viewPager.setAdapter(this); - this.viewPager.setOnPageChangeListener(this); - } + @Override + public Fragment getItem(int position) { + TabInfo info = tabInfos.get(position); + return Fragment.instantiate(context, info.clss.getName(), info.bundle); + } - public void addTab(TabHost.TabSpec tabSpec, Class clss, Bundle bundle) { - tabSpec.setContent(new DummyTabFactory(context)); + @Override + public void onTabChanged(String tabId) { + int position = tabHost.getCurrentTab(); + viewPager.setCurrentItem(position); + } - TabInfo info = new TabInfo(clss, bundle); - - tabInfos.add(info); - tabHost.addTab(tabSpec); - notifyDataSetChanged(); - } + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + } - @Override - public int getCount() { - return tabInfos.size(); - } + @Override + public void onPageSelected(int position) { + /* + * Unfortunately when TabHost changes the current tab, it kindly also takes + * care of putting focus on it when not in touch mode. The jerk. This hack + * tries to prevent this from pulling focus out of our ViewPager. + */ + TabWidget tabWidget = tabHost.getTabWidget(); + int oldFocusability = tabWidget.getDescendantFocusability(); + tabWidget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + tabHost.setCurrentTab(position); + tabWidget.setDescendantFocusability(oldFocusability); + } - @Override - public Fragment getItem(int position) { - TabInfo info = tabInfos.get(position); - return Fragment.instantiate(context, info.clss.getName(), info.bundle); - } + @Override + public void onPageScrollStateChanged(int state) { + } - @Override - public void onTabChanged(String tabId) { - int position = tabHost.getCurrentTab(); - viewPager.setCurrentItem(position); - } - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {} - - @Override - public void onPageSelected(int position) { - /* - * Unfortunately when TabHost changes the current tab, it kindly also takes - * care of putting focus on it when not in touch mode. The jerk. This hack - * tries to prevent this from pulling focus out of our ViewPager. + /** + * An object to hold a tab's info. + * + * @author Jimmy Shih */ - TabWidget tabWidget = tabHost.getTabWidget(); - int oldFocusability = tabWidget.getDescendantFocusability(); - tabWidget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); - tabHost.setCurrentTab(position); - tabWidget.setDescendantFocusability(oldFocusability); - } + private static final class TabInfo { - @Override - public void onPageScrollStateChanged(int state) {} + private final Class clss; + private final Bundle bundle; + + public TabInfo(Class clss, Bundle bunlde) { + this.clss = clss; + this.bundle = bunlde; + } + } + + /** + * A dummy {@link TabContentFactory} that creates an empty view to satisfy the + * {@link TabHost} API. + * + * @author Jimmy Shih + */ + private static class DummyTabFactory implements TabHost.TabContentFactory { + + private final Context context; + + public DummyTabFactory(Context context) { + this.context = context; + } + + @Override + public View createTabContent(String tag) { + View view = new View(context); + view.setMinimumWidth(0); + view.setMinimumHeight(0); + return view; + } + } } diff --git a/src/main/java/com/google/android/apps/mytracks/TrackController.java b/src/main/java/com/google/android/apps/mytracks/TrackController.java index db19580df..39bb92c27 100644 --- a/src/main/java/com/google/android/apps/mytracks/TrackController.java +++ b/src/main/java/com/google/android/apps/mytracks/TrackController.java @@ -31,119 +31,119 @@ import com.google.android.maps.mytracks.R; /** * Track controller for record, pause, resume, and stop. - * + * * @author Jimmy Shih */ public class TrackController { - private static final String TAG = TrackController.class.getSimpleName(); + private static final String TAG = TrackController.class.getSimpleName(); - private final Activity activity; - private final TrackRecordingServiceConnection trackRecordingServiceConnection; - private final Handler handlerUpdateTotalTime; - private final View containerView; - private final TextView statusTextView; - private final TextView totalTimeTextView; - private final ImageButton recordImageButton; - private final ImageButton stopImageButton; - private final boolean alwaysShow; + private final Activity activity; + private final TrackRecordingServiceConnection trackRecordingServiceConnection; + private final Handler handlerUpdateTotalTime; + private final View containerView; + private final TextView statusTextView; + private final TextView totalTimeTextView; + private final ImageButton recordImageButton; + private final ImageButton stopImageButton; + private final boolean alwaysShow; - private boolean isRecording; - private boolean isPaused; - private long totalTime = 0; + private boolean isRecording; + private boolean isPaused; + private long totalTime = 0; - // the timestamp for the toal time - private long totalTimeTimestamp = 0; + // the timestamp for the toal time + private long totalTimeTimestamp = 0; - private boolean isResumed = false; + private boolean isResumed = false; - // A runnable to update the total time. - private final Runnable updateTotalTimeRunnable = new Runnable() { - public void run() { - if (isResumed && isRecording && !isPaused) { - totalTimeTextView.setText(StringUtils.formatElapsedTimeWithHour(System.currentTimeMillis() - totalTimeTimestamp + totalTime)); - handlerUpdateTotalTime.postDelayed(this, UnitConversions.ONE_SECOND); - } - } - }; + // A runnable to update the total time. + private final Runnable updateTotalTimeRunnable = new Runnable() { + public void run() { + if (isResumed && isRecording && !isPaused) { + totalTimeTextView.setText(StringUtils.formatElapsedTimeWithHour(System.currentTimeMillis() - totalTimeTimestamp + totalTime)); + handlerUpdateTotalTime.postDelayed(this, UnitConversions.ONE_SECOND); + } + } + }; - public TrackController(Activity activity, TrackRecordingServiceConnection trackRecordingServiceConnection, boolean alwaysShow, OnClickListener recordListener, OnClickListener stopListener) { - this.activity = activity; - this.trackRecordingServiceConnection = trackRecordingServiceConnection; - this.alwaysShow = alwaysShow; - handlerUpdateTotalTime = new Handler(); - containerView = activity.findViewById(R.id.track_controller_container); - statusTextView = activity.findViewById(R.id.track_controller_status); - totalTimeTextView = activity.findViewById(R.id.track_controller_total_time); + public TrackController(Activity activity, TrackRecordingServiceConnection trackRecordingServiceConnection, boolean alwaysShow, OnClickListener recordListener, OnClickListener stopListener) { + this.activity = activity; + this.trackRecordingServiceConnection = trackRecordingServiceConnection; + this.alwaysShow = alwaysShow; + handlerUpdateTotalTime = new Handler(); + containerView = activity.findViewById(R.id.track_controller_container); + statusTextView = activity.findViewById(R.id.track_controller_status); + totalTimeTextView = activity.findViewById(R.id.track_controller_total_time); - recordImageButton = activity.findViewById(R.id.track_controller_record); - recordImageButton.setOnClickListener(recordListener); + recordImageButton = activity.findViewById(R.id.track_controller_record); + recordImageButton.setOnClickListener(recordListener); - stopImageButton = activity.findViewById(R.id.track_controller_stop); - stopImageButton.setOnClickListener(stopListener); - } - - public void update(boolean recording, boolean paused) { - if (!isResumed) { - return; - } - isRecording = recording; - isPaused = paused; - boolean visible = alwaysShow || isRecording; - containerView.setVisibility(visible ? View.VISIBLE : View.GONE); - - if (!visible) { - handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable); - return; + stopImageButton = activity.findViewById(R.id.track_controller_stop); + stopImageButton.setOnClickListener(stopListener); } - recordImageButton.setImageResource(isRecording && !isPaused ? R.drawable.button_pause : R.drawable.button_record); - recordImageButton.setContentDescription(activity.getString(isRecording && !isPaused ? R.string.image_pause : R.string.image_record)); + public void update(boolean recording, boolean paused) { + if (!isResumed) { + return; + } + isRecording = recording; + isPaused = paused; + boolean visible = alwaysShow || isRecording; + containerView.setVisibility(visible ? View.VISIBLE : View.GONE); - stopImageButton.setImageResource(isRecording ? R.drawable.button_stop : R.drawable.ic_button_stop_disabled); - stopImageButton.setEnabled(isRecording); + if (!visible) { + handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable); + return; + } - statusTextView.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE); - if (isRecording) { - statusTextView.setTextColor(activity.getResources().getColor(isPaused ? android.R.color.white : R.color.recording_text)); - statusTextView.setText(isPaused ? R.string.generic_paused : R.string.generic_recording); + recordImageButton.setImageResource(isRecording && !isPaused ? R.drawable.button_pause : R.drawable.button_record); + recordImageButton.setContentDescription(activity.getString(isRecording && !isPaused ? R.string.image_pause : R.string.image_record)); + + stopImageButton.setImageResource(isRecording ? R.drawable.button_stop : R.drawable.ic_button_stop_disabled); + stopImageButton.setEnabled(isRecording); + + statusTextView.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE); + if (isRecording) { + statusTextView.setTextColor(activity.getResources().getColor(isPaused ? android.R.color.white : R.color.recording_text)); + statusTextView.setText(isPaused ? R.string.generic_paused : R.string.generic_recording); + } + + handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable); + totalTimeTextView.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE); + if (isRecording) { + totalTime = getTotalTime(); + totalTimeTextView.setText(StringUtils.formatElapsedTimeWithHour(totalTime)); + if (!isPaused) { + totalTimeTimestamp = System.currentTimeMillis(); + handlerUpdateTotalTime.postDelayed(updateTotalTimeRunnable, UnitConversions.ONE_SECOND); + } + } } - handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable); - totalTimeTextView.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE); - if (isRecording) { - totalTime = getTotalTime(); - totalTimeTextView.setText(StringUtils.formatElapsedTimeWithHour(totalTime)); - if (!isPaused) { - totalTimeTimestamp = System.currentTimeMillis(); - handlerUpdateTotalTime.postDelayed(updateTotalTimeRunnable, UnitConversions.ONE_SECOND); - } + public void onResume(boolean recording, boolean paused) { + isResumed = true; + update(recording, paused); } - } - public void onResume(boolean recording, boolean paused) { - isResumed = true; - update(recording, paused); - } + public void onPause() { + isResumed = false; + handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable); + } - public void onPause() { - isResumed = false; - handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable); - } + public void hide() { + containerView.setVisibility(View.GONE); + } - public void hide() { - containerView.setVisibility(View.GONE); - } + public void show() { + containerView.setVisibility(View.VISIBLE); + } - public void show() { - containerView.setVisibility(View.VISIBLE); - } - - /** - * Gets the total time for the current recording track. - */ - private long getTotalTime() { - ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound(); - return trackRecordingService != null ? trackRecordingService.getTotalTime() : 0L; - } + /** + * Gets the total time for the current recording track. + */ + private long getTotalTime() { + ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound(); + return trackRecordingService != null ? trackRecordingService.getTotalTime() : 0L; + } } diff --git a/src/main/java/com/google/android/apps/mytracks/TrackDetailActivity.java b/src/main/java/com/google/android/apps/mytracks/TrackDetailActivity.java index 3ffd091c2..5bbb71a51 100644 --- a/src/main/java/com/google/android/apps/mytracks/TrackDetailActivity.java +++ b/src/main/java/com/google/android/apps/mytracks/TrackDetailActivity.java @@ -70,451 +70,445 @@ import java.util.Date; /** * An activity to show the track detail. - * + * * @author Leif Hendrik Wilden * @author Rodrigo Damazio */ public class TrackDetailActivity extends AbstractTrackActivity implements ChooseActivityTypeCaller, ConfirmDeleteDialogFragment.ConfirmDeleteCaller { - private static final String TAG = TrackDetailActivity.class.getSimpleName(); + public static final String EXTRA_TRACK_ID = "track_id"; + public static final String EXTRA_MARKER_ID = "marker_id"; + private static final String TAG = TrackDetailActivity.class.getSimpleName(); + private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key"; + private static final String PHOTO_URI_KEY = "photo_uri_key"; + private static final String HAS_PHOTO_KEY = "has_photo_key"; + private static final String JPEG_EXTENSION = "jpeg"; - public static final String EXTRA_TRACK_ID = "track_id"; - public static final String EXTRA_MARKER_ID = "marker_id"; + private static final int CAMERA_REQUEST_CODE = 5; + private static final int EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 6; - private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key"; - private static final String PHOTO_URI_KEY = "photo_uri_key"; - private static final String HAS_PHOTO_KEY = "has_photo_key"; - private static final String JPEG_EXTENSION = "jpeg"; + // The following are set in onCreate + private boolean hasCamera; + private Uri photoUri; + private boolean hasPhoto; + private ContentProviderUtils contentProviderUtils; + private SharedPreferences sharedPreferences; + private TrackRecordingServiceConnection trackRecordingServiceConnection; + private TrackDataHub trackDataHub; + private TabHost tabHost; + private TrackController trackController; - private static final int CAMERA_REQUEST_CODE = 5; - private static final int EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 6; + // From intent + private long trackId; - // The following are set in onCreate - private boolean hasCamera; - private Uri photoUri; - private boolean hasPhoto; - private ContentProviderUtils contentProviderUtils; - private SharedPreferences sharedPreferences; - private TrackRecordingServiceConnection trackRecordingServiceConnection; - private TrackDataHub trackDataHub; - private TabHost tabHost; - private TrackController trackController; - - // From intent - private long trackId; - - // Preferences - private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; - private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT; - - private MenuItem insertMarkerMenuItem; - private MenuItem insertPhotoMenuItem; - private MenuItem markerListMenuItem; - private MenuItem shareMenuItem; - private MenuItem voiceFrequencyMenuItem; - private MenuItem splitFrequencyMenuItem; - - private final Runnable bindChangedCallback = new Runnable() { - @Override - public void run() { - // After binding changes (is available), update the total time in - // trackController. - runOnUiThread(new Runnable() { - @Override + // Preferences + private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; + private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT; + private final Runnable bindChangedCallback = new Runnable() { + @Override public void run() { - trackController.update(isRecording(), recordingTrackPaused); - if (hasPhoto && photoUri != null) { - hasPhoto = false; - WaypointCreationRequest waypointCreationRequest = new WaypointCreationRequest(WaypointType.WAYPOINT, false, null, null, null, null, photoUri.toString()); - long markerId = TrackRecordingServiceConnectionUtils.addMarker(TrackDetailActivity.this, trackRecordingServiceConnection, waypointCreationRequest); - if (markerId != -1L) { - FileUtils.updateMediaScanner(TrackDetailActivity.this, photoUri); - } - } + // After binding changes (is available), update the total time in + // trackController. + runOnUiThread(new Runnable() { + @Override + public void run() { + trackController.update(isRecording(), recordingTrackPaused); + if (hasPhoto && photoUri != null) { + hasPhoto = false; + WaypointCreationRequest waypointCreationRequest = new WaypointCreationRequest(WaypointType.WAYPOINT, false, null, null, null, null, photoUri.toString()); + long markerId = TrackRecordingServiceConnectionUtils.addMarker(TrackDetailActivity.this, trackRecordingServiceConnection, waypointCreationRequest); + if (markerId != -1L) { + FileUtils.updateMediaScanner(TrackDetailActivity.this, photoUri); + } + } + } + }); } - }); - } - }; - - /* - * Note that sharedPreferenceChangeListener cannot be an anonymous inner - * class. Anonymous inner class will get garbage collected. - */ - private final OnSharedPreferenceChangeListener - sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { - @Override + }; + /* + * Note that sharedPreferenceChangeListener cannot be an anonymous inner + * class. Anonymous inner class will get garbage collected. + */ + private final OnSharedPreferenceChangeListener + sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { + @Override public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { - if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.recording_track_id_key))) { - recordingTrackId = PreferencesUtils.getLong(TrackDetailActivity.this, R.string.recording_track_id_key); - } + if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.recording_track_id_key))) { + recordingTrackId = PreferencesUtils.getLong(TrackDetailActivity.this, R.string.recording_track_id_key); + } - if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.recording_track_paused_key))) { - recordingTrackPaused = PreferencesUtils.getBoolean(TrackDetailActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); - } + if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.recording_track_paused_key))) { + recordingTrackPaused = PreferencesUtils.getBoolean(TrackDetailActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); + } - if (key == null) return; + if (key == null) return; - runOnUiThread(new Runnable() { + runOnUiThread(new Runnable() { + @Override + public void run() { + TrackDetailActivity.this.invalidateOptionsMenu(); + boolean isRecording = isRecording(); + trackController.update(isRecording, recordingTrackPaused); + } + }); + } + }; + private MenuItem insertMarkerMenuItem; + private MenuItem insertPhotoMenuItem; + private MenuItem markerListMenuItem; + private MenuItem shareMenuItem; + private MenuItem voiceFrequencyMenuItem; + private MenuItem splitFrequencyMenuItem; + private final OnClickListener recordListener = new OnClickListener() { + @Override + public void onClick(View v) { + if (recordingTrackPaused) { + // Paused -> Resume + updateMenuItems(false); + TrackRecordingServiceConnectionUtils.resumeTrack(trackRecordingServiceConnection); + trackController.update(true, false); + } else { + // Recording -> Paused + updateMenuItems(true); + TrackRecordingServiceConnectionUtils.pauseTrack(trackRecordingServiceConnection); + trackController.update(true, true); + } + } + }; + + private final OnClickListener stopListener = new OnClickListener() { + @Override + public void onClick(View v) { + TrackRecordingServiceConnectionUtils.stopRecording(TrackDetailActivity.this, trackRecordingServiceConnection, true); + updateMenuItems(true); + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA); + photoUri = savedInstanceState != null ? (Uri) savedInstanceState.getParcelable(PHOTO_URI_KEY) : null; + hasPhoto = savedInstanceState != null && savedInstanceState.getBoolean(HAS_PHOTO_KEY, false); + + contentProviderUtils = ContentProviderUtils.Factory.get(this); + handleIntent(getIntent()); + + sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); + + trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback); + trackDataHub = TrackDataHub.newInstance(this); + + tabHost = findViewById(android.R.id.tabhost); + tabHost.setup(); + + ViewPager viewPager = findViewById(R.id.pager); + TabsAdapter tabsAdapter = new TabsAdapter(this, tabHost, viewPager); + + TabSpec statsTabSpec = tabHost.newTabSpec(StatsFragment.STATS_FRAGMENT_TAG).setIndicator(getString(R.string.track_detail_stats_tab), getResources().getDrawable(R.drawable.ic_tab_stats)); + tabsAdapter.addTab(statsTabSpec, StatsFragment.class, null); + + TabSpec chartTabSpec = tabHost.newTabSpec(ChartFragment.CHART_FRAGMENT_TAG).setIndicator(getString(R.string.track_detail_chart_tab), getResources().getDrawable(R.drawable.ic_tab_chart)); + tabsAdapter.addTab(chartTabSpec, ChartFragment.class, null); + + if (savedInstanceState != null) { + tabHost.setCurrentTabByTag(savedInstanceState.getString(CURRENT_TAB_TAG_KEY)); + } + + // Set the background after all three tabs are added + for (int i = 0; i < tabHost.getTabWidget().getChildCount(); i++) { + tabHost.getTabWidget().getChildAt(i).setBackgroundResource(R.drawable.tab_indicator_mytracks); + } + + trackController = new TrackController(this, trackRecordingServiceConnection, false, recordListener, stopListener); + } + + @Override + protected void onStart() { + super.onStart(); + + sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null); + + TrackRecordingServiceConnectionUtils.startConnection(this, trackRecordingServiceConnection); + trackDataHub.start(); + } + + @Override + protected void onResume() { + super.onResume(); + trackDataHub.loadTrack(trackId); + + // Update UI + this.invalidateOptionsMenu(); + trackController.onResume(isRecording(), recordingTrackPaused); + } + + @Override + protected void onPause() { + super.onPause(); + trackController.onPause(); + } + + @Override + protected void onStop() { + super.onStop(); + sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + trackRecordingServiceConnection.unbind(); + trackDataHub.stop(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(CURRENT_TAB_TAG_KEY, tabHost.getCurrentTabTag()); + if (photoUri != null) { + outState.putParcelable(PHOTO_URI_KEY, photoUri); + } + outState.putBoolean(HAS_PHOTO_KEY, hasPhoto); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == CAMERA_REQUEST_CODE) { + if (resultCode == RESULT_CANCELED) { + Toast.makeText(this, R.string.marker_add_canceled, Toast.LENGTH_LONG).show(); + return; + } + hasPhoto = resultCode == RESULT_OK; + + if (hasPhoto) { + //Register photo in media scanner + Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + mediaScanIntent.setData(photoUri); + this.sendBroadcast(mediaScanIntent); + return; + } + } + super.onActivityResult(requestCode, resultCode, data); + } + + @Override + protected int getLayoutResId() { + return R.layout.track_detail; + } + + @Override + protected void onHomeSelected() { + /* + * TODO: Investigate + * According to + * http://developer.android.com/training/implementing-navigation + * /ancestral.html, we should use NavUtils.shouldUpRecreateTask instead of + * always creating a new back stack. However, NavUtils.shouldUpRecreateTask + * seems to always return false. + */ + TaskStackBuilder.create(this).addParentStack(TrackDetailActivity.class).startActivities(); + finish(); + } + + @Override + public void onNewIntent(Intent intent) { + setIntent(intent); + handleIntent(intent); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.track_detail, menu); + + insertMarkerMenuItem = menu.findItem(R.id.track_detail_insert_marker); + insertPhotoMenuItem = menu.findItem(R.id.track_detail_insert_photo); + insertPhotoMenuItem.setVisible(new Intent(MediaStore.ACTION_IMAGE_CAPTURE).resolveActivity(getPackageManager()) != null); + + shareMenuItem = menu.findItem(R.id.track_detail_share); + ShareActionProvider shareActionProvider = (ShareActionProvider) shareMenuItem.getActionProvider(); + //TODO: Share the actual track when track is finished? How to get the file path or create a new file? + shareActionProvider.setShareIntent(IntentUtils.newShareFileIntent(this, trackId, "", TrackFileFormat.KML)); + + voiceFrequencyMenuItem = menu.findItem(R.id.track_detail_voice_frequency); + splitFrequencyMenuItem = menu.findItem(R.id.track_detail_split_frequency); + markerListMenuItem = menu.findItem(R.id.track_detail_markers); + + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + updateMenuItems(recordingTrackPaused); + return super.onPrepareOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + Intent intent; + switch (item.getItemId()) { + case R.id.track_detail_insert_marker: + intent = IntentUtils + .newIntent(this, MarkerEditActivity.class) + .putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId); + startActivity(intent); + return true; + case R.id.track_detail_insert_photo: + if (!FileUtils.isExternalStorageWriteable() || ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE); + return false; + } + takePicture(); + return true; + case R.id.track_detail_markers: + intent = IntentUtils.newIntent(this, MarkerListActivity.class) + .putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId); + startActivity(intent); + return true; + case R.id.track_detail_voice_frequency: + FrequencyDialogFragment.newInstance(R.string.voice_frequency_key, PreferencesUtils.VOICE_FREQUENCY_DEFAULT, R.string.menu_voice_frequency) + .show(getSupportFragmentManager(), FrequencyDialogFragment.FREQUENCY_DIALOG_TAG); + return true; + case R.id.track_detail_split_frequency: + FrequencyDialogFragment.newInstance(R.string.split_frequency_key, PreferencesUtils.SPLIT_FREQUENCY_DEFAULT, R.string.menu_split_frequency) + .show(getSupportFragmentManager(), FrequencyDialogFragment.FREQUENCY_DIALOG_TAG); + return true; + case R.id.track_detail_edit: + intent = IntentUtils.newIntent(this, TrackEditActivity.class) + .putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId); + startActivity(intent); + return true; + case R.id.track_detail_delete: + deleteTracks(new long[]{trackId}); + return true; + case R.id.track_detail_settings: + intent = IntentUtils.newIntent(this, SettingsActivity.class); + startActivity(intent); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + private void takePicture() { + File dir = FileUtils.getPhotoDir(trackId); + FileUtils.ensureDirectoryExists(dir); + + String fileName = SimpleDateFormat.getDateTimeInstance().format(new Date()); + File file = new File(dir, FileUtils.buildUniqueFileName(dir, fileName, JPEG_EXTENSION)); + + if (file != null) { + photoUri = FileProvider.getUriForFile(this, FileUtils.FILEPROVIDER, file); + Log.d(TAG, "Taking photo to URI: " + photoUri); + Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE) + .putExtra(MediaStore.EXTRA_OUTPUT, photoUri); + startActivityForResult(intent, CAMERA_REQUEST_CODE); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + if (requestCode == EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) { + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { + Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show(); + } else { + takePicture(); + } + return; + } + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + + @Override + protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() { + return trackRecordingServiceConnection; + } + + @Override + protected void onDeleted() { + runOnUiThread(new Runnable() { @Override public void run() { - TrackDetailActivity.this.invalidateOptionsMenu(); - boolean isRecording = isRecording(); - trackController.update(isRecording, recordingTrackPaused); + finish(); } - }); - } - }; - - private final OnClickListener recordListener = new OnClickListener() { - @Override - public void onClick(View v) { - if (recordingTrackPaused) { - // Paused -> Resume - updateMenuItems(false); - TrackRecordingServiceConnectionUtils.resumeTrack(trackRecordingServiceConnection); - trackController.update(true, false); - } else { - // Recording -> Paused - updateMenuItems(true); - TrackRecordingServiceConnectionUtils.pauseTrack(trackRecordingServiceConnection); - trackController.update(true, true); - } - } - }; - - private final OnClickListener stopListener = new OnClickListener() { - @Override - public void onClick(View v) { - TrackRecordingServiceConnectionUtils.stopRecording(TrackDetailActivity.this, trackRecordingServiceConnection, true); - updateMenuItems(true); - } - }; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA); - photoUri = savedInstanceState != null ? (Uri) savedInstanceState.getParcelable(PHOTO_URI_KEY) : null; - hasPhoto = savedInstanceState != null && savedInstanceState.getBoolean(HAS_PHOTO_KEY, false); - - contentProviderUtils = ContentProviderUtils.Factory.get(this); - handleIntent(getIntent()); - - sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); - - trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback); - trackDataHub = TrackDataHub.newInstance(this); - - tabHost = findViewById(android.R.id.tabhost); - tabHost.setup(); - - ViewPager viewPager = findViewById(R.id.pager); - TabsAdapter tabsAdapter = new TabsAdapter(this, tabHost, viewPager); - - TabSpec statsTabSpec = tabHost.newTabSpec(StatsFragment.STATS_FRAGMENT_TAG).setIndicator(getString(R.string.track_detail_stats_tab), getResources().getDrawable(R.drawable.ic_tab_stats)); - tabsAdapter.addTab(statsTabSpec, StatsFragment.class, null); - - TabSpec chartTabSpec = tabHost.newTabSpec(ChartFragment.CHART_FRAGMENT_TAG).setIndicator(getString(R.string.track_detail_chart_tab),getResources().getDrawable(R.drawable.ic_tab_chart)); - tabsAdapter.addTab(chartTabSpec, ChartFragment.class, null); - - if (savedInstanceState != null) { - tabHost.setCurrentTabByTag(savedInstanceState.getString(CURRENT_TAB_TAG_KEY)); - } - - // Set the background after all three tabs are added - for (int i = 0; i < tabHost.getTabWidget().getChildCount(); i++) { - tabHost.getTabWidget().getChildAt(i).setBackgroundResource(R.drawable.tab_indicator_mytracks); + }); } - trackController = new TrackController(this, trackRecordingServiceConnection, false, recordListener, stopListener); - } - - @Override - protected void onStart() { - super.onStart(); - - sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); - sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null); - - TrackRecordingServiceConnectionUtils.startConnection(this, trackRecordingServiceConnection); - trackDataHub.start(); - } - - @Override - protected void onResume() { - super.onResume(); - trackDataHub.loadTrack(trackId); - - // Update UI - this.invalidateOptionsMenu(); - trackController.onResume(isRecording(), recordingTrackPaused); - } - - @Override - protected void onPause() { - super.onPause(); - trackController.onPause(); - } - - @Override - protected void onStop() { - super.onStop(); - sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); - trackRecordingServiceConnection.unbind(); - trackDataHub.stop(); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putString(CURRENT_TAB_TAG_KEY, tabHost.getCurrentTabTag()); - if (photoUri != null) { - outState.putParcelable(PHOTO_URI_KEY, photoUri); - } - outState.putBoolean(HAS_PHOTO_KEY, hasPhoto); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == CAMERA_REQUEST_CODE) { - if (resultCode == RESULT_CANCELED) { - Toast.makeText(this, R.string.marker_add_canceled, Toast.LENGTH_LONG).show(); - return; - } - hasPhoto = resultCode == RESULT_OK; - - if (hasPhoto) { - //Register photo in media scanner - Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); - mediaScanIntent.setData(photoUri); - this.sendBroadcast(mediaScanIntent); - return; - } - } - super.onActivityResult(requestCode, resultCode, data); - } - - @Override - protected int getLayoutResId() { - return R.layout.track_detail; - } - - @Override - protected void onHomeSelected() { - /* - * TODO: Investigate - * According to - * http://developer.android.com/training/implementing-navigation - * /ancestral.html, we should use NavUtils.shouldUpRecreateTask instead of - * always creating a new back stack. However, NavUtils.shouldUpRecreateTask - * seems to always return false. + /** + * Gets the {@link TrackDataHub}. */ - TaskStackBuilder.create(this).addParentStack(TrackDetailActivity.class).startActivities(); - finish(); - } + public TrackDataHub getTrackDataHub() { + return trackDataHub; + } - @Override - public void onNewIntent(Intent intent) { - setIntent(intent); - handleIntent(intent); - } + /** + * Gets the track id. + */ + public long getTrackId() { + return trackId; + } - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.track_detail, menu); - - insertMarkerMenuItem = menu.findItem(R.id.track_detail_insert_marker); - insertPhotoMenuItem = menu.findItem(R.id.track_detail_insert_photo); - insertPhotoMenuItem.setVisible(new Intent(MediaStore.ACTION_IMAGE_CAPTURE).resolveActivity(getPackageManager()) != null); - - shareMenuItem = menu.findItem(R.id.track_detail_share); - ShareActionProvider shareActionProvider = (ShareActionProvider) shareMenuItem.getActionProvider(); - //TODO: Share the actual track when track is finished? How to get the file path or create a new file? - shareActionProvider.setShareIntent(IntentUtils.newShareFileIntent(this, trackId, "", TrackFileFormat.KML)); - - voiceFrequencyMenuItem = menu.findItem(R.id.track_detail_voice_frequency); - splitFrequencyMenuItem = menu.findItem(R.id.track_detail_split_frequency); - markerListMenuItem = menu.findItem(R.id.track_detail_markers); - - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - updateMenuItems(recordingTrackPaused); - return super.onPrepareOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - Intent intent; - switch (item.getItemId()) { - case R.id.track_detail_insert_marker: - intent = IntentUtils - .newIntent(this, MarkerEditActivity.class) - .putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId); - startActivity(intent); - return true; - case R.id.track_detail_insert_photo: - if (!FileUtils.isExternalStorageWriteable() || ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { - ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE); - return false; + /** + * Handles the data in the intent. + */ + private void handleIntent(Intent intent) { + trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L); + long markerId = intent.getLongExtra(EXTRA_MARKER_ID, -1L); + if (markerId != -1L) { + // Use the trackId from the marker + Waypoint waypoint = contentProviderUtils.getWaypoint(markerId); + if (waypoint == null) { + finish(); + return; + } + trackId = waypoint.getTrackId(); } - takePicture(); - return true; - case R.id.track_detail_markers: - intent = IntentUtils.newIntent(this, MarkerListActivity.class) - .putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId); - startActivity(intent); - return true; - case R.id.track_detail_voice_frequency: - FrequencyDialogFragment.newInstance(R.string.voice_frequency_key, PreferencesUtils.VOICE_FREQUENCY_DEFAULT, R.string.menu_voice_frequency) - .show(getSupportFragmentManager(), FrequencyDialogFragment.FREQUENCY_DIALOG_TAG); - return true; - case R.id.track_detail_split_frequency: - FrequencyDialogFragment.newInstance(R.string.split_frequency_key, PreferencesUtils.SPLIT_FREQUENCY_DEFAULT, R.string.menu_split_frequency) - .show(getSupportFragmentManager(), FrequencyDialogFragment.FREQUENCY_DIALOG_TAG); - return true; - case R.id.track_detail_edit: - intent = IntentUtils.newIntent(this, TrackEditActivity.class) - .putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId); - startActivity(intent); - return true; - case R.id.track_detail_delete: - deleteTracks(new long[] { trackId }); - return true; - case R.id.track_detail_settings: - intent = IntentUtils.newIntent(this, SettingsActivity.class); - startActivity(intent); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - private void takePicture() { - File dir = FileUtils.getPhotoDir(trackId); - FileUtils.ensureDirectoryExists(dir); - - String fileName = SimpleDateFormat.getDateTimeInstance().format(new Date()); - File file = new File(dir, FileUtils.buildUniqueFileName(dir, fileName, JPEG_EXTENSION)); - - if (file != null) { - photoUri = FileProvider.getUriForFile(this, FileUtils.FILEPROVIDER, file); - Log.d(TAG, "Taking photo to URI: " + photoUri); - Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE) - .putExtra(MediaStore.EXTRA_OUTPUT, photoUri); - startActivityForResult(intent, CAMERA_REQUEST_CODE); - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - if (requestCode == EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) { - if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { - Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show(); - } else { - takePicture(); - } - return; - } - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - } - - @Override - protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() { - return trackRecordingServiceConnection; - } - - @Override - protected void onDeleted() { - runOnUiThread(new Runnable() { - @Override - public void run() { - finish(); - } - }); - } - - /** - * Gets the {@link TrackDataHub}. - */ - public TrackDataHub getTrackDataHub() { - return trackDataHub; - } - - /** - * Gets the track id. - */ - public long getTrackId() { - return trackId; - } - - /** - * Handles the data in the intent. - */ - private void handleIntent(Intent intent) { - trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L); - long markerId = intent.getLongExtra(EXTRA_MARKER_ID, -1L); - if (markerId != -1L) { - // Use the trackId from the marker - Waypoint waypoint = contentProviderUtils.getWaypoint(markerId); - if (waypoint == null) { - finish(); - return; - } - trackId = waypoint.getTrackId(); - } - if (trackId == -1L) { - finish(); - return; - } - Track track = contentProviderUtils.getTrack(trackId); - if (track == null) { - // Use the last track if markerId is not set - if (markerId == -1L) { - track = contentProviderUtils.getLastTrack(); - if (track != null) { - trackId = track.getId(); - return; + if (trackId == -1L) { + finish(); + return; + } + Track track = contentProviderUtils.getTrack(trackId); + if (track == null) { + // Use the last track if markerId is not set + if (markerId == -1L) { + track = contentProviderUtils.getLastTrack(); + if (track != null) { + trackId = track.getId(); + return; + } + } + finish(); } - } - finish(); } - } - /** - * Updates the menu items. - */ - private void updateMenuItems(boolean isPaused) { - insertMarkerMenuItem.setVisible(isRecording() && !isPaused); - insertPhotoMenuItem.setVisible(hasCamera && isRecording() && !isPaused); - shareMenuItem.setVisible(!isRecording()); - markerListMenuItem.setShowAsAction(isRecording() ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_IF_ROOM); - voiceFrequencyMenuItem.setVisible(isRecording()); - splitFrequencyMenuItem.setVisible(isRecording()); - String title; - if (isRecording()) { - title = getString(isPaused ? R.string.generic_paused : R.string.generic_recording); - } else { - Track track = contentProviderUtils.getTrack(trackId); - title = track != null ? track.getName() : ""; + /** + * Updates the menu items. + */ + private void updateMenuItems(boolean isPaused) { + insertMarkerMenuItem.setVisible(isRecording() && !isPaused); + insertPhotoMenuItem.setVisible(hasCamera && isRecording() && !isPaused); + shareMenuItem.setVisible(!isRecording()); + markerListMenuItem.setShowAsAction(isRecording() ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_IF_ROOM); + voiceFrequencyMenuItem.setVisible(isRecording()); + splitFrequencyMenuItem.setVisible(isRecording()); + String title; + if (isRecording()) { + title = getString(isPaused ? R.string.generic_paused : R.string.generic_recording); + } else { + Track track = contentProviderUtils.getTrack(trackId); + title = track != null ? track.getName() : ""; + } + setTitle(title); } - setTitle(title); - } - - public void chooseActivityType(String category) { - ChooseActivityTypeDialogFragment.newInstance(category).show(getSupportFragmentManager(), ChooseActivityTypeDialogFragment.CHOOSE_ACTIVITY_TYPE_DIALOG_TAG); - } - @Override - public void onChooseActivityTypeDone(String iconValue) { - Track track = contentProviderUtils.getTrack(trackId); - String category = getString(TrackIconUtils.getIconActivityType(iconValue)); - TrackUtils.updateTrack(this, track, null, category, null, contentProviderUtils); - } + public void chooseActivityType(String category) { + ChooseActivityTypeDialogFragment.newInstance(category).show(getSupportFragmentManager(), ChooseActivityTypeDialogFragment.CHOOSE_ACTIVITY_TYPE_DIALOG_TAG); + } - private boolean isRecording() { - return trackId == recordingTrackId; - } + @Override + public void onChooseActivityTypeDone(String iconValue) { + Track track = contentProviderUtils.getTrack(trackId); + String category = getString(TrackIconUtils.getIconActivityType(iconValue)); + TrackUtils.updateTrack(this, track, null, category, null, contentProviderUtils); + } + + private boolean isRecording() { + return trackId == recordingTrackId; + } } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/TrackEditActivity.java b/src/main/java/com/google/android/apps/mytracks/TrackEditActivity.java index ae48f6bc0..343b8715f 100644 --- a/src/main/java/com/google/android/apps/mytracks/TrackEditActivity.java +++ b/src/main/java/com/google/android/apps/mytracks/TrackEditActivity.java @@ -41,168 +41,168 @@ import com.google.android.maps.mytracks.R; /** * An activity that let's the user see and edit the user editable track meta * data such as track name, activity type, and track description. - * + * * @author Leif Hendrik Wilden */ public class TrackEditActivity extends AbstractActivity implements ChooseActivityTypeCaller { - public static final String EXTRA_TRACK_ID = "track_id"; - public static final String EXTRA_NEW_TRACK = "new_track"; + public static final String EXTRA_TRACK_ID = "track_id"; + public static final String EXTRA_NEW_TRACK = "new_track"; - private static final String TAG = TrackEditActivity.class.getSimpleName(); - private static final String ICON_VALUE_KEY = "icon_value_key"; + private static final String TAG = TrackEditActivity.class.getSimpleName(); + private static final String ICON_VALUE_KEY = "icon_value_key"; - private TrackRecordingServiceConnection trackRecordingServiceConnection; - private ContentProviderUtils contentProviderUtils; - private Track track; - private String iconValue; + private TrackRecordingServiceConnection trackRecordingServiceConnection; + private ContentProviderUtils contentProviderUtils; + private Track track; + private String iconValue; - private EditText name; - private AutoCompleteTextView activityType; - private Spinner activityTypeIcon; - private EditText description; + private EditText name; + private AutoCompleteTextView activityType; + private Spinner activityTypeIcon; + private EditText description; - @Override - protected void onCreate(Bundle bundle) { - super.onCreate(bundle); + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); - trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null); - Long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L); - if (trackId == -1L) { - Log.e(TAG, "invalid trackId"); - finish(); - return; - } - - contentProviderUtils = ContentProviderUtils.Factory.get(this); - track = contentProviderUtils.getTrack(trackId); - if (track == null) { - Log.e(TAG, "No track for " + trackId); - finish(); - return; - } - - name = findViewById(R.id.track_edit_name); - name.setText(track.getName()); - - activityType = findViewById(R.id.track_edit_activity_type); - activityType.setText(track.getCategory()); - - ArrayAdapter adapter = ArrayAdapter.createFromResource( - this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line); - activityType.setAdapter(adapter); - activityType.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - setActivityTypeIcon(TrackIconUtils.getIconValue( - TrackEditActivity.this, (String) activityType.getAdapter().getItem(position))); - } - }); - activityType.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (!hasFocus) { - setActivityTypeIcon(TrackIconUtils.getIconValue( - TrackEditActivity.this, activityType.getText().toString())); + trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null); + Long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L); + if (trackId == -1L) { + Log.e(TAG, "invalid trackId"); + finish(); + return; } - } - }); - iconValue = null; - if (bundle != null) { - iconValue = bundle.getString(ICON_VALUE_KEY); - } - if (iconValue == null) { - iconValue = track.getIcon(); + contentProviderUtils = ContentProviderUtils.Factory.get(this); + track = contentProviderUtils.getTrack(trackId); + if (track == null) { + Log.e(TAG, "No track for " + trackId); + finish(); + return; + } + + name = findViewById(R.id.track_edit_name); + name.setText(track.getName()); + + activityType = findViewById(R.id.track_edit_activity_type); + activityType.setText(track.getCategory()); + + ArrayAdapter adapter = ArrayAdapter.createFromResource( + this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line); + activityType.setAdapter(adapter); + activityType.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + setActivityTypeIcon(TrackIconUtils.getIconValue( + TrackEditActivity.this, (String) activityType.getAdapter().getItem(position))); + } + }); + activityType.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus) { + setActivityTypeIcon(TrackIconUtils.getIconValue( + TrackEditActivity.this, activityType.getText().toString())); + } + } + }); + + iconValue = null; + if (bundle != null) { + iconValue = bundle.getString(ICON_VALUE_KEY); + } + if (iconValue == null) { + iconValue = track.getIcon(); + } + + activityTypeIcon = findViewById(R.id.track_edit_activity_type_icon); + activityTypeIcon.setAdapter(TrackIconUtils.getIconSpinnerAdapter(this, iconValue)); + activityTypeIcon.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP) { + ChooseActivityTypeDialogFragment.newInstance(activityType.getText().toString()).show( + getSupportFragmentManager(), + ChooseActivityTypeDialogFragment.CHOOSE_ACTIVITY_TYPE_DIALOG_TAG); + } + return true; + } + }); + activityTypeIcon.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { + ChooseActivityTypeDialogFragment.newInstance(activityType.getText().toString()).show( + getSupportFragmentManager(), + ChooseActivityTypeDialogFragment.CHOOSE_ACTIVITY_TYPE_DIALOG_TAG); + } + return true; + } + }); + + description = findViewById(R.id.track_edit_description); + description.setText(track.getDescription()); + + Button save = findViewById(R.id.track_edit_save); + save.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + TrackUtils.updateTrack(TrackEditActivity.this, track, name.getText().toString(), + activityType.getText().toString(), description.getText().toString(), + contentProviderUtils); + finish(); + } + }); + + Button cancel = findViewById(R.id.track_edit_cancel); + if (getIntent().getBooleanExtra(EXTRA_NEW_TRACK, false)) { + setTitle(R.string.track_edit_new_track_title); + cancel.setVisibility(View.GONE); + } else { + setTitle(R.string.menu_edit); + cancel.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + cancel.setVisibility(View.VISIBLE); + } } - activityTypeIcon = findViewById(R.id.track_edit_activity_type_icon); - activityTypeIcon.setAdapter(TrackIconUtils.getIconSpinnerAdapter(this, iconValue)); - activityTypeIcon.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_UP) { - ChooseActivityTypeDialogFragment.newInstance(activityType.getText().toString()).show( - getSupportFragmentManager(), - ChooseActivityTypeDialogFragment.CHOOSE_ACTIVITY_TYPE_DIALOG_TAG); - } - return true; - } - }); - activityTypeIcon.setOnKeyListener(new View.OnKeyListener() { - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { - ChooseActivityTypeDialogFragment.newInstance(activityType.getText().toString()).show( - getSupportFragmentManager(), - ChooseActivityTypeDialogFragment.CHOOSE_ACTIVITY_TYPE_DIALOG_TAG); - } - return true; - } - }); - description = findViewById(R.id.track_edit_description); - description.setText(track.getDescription()); - - Button save = findViewById(R.id.track_edit_save); - save.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - TrackUtils.updateTrack(TrackEditActivity.this, track, name.getText().toString(), - activityType.getText().toString(), description.getText().toString(), - contentProviderUtils); - finish(); - } - }); - - Button cancel = findViewById(R.id.track_edit_cancel); - if (getIntent().getBooleanExtra(EXTRA_NEW_TRACK, false)) { - setTitle(R.string.track_edit_new_track_title); - cancel.setVisibility(View.GONE); - } else { - setTitle(R.string.menu_edit); - cancel.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - finish(); - } - }); - cancel.setVisibility(View.VISIBLE); + @Override + protected void onStart() { + super.onStart(); + TrackRecordingServiceConnectionUtils.startConnection(this, trackRecordingServiceConnection); } - } + @Override + protected void onStop() { + super.onStop(); + trackRecordingServiceConnection.unbind(); + } - @Override - protected void onStart() { - super.onStart(); - TrackRecordingServiceConnectionUtils.startConnection(this, trackRecordingServiceConnection); - } + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(ICON_VALUE_KEY, iconValue); + } - @Override - protected void onStop() { - super.onStop(); - trackRecordingServiceConnection.unbind(); - } + @Override + protected int getLayoutResId() { + return R.layout.track_edit; + } - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putString(ICON_VALUE_KEY, iconValue); - } + private void setActivityTypeIcon(String value) { + iconValue = value; + TrackIconUtils.setIconSpinner(activityTypeIcon, value); + } - @Override - protected int getLayoutResId() { - return R.layout.track_edit; - } - - private void setActivityTypeIcon(String value) { - iconValue = value; - TrackIconUtils.setIconSpinner(activityTypeIcon, value); - } - - @Override - public void onChooseActivityTypeDone(String value) { - setActivityTypeIcon(value); - activityType.setText(getString(TrackIconUtils.getIconActivityType(value))); - } + @Override + public void onChooseActivityTypeDone(String value) { + setActivityTypeIcon(value); + activityType.setText(getString(TrackIconUtils.getIconActivityType(value))); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/TrackListActivity.java b/src/main/java/com/google/android/apps/mytracks/TrackListActivity.java index 0eb614717..3d9018fe4 100644 --- a/src/main/java/com/google/android/apps/mytracks/TrackListActivity.java +++ b/src/main/java/com/google/android/apps/mytracks/TrackListActivity.java @@ -78,579 +78,569 @@ import java.util.Locale; /** * An activity displaying a list of tracks. - * + * * @author Leif Hendrik Wilden */ public class TrackListActivity extends AbstractTrackActivity implements FileTypeCaller, ConfirmDeleteDialogFragment.ConfirmDeleteCaller { - private static final String TAG = TrackListActivity.class.getSimpleName(); - private static final String[] PROJECTION = new String[] { TracksColumns._ID, TracksColumns.NAME, - TracksColumns.DESCRIPTION, TracksColumns.CATEGORY, TracksColumns.STARTTIME, - TracksColumns.TOTALDISTANCE, TracksColumns.TOTALTIME, TracksColumns.ICON }; - - // Callback when the trackRecordingServiceConnection binding changes. - private final Runnable bindChangedCallback = new Runnable() { - @Override - public void run() { - /* - * After binding changes (e.g., becomes available), update the total time - * in trackController. - */ - runOnUiThread(new Runnable() { - @Override - public void run() { - trackController.update(recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, recordingTrackPaused); + private static final String TAG = TrackListActivity.class.getSimpleName(); + private static final String[] PROJECTION = new String[]{TracksColumns._ID, TracksColumns.NAME, + TracksColumns.DESCRIPTION, TracksColumns.CATEGORY, TracksColumns.STARTTIME, + TracksColumns.TOTALDISTANCE, TracksColumns.TOTALTIME, TracksColumns.ICON}; + // The following are set in onCreate + private ContentProviderUtils contentProviderUtils; + private SharedPreferences sharedPreferences; + private TrackRecordingServiceConnection trackRecordingServiceConnection; + private TrackController trackController; + private ListView listView; + private ResourceCursorAdapter resourceCursorAdapter; + private final LoaderCallbacks loaderCallbacks = new LoaderCallbacks() { + @Override + public Loader onCreateLoader(int arg0, Bundle arg1) { + return new CursorLoader(TrackListActivity.this, TracksColumns.CONTENT_URI, PROJECTION, null, + null, TrackUtils.TRACK_SORT_ORDER); } - }); - if (!startGps && !startNewRecording) { - return; - } + @Override + public void onLoadFinished(@NonNull Loader loader, Cursor cursor) { + resourceCursorAdapter.swapCursor(cursor); + } - ITrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound(); - if (service == null) { - Log.d(TAG, "service not available to start gps or a new recording"); - return; - } - if (startNewRecording) { - startGps = false; + @Override + public void onLoaderReset(@NonNull Loader loader) { + resourceCursorAdapter.swapCursor(null); + } + }; + // Preferences + private boolean metricUnits = true; + private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; + // Callback when an item is selected in the contextual action mode + private final ContextualActionModeCallback + contextualActionModeCallback = new ContextualActionModeCallback() { - long trackId = service.startNewTrack(); - startNewRecording = false; - Intent intent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class) - .putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId); - startActivity(intent); - Toast.makeText( - TrackListActivity.this, R.string.track_list_record_success, Toast.LENGTH_SHORT) - .show(); - } - if (startGps) { - service.startGps(); - startGps = false; - } - } - }; + @Override + public void onPrepare(Menu menu, int[] positions, long[] ids, boolean showSelectAll) { + boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; + boolean isSingleSelection = ids.length == 1; - /* - * Note that sharedPreferenceChangeListenr cannot be an anonymous inner class. - * Anonymous inner class will get garbage collected. - */ - private final OnSharedPreferenceChangeListener - sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { - @Override + menu.findItem(R.id.list_context_menu_share).setVisible(!isRecording && isSingleSelection); + menu.findItem(R.id.list_context_menu_show_on_map).setVisible(false); + menu.findItem(R.id.list_context_menu_edit).setVisible(isSingleSelection); + menu.findItem(R.id.list_context_menu_select_all).setVisible(showSelectAll); + } + + @Override + public boolean onClick(int itemId, int[] positions, long[] ids) { + return handleContextItem(itemId, ids); + } + }; + private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT; + /* + * Note that sharedPreferenceChangeListenr cannot be an anonymous inner class. + * Anonymous inner class will get garbage collected. + */ + private final OnSharedPreferenceChangeListener + sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { + @Override public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { - if (key == null || key.equals( - PreferencesUtils.getKey(TrackListActivity.this, R.string.stats_units_key))) { - metricUnits = PreferencesUtils.isMetricUnits(TrackListActivity.this); - } - if (key == null || key.equals( - PreferencesUtils.getKey(TrackListActivity.this, R.string.recording_track_id_key))) { - recordingTrackId = PreferencesUtils.getLong( - TrackListActivity.this, R.string.recording_track_id_key); - if (key != null && recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) { - trackRecordingServiceConnection.startAndBind(); + if (key == null || key.equals( + PreferencesUtils.getKey(TrackListActivity.this, R.string.stats_units_key))) { + metricUnits = PreferencesUtils.isMetricUnits(TrackListActivity.this); } - } - if (key == null || key.equals(PreferencesUtils.getKey( - TrackListActivity.this, R.string.recording_track_paused_key))) { - recordingTrackPaused = PreferencesUtils.getBoolean(TrackListActivity.this, - R.string.recording_track_paused_key, - PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); - } - if (key != null) { + if (key == null || key.equals( + PreferencesUtils.getKey(TrackListActivity.this, R.string.recording_track_id_key))) { + recordingTrackId = PreferencesUtils.getLong( + TrackListActivity.this, R.string.recording_track_id_key); + if (key != null && recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) { + trackRecordingServiceConnection.startAndBind(); + } + } + if (key == null || key.equals(PreferencesUtils.getKey( + TrackListActivity.this, R.string.recording_track_paused_key))) { + recordingTrackPaused = PreferencesUtils.getBoolean(TrackListActivity.this, + R.string.recording_track_paused_key, + PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); + } + if (key != null) { + runOnUiThread(new Runnable() { + @Override + public void run() { + TrackListActivity.this.invalidateOptionsMenu(); + LoaderManager.getInstance(TrackListActivity.this).restartLoader(0, null, loaderCallbacks); + boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; + trackController.update(isRecording, recordingTrackPaused); + } + }); + } + } + }; + // Menu items + private MenuItem searchMenuItem; + private MenuItem startGpsMenuItem; + private MenuItem aggregatedStatisticsMenuItem; + private MenuItem exportAllMenuItem; + private MenuItem importAllMenuItem; + private MenuItem deleteAllMenuItem; + private final OnClickListener stopListener = new OnClickListener() { + @Override + public void onClick(View v) { + updateMenuItems(false, false); + TrackRecordingServiceConnectionUtils.stopRecording( + TrackListActivity.this, trackRecordingServiceConnection, true); + } + }; + private boolean startGps = false; // true to start gps + private boolean startNewRecording = false; // true to start a new recording + // Callback when the trackRecordingServiceConnection binding changes. + private final Runnable bindChangedCallback = new Runnable() { + @Override + public void run() { + /* + * After binding changes (e.g., becomes available), update the total time + * in trackController. + */ runOnUiThread(new Runnable() { @Override - public void run() { - TrackListActivity.this.invalidateOptionsMenu(); - LoaderManager.getInstance(TrackListActivity.this).restartLoader(0, null, loaderCallbacks); - boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; - trackController.update(isRecording, recordingTrackPaused); - } + public void run() { + trackController.update(recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, recordingTrackPaused); + } }); - } - } - }; - // Callback when an item is selected in the contextual action mode - private final ContextualActionModeCallback - contextualActionModeCallback = new ContextualActionModeCallback() { - - @Override - public void onPrepare(Menu menu, int[] positions, long[] ids, boolean showSelectAll) { - boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; - boolean isSingleSelection = ids.length == 1; - - menu.findItem(R.id.list_context_menu_share).setVisible(!isRecording && isSingleSelection); - menu.findItem(R.id.list_context_menu_show_on_map).setVisible(false); - menu.findItem(R.id.list_context_menu_edit).setVisible(isSingleSelection); - menu.findItem(R.id.list_context_menu_select_all).setVisible(showSelectAll); - } - - @Override - public boolean onClick(int itemId, int[] positions, long[] ids) { - return handleContextItem(itemId, ids); - } - }; - - private final OnClickListener recordListener = new OnClickListener() { - public void onClick(View v) { - if (recordingTrackId == PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) { - // Not recording -> Recording - updateMenuItems(false, true); - startRecording(); - } else { - if (recordingTrackPaused) { - // Paused -> Resume - updateMenuItems(false, true); - TrackRecordingServiceConnectionUtils.resumeTrack(trackRecordingServiceConnection); - trackController.update(true, false); - } else { - // Recording -> Paused - updateMenuItems(false, true); - TrackRecordingServiceConnectionUtils.pauseTrack(trackRecordingServiceConnection); - trackController.update(true, true); - } - } - } - }; - - private final OnClickListener stopListener = new OnClickListener() { - @Override - public void onClick(View v) { - updateMenuItems(false, false); - TrackRecordingServiceConnectionUtils.stopRecording( - TrackListActivity.this, trackRecordingServiceConnection, true); - } - }; - - private final LoaderCallbacks loaderCallbacks = new LoaderCallbacks() { - @Override - public Loader onCreateLoader(int arg0, Bundle arg1) { - return new CursorLoader(TrackListActivity.this, TracksColumns.CONTENT_URI, PROJECTION, null, - null, TrackUtils.TRACK_SORT_ORDER); - } - - @Override - public void onLoadFinished(@NonNull Loader loader, Cursor cursor) { - resourceCursorAdapter.swapCursor(cursor); - } - - @Override - public void onLoaderReset(@NonNull Loader loader) { - resourceCursorAdapter.swapCursor(null); - } - }; - - // The following are set in onCreate - private ContentProviderUtils contentProviderUtils; - private SharedPreferences sharedPreferences; - private TrackRecordingServiceConnection trackRecordingServiceConnection; - private TrackController trackController; - private ListView listView; - private ResourceCursorAdapter resourceCursorAdapter; - - // Preferences - private boolean metricUnits = true; - private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; - private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT; - - // Menu items - private MenuItem searchMenuItem; - private MenuItem startGpsMenuItem; - private MenuItem aggregatedStatisticsMenuItem; - private MenuItem exportAllMenuItem; - private MenuItem importAllMenuItem; - private MenuItem deleteAllMenuItem; - - private boolean startGps = false; // true to start gps - private boolean startNewRecording = false; // true to start a new recording - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (BuildConfig.DEBUG) { - Log.d(TAG, "Enabling strict mode"); - StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() - .detectAll() - .penaltyLog() - .build()); - StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() - .detectAll() - .penaltyLog() - .build()); - } - - contentProviderUtils = ContentProviderUtils.Factory.get(this); - sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); - - trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback); - trackController = new TrackController(this, trackRecordingServiceConnection, true, recordListener, stopListener); - - setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); - // Show trackController when search dialog is dismissed - SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE); - searchManager.setOnDismissListener(new SearchManager.OnDismissListener() { - @Override - public void onDismiss() { - trackController.show(); - } - }); - - listView = findViewById(R.id.track_list); - listView.setEmptyView(findViewById(R.id.track_list_empty_view)); - listView.setOnItemClickListener(new OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class) - .putExtra(TrackDetailActivity.EXTRA_TRACK_ID, id); - startActivity(newIntent); - } - }); - resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) { - @Override - public void bindView(View view, Context context, Cursor cursor) { - int idIndex = cursor.getColumnIndex(TracksColumns._ID); - int iconIndex = cursor.getColumnIndex(TracksColumns.ICON); - int nameIndex = cursor.getColumnIndex(TracksColumns.NAME); - int totalTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME); - int totalDistanceIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE); - int startTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME); - int categoryIndex = cursor.getColumnIndex(TracksColumns.CATEGORY); - int descriptionIndex = cursor.getColumnIndex(TracksColumns.DESCRIPTION); - - long trackId = cursor.getLong(idIndex); - boolean isRecording = trackId == recordingTrackId; - String icon = cursor.getString(iconIndex); - int iconId = TrackIconUtils.getIconDrawable(icon); - String name = cursor.getString(nameIndex); - String totalTime = StringUtils.formatElapsedTime(cursor.getLong(totalTimeIndex)); - String totalDistance = StringUtils.formatDistance(TrackListActivity.this, cursor.getDouble(totalDistanceIndex), metricUnits); - int markerCount = contentProviderUtils.getWaypointCount(trackId); - long startTime = cursor.getLong(startTimeIndex); - String category = icon != null && !icon.equals("") ? null : cursor.getString(categoryIndex); - String description = cursor.getString(descriptionIndex); - - ListItemUtils.setListItem(TrackListActivity.this, view, isRecording, recordingTrackPaused, - iconId, R.string.image_track, name, totalTime, totalDistance, markerCount, - startTime, true, category, description, null); - } - }; - listView.setAdapter(resourceCursorAdapter); - AbstractTrackActivity.configureListViewContextualMenu(listView, contextualActionModeCallback); - - LoaderManager.getInstance(this).initLoader(0, null, loaderCallbacks); - showStartupDialogs(); - } - - @Override - protected void onStart() { - super.onStart(); - - sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); - sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null); - TrackRecordingServiceConnectionUtils.startConnection(this, trackRecordingServiceConnection); - } - - @Override - protected void onResume() { - super.onResume(); - - // Update UI - this.invalidateOptionsMenu(); - LoaderManager.getInstance(this).restartLoader(0, null, loaderCallbacks); - boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; - trackController.onResume(isRecording, recordingTrackPaused); - } - - @Override - protected void onPause() { - super.onPause(); - - // Update UI - trackController.onPause(); - } - - @Override - protected void onStop() { - super.onStop(); - - sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); - trackRecordingServiceConnection.unbind(); - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - if (requestCode == GPS_REQUEST_CODE) { - if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this, R.string.permission_gps_failed, Toast.LENGTH_SHORT).show(); - finish(); - } - } - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - } - - @Override - protected int getLayoutResId() { - return R.layout.track_list; - } - - @Override - protected boolean configureActionBarHomeAsUp() { - return false; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.track_list, menu); - - searchMenuItem = menu.findItem(R.id.track_list_search); - AbstractTrackActivity.configureSearchWidget(this, searchMenuItem, trackController); - - startGpsMenuItem = menu.findItem(R.id.track_list_start_gps); - aggregatedStatisticsMenuItem = menu.findItem(R.id.track_list_aggregated_statistics); - exportAllMenuItem = menu.findItem(R.id.track_list_export_all); - importAllMenuItem = menu.findItem(R.id.track_list_import_all); - deleteAllMenuItem = menu.findItem(R.id.track_list_delete_all); - - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - boolean isGpsStarted = TrackRecordingServiceConnectionUtils.isRecordingServiceRunning(this); - boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; - updateMenuItems(isGpsStarted, isRecording); - - View searchView = searchMenuItem.getActionView(); - if (searchView instanceof SearchView) { - ((SearchView) searchView).setQuery("", false); - } - return super.onPrepareOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - Intent intent; - switch (item.getItemId()) { - case R.id.track_list_start_gps: - LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE); - if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { - intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); - startActivity(intent); - } else { - startGps = !TrackRecordingServiceConnectionUtils.isRecordingServiceRunning(this); - - // Show toast - Toast toast = Toast.makeText(this, startGps ? R.string.gps_starting : R.string.gps_stopping, Toast.LENGTH_SHORT); - toast.setGravity(Gravity.CENTER, 0, 0); - toast.show(); - - // Invoke trackRecordingService - if (startGps) { - trackRecordingServiceConnection.startAndBind(); - bindChangedCallback.run(); - } else { - ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound(); - if (trackRecordingService != null) { - trackRecordingService.stopGps(); + if (!startGps && !startNewRecording) { + return; + } + + ITrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound(); + if (service == null) { + Log.d(TAG, "service not available to start gps or a new recording"); + return; + } + if (startNewRecording) { + startGps = false; + + long trackId = service.startNewTrack(); + startNewRecording = false; + Intent intent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class) + .putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId); + startActivity(intent); + Toast.makeText( + TrackListActivity.this, R.string.track_list_record_success, Toast.LENGTH_SHORT) + .show(); + } + if (startGps) { + service.startGps(); + startGps = false; } - trackRecordingServiceConnection.unbindAndStop(); - } - - // Update menu after starting or stopping gps - this.invalidateOptionsMenu(); } - return true; - case R.id.track_list_markers: - intent = IntentUtils.newIntent(this, MarkerListActivity.class); - startActivity(intent); - return true; - case R.id.track_list_aggregated_statistics: - intent = IntentUtils.newIntent(this, AggregatedStatsActivity.class); - startActivity(intent); - return true; - case R.id.track_list_export_all: - FileTypeDialogFragment.newInstance(R.id.track_list_export_all, - R.string.export_all_title, R.string.export_all_option) - .show(getSupportFragmentManager(), FileTypeDialogFragment.FILE_TYPE_DIALOG_TAG); - return true; - case R.id.track_list_import_all: - FileTypeDialogFragment.newInstance(R.id.track_list_import_all, - R.string.import_selection_title, R.string.import_selection_option) - .show(getSupportFragmentManager(), FileTypeDialogFragment.FILE_TYPE_DIALOG_TAG); - return true; - case R.id.track_list_delete_all: - deleteTracks(new long[] {-1L}); - return true; - case R.id.track_list_settings: - intent = IntentUtils.newIntent(this, SettingsActivity.class); - startActivity(intent); - return true; - } - return super.onOptionsItemSelected(item); - } + }; + private final OnClickListener recordListener = new OnClickListener() { + public void onClick(View v) { + if (recordingTrackId == PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) { + // Not recording -> Recording + updateMenuItems(false, true); + startRecording(); + } else { + if (recordingTrackPaused) { + // Paused -> Resume + updateMenuItems(false, true); + TrackRecordingServiceConnectionUtils.resumeTrack(trackRecordingServiceConnection); + trackController.update(true, false); + } else { + // Recording -> Paused + updateMenuItems(false, true); + TrackRecordingServiceConnectionUtils.pauseTrack(trackRecordingServiceConnection); + trackController.update(true, true); + } + } + } + }; - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - getMenuInflater().inflate(R.menu.list_context_menu, menu); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; - contextualActionModeCallback.onPrepare( - menu, new int[] { info.position }, new long[] { info.id }, false); - } + if (BuildConfig.DEBUG) { + Log.d(TAG, "Enabling strict mode"); + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .detectAll() + .penaltyLog() + .build()); + StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() + .detectAll() + .penaltyLog() + .build()); + } - @Override - public boolean onContextItemSelected(MenuItem item) { - AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); - if (handleContextItem(item.getItemId(), new long[] { info.id })) { - return true; - } - return super.onContextItemSelected(item); - } + contentProviderUtils = ContentProviderUtils.Factory.get(this); + sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_SEARCH && searchMenuItem != null) { - return true; - } - return super.onKeyUp(keyCode, event); - } + trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback); + trackController = new TrackController(this, trackRecordingServiceConnection, true, recordListener, stopListener); - @Override - public boolean onSearchRequested() { - // Hide trackController when search dialog is shown - trackController.hide(); - return super.onSearchRequested(); - } + setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); + // Show trackController when search dialog is dismissed + SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE); + searchManager.setOnDismissListener(new SearchManager.OnDismissListener() { + @Override + public void onDismiss() { + trackController.show(); + } + }); - @Override - protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() { - return trackRecordingServiceConnection; - } + listView = findViewById(R.id.track_list); + listView.setEmptyView(findViewById(R.id.track_list_empty_view)); + listView.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class) + .putExtra(TrackDetailActivity.EXTRA_TRACK_ID, id); + startActivity(newIntent); + } + }); + resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) { + @Override + public void bindView(View view, Context context, Cursor cursor) { + int idIndex = cursor.getColumnIndex(TracksColumns._ID); + int iconIndex = cursor.getColumnIndex(TracksColumns.ICON); + int nameIndex = cursor.getColumnIndex(TracksColumns.NAME); + int totalTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME); + int totalDistanceIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE); + int startTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME); + int categoryIndex = cursor.getColumnIndex(TracksColumns.CATEGORY); + int descriptionIndex = cursor.getColumnIndex(TracksColumns.DESCRIPTION); - @Override - protected void onDeleted() { - // Do nothing - } + long trackId = cursor.getLong(idIndex); + boolean isRecording = trackId == recordingTrackId; + String icon = cursor.getString(iconIndex); + int iconId = TrackIconUtils.getIconDrawable(icon); + String name = cursor.getString(nameIndex); + String totalTime = StringUtils.formatElapsedTime(cursor.getLong(totalTimeIndex)); + String totalDistance = StringUtils.formatDistance(TrackListActivity.this, cursor.getDouble(totalDistanceIndex), metricUnits); + int markerCount = contentProviderUtils.getWaypointCount(trackId); + long startTime = cursor.getLong(startTimeIndex); + String category = icon != null && !icon.equals("") ? null : cursor.getString(categoryIndex); + String description = cursor.getString(descriptionIndex); - @Override - public void onFileTypeDone(int menuId, TrackFileFormat trackFileFormat) { - Intent intent; - switch (menuId) { - case R.id.track_list_export_all: - intent = IntentUtils.newIntent(this, SaveActivity.class) - .putExtra(SaveActivity.EXTRA_TRACK_IDS, new long[] {-1L}) - .putExtra(SaveActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) trackFileFormat); - startActivity(intent); - break; - case R.id.track_list_import_all: - intent = IntentUtils.newIntent(this, ImportActivity.class) - .putExtra(ImportActivity.EXTRA_IMPORT_ALL, true) - .putExtra(ImportActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) trackFileFormat); - startActivity(intent); - break; - default: - } - } + ListItemUtils.setListItem(TrackListActivity.this, view, isRecording, recordingTrackPaused, + iconId, R.string.image_track, name, totalTime, totalDistance, markerCount, + startTime, true, category, description, null); + } + }; + listView.setAdapter(resourceCursorAdapter); + AbstractTrackActivity.configureListViewContextualMenu(listView, contextualActionModeCallback); - /** - * Shows start up dialogs. - */ - public void showStartupDialogs() { - // If stats_units_key is undefined, set it - if (PreferencesUtils.getString(this, R.string.stats_units_key, "").equals("")) { - String statsUnits = getString(Locale.US.equals(Locale.getDefault()) ? R.string.stats_units_imperial : R.string.stats_units_metric); - PreferencesUtils.setString(this, R.string.stats_units_key, statsUnits); + LoaderManager.getInstance(this).initLoader(0, null, loaderCallbacks); + showStartupDialogs(); } - requestGPSPermissions(); - } + @Override + protected void onStart() { + super.onStart(); - private void requestGPSPermissions() { - if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}, GPS_REQUEST_CODE); + sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null); + TrackRecordingServiceConnectionUtils.startConnection(this, trackRecordingServiceConnection); } - } - /** - * Updates the menu items. - * - * @param isGpsStarted true if gps is started - * @param isRecording true if recording - */ - private void updateMenuItems(boolean isGpsStarted, boolean isRecording) { - boolean hasTrack = listView != null && listView.getCount() != 0; - if (startGpsMenuItem != null) { - startGpsMenuItem.setVisible(!isRecording); - if (!isRecording) { - startGpsMenuItem.setTitle(isGpsStarted ? R.string.menu_stop_gps : R.string.menu_start_gps); - startGpsMenuItem.setIcon(isGpsStarted ? R.drawable.ic_menu_stop_gps : R.drawable.ic_menu_start_gps); - TrackIconUtils.setMenuIconColor(startGpsMenuItem); - } - } - if (aggregatedStatisticsMenuItem != null) { - aggregatedStatisticsMenuItem.setVisible(hasTrack); - } - if (exportAllMenuItem != null) { - exportAllMenuItem.setVisible(hasTrack && !isRecording); - } - if (importAllMenuItem != null) { - importAllMenuItem.setVisible(!isRecording); - } - if (deleteAllMenuItem != null) { - deleteAllMenuItem.setVisible(hasTrack && !isRecording); - } - } - - /** - * Starts a new recording. - */ - private void startRecording() { - startNewRecording = true; - trackRecordingServiceConnection.startAndBind(); + @Override + protected void onResume() { + super.onResume(); - /* - * If the binding has happened, then invoke the callback to start a new - * recording. If the binding hasn't happened, then invoking the callback - * will have no effect. But when the binding occurs, the callback will get - * invoked. + // Update UI + this.invalidateOptionsMenu(); + LoaderManager.getInstance(this).restartLoader(0, null, loaderCallbacks); + boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; + trackController.onResume(isRecording, recordingTrackPaused); + } + + @Override + protected void onPause() { + super.onPause(); + + // Update UI + trackController.onPause(); + } + + @Override + protected void onStop() { + super.onStop(); + + sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + trackRecordingServiceConnection.unbind(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + if (requestCode == GPS_REQUEST_CODE) { + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + Toast.makeText(this, R.string.permission_gps_failed, Toast.LENGTH_SHORT).show(); + finish(); + } + } + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + + @Override + protected int getLayoutResId() { + return R.layout.track_list; + } + + @Override + protected boolean configureActionBarHomeAsUp() { + return false; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.track_list, menu); + + searchMenuItem = menu.findItem(R.id.track_list_search); + AbstractTrackActivity.configureSearchWidget(this, searchMenuItem, trackController); + + startGpsMenuItem = menu.findItem(R.id.track_list_start_gps); + aggregatedStatisticsMenuItem = menu.findItem(R.id.track_list_aggregated_statistics); + exportAllMenuItem = menu.findItem(R.id.track_list_export_all); + importAllMenuItem = menu.findItem(R.id.track_list_import_all); + deleteAllMenuItem = menu.findItem(R.id.track_list_delete_all); + + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + boolean isGpsStarted = TrackRecordingServiceConnectionUtils.isRecordingServiceRunning(this); + boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; + updateMenuItems(isGpsStarted, isRecording); + + View searchView = searchMenuItem.getActionView(); + if (searchView instanceof SearchView) { + ((SearchView) searchView).setQuery("", false); + } + return super.onPrepareOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + Intent intent; + switch (item.getItemId()) { + case R.id.track_list_start_gps: + LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE); + if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { + intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); + startActivity(intent); + } else { + startGps = !TrackRecordingServiceConnectionUtils.isRecordingServiceRunning(this); + + // Show toast + Toast toast = Toast.makeText(this, startGps ? R.string.gps_starting : R.string.gps_stopping, Toast.LENGTH_SHORT); + toast.setGravity(Gravity.CENTER, 0, 0); + toast.show(); + + // Invoke trackRecordingService + if (startGps) { + trackRecordingServiceConnection.startAndBind(); + bindChangedCallback.run(); + } else { + ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound(); + if (trackRecordingService != null) { + trackRecordingService.stopGps(); + } + trackRecordingServiceConnection.unbindAndStop(); + } + + // Update menu after starting or stopping gps + this.invalidateOptionsMenu(); + } + return true; + case R.id.track_list_markers: + intent = IntentUtils.newIntent(this, MarkerListActivity.class); + startActivity(intent); + return true; + case R.id.track_list_aggregated_statistics: + intent = IntentUtils.newIntent(this, AggregatedStatsActivity.class); + startActivity(intent); + return true; + case R.id.track_list_export_all: + FileTypeDialogFragment.newInstance(R.id.track_list_export_all, + R.string.export_all_title, R.string.export_all_option) + .show(getSupportFragmentManager(), FileTypeDialogFragment.FILE_TYPE_DIALOG_TAG); + return true; + case R.id.track_list_import_all: + FileTypeDialogFragment.newInstance(R.id.track_list_import_all, + R.string.import_selection_title, R.string.import_selection_option) + .show(getSupportFragmentManager(), FileTypeDialogFragment.FILE_TYPE_DIALOG_TAG); + return true; + case R.id.track_list_delete_all: + deleteTracks(new long[]{-1L}); + return true; + case R.id.track_list_settings: + intent = IntentUtils.newIntent(this, SettingsActivity.class); + startActivity(intent); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + getMenuInflater().inflate(R.menu.list_context_menu, menu); + + AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; + contextualActionModeCallback.onPrepare( + menu, new int[]{info.position}, new long[]{info.id}, false); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); + if (handleContextItem(item.getItemId(), new long[]{info.id})) { + return true; + } + return super.onContextItemSelected(item); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_SEARCH && searchMenuItem != null) { + return true; + } + return super.onKeyUp(keyCode, event); + } + + @Override + public boolean onSearchRequested() { + // Hide trackController when search dialog is shown + trackController.hide(); + return super.onSearchRequested(); + } + + @Override + protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() { + return trackRecordingServiceConnection; + } + + @Override + protected void onDeleted() { + // Do nothing + } + + @Override + public void onFileTypeDone(int menuId, TrackFileFormat trackFileFormat) { + Intent intent; + switch (menuId) { + case R.id.track_list_export_all: + intent = IntentUtils.newIntent(this, SaveActivity.class) + .putExtra(SaveActivity.EXTRA_TRACK_IDS, new long[]{-1L}) + .putExtra(SaveActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) trackFileFormat); + startActivity(intent); + break; + case R.id.track_list_import_all: + intent = IntentUtils.newIntent(this, ImportActivity.class) + .putExtra(ImportActivity.EXTRA_IMPORT_ALL, true) + .putExtra(ImportActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) trackFileFormat); + startActivity(intent); + break; + default: + } + } + + /** + * Shows start up dialogs. */ - bindChangedCallback.run(); - } - - /** - * Handles a context item selection. - * - * @param itemId the menu item id - * @param trackIds the track ids - * @return true if handled. - */ - private boolean handleContextItem(int itemId, long[] trackIds) { - switch (itemId) { - case R.id.list_context_menu_share: - //TODO - Log.e(TAG, "Not implemented"); - return true; - case R.id.list_context_menu_edit: - Intent intent = IntentUtils.newIntent(this, TrackEditActivity.class) - .putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackIds[0]); - startActivity(intent); - return true; - case R.id.list_context_menu_delete: - if (trackIds.length > 1 && trackIds.length == listView.getCount()) { - trackIds = new long[] {-1L}; + public void showStartupDialogs() { + // If stats_units_key is undefined, set it + if (PreferencesUtils.getString(this, R.string.stats_units_key, "").equals("")) { + String statsUnits = getString(Locale.US.equals(Locale.getDefault()) ? R.string.stats_units_imperial : R.string.stats_units_metric); + PreferencesUtils.setString(this, R.string.stats_units_key, statsUnits); } - deleteTracks(trackIds); - return true; - case R.id.list_context_menu_select_all: - int size = listView.getCount(); - for (int i = 0; i < size; i++) { - listView.setItemChecked(i, true); + + requestGPSPermissions(); + } + + private void requestGPSPermissions() { + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}, GPS_REQUEST_CODE); + } + } + + /** + * Updates the menu items. + * + * @param isGpsStarted true if gps is started + * @param isRecording true if recording + */ + private void updateMenuItems(boolean isGpsStarted, boolean isRecording) { + boolean hasTrack = listView != null && listView.getCount() != 0; + if (startGpsMenuItem != null) { + startGpsMenuItem.setVisible(!isRecording); + if (!isRecording) { + startGpsMenuItem.setTitle(isGpsStarted ? R.string.menu_stop_gps : R.string.menu_start_gps); + startGpsMenuItem.setIcon(isGpsStarted ? R.drawable.ic_menu_stop_gps : R.drawable.ic_menu_start_gps); + TrackIconUtils.setMenuIconColor(startGpsMenuItem); + } + } + if (aggregatedStatisticsMenuItem != null) { + aggregatedStatisticsMenuItem.setVisible(hasTrack); + } + if (exportAllMenuItem != null) { + exportAllMenuItem.setVisible(hasTrack && !isRecording); + } + if (importAllMenuItem != null) { + importAllMenuItem.setVisible(!isRecording); + } + if (deleteAllMenuItem != null) { + deleteAllMenuItem.setVisible(hasTrack && !isRecording); + } + } + + /** + * Starts a new recording. + */ + private void startRecording() { + startNewRecording = true; + trackRecordingServiceConnection.startAndBind(); + + /* + * If the binding has happened, then invoke the callback to start a new + * recording. If the binding hasn't happened, then invoking the callback + * will have no effect. But when the binding occurs, the callback will get + * invoked. + */ + bindChangedCallback.run(); + } + + /** + * Handles a context item selection. + * + * @param itemId the menu item id + * @param trackIds the track ids + * @return true if handled. + */ + private boolean handleContextItem(int itemId, long[] trackIds) { + switch (itemId) { + case R.id.list_context_menu_share: + //TODO + Log.e(TAG, "Not implemented"); + return true; + case R.id.list_context_menu_edit: + Intent intent = IntentUtils.newIntent(this, TrackEditActivity.class) + .putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackIds[0]); + startActivity(intent); + return true; + case R.id.list_context_menu_delete: + if (trackIds.length > 1 && trackIds.length == listView.getCount()) { + trackIds = new long[]{-1L}; + } + deleteTracks(trackIds); + return true; + case R.id.list_context_menu_select_all: + int size = listView.getCount(); + for (int i = 0; i < size; i++) { + listView.setItemChecked(i, true); + } + return false; } return false; } - return false; - } } diff --git a/src/main/java/com/google/android/apps/mytracks/content/ContentProviderUtils.java b/src/main/java/com/google/android/apps/mytracks/content/ContentProviderUtils.java index a7371a36f..fd507e41c 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/ContentProviderUtils.java +++ b/src/main/java/com/google/android/apps/mytracks/content/ContentProviderUtils.java @@ -16,386 +16,384 @@ package com.google.android.apps.mytracks.content; -import com.google.android.apps.mytracks.content.Waypoint.WaypointType; - import android.content.Context; import android.database.Cursor; import android.location.Location; import android.location.LocationManager; import android.net.Uri; +import com.google.android.apps.mytracks.content.Waypoint.WaypointType; + import java.util.Iterator; import java.util.List; /** * Utilities to access data from the app's content provider. - * + * * @author Rodrigo Damazio */ public interface ContentProviderUtils { - /** - * The authority (the first part of the URI) for the app's content - * provider. - */ - String AUTHORITY = "com.google.android.maps.mytracks"; - - /** - * Clears a track. Removes waypoints and trackpoints. Only keeps the track id. - * - * @param trackId the track id - */ - void clearTrack(Context context, long trackId); - - /** - * Creates a {@link Track} from a cursor. - * - * @param cursor the cursor pointing to the track - */ - Track createTrack(Cursor cursor); - - /** - * Deletes all tracks (including waypoints and track points). - */ - void deleteAllTracks(Context context); - - /** - * Deletes a track. - * - * @param trackId the track id - */ - void deleteTrack(Context context, long trackId); - - /** - * Gets all the tracks. If no track exists, an empty list is returned. - *

- * Note that the returned tracks do not have any track points attached. - */ - List getAllTracks(); - - /** - * Gets the last track. Returns null if doesn't exist. - */ - Track getLastTrack(); - - /** - * Gets a track by a track id. Returns null if not found. - *

- * Note that the returned track doesn't have any track points attached. - * - * @param trackId the track id. - */ - Track getTrack(long trackId); - - /** - * Gets a track cursor. The caller owns the returned cursor and is responsible - * for closing it. - * - * @param selection the selection. Can be null - * @param selectionArgs the selection arguments. Can be null - * @param sortOrder the sort order. Can be null - */ - Cursor getTrackCursor(String selection, String[] selectionArgs, String sortOrder); - - /** - * Inserts a track. - *

- * Note: This doesn't insert any track points. - * - * @param track the track - * @return the content provider URI of the inserted track. - */ - Uri insertTrack(Track track); - - /** - * Updates a track. - *

- * Note: This doesn't update any track points. - * - * @param track the track - */ - void updateTrack(Track track); - - /** - * Creates a waypoint from a cursor. - * - * @param cursor the cursor pointing to the waypoint - */ - Waypoint createWaypoint(Cursor cursor); - - /** - * Deletes a waypoint. If deleting a statistics waypoint, this will also - * correct the next statistics waypoint after the deleted one to reflect the - * deletion. The generator is used to update the next statistics waypoint. - * - * @param waypointId the waypoint id - * @param descriptionGenerator the description generator. Can be null for - * waypoint marker - */ - void deleteWaypoint(Context context, long waypointId, DescriptionGenerator descriptionGenerator); - - /** - * Gets the first waypoint id for a track. The first waypoint is special as it - * contains the stats for the track. Returns -1L if it doesn't exist. - * - * @param trackId the track id - */ - long getFirstWaypointId(long trackId); - - /** - * Gets the last waypoint for a type. Returns null if it doesn't exist. - * - * @param trackId the track id - * @param waypointType the waypoint type - */ - Waypoint getLastWaypoint(long trackId, WaypointType waypointType); - - /** - * Gets the next waypoint number for a type. Returns -1 if not able to get the - * next waypoint number. - * - * @param trackId the track id - * @param waypointType the waypoint type - */ - int getNextWaypointNumber(long trackId, WaypointType waypointType); - - /** - * Gets a waypoint from a waypoint id. Returns null if not found. - * - * @param waypointId the waypoint id - */ - Waypoint getWaypoint(long waypointId); - - /** - * Gets a waypoint cursor. The caller owns the returned cursor and is - * responsible for closing it. - * - * @param selection the selection. Can be null - * @param selectionArgs the selection arguments. Can be null - * @param sortOrder the sort order. Can be null - * @param maxWaypoints the maximum number of waypoints to return. -1 for no - * limit - */ - Cursor getWaypointCursor(String selection, String[] selectionArgs, String sortOrder, int maxWaypoints); - - /** - * Gets a waypoint cursor for a track. The caller owns the returned cursor and - * is responsible for closing it. - * - * @param trackId the track id - * @param minWaypointId the minimum waypoint id. -1L to ignore - * @param maxWaypoints the maximum number of waypoints to return. -1 for no - * limit - */ - Cursor getWaypointCursor(long trackId, long minWaypointId, int maxWaypoints); - - /** - * Gets the number of waypoints for a track. - * - * @param trackId the track id - */ - int getWaypointCount(long trackId); - - /** - * Inserts a waypoint. - * - * @param waypoint the waypoint - * @return the content provider URI of the inserted waypoint. - */ - Uri insertWaypoint(Waypoint waypoint); - - /** - * Updates a waypoint. Returns true if successful. - * - * @param waypoint the waypoint - */ - boolean updateWaypoint(Waypoint waypoint); - - /** - * Inserts multiple track points. - * - * @param locations an array of locations - * @param length the number of locations (from the beginning of the array) to - * insert, or -1 for all of them - * @param trackId the track id - * @return the number of points inserted - */ - int bulkInsertTrackPoint(Location[] locations, int length, long trackId); - - /** - * Creates a location object from a cursor. - * - * @param cursor the cursor pointing to the location - */ - Location createTrackPoint(Cursor cursor); - - /** - * Gets the first location id for a track. Returns -1L if it doesn't exist. - * - * @param trackId the track id - */ - long getFirstTrackPointId(long trackId); - - /** - * Gets the last location id for a track. Returns -1L if it doesn't exist. - * - * @param trackId the track id - */ - long getLastTrackPointId(long trackId); - - /** - * Gets the track point id of a location. - * - * @param trackId the track id - * @param location the location - * @return track point id if the location is in the track. -1L otherwise. - */ - long getTrackPointId(long trackId, Location location); - - /** - * Gets the first valid location for a track. Returns null if it doesn't - * exist. - * - * @param trackId the track id - */ - Location getFirstValidTrackPoint(long trackId); - - /** - * Gets the last valid location for a track. Returns null if it doesn't exist. - * - * @param trackId the track id - */ - Location getLastValidTrackPoint(long trackId); - - - /** - * Gets the last valid location. - */ - Location getLastValidTrackPoint(); - - /** - * Creates a location cursor. The caller owns the returned cursor and is - * responsible for closing it. - * - * @param trackId the track id - * @param startTrackPointId the starting track point id. -1L to ignore - * @param maxLocations maximum number of locations to return. -1 for no limit - * @param descending true to sort the result in descending order (latest - * location first) - */ - Cursor getTrackPointCursor( - long trackId, long startTrackPointId, int maxLocations, boolean descending); - - /** - * Creates a new read-only iterator over a given track's points. It provides a - * lightweight way of iterating over long tracks without failing due to the - * underlying cursor limitations. Since it's a read-only iterator, - * {@link Iterator#remove()} always throws - * {@link UnsupportedOperationException}. Each call to - * {@link LocationIterator#next()} may advance to the next DB record, and if - * so, the iterator calls {@link LocationFactory#createLocation()} and - * populates it with information retrieved from the record. When done with - * iteration, {@link LocationIterator#close()} must be called. - * - * @param trackId the track id - * @param startTrackPointId the starting track point id. -1L to ignore - * @param descending true to sort the result in descending order (latest - * location first) - * @param locationFactory the location factory - */ - LocationIterator getTrackPointLocationIterator( - long trackId, long startTrackPointId, boolean descending, LocationFactory locationFactory); - - /** - * Inserts a track point. - * - * @param location the location - * @param trackId the track id - * @return the content provider URI of the inserted track point - */ - Uri insertTrackPoint(Location location, long trackId); - - /** - * A lightweight wrapper around the original {@link Cursor} with a method to - * clean up. - */ - interface LocationIterator extends Iterator { + /** + * The authority (the first part of the URI) for the app's content + * provider. + */ + String AUTHORITY = "com.google.android.maps.mytracks"; + /** + * The default {@link LocationFactory} which creates a location each time. + */ + LocationFactory DEFAULT_LOCATION_FACTORY = new LocationFactory() { + @Override + public Location createLocation() { + return new SensorDataSetLocation(LocationManager.GPS_PROVIDER); + } + }; /** - * Gets the most recently retrieved track point id by {@link #next()}. + * Clears a track. Removes waypoints and trackpoints. Only keeps the track id. + * + * @param trackId the track id */ - long getLocationId(); + void clearTrack(Context context, long trackId); /** - * Closes the iterator. + * Creates a {@link Track} from a cursor. + * + * @param cursor the cursor pointing to the track */ - void close(); - } - - /** - * A factory for creating new {@link Location}. - */ - interface LocationFactory { + Track createTrack(Cursor cursor); /** - * Creates a new {@link Location}. An implementation can create new - * instances or reuse existing instances for optimization. + * Deletes all tracks (including waypoints and track points). */ - Location createLocation(); - } - - /** - * The default {@link LocationFactory} which creates a location each time. - */ - LocationFactory DEFAULT_LOCATION_FACTORY = new LocationFactory() { - @Override - public Location createLocation() { - return new SensorDataSetLocation(LocationManager.GPS_PROVIDER); - } - }; - - /** - * A factory which can produce instances of {@link ContentProviderUtils}, and - * can be overridden for testing. - */ - class Factory { - - private static Factory instance = new Factory(); + void deleteAllTracks(Context context); /** - * Creates an instance of {@link ContentProviderUtils}. - * - * @param context the context + * Deletes a track. + * + * @param trackId the track id */ - public static ContentProviderUtils get(Context context) { - return instance.newForContext(context); + void deleteTrack(Context context, long trackId); + + /** + * Gets all the tracks. If no track exists, an empty list is returned. + *

+ * Note that the returned tracks do not have any track points attached. + */ + List getAllTracks(); + + /** + * Gets the last track. Returns null if doesn't exist. + */ + Track getLastTrack(); + + /** + * Gets a track by a track id. Returns null if not found. + *

+ * Note that the returned track doesn't have any track points attached. + * + * @param trackId the track id. + */ + Track getTrack(long trackId); + + /** + * Gets a track cursor. The caller owns the returned cursor and is responsible + * for closing it. + * + * @param selection the selection. Can be null + * @param selectionArgs the selection arguments. Can be null + * @param sortOrder the sort order. Can be null + */ + Cursor getTrackCursor(String selection, String[] selectionArgs, String sortOrder); + + /** + * Inserts a track. + *

+ * Note: This doesn't insert any track points. + * + * @param track the track + * @return the content provider URI of the inserted track. + */ + Uri insertTrack(Track track); + + /** + * Updates a track. + *

+ * Note: This doesn't update any track points. + * + * @param track the track + */ + void updateTrack(Track track); + + /** + * Creates a waypoint from a cursor. + * + * @param cursor the cursor pointing to the waypoint + */ + Waypoint createWaypoint(Cursor cursor); + + /** + * Deletes a waypoint. If deleting a statistics waypoint, this will also + * correct the next statistics waypoint after the deleted one to reflect the + * deletion. The generator is used to update the next statistics waypoint. + * + * @param waypointId the waypoint id + * @param descriptionGenerator the description generator. Can be null for + * waypoint marker + */ + void deleteWaypoint(Context context, long waypointId, DescriptionGenerator descriptionGenerator); + + /** + * Gets the first waypoint id for a track. The first waypoint is special as it + * contains the stats for the track. Returns -1L if it doesn't exist. + * + * @param trackId the track id + */ + long getFirstWaypointId(long trackId); + + /** + * Gets the last waypoint for a type. Returns null if it doesn't exist. + * + * @param trackId the track id + * @param waypointType the waypoint type + */ + Waypoint getLastWaypoint(long trackId, WaypointType waypointType); + + /** + * Gets the next waypoint number for a type. Returns -1 if not able to get the + * next waypoint number. + * + * @param trackId the track id + * @param waypointType the waypoint type + */ + int getNextWaypointNumber(long trackId, WaypointType waypointType); + + /** + * Gets a waypoint from a waypoint id. Returns null if not found. + * + * @param waypointId the waypoint id + */ + Waypoint getWaypoint(long waypointId); + + /** + * Gets a waypoint cursor. The caller owns the returned cursor and is + * responsible for closing it. + * + * @param selection the selection. Can be null + * @param selectionArgs the selection arguments. Can be null + * @param sortOrder the sort order. Can be null + * @param maxWaypoints the maximum number of waypoints to return. -1 for no + * limit + */ + Cursor getWaypointCursor(String selection, String[] selectionArgs, String sortOrder, int maxWaypoints); + + /** + * Gets a waypoint cursor for a track. The caller owns the returned cursor and + * is responsible for closing it. + * + * @param trackId the track id + * @param minWaypointId the minimum waypoint id. -1L to ignore + * @param maxWaypoints the maximum number of waypoints to return. -1 for no + * limit + */ + Cursor getWaypointCursor(long trackId, long minWaypointId, int maxWaypoints); + + /** + * Gets the number of waypoints for a track. + * + * @param trackId the track id + */ + int getWaypointCount(long trackId); + + /** + * Inserts a waypoint. + * + * @param waypoint the waypoint + * @return the content provider URI of the inserted waypoint. + */ + Uri insertWaypoint(Waypoint waypoint); + + /** + * Updates a waypoint. Returns true if successful. + * + * @param waypoint the waypoint + */ + boolean updateWaypoint(Waypoint waypoint); + + /** + * Inserts multiple track points. + * + * @param locations an array of locations + * @param length the number of locations (from the beginning of the array) to + * insert, or -1 for all of them + * @param trackId the track id + * @return the number of points inserted + */ + int bulkInsertTrackPoint(Location[] locations, int length, long trackId); + + /** + * Creates a location object from a cursor. + * + * @param cursor the cursor pointing to the location + */ + Location createTrackPoint(Cursor cursor); + + /** + * Gets the first location id for a track. Returns -1L if it doesn't exist. + * + * @param trackId the track id + */ + long getFirstTrackPointId(long trackId); + + /** + * Gets the last location id for a track. Returns -1L if it doesn't exist. + * + * @param trackId the track id + */ + long getLastTrackPointId(long trackId); + + /** + * Gets the track point id of a location. + * + * @param trackId the track id + * @param location the location + * @return track point id if the location is in the track. -1L otherwise. + */ + long getTrackPointId(long trackId, Location location); + + /** + * Gets the first valid location for a track. Returns null if it doesn't + * exist. + * + * @param trackId the track id + */ + Location getFirstValidTrackPoint(long trackId); + + /** + * Gets the last valid location for a track. Returns null if it doesn't exist. + * + * @param trackId the track id + */ + Location getLastValidTrackPoint(long trackId); + + /** + * Gets the last valid location. + */ + Location getLastValidTrackPoint(); + + /** + * Creates a location cursor. The caller owns the returned cursor and is + * responsible for closing it. + * + * @param trackId the track id + * @param startTrackPointId the starting track point id. -1L to ignore + * @param maxLocations maximum number of locations to return. -1 for no limit + * @param descending true to sort the result in descending order (latest + * location first) + */ + Cursor getTrackPointCursor( + long trackId, long startTrackPointId, int maxLocations, boolean descending); + + /** + * Creates a new read-only iterator over a given track's points. It provides a + * lightweight way of iterating over long tracks without failing due to the + * underlying cursor limitations. Since it's a read-only iterator, + * {@link Iterator#remove()} always throws + * {@link UnsupportedOperationException}. Each call to + * {@link LocationIterator#next()} may advance to the next DB record, and if + * so, the iterator calls {@link LocationFactory#createLocation()} and + * populates it with information retrieved from the record. When done with + * iteration, {@link LocationIterator#close()} must be called. + * + * @param trackId the track id + * @param startTrackPointId the starting track point id. -1L to ignore + * @param descending true to sort the result in descending order (latest + * location first) + * @param locationFactory the location factory + */ + LocationIterator getTrackPointLocationIterator( + long trackId, long startTrackPointId, boolean descending, LocationFactory locationFactory); + + /** + * Inserts a track point. + * + * @param location the location + * @param trackId the track id + * @return the content provider URI of the inserted track point + */ + Uri insertTrackPoint(Location location, long trackId); + + /** + * A lightweight wrapper around the original {@link Cursor} with a method to + * clean up. + */ + interface LocationIterator extends Iterator { + + /** + * Gets the most recently retrieved track point id by {@link #next()}. + */ + long getLocationId(); + + /** + * Closes the iterator. + */ + void close(); } /** - * Returns the factory instance. + * A factory for creating new {@link Location}. */ - public static Factory getInstance() { - return instance; + interface LocationFactory { + + /** + * Creates a new {@link Location}. An implementation can create new + * instances or reuse existing instances for optimization. + */ + Location createLocation(); } /** - * Overrides the factory instance for testing. Don't forget to set it back - * to the original value after testing. - * - * @param factory the factory + * A factory which can produce instances of {@link ContentProviderUtils}, and + * can be overridden for testing. */ - public static void overrideInstance(Factory factory) { - instance = factory; - } + class Factory { - /** - * Creates an instance of {@link ContentProviderUtils}. Allows subclasses - * to override for testing. - * - * @param context the context - */ - protected ContentProviderUtils newForContext(Context context) { - return new ContentProviderUtilsImpl(context.getContentResolver()); + private static Factory instance = new Factory(); + + /** + * Creates an instance of {@link ContentProviderUtils}. + * + * @param context the context + */ + public static ContentProviderUtils get(Context context) { + return instance.newForContext(context); + } + + /** + * Returns the factory instance. + */ + public static Factory getInstance() { + return instance; + } + + /** + * Overrides the factory instance for testing. Don't forget to set it back + * to the original value after testing. + * + * @param factory the factory + */ + public static void overrideInstance(Factory factory) { + instance = factory; + } + + /** + * Creates an instance of {@link ContentProviderUtils}. Allows subclasses + * to override for testing. + * + * @param context the context + */ + protected ContentProviderUtils newForContext(Context context) { + return new ContentProviderUtilsImpl(context.getContentResolver()); + } } - } } diff --git a/src/main/java/com/google/android/apps/mytracks/content/ContentProviderUtilsImpl.java b/src/main/java/com/google/android/apps/mytracks/content/ContentProviderUtilsImpl.java index 85183a759..0aa012f62 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/ContentProviderUtilsImpl.java +++ b/src/main/java/com/google/android/apps/mytracks/content/ContentProviderUtilsImpl.java @@ -36,1087 +36,1087 @@ import java.util.NoSuchElementException; /** * {@link ContentProviderUtils} implementation. - * + * * @author Leif Hendrik Wilden */ public class ContentProviderUtilsImpl implements ContentProviderUtils { - private static final String TAG = ContentProviderUtilsImpl.class.getSimpleName(); + private static final String TAG = ContentProviderUtilsImpl.class.getSimpleName(); - private static final int MAX_LATITUDE = 90000000; + private static final int MAX_LATITUDE = 90000000; - private final ContentResolver contentResolver; - private int defaultCursorBatchSize = 2000; + private final ContentResolver contentResolver; + private int defaultCursorBatchSize = 2000; - public ContentProviderUtilsImpl(ContentResolver contentResolver) { - this.contentResolver = contentResolver; - } - - @Override - public void clearTrack(Context context, long trackId) { - deleteTrackPointsAndWaypoints(context, trackId); - Track track = new Track(); - track.setId(trackId); - updateTrack(track); - } - - @Override - public Track createTrack(Cursor cursor) { - int idIndex = cursor.getColumnIndexOrThrow(TracksColumns._ID); - int nameIndex = cursor.getColumnIndexOrThrow(TracksColumns.NAME); - int descriptionIndex = cursor.getColumnIndexOrThrow(TracksColumns.DESCRIPTION); - int categoryIndex = cursor.getColumnIndexOrThrow(TracksColumns.CATEGORY); - int startIdIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTID); - int stopIdIndex = cursor.getColumnIndexOrThrow(TracksColumns.STOPID); - int startTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME); - int stopTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STOPTIME); - int numPointsIndex = cursor.getColumnIndexOrThrow(TracksColumns.NUMPOINTS); - int totalDistanceIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE); - int totalTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME); - int movingTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.MOVINGTIME); - int minLatIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINLAT); - int maxLatIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXLAT); - int minLonIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINLON); - int maxLonIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXLON); - int maxSpeedIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXSPEED); - int minElevationIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINELEVATION); - int maxElevationIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXELEVATION); - int elevationGainIndex = cursor.getColumnIndexOrThrow(TracksColumns.ELEVATIONGAIN); - int minGradeIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINGRADE); - int maxGradeIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXGRADE); - int iconIndex = cursor.getColumnIndexOrThrow(TracksColumns.ICON); - - Track track = new Track(); - TripStatistics tripStatistics = track.getTripStatistics(); - if (!cursor.isNull(idIndex)) { - track.setId(cursor.getLong(idIndex)); - } - if (!cursor.isNull(nameIndex)) { - track.setName(cursor.getString(nameIndex)); - } - if (!cursor.isNull(descriptionIndex)) { - track.setDescription(cursor.getString(descriptionIndex)); - } - if (!cursor.isNull(categoryIndex)) { - track.setCategory(cursor.getString(categoryIndex)); - } - if (!cursor.isNull(startIdIndex)) { - track.setStartId(cursor.getLong(startIdIndex)); - } - if (!cursor.isNull(stopIdIndex)) { - track.setStopId(cursor.getLong(stopIdIndex)); - } - if (!cursor.isNull(startTimeIndex)) { - tripStatistics.setStartTime(cursor.getLong(startTimeIndex)); - } - if (!cursor.isNull(stopTimeIndex)) { - tripStatistics.setStopTime(cursor.getLong(stopTimeIndex)); - } - if (!cursor.isNull(numPointsIndex)) { - track.setNumberOfPoints(cursor.getInt(numPointsIndex)); - } - if (!cursor.isNull(totalDistanceIndex)) { - tripStatistics.setTotalDistance(cursor.getFloat(totalDistanceIndex)); - } - if (!cursor.isNull(totalTimeIndex)) { - tripStatistics.setTotalTime(cursor.getLong(totalTimeIndex)); - } - if (!cursor.isNull(movingTimeIndex)) { - tripStatistics.setMovingTime(cursor.getLong(movingTimeIndex)); - } - if (!cursor.isNull(minLatIndex) && !cursor.isNull(maxLatIndex) && !cursor.isNull(minLonIndex) && !cursor.isNull(maxLonIndex)) { - int bottom = cursor.getInt(minLatIndex); - int top = cursor.getInt(maxLatIndex); - int left = cursor.getInt(minLonIndex); - int right = cursor.getInt(maxLonIndex); - tripStatistics.setBounds(left, top, right, bottom); - } - if (!cursor.isNull(maxSpeedIndex)) { - tripStatistics.setMaxSpeed(cursor.getFloat(maxSpeedIndex)); - } - if (!cursor.isNull(minElevationIndex)) { - tripStatistics.setMinElevation(cursor.getFloat(minElevationIndex)); - } - if (!cursor.isNull(maxElevationIndex)) { - tripStatistics.setMaxElevation(cursor.getFloat(maxElevationIndex)); - } - if (!cursor.isNull(elevationGainIndex)) { - tripStatistics.setTotalElevationGain(cursor.getFloat(elevationGainIndex)); - } - if (!cursor.isNull(minGradeIndex)) { - tripStatistics.setMinGrade(cursor.getFloat(minGradeIndex)); - } - if (!cursor.isNull(maxGradeIndex)) { - tripStatistics.setMaxGrade(cursor.getFloat(maxGradeIndex)); - } - if (!cursor.isNull(iconIndex)) { - track.setIcon(cursor.getString(iconIndex)); - } - return track; - } - - @Override - public void deleteAllTracks(Context context) { - contentResolver.delete(TrackPointsColumns.CONTENT_URI, null, null); - contentResolver.delete(WaypointsColumns.CONTENT_URI, null, null); - // Delete tracks last since it triggers a database vaccum call - contentResolver.delete(TracksColumns.CONTENT_URI, null, null); - - File dir = FileUtils.getPhotoDir(); - deleteDirectoryRecurse(context, dir); - } - - @Override - public void deleteTrack(Context context, long trackId) { - deleteTrackPointsAndWaypoints(context, trackId); - - // Delete track last since it triggers a database vaccum call - contentResolver.delete(TracksColumns.CONTENT_URI, TracksColumns._ID + "=?", - new String[] { Long.toString(trackId) }); - } - - /** - * Deletes track points and waypoints of a track. Assumes - * {@link TracksColumns#STARTID}, {@link TracksColumns#STOPID}, and - * {@link TracksColumns#NUMPOINTS} will be updated by the caller. - * - * @param trackId the track id - */ - private void deleteTrackPointsAndWaypoints(Context context, long trackId) { - Track track = getTrack(trackId); - if (track != null) { - String where = TrackPointsColumns._ID + ">=? AND " + TrackPointsColumns._ID + "<=?"; - String[] selectionArgs = new String[] { - Long.toString(track.getStartId()), Long.toString(track.getStopId()) }; - contentResolver.delete(TrackPointsColumns.CONTENT_URI, where, selectionArgs); - } - contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns.TRACKID + "=?", - new String[] { Long.toString(trackId) }); - deleteDirectoryRecurse(context, FileUtils.getPhotoDir(trackId)); - } - - /** - * Delete the directory recursively. - * - * @param dir the directory - */ - private void deleteDirectoryRecurse(Context context, File dir) { - if (FileUtils.isDirectory(dir)) { - for (File child : dir.listFiles()) { - deleteDirectoryRecurse(context, child); - } - } - if (dir.exists()) { - dir.delete(); - FileUtils.updateMediaScanner(context, Uri.fromFile(dir)); - } - } - - @Override - public List getAllTracks() { - ArrayList tracks = new ArrayList<>(); - try (Cursor cursor = getTrackCursor(null, null, null, TracksColumns._ID)) { - if (cursor != null && cursor.moveToFirst()) { - tracks.ensureCapacity(cursor.getCount()); - do { - tracks.add(createTrack(cursor)); - } while (cursor.moveToNext()); - } - } - return tracks; - } - - @Override - public Track getLastTrack() { - try (Cursor cursor = getTrackCursor(null, null, null,TracksColumns.STARTTIME + " DESC")) { - // Using the same order as shown in the track list - if (cursor != null && cursor.moveToNext()) { - return createTrack(cursor); - } - } - return null; - } - - @Override - public Track getTrack(long trackId) { - if (trackId < 0) { - return null; - } - try (Cursor cursor = getTrackCursor(null, TracksColumns._ID + "=?", - new String[]{Long.toString(trackId)}, TracksColumns._ID)) { - if (cursor != null && cursor.moveToNext()) { - return createTrack(cursor); - } - } - return null; - } - - @Override - public Cursor getTrackCursor(String selection, String[] selectionArgs, String sortOrder) { - return getTrackCursor(null, selection, selectionArgs, sortOrder); - } - - @Override - public Uri insertTrack(Track track) { - return contentResolver.insert(TracksColumns.CONTENT_URI, createContentValues(track)); - } - - @Override - public void updateTrack(Track track) { - contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(track), - TracksColumns._ID + "=?", new String[] { Long.toString(track.getId()) }); - } - - private ContentValues createContentValues(Track track) { - ContentValues values = new ContentValues(); - TripStatistics tripStatistics = track.getTripStatistics(); - - // Value < 0 indicates no id is available - if (track.getId() >= 0) { - values.put(TracksColumns._ID, track.getId()); - } - values.put(TracksColumns.NAME, track.getName()); - values.put(TracksColumns.DESCRIPTION, track.getDescription()); - values.put(TracksColumns.CATEGORY, track.getCategory()); - values.put(TracksColumns.STARTID, track.getStartId()); - values.put(TracksColumns.STOPID, track.getStopId()); - values.put(TracksColumns.STARTTIME, tripStatistics.getStartTime()); - values.put(TracksColumns.STOPTIME, tripStatistics.getStopTime()); - values.put(TracksColumns.NUMPOINTS, track.getNumberOfPoints()); - values.put(TracksColumns.TOTALDISTANCE, tripStatistics.getTotalDistance()); - values.put(TracksColumns.TOTALTIME, tripStatistics.getTotalTime()); - values.put(TracksColumns.MOVINGTIME, tripStatistics.getMovingTime()); - values.put(TracksColumns.MINLAT, tripStatistics.getBottom()); - values.put(TracksColumns.MAXLAT, tripStatistics.getTop()); - values.put(TracksColumns.MINLON, tripStatistics.getLeft()); - values.put(TracksColumns.MAXLON, tripStatistics.getRight()); - values.put(TracksColumns.AVGSPEED, tripStatistics.getAverageSpeed()); - values.put(TracksColumns.AVGMOVINGSPEED, tripStatistics.getAverageMovingSpeed()); - values.put(TracksColumns.MAXSPEED, tripStatistics.getMaxSpeed()); - values.put(TracksColumns.MINELEVATION, tripStatistics.getMinElevation()); - values.put(TracksColumns.MAXELEVATION, tripStatistics.getMaxElevation()); - values.put(TracksColumns.ELEVATIONGAIN, tripStatistics.getTotalElevationGain()); - values.put(TracksColumns.MINGRADE, tripStatistics.getMinGrade()); - values.put(TracksColumns.MAXGRADE, tripStatistics.getMaxGrade()); - values.put(TracksColumns.ICON, track.getIcon()); - - return values; - } - - /** - * Gets a track cursor. - * - * @param projection the projection - * @param selection the selection - * @param selectionArgs the selection arguments - * @param sortOrder the sort oder - */ - private Cursor getTrackCursor(String[] projection, String selection, String[] selectionArgs, String sortOrder) { - return contentResolver.query(TracksColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder); - } - - @Override - public Waypoint createWaypoint(Cursor cursor) { - int idIndex = cursor.getColumnIndexOrThrow(WaypointsColumns._ID); - int nameIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.NAME); - int descriptionIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.DESCRIPTION); - int categoryIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.CATEGORY); - int iconIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ICON); - int trackIdIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TRACKID); - int typeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TYPE); - int lengthIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.LENGTH); - int durationIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.DURATION); - int startTimeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.STARTTIME); - int startIdIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.STARTID); - int stopIdIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.STOPID); - int longitudeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.LONGITUDE); - int latitudeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.LATITUDE); - int timeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME); - int altitudeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ALTITUDE); - int accuracyIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ACCURACY); - int speedIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.SPEED); - int bearingIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.BEARING); - int totalDistanceIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TOTALDISTANCE); - int totalTimeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TOTALTIME); - int movingTimeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MOVINGTIME); - int maxSpeedIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXSPEED); - int minElevationIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MINELEVATION); - int maxElevationIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXELEVATION); - int elevationGainIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ELEVATIONGAIN); - int minGradeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MINGRADE); - int maxGradeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXGRADE); - int photoUrlIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.PHOTOURL); - - Waypoint waypoint = new Waypoint(); - - if (!cursor.isNull(idIndex)) { - waypoint.setId(cursor.getLong(idIndex)); - } - if (!cursor.isNull(nameIndex)) { - waypoint.setName(cursor.getString(nameIndex)); - } - if (!cursor.isNull(descriptionIndex)) { - waypoint.setDescription(cursor.getString(descriptionIndex)); - } - if (!cursor.isNull(categoryIndex)) { - waypoint.setCategory(cursor.getString(categoryIndex)); - } - if (!cursor.isNull(iconIndex)) { - waypoint.setIcon(cursor.getString(iconIndex)); - } - if (!cursor.isNull(trackIdIndex)) { - waypoint.setTrackId(cursor.getLong(trackIdIndex)); - } - if (!cursor.isNull(typeIndex)) { - waypoint.setType(WaypointType.values()[cursor.getInt(typeIndex)]); - } - if (!cursor.isNull(lengthIndex)) { - waypoint.setLength(cursor.getFloat(lengthIndex)); - } - if (!cursor.isNull(durationIndex)) { - waypoint.setDuration(cursor.getLong(durationIndex)); - } - if (!cursor.isNull(startIdIndex)) { - waypoint.setStartId(cursor.getLong(startIdIndex)); - } - if (!cursor.isNull(stopIdIndex)) { - waypoint.setStopId(cursor.getLong(stopIdIndex)); + public ContentProviderUtilsImpl(ContentResolver contentResolver) { + this.contentResolver = contentResolver; } - Location location = new Location(""); - if (!cursor.isNull(longitudeIndex) && !cursor.isNull(latitudeIndex)) { - location.setLongitude(((double) cursor.getInt(longitudeIndex)) / 1E6); - location.setLatitude(((double) cursor.getInt(latitudeIndex)) / 1E6); + @Override + public void clearTrack(Context context, long trackId) { + deleteTrackPointsAndWaypoints(context, trackId); + Track track = new Track(); + track.setId(trackId); + updateTrack(track); } - if (!cursor.isNull(timeIndex)) { - location.setTime(cursor.getLong(timeIndex)); - } - if (!cursor.isNull(altitudeIndex)) { - location.setAltitude(cursor.getFloat(altitudeIndex)); - } - if (!cursor.isNull(accuracyIndex)) { - location.setAccuracy(cursor.getFloat(accuracyIndex)); - } - if (!cursor.isNull(speedIndex)) { - location.setSpeed(cursor.getFloat(speedIndex)); - } - if (!cursor.isNull(bearingIndex)) { - location.setBearing(cursor.getFloat(bearingIndex)); - } - waypoint.setLocation(location); - TripStatistics tripStatistics = new TripStatistics(); - boolean hasTripStatistics = false; - if (!cursor.isNull(startTimeIndex)) { - tripStatistics.setStartTime(cursor.getLong(startTimeIndex)); - hasTripStatistics = true; - } - if (!cursor.isNull(totalDistanceIndex)) { - tripStatistics.setTotalDistance(cursor.getFloat(totalDistanceIndex)); - hasTripStatistics = true; - } - if (!cursor.isNull(totalTimeIndex)) { - tripStatistics.setTotalTime(cursor.getLong(totalTimeIndex)); - hasTripStatistics = true; - } - if (!cursor.isNull(movingTimeIndex)) { - tripStatistics.setMovingTime(cursor.getLong(movingTimeIndex)); - hasTripStatistics = true; - } - if (!cursor.isNull(maxSpeedIndex)) { - tripStatistics.setMaxSpeed(cursor.getFloat(maxSpeedIndex)); - hasTripStatistics = true; - } - if (!cursor.isNull(minElevationIndex)) { - tripStatistics.setMinElevation(cursor.getFloat(minElevationIndex)); - hasTripStatistics = true; - } - if (!cursor.isNull(maxElevationIndex)) { - tripStatistics.setMaxElevation(cursor.getFloat(maxElevationIndex)); - hasTripStatistics = true; - } - if (!cursor.isNull(elevationGainIndex)) { - tripStatistics.setTotalElevationGain(cursor.getFloat(elevationGainIndex)); - hasTripStatistics = true; - } - if (!cursor.isNull(minGradeIndex)) { - tripStatistics.setMinGrade(cursor.getFloat(minGradeIndex)); - hasTripStatistics = true; - } - if (!cursor.isNull(maxGradeIndex)) { - tripStatistics.setMaxGrade(cursor.getFloat(maxGradeIndex)); - hasTripStatistics = true; - } - if (hasTripStatistics) { - waypoint.setTripStatistics(tripStatistics); - } - - if (!cursor.isNull(photoUrlIndex)) { - waypoint.setPhotoUrl(cursor.getString(photoUrlIndex)); - } - return waypoint; - } + @Override + public Track createTrack(Cursor cursor) { + int idIndex = cursor.getColumnIndexOrThrow(TracksColumns._ID); + int nameIndex = cursor.getColumnIndexOrThrow(TracksColumns.NAME); + int descriptionIndex = cursor.getColumnIndexOrThrow(TracksColumns.DESCRIPTION); + int categoryIndex = cursor.getColumnIndexOrThrow(TracksColumns.CATEGORY); + int startIdIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTID); + int stopIdIndex = cursor.getColumnIndexOrThrow(TracksColumns.STOPID); + int startTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME); + int stopTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STOPTIME); + int numPointsIndex = cursor.getColumnIndexOrThrow(TracksColumns.NUMPOINTS); + int totalDistanceIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE); + int totalTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME); + int movingTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.MOVINGTIME); + int minLatIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINLAT); + int maxLatIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXLAT); + int minLonIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINLON); + int maxLonIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXLON); + int maxSpeedIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXSPEED); + int minElevationIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINELEVATION); + int maxElevationIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXELEVATION); + int elevationGainIndex = cursor.getColumnIndexOrThrow(TracksColumns.ELEVATIONGAIN); + int minGradeIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINGRADE); + int maxGradeIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXGRADE); + int iconIndex = cursor.getColumnIndexOrThrow(TracksColumns.ICON); - @Override - public void deleteWaypoint(Context context, long waypointId, DescriptionGenerator descriptionGenerator) { - final Waypoint waypoint = getWaypoint(waypointId); - if (waypoint != null && waypoint.getType() == WaypointType.STATISTICS - && descriptionGenerator != null) { - final Waypoint nextWaypoint = getNextStatisticsWaypointAfter(waypoint); - if (nextWaypoint == null) { - Log.d(TAG, "Unable to find the next statistics marker after deleting one."); - } else { - nextWaypoint.getTripStatistics().merge(waypoint.getTripStatistics()); - nextWaypoint.setDescription( - descriptionGenerator.generateWaypointDescription(nextWaypoint.getTripStatistics())); - if (!updateWaypoint(nextWaypoint)) { - Log.e(TAG, "Unable to update the next statistics marker after deleting one."); + Track track = new Track(); + TripStatistics tripStatistics = track.getTripStatistics(); + if (!cursor.isNull(idIndex)) { + track.setId(cursor.getLong(idIndex)); } - } - } - if (waypoint != null && waypoint.hasPhoto()) { - Uri uri = waypoint.getPhotoURI(); - File file = new File(uri.getPath()); - if (file.exists()) { - File parent = file.getParentFile(); - file.delete(); - FileUtils.updateMediaScanner(context, uri); - if (parent.listFiles().length == 0) { - parent.delete(); + if (!cursor.isNull(nameIndex)) { + track.setName(cursor.getString(nameIndex)); } - } - } - contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns._ID + "=?", new String[] { Long.toString(waypointId) }); - } - - @Override - public long getFirstWaypointId(long trackId) { - if (trackId < 0) { - return -1L; - } - try (Cursor cursor = getWaypointCursor(new String[]{WaypointsColumns._ID}, - WaypointsColumns.TRACKID + "=?", new String[]{Long.toString(trackId)}, - WaypointsColumns._ID, 1)) { - if (cursor != null && cursor.moveToFirst()) { - return cursor.getLong(cursor.getColumnIndexOrThrow(WaypointsColumns._ID)); - } - } - return -1L; - } - - @Override - public Waypoint getLastWaypoint(long trackId, WaypointType waypointType) { - if (trackId < 0) { - return null; - } - Cursor cursor = null; - try { - String selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns.TYPE + "=?"; - String[] selectionArgs = new String[] { - Long.toString(trackId), Integer.toString(waypointType.ordinal()) }; - cursor = getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID + " DESC", 1); - if (cursor != null && cursor.moveToFirst()) { - return createWaypoint(cursor); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return null; - } - - @Override - public int getNextWaypointNumber(long trackId, WaypointType waypointType) { - if (trackId < 0) { - return -1; - } - Cursor cursor = null; - try { - String[] projection = { WaypointsColumns._ID }; - String selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns.TYPE + "=?"; - String[] selectionArgs = new String[] { - Long.toString(trackId), Integer.toString(waypointType.ordinal()) }; - cursor = getWaypointCursor(projection, selection, selectionArgs, WaypointsColumns._ID, -1); - if (cursor != null) { - int count = cursor.getCount(); - /* - * For statistics markers, the first marker is for the track statistics, - * so return the count as the next user visible number. - */ - return waypointType == WaypointType.STATISTICS ? count : count + 1; - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return -1; - } - - @Override - public Waypoint getWaypoint(long waypointId) { - if (waypointId < 0) { - return null; - } - try (Cursor cursor = getWaypointCursor(null, WaypointsColumns._ID + "=?", - new String[]{Long.toString(waypointId)}, WaypointsColumns._ID, 1)) { - if (cursor != null && cursor.moveToFirst()) { - return createWaypoint(cursor); - } - } - return null; - } - - @Override - public Cursor getWaypointCursor( - String selection, String[] selectionArgs, String sortOrder, int maxWaypoints) { - return getWaypointCursor(null, selection, selectionArgs, sortOrder, maxWaypoints); - } - - @Override - public Cursor getWaypointCursor(long trackId, long minWaypointId, int maxWaypoints) { - if (trackId < 0) { - return null; - } - - String selection; - String[] selectionArgs; - if (minWaypointId >= 0) { - selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns._ID + ">=?"; - selectionArgs = new String[] { Long.toString(trackId), Long.toString(minWaypointId) }; - } else { - selection = WaypointsColumns.TRACKID + "=?"; - selectionArgs = new String[] { Long.toString(trackId) }; - } - return getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID, maxWaypoints); - } - - @Override - public int getWaypointCount(long trackId) { - if (trackId < 0) { - return 0; - } - - String[] projection = new String[] { "count(*) AS count" }; - String selection = WaypointsColumns.TRACKID + "=?"; - String[] selectionArgs = new String[] { Long.toString(trackId) }; - Cursor cursor = contentResolver.query(WaypointsColumns.CONTENT_URI, projection, selection, selectionArgs, WaypointsColumns._ID); - - cursor.moveToFirst(); - int count = cursor.getInt(0); - cursor.close(); - // not count the first waypoint - return count > 0 ? count - 1 : 0; - } - - @Override - public Uri insertWaypoint(Waypoint waypoint) { - waypoint.setId(-1L); - return contentResolver.insert(WaypointsColumns.CONTENT_URI, createContentValues(waypoint)); - } - - @Override - public boolean updateWaypoint(Waypoint waypoint) { - int rows = contentResolver.update(WaypointsColumns.CONTENT_URI, createContentValues(waypoint), - WaypointsColumns._ID + "=?", new String[] { Long.toString(waypoint.getId()) }); - return rows == 1; - } - - ContentValues createContentValues(Waypoint waypoint) { - ContentValues values = new ContentValues(); - - // Value < 0 indicates no id is available - if (waypoint.getId() >= 0) { - values.put(WaypointsColumns._ID, waypoint.getId()); - } - values.put(WaypointsColumns.NAME, waypoint.getName()); - values.put(WaypointsColumns.DESCRIPTION, waypoint.getDescription()); - values.put(WaypointsColumns.CATEGORY, waypoint.getCategory()); - values.put(WaypointsColumns.ICON, waypoint.getIcon()); - values.put(WaypointsColumns.TRACKID, waypoint.getTrackId()); - values.put(WaypointsColumns.TYPE, waypoint.getType().ordinal()); - values.put(WaypointsColumns.LENGTH, waypoint.getLength()); - values.put(WaypointsColumns.DURATION, waypoint.getDuration()); - values.put(WaypointsColumns.STARTID, waypoint.getStartId()); - values.put(WaypointsColumns.STOPID, waypoint.getStopId()); - - Location location = waypoint.getLocation(); - if (location != null) { - values.put(WaypointsColumns.LONGITUDE, (int) (location.getLongitude() * 1E6)); - values.put(WaypointsColumns.LATITUDE, (int) (location.getLatitude() * 1E6)); - values.put(WaypointsColumns.TIME, location.getTime()); - if (location.hasAltitude()) { - values.put(WaypointsColumns.ALTITUDE, location.getAltitude()); - } - if (location.hasAccuracy()) { - values.put(WaypointsColumns.ACCURACY, location.getAccuracy()); - } - if (location.hasSpeed()) { - values.put(WaypointsColumns.SPEED, location.getSpeed()); - } - if (location.hasBearing()) { - values.put(WaypointsColumns.BEARING, location.getBearing()); - } - } - - TripStatistics tripStatistics = waypoint.getTripStatistics(); - if (tripStatistics != null) { - values.put(WaypointsColumns.STARTTIME, tripStatistics.getStartTime()); - values.put(WaypointsColumns.TOTALDISTANCE, tripStatistics.getTotalDistance()); - values.put(WaypointsColumns.TOTALTIME, tripStatistics.getTotalTime()); - values.put(WaypointsColumns.MOVINGTIME, tripStatistics.getMovingTime()); - values.put(WaypointsColumns.AVGSPEED, tripStatistics.getAverageSpeed()); - values.put(WaypointsColumns.AVGMOVINGSPEED, tripStatistics.getAverageMovingSpeed()); - values.put(WaypointsColumns.MAXSPEED, tripStatistics.getMaxSpeed()); - values.put(WaypointsColumns.MINELEVATION, tripStatistics.getMinElevation()); - values.put(WaypointsColumns.MAXELEVATION, tripStatistics.getMaxElevation()); - values.put(WaypointsColumns.ELEVATIONGAIN, tripStatistics.getTotalElevationGain()); - values.put(WaypointsColumns.MINGRADE, tripStatistics.getMinGrade()); - values.put(WaypointsColumns.MAXGRADE, tripStatistics.getMaxGrade()); - } - - values.put(WaypointsColumns.PHOTOURL, waypoint.getPhotoUrl()); - return values; - } - - private Waypoint getNextStatisticsWaypointAfter(Waypoint waypoint) { - Cursor cursor = null; - try { - String selection = WaypointsColumns._ID + ">? AND " + WaypointsColumns.TRACKID + "=? AND " - + WaypointsColumns.TYPE + "=" + WaypointType.STATISTICS.ordinal(); - String[] selectionArgs = new String[] { - Long.toString(waypoint.getId()), Long.toString(waypoint.getTrackId()) }; - cursor = getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID, 1); - if (cursor != null && cursor.moveToFirst()) { - return createWaypoint(cursor); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return null; - } - - /** - * Gets a waypoint cursor. - * - * @param projection the projection - * @param selection the selection - * @param selectionArgs the selection args - * @param sortOrder the sort order - * @param maxWaypoints the maximum number of waypoints - */ - private Cursor getWaypointCursor(String[] projection, String selection, String[] selectionArgs, - String sortOrder, int maxWaypoints) { - if (sortOrder == null) { - sortOrder = WaypointsColumns._ID; - } - if (maxWaypoints >= 0) { - sortOrder += " LIMIT " + maxWaypoints; - } - return contentResolver.query( - WaypointsColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder); - } - - @Override - public int bulkInsertTrackPoint(Location[] locations, int length, long trackId) { - if (length == -1) { - length = locations.length; - } - ContentValues[] values = new ContentValues[length]; - for (int i = 0; i < length; i++) { - values[i] = createContentValues(locations[i], trackId); - } - return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI, values); - } - - @Override - public Location createTrackPoint(Cursor cursor) { - Location location = new SensorDataSetLocation(""); - fillTrackPoint(cursor, new CachedTrackPointsIndexes(cursor), location); - return location; - } - - @Override - public long getFirstTrackPointId(long trackId) { - if (trackId < 0) { - return -1L; - } - Cursor cursor = null; - try { - String selection = TrackPointsColumns._ID + "=(select min(" + TrackPointsColumns._ID - + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID - + "=?)"; - String[] selectionArgs = new String[] { Long.toString(trackId) }; - cursor = getTrackPointCursor(new String[] { TrackPointsColumns._ID }, selection, - selectionArgs, TrackPointsColumns._ID); - if (cursor != null && cursor.moveToFirst()) { - return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return -1L; - } - - @Override - public long getLastTrackPointId(long trackId) { - if (trackId < 0) { - return -1L; - } - Cursor cursor = null; - try { - String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID - + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID - + "=?)"; - String[] selectionArgs = new String[] { Long.toString(trackId) }; - cursor = getTrackPointCursor(new String[] { TrackPointsColumns._ID }, selection, - selectionArgs, TrackPointsColumns._ID); - if (cursor != null && cursor.moveToFirst()) { - return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return -1L; - } - - @Override - public long getTrackPointId(long trackId, Location location) { - if (trackId < 0) { - return -1L; - } - Cursor cursor = null; - try { - String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID - + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID - + "=? AND " + TrackPointsColumns.TIME + "=?)"; - String[] selectionArgs = new String[] { - Long.toString(trackId), Long.toString(location.getTime()) }; - cursor = getTrackPointCursor(new String[] { TrackPointsColumns._ID }, selection, - selectionArgs, TrackPointsColumns._ID); - if (cursor != null && cursor.moveToFirst()) { - return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return -1L; - } - - @Override - public Location getFirstValidTrackPoint(long trackId) { - if (trackId < 0) { - return null; - } - String selection = TrackPointsColumns._ID + "=(select min(" + TrackPointsColumns._ID + ") from " - + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " - + TrackPointsColumns.LATITUDE + "<=" + MAX_LATITUDE + ")"; - String[] selectionArgs = new String[] { Long.toString(trackId) }; - return findTrackPointBy(selection, selectionArgs); - } - - @Override - public Location getLastValidTrackPoint(long trackId) { - if (trackId < 0) { - return null; - } - String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID + ") from " - + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " - + TrackPointsColumns.LATITUDE + "<=" + MAX_LATITUDE + ")"; - String[] selectionArgs = new String[] { Long.toString(trackId) }; - return findTrackPointBy(selection, selectionArgs); - } - - @Override - public Location getLastValidTrackPoint() { - String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID + ") from " - + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.LATITUDE + "<=" - + MAX_LATITUDE + ")"; - return findTrackPointBy(selection, null); - } - - @Override - public Cursor getTrackPointCursor( - long trackId, long startTrackPointId, int maxLocations, boolean descending) { - if (trackId < 0) { - return null; - } - - String selection; - String[] selectionArgs; - if (startTrackPointId >= 0) { - String comparison = descending ? "<=" : ">="; - selection = TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns._ID + comparison - + "?"; - selectionArgs = new String[] { Long.toString(trackId), Long.toString(startTrackPointId) }; - } else { - selection = TrackPointsColumns.TRACKID + "=?"; - selectionArgs = new String[] { Long.toString(trackId) }; - } - - String sortOrder = TrackPointsColumns._ID; - if (descending) { - sortOrder += " DESC"; - } - if (maxLocations >= 0) { - sortOrder += " LIMIT " + maxLocations; - } - return getTrackPointCursor(null, selection, selectionArgs, sortOrder); - } - - @Override - public LocationIterator getTrackPointLocationIterator(final long trackId, - final long startTrackPointId, final boolean descending, - final LocationFactory locationFactory) { - if (locationFactory == null) { - throw new IllegalArgumentException("locationFactory is null"); - } - return new LocationIterator() { - private long lastTrackPointId = -1L; - private Cursor cursor = getCursor(startTrackPointId); - private final CachedTrackPointsIndexes - indexes = cursor != null ? new CachedTrackPointsIndexes(cursor) - : null; - - /** - * Gets the track point cursor. - * - * @param trackPointId the starting track point id - */ - private Cursor getCursor(long trackPointId) { - return getTrackPointCursor(trackId, trackPointId, defaultCursorBatchSize, descending); - } - - /** - * Advances the cursor to the next batch. Returns true if successful. - */ - private boolean advanceCursorToNextBatch() { - long trackPointId = lastTrackPointId == -1L ? -1L - : lastTrackPointId + (descending ? -1 : 1); - Log.d(TAG, "Advancing track point id: " + trackPointId); - cursor.close(); - cursor = getCursor(trackPointId); - return cursor != null; - } - - @Override - public long getLocationId() { - return lastTrackPointId; - } - - @Override - public boolean hasNext() { - if (cursor == null) { - return false; + if (!cursor.isNull(descriptionIndex)) { + track.setDescription(cursor.getString(descriptionIndex)); } - if (cursor.isAfterLast()) { - return false; + if (!cursor.isNull(categoryIndex)) { + track.setCategory(cursor.getString(categoryIndex)); } - if (cursor.isLast()) { - if (cursor.getCount() != defaultCursorBatchSize) { - return false; - } - return advanceCursorToNextBatch() && !cursor.isAfterLast(); + if (!cursor.isNull(startIdIndex)) { + track.setStartId(cursor.getLong(startIdIndex)); } - return true; - } + if (!cursor.isNull(stopIdIndex)) { + track.setStopId(cursor.getLong(stopIdIndex)); + } + if (!cursor.isNull(startTimeIndex)) { + tripStatistics.setStartTime(cursor.getLong(startTimeIndex)); + } + if (!cursor.isNull(stopTimeIndex)) { + tripStatistics.setStopTime(cursor.getLong(stopTimeIndex)); + } + if (!cursor.isNull(numPointsIndex)) { + track.setNumberOfPoints(cursor.getInt(numPointsIndex)); + } + if (!cursor.isNull(totalDistanceIndex)) { + tripStatistics.setTotalDistance(cursor.getFloat(totalDistanceIndex)); + } + if (!cursor.isNull(totalTimeIndex)) { + tripStatistics.setTotalTime(cursor.getLong(totalTimeIndex)); + } + if (!cursor.isNull(movingTimeIndex)) { + tripStatistics.setMovingTime(cursor.getLong(movingTimeIndex)); + } + if (!cursor.isNull(minLatIndex) && !cursor.isNull(maxLatIndex) && !cursor.isNull(minLonIndex) && !cursor.isNull(maxLonIndex)) { + int bottom = cursor.getInt(minLatIndex); + int top = cursor.getInt(maxLatIndex); + int left = cursor.getInt(minLonIndex); + int right = cursor.getInt(maxLonIndex); + tripStatistics.setBounds(left, top, right, bottom); + } + if (!cursor.isNull(maxSpeedIndex)) { + tripStatistics.setMaxSpeed(cursor.getFloat(maxSpeedIndex)); + } + if (!cursor.isNull(minElevationIndex)) { + tripStatistics.setMinElevation(cursor.getFloat(minElevationIndex)); + } + if (!cursor.isNull(maxElevationIndex)) { + tripStatistics.setMaxElevation(cursor.getFloat(maxElevationIndex)); + } + if (!cursor.isNull(elevationGainIndex)) { + tripStatistics.setTotalElevationGain(cursor.getFloat(elevationGainIndex)); + } + if (!cursor.isNull(minGradeIndex)) { + tripStatistics.setMinGrade(cursor.getFloat(minGradeIndex)); + } + if (!cursor.isNull(maxGradeIndex)) { + tripStatistics.setMaxGrade(cursor.getFloat(maxGradeIndex)); + } + if (!cursor.isNull(iconIndex)) { + track.setIcon(cursor.getString(iconIndex)); + } + return track; + } - @Override - public Location next() { - if (cursor == null) { - throw new NoSuchElementException(); + @Override + public void deleteAllTracks(Context context) { + contentResolver.delete(TrackPointsColumns.CONTENT_URI, null, null); + contentResolver.delete(WaypointsColumns.CONTENT_URI, null, null); + // Delete tracks last since it triggers a database vaccum call + contentResolver.delete(TracksColumns.CONTENT_URI, null, null); + + File dir = FileUtils.getPhotoDir(); + deleteDirectoryRecurse(context, dir); + } + + @Override + public void deleteTrack(Context context, long trackId) { + deleteTrackPointsAndWaypoints(context, trackId); + + // Delete track last since it triggers a database vaccum call + contentResolver.delete(TracksColumns.CONTENT_URI, TracksColumns._ID + "=?", + new String[]{Long.toString(trackId)}); + } + + /** + * Deletes track points and waypoints of a track. Assumes + * {@link TracksColumns#STARTID}, {@link TracksColumns#STOPID}, and + * {@link TracksColumns#NUMPOINTS} will be updated by the caller. + * + * @param trackId the track id + */ + private void deleteTrackPointsAndWaypoints(Context context, long trackId) { + Track track = getTrack(trackId); + if (track != null) { + String where = TrackPointsColumns._ID + ">=? AND " + TrackPointsColumns._ID + "<=?"; + String[] selectionArgs = new String[]{ + Long.toString(track.getStartId()), Long.toString(track.getStopId())}; + contentResolver.delete(TrackPointsColumns.CONTENT_URI, where, selectionArgs); } - if (!cursor.moveToNext()) { - if (!advanceCursorToNextBatch() || !cursor.moveToNext()) { - throw new NoSuchElementException(); - } + contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns.TRACKID + "=?", + new String[]{Long.toString(trackId)}); + deleteDirectoryRecurse(context, FileUtils.getPhotoDir(trackId)); + } + + /** + * Delete the directory recursively. + * + * @param dir the directory + */ + private void deleteDirectoryRecurse(Context context, File dir) { + if (FileUtils.isDirectory(dir)) { + for (File child : dir.listFiles()) { + deleteDirectoryRecurse(context, child); + } } - lastTrackPointId = cursor.getLong(indexes.idIndex); - Location location = locationFactory.createLocation(); - fillTrackPoint(cursor, indexes, location); + if (dir.exists()) { + dir.delete(); + FileUtils.updateMediaScanner(context, Uri.fromFile(dir)); + } + } + + @Override + public List getAllTracks() { + ArrayList tracks = new ArrayList<>(); + try (Cursor cursor = getTrackCursor(null, null, null, TracksColumns._ID)) { + if (cursor != null && cursor.moveToFirst()) { + tracks.ensureCapacity(cursor.getCount()); + do { + tracks.add(createTrack(cursor)); + } while (cursor.moveToNext()); + } + } + return tracks; + } + + @Override + public Track getLastTrack() { + try (Cursor cursor = getTrackCursor(null, null, null, TracksColumns.STARTTIME + " DESC")) { + // Using the same order as shown in the track list + if (cursor != null && cursor.moveToNext()) { + return createTrack(cursor); + } + } + return null; + } + + @Override + public Track getTrack(long trackId) { + if (trackId < 0) { + return null; + } + try (Cursor cursor = getTrackCursor(null, TracksColumns._ID + "=?", + new String[]{Long.toString(trackId)}, TracksColumns._ID)) { + if (cursor != null && cursor.moveToNext()) { + return createTrack(cursor); + } + } + return null; + } + + @Override + public Cursor getTrackCursor(String selection, String[] selectionArgs, String sortOrder) { + return getTrackCursor(null, selection, selectionArgs, sortOrder); + } + + @Override + public Uri insertTrack(Track track) { + return contentResolver.insert(TracksColumns.CONTENT_URI, createContentValues(track)); + } + + @Override + public void updateTrack(Track track) { + contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(track), + TracksColumns._ID + "=?", new String[]{Long.toString(track.getId())}); + } + + private ContentValues createContentValues(Track track) { + ContentValues values = new ContentValues(); + TripStatistics tripStatistics = track.getTripStatistics(); + + // Value < 0 indicates no id is available + if (track.getId() >= 0) { + values.put(TracksColumns._ID, track.getId()); + } + values.put(TracksColumns.NAME, track.getName()); + values.put(TracksColumns.DESCRIPTION, track.getDescription()); + values.put(TracksColumns.CATEGORY, track.getCategory()); + values.put(TracksColumns.STARTID, track.getStartId()); + values.put(TracksColumns.STOPID, track.getStopId()); + values.put(TracksColumns.STARTTIME, tripStatistics.getStartTime()); + values.put(TracksColumns.STOPTIME, tripStatistics.getStopTime()); + values.put(TracksColumns.NUMPOINTS, track.getNumberOfPoints()); + values.put(TracksColumns.TOTALDISTANCE, tripStatistics.getTotalDistance()); + values.put(TracksColumns.TOTALTIME, tripStatistics.getTotalTime()); + values.put(TracksColumns.MOVINGTIME, tripStatistics.getMovingTime()); + values.put(TracksColumns.MINLAT, tripStatistics.getBottom()); + values.put(TracksColumns.MAXLAT, tripStatistics.getTop()); + values.put(TracksColumns.MINLON, tripStatistics.getLeft()); + values.put(TracksColumns.MAXLON, tripStatistics.getRight()); + values.put(TracksColumns.AVGSPEED, tripStatistics.getAverageSpeed()); + values.put(TracksColumns.AVGMOVINGSPEED, tripStatistics.getAverageMovingSpeed()); + values.put(TracksColumns.MAXSPEED, tripStatistics.getMaxSpeed()); + values.put(TracksColumns.MINELEVATION, tripStatistics.getMinElevation()); + values.put(TracksColumns.MAXELEVATION, tripStatistics.getMaxElevation()); + values.put(TracksColumns.ELEVATIONGAIN, tripStatistics.getTotalElevationGain()); + values.put(TracksColumns.MINGRADE, tripStatistics.getMinGrade()); + values.put(TracksColumns.MAXGRADE, tripStatistics.getMaxGrade()); + values.put(TracksColumns.ICON, track.getIcon()); + + return values; + } + + /** + * Gets a track cursor. + * + * @param projection the projection + * @param selection the selection + * @param selectionArgs the selection arguments + * @param sortOrder the sort oder + */ + private Cursor getTrackCursor(String[] projection, String selection, String[] selectionArgs, String sortOrder) { + return contentResolver.query(TracksColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder); + } + + @Override + public Waypoint createWaypoint(Cursor cursor) { + int idIndex = cursor.getColumnIndexOrThrow(WaypointsColumns._ID); + int nameIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.NAME); + int descriptionIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.DESCRIPTION); + int categoryIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.CATEGORY); + int iconIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ICON); + int trackIdIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TRACKID); + int typeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TYPE); + int lengthIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.LENGTH); + int durationIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.DURATION); + int startTimeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.STARTTIME); + int startIdIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.STARTID); + int stopIdIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.STOPID); + int longitudeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.LONGITUDE); + int latitudeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.LATITUDE); + int timeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME); + int altitudeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ALTITUDE); + int accuracyIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ACCURACY); + int speedIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.SPEED); + int bearingIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.BEARING); + int totalDistanceIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TOTALDISTANCE); + int totalTimeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TOTALTIME); + int movingTimeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MOVINGTIME); + int maxSpeedIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXSPEED); + int minElevationIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MINELEVATION); + int maxElevationIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXELEVATION); + int elevationGainIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ELEVATIONGAIN); + int minGradeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MINGRADE); + int maxGradeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXGRADE); + int photoUrlIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.PHOTOURL); + + Waypoint waypoint = new Waypoint(); + + if (!cursor.isNull(idIndex)) { + waypoint.setId(cursor.getLong(idIndex)); + } + if (!cursor.isNull(nameIndex)) { + waypoint.setName(cursor.getString(nameIndex)); + } + if (!cursor.isNull(descriptionIndex)) { + waypoint.setDescription(cursor.getString(descriptionIndex)); + } + if (!cursor.isNull(categoryIndex)) { + waypoint.setCategory(cursor.getString(categoryIndex)); + } + if (!cursor.isNull(iconIndex)) { + waypoint.setIcon(cursor.getString(iconIndex)); + } + if (!cursor.isNull(trackIdIndex)) { + waypoint.setTrackId(cursor.getLong(trackIdIndex)); + } + if (!cursor.isNull(typeIndex)) { + waypoint.setType(WaypointType.values()[cursor.getInt(typeIndex)]); + } + if (!cursor.isNull(lengthIndex)) { + waypoint.setLength(cursor.getFloat(lengthIndex)); + } + if (!cursor.isNull(durationIndex)) { + waypoint.setDuration(cursor.getLong(durationIndex)); + } + if (!cursor.isNull(startIdIndex)) { + waypoint.setStartId(cursor.getLong(startIdIndex)); + } + if (!cursor.isNull(stopIdIndex)) { + waypoint.setStopId(cursor.getLong(stopIdIndex)); + } + + Location location = new Location(""); + if (!cursor.isNull(longitudeIndex) && !cursor.isNull(latitudeIndex)) { + location.setLongitude(((double) cursor.getInt(longitudeIndex)) / 1E6); + location.setLatitude(((double) cursor.getInt(latitudeIndex)) / 1E6); + } + if (!cursor.isNull(timeIndex)) { + location.setTime(cursor.getLong(timeIndex)); + } + if (!cursor.isNull(altitudeIndex)) { + location.setAltitude(cursor.getFloat(altitudeIndex)); + } + if (!cursor.isNull(accuracyIndex)) { + location.setAccuracy(cursor.getFloat(accuracyIndex)); + } + if (!cursor.isNull(speedIndex)) { + location.setSpeed(cursor.getFloat(speedIndex)); + } + if (!cursor.isNull(bearingIndex)) { + location.setBearing(cursor.getFloat(bearingIndex)); + } + waypoint.setLocation(location); + + TripStatistics tripStatistics = new TripStatistics(); + boolean hasTripStatistics = false; + if (!cursor.isNull(startTimeIndex)) { + tripStatistics.setStartTime(cursor.getLong(startTimeIndex)); + hasTripStatistics = true; + } + if (!cursor.isNull(totalDistanceIndex)) { + tripStatistics.setTotalDistance(cursor.getFloat(totalDistanceIndex)); + hasTripStatistics = true; + } + if (!cursor.isNull(totalTimeIndex)) { + tripStatistics.setTotalTime(cursor.getLong(totalTimeIndex)); + hasTripStatistics = true; + } + if (!cursor.isNull(movingTimeIndex)) { + tripStatistics.setMovingTime(cursor.getLong(movingTimeIndex)); + hasTripStatistics = true; + } + if (!cursor.isNull(maxSpeedIndex)) { + tripStatistics.setMaxSpeed(cursor.getFloat(maxSpeedIndex)); + hasTripStatistics = true; + } + if (!cursor.isNull(minElevationIndex)) { + tripStatistics.setMinElevation(cursor.getFloat(minElevationIndex)); + hasTripStatistics = true; + } + if (!cursor.isNull(maxElevationIndex)) { + tripStatistics.setMaxElevation(cursor.getFloat(maxElevationIndex)); + hasTripStatistics = true; + } + if (!cursor.isNull(elevationGainIndex)) { + tripStatistics.setTotalElevationGain(cursor.getFloat(elevationGainIndex)); + hasTripStatistics = true; + } + if (!cursor.isNull(minGradeIndex)) { + tripStatistics.setMinGrade(cursor.getFloat(minGradeIndex)); + hasTripStatistics = true; + } + if (!cursor.isNull(maxGradeIndex)) { + tripStatistics.setMaxGrade(cursor.getFloat(maxGradeIndex)); + hasTripStatistics = true; + } + if (hasTripStatistics) { + waypoint.setTripStatistics(tripStatistics); + } + + if (!cursor.isNull(photoUrlIndex)) { + waypoint.setPhotoUrl(cursor.getString(photoUrlIndex)); + } + return waypoint; + } + + @Override + public void deleteWaypoint(Context context, long waypointId, DescriptionGenerator descriptionGenerator) { + final Waypoint waypoint = getWaypoint(waypointId); + if (waypoint != null && waypoint.getType() == WaypointType.STATISTICS + && descriptionGenerator != null) { + final Waypoint nextWaypoint = getNextStatisticsWaypointAfter(waypoint); + if (nextWaypoint == null) { + Log.d(TAG, "Unable to find the next statistics marker after deleting one."); + } else { + nextWaypoint.getTripStatistics().merge(waypoint.getTripStatistics()); + nextWaypoint.setDescription( + descriptionGenerator.generateWaypointDescription(nextWaypoint.getTripStatistics())); + if (!updateWaypoint(nextWaypoint)) { + Log.e(TAG, "Unable to update the next statistics marker after deleting one."); + } + } + } + if (waypoint != null && waypoint.hasPhoto()) { + Uri uri = waypoint.getPhotoURI(); + File file = new File(uri.getPath()); + if (file.exists()) { + File parent = file.getParentFile(); + file.delete(); + FileUtils.updateMediaScanner(context, uri); + if (parent.listFiles().length == 0) { + parent.delete(); + } + } + } + contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns._ID + "=?", new String[]{Long.toString(waypointId)}); + } + + @Override + public long getFirstWaypointId(long trackId) { + if (trackId < 0) { + return -1L; + } + try (Cursor cursor = getWaypointCursor(new String[]{WaypointsColumns._ID}, + WaypointsColumns.TRACKID + "=?", new String[]{Long.toString(trackId)}, + WaypointsColumns._ID, 1)) { + if (cursor != null && cursor.moveToFirst()) { + return cursor.getLong(cursor.getColumnIndexOrThrow(WaypointsColumns._ID)); + } + } + return -1L; + } + + @Override + public Waypoint getLastWaypoint(long trackId, WaypointType waypointType) { + if (trackId < 0) { + return null; + } + Cursor cursor = null; + try { + String selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns.TYPE + "=?"; + String[] selectionArgs = new String[]{ + Long.toString(trackId), Integer.toString(waypointType.ordinal())}; + cursor = getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID + " DESC", 1); + if (cursor != null && cursor.moveToFirst()) { + return createWaypoint(cursor); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + @Override + public int getNextWaypointNumber(long trackId, WaypointType waypointType) { + if (trackId < 0) { + return -1; + } + Cursor cursor = null; + try { + String[] projection = {WaypointsColumns._ID}; + String selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns.TYPE + "=?"; + String[] selectionArgs = new String[]{ + Long.toString(trackId), Integer.toString(waypointType.ordinal())}; + cursor = getWaypointCursor(projection, selection, selectionArgs, WaypointsColumns._ID, -1); + if (cursor != null) { + int count = cursor.getCount(); + /* + * For statistics markers, the first marker is for the track statistics, + * so return the count as the next user visible number. + */ + return waypointType == WaypointType.STATISTICS ? count : count + 1; + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return -1; + } + + @Override + public Waypoint getWaypoint(long waypointId) { + if (waypointId < 0) { + return null; + } + try (Cursor cursor = getWaypointCursor(null, WaypointsColumns._ID + "=?", + new String[]{Long.toString(waypointId)}, WaypointsColumns._ID, 1)) { + if (cursor != null && cursor.moveToFirst()) { + return createWaypoint(cursor); + } + } + return null; + } + + @Override + public Cursor getWaypointCursor( + String selection, String[] selectionArgs, String sortOrder, int maxWaypoints) { + return getWaypointCursor(null, selection, selectionArgs, sortOrder, maxWaypoints); + } + + @Override + public Cursor getWaypointCursor(long trackId, long minWaypointId, int maxWaypoints) { + if (trackId < 0) { + return null; + } + + String selection; + String[] selectionArgs; + if (minWaypointId >= 0) { + selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns._ID + ">=?"; + selectionArgs = new String[]{Long.toString(trackId), Long.toString(minWaypointId)}; + } else { + selection = WaypointsColumns.TRACKID + "=?"; + selectionArgs = new String[]{Long.toString(trackId)}; + } + return getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID, maxWaypoints); + } + + @Override + public int getWaypointCount(long trackId) { + if (trackId < 0) { + return 0; + } + + String[] projection = new String[]{"count(*) AS count"}; + String selection = WaypointsColumns.TRACKID + "=?"; + String[] selectionArgs = new String[]{Long.toString(trackId)}; + Cursor cursor = contentResolver.query(WaypointsColumns.CONTENT_URI, projection, selection, selectionArgs, WaypointsColumns._ID); + + cursor.moveToFirst(); + int count = cursor.getInt(0); + cursor.close(); + // not count the first waypoint + return count > 0 ? count - 1 : 0; + } + + @Override + public Uri insertWaypoint(Waypoint waypoint) { + waypoint.setId(-1L); + return contentResolver.insert(WaypointsColumns.CONTENT_URI, createContentValues(waypoint)); + } + + @Override + public boolean updateWaypoint(Waypoint waypoint) { + int rows = contentResolver.update(WaypointsColumns.CONTENT_URI, createContentValues(waypoint), + WaypointsColumns._ID + "=?", new String[]{Long.toString(waypoint.getId())}); + return rows == 1; + } + + ContentValues createContentValues(Waypoint waypoint) { + ContentValues values = new ContentValues(); + + // Value < 0 indicates no id is available + if (waypoint.getId() >= 0) { + values.put(WaypointsColumns._ID, waypoint.getId()); + } + values.put(WaypointsColumns.NAME, waypoint.getName()); + values.put(WaypointsColumns.DESCRIPTION, waypoint.getDescription()); + values.put(WaypointsColumns.CATEGORY, waypoint.getCategory()); + values.put(WaypointsColumns.ICON, waypoint.getIcon()); + values.put(WaypointsColumns.TRACKID, waypoint.getTrackId()); + values.put(WaypointsColumns.TYPE, waypoint.getType().ordinal()); + values.put(WaypointsColumns.LENGTH, waypoint.getLength()); + values.put(WaypointsColumns.DURATION, waypoint.getDuration()); + values.put(WaypointsColumns.STARTID, waypoint.getStartId()); + values.put(WaypointsColumns.STOPID, waypoint.getStopId()); + + Location location = waypoint.getLocation(); + if (location != null) { + values.put(WaypointsColumns.LONGITUDE, (int) (location.getLongitude() * 1E6)); + values.put(WaypointsColumns.LATITUDE, (int) (location.getLatitude() * 1E6)); + values.put(WaypointsColumns.TIME, location.getTime()); + if (location.hasAltitude()) { + values.put(WaypointsColumns.ALTITUDE, location.getAltitude()); + } + if (location.hasAccuracy()) { + values.put(WaypointsColumns.ACCURACY, location.getAccuracy()); + } + if (location.hasSpeed()) { + values.put(WaypointsColumns.SPEED, location.getSpeed()); + } + if (location.hasBearing()) { + values.put(WaypointsColumns.BEARING, location.getBearing()); + } + } + + TripStatistics tripStatistics = waypoint.getTripStatistics(); + if (tripStatistics != null) { + values.put(WaypointsColumns.STARTTIME, tripStatistics.getStartTime()); + values.put(WaypointsColumns.TOTALDISTANCE, tripStatistics.getTotalDistance()); + values.put(WaypointsColumns.TOTALTIME, tripStatistics.getTotalTime()); + values.put(WaypointsColumns.MOVINGTIME, tripStatistics.getMovingTime()); + values.put(WaypointsColumns.AVGSPEED, tripStatistics.getAverageSpeed()); + values.put(WaypointsColumns.AVGMOVINGSPEED, tripStatistics.getAverageMovingSpeed()); + values.put(WaypointsColumns.MAXSPEED, tripStatistics.getMaxSpeed()); + values.put(WaypointsColumns.MINELEVATION, tripStatistics.getMinElevation()); + values.put(WaypointsColumns.MAXELEVATION, tripStatistics.getMaxElevation()); + values.put(WaypointsColumns.ELEVATIONGAIN, tripStatistics.getTotalElevationGain()); + values.put(WaypointsColumns.MINGRADE, tripStatistics.getMinGrade()); + values.put(WaypointsColumns.MAXGRADE, tripStatistics.getMaxGrade()); + } + + values.put(WaypointsColumns.PHOTOURL, waypoint.getPhotoUrl()); + return values; + } + + private Waypoint getNextStatisticsWaypointAfter(Waypoint waypoint) { + Cursor cursor = null; + try { + String selection = WaypointsColumns._ID + ">? AND " + WaypointsColumns.TRACKID + "=? AND " + + WaypointsColumns.TYPE + "=" + WaypointType.STATISTICS.ordinal(); + String[] selectionArgs = new String[]{ + Long.toString(waypoint.getId()), Long.toString(waypoint.getTrackId())}; + cursor = getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID, 1); + if (cursor != null && cursor.moveToFirst()) { + return createWaypoint(cursor); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + /** + * Gets a waypoint cursor. + * + * @param projection the projection + * @param selection the selection + * @param selectionArgs the selection args + * @param sortOrder the sort order + * @param maxWaypoints the maximum number of waypoints + */ + private Cursor getWaypointCursor(String[] projection, String selection, String[] selectionArgs, + String sortOrder, int maxWaypoints) { + if (sortOrder == null) { + sortOrder = WaypointsColumns._ID; + } + if (maxWaypoints >= 0) { + sortOrder += " LIMIT " + maxWaypoints; + } + return contentResolver.query( + WaypointsColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder); + } + + @Override + public int bulkInsertTrackPoint(Location[] locations, int length, long trackId) { + if (length == -1) { + length = locations.length; + } + ContentValues[] values = new ContentValues[length]; + for (int i = 0; i < length; i++) { + values[i] = createContentValues(locations[i], trackId); + } + return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI, values); + } + + @Override + public Location createTrackPoint(Cursor cursor) { + Location location = new SensorDataSetLocation(""); + fillTrackPoint(cursor, new CachedTrackPointsIndexes(cursor), location); return location; - } + } - @Override - public void close() { - if (cursor != null) { - cursor.close(); - cursor = null; + @Override + public long getFirstTrackPointId(long trackId) { + if (trackId < 0) { + return -1L; } - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } - - @Override - public Uri insertTrackPoint(Location location, long trackId) { - return contentResolver.insert( - TrackPointsColumns.CONTENT_URI, createContentValues(location, trackId)); - } - - /** - * Creates the {@link ContentValues} for a {@link Location}. - * - * @param location the location - * @param trackId the track id - */ - private ContentValues createContentValues(Location location, long trackId) { - ContentValues values = new ContentValues(); - values.put(TrackPointsColumns.TRACKID, trackId); - values.put(TrackPointsColumns.LONGITUDE, (int) (location.getLongitude() * 1E6)); - values.put(TrackPointsColumns.LATITUDE, (int) (location.getLatitude() * 1E6)); - - // Hack for Samsung phones that don't properly populate the time field - long time = location.getTime(); - if (time == 0) { - time = System.currentTimeMillis(); - } - values.put(TrackPointsColumns.TIME, time); - if (location.hasAltitude()) { - values.put(TrackPointsColumns.ALTITUDE, location.getAltitude()); - } - if (location.hasAccuracy()) { - values.put(TrackPointsColumns.ACCURACY, location.getAccuracy()); - } - if (location.hasSpeed()) { - values.put(TrackPointsColumns.SPEED, location.getSpeed()); - } - if (location.hasBearing()) { - values.put(TrackPointsColumns.BEARING, location.getBearing()); + Cursor cursor = null; + try { + String selection = TrackPointsColumns._ID + "=(select min(" + TrackPointsColumns._ID + + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + + "=?)"; + String[] selectionArgs = new String[]{Long.toString(trackId)}; + cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, + selectionArgs, TrackPointsColumns._ID); + if (cursor != null && cursor.moveToFirst()) { + return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return -1L; } - //SensorData - if (location instanceof SensorDataSetLocation) { - SensorDataSetLocation sensorDataSetLocation = (SensorDataSetLocation) location; - SensorDataSet sensorDataSet = sensorDataSetLocation.getSensorDataSet(); - if (sensorDataSet != null && sensorDataSet.hasHeartRate()) { - values.put(TrackPointsColumns.SENSOR_HEARTRATE, sensorDataSetLocation.getSensorDataSet().getHeartRate()); - } - if (sensorDataSet != null && sensorDataSet.hasCadence()) { - values.put(TrackPointsColumns.SENSOR_CADENCE, sensorDataSetLocation.getSensorDataSet().getCadence()); - } - if (sensorDataSet != null && sensorDataSet.hasPower()) { - values.put(TrackPointsColumns.SENSOR_POWER, sensorDataSetLocation.getSensorDataSet().getPower()); - } + @Override + public long getLastTrackPointId(long trackId) { + if (trackId < 0) { + return -1L; + } + Cursor cursor = null; + try { + String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID + + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + + "=?)"; + String[] selectionArgs = new String[]{Long.toString(trackId)}; + cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, + selectionArgs, TrackPointsColumns._ID); + if (cursor != null && cursor.moveToFirst()) { + return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return -1L; } - return values; - } - /** - * Fills a track point from a cursor. - * - * @param cursor the cursor pointing to a location. - * @param indexes the cached track points indexes - * @param location the track point - */ - private void fillTrackPoint(Cursor cursor, CachedTrackPointsIndexes indexes, Location location) { - location.reset(); + @Override + public long getTrackPointId(long trackId, Location location) { + if (trackId < 0) { + return -1L; + } + Cursor cursor = null; + try { + String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID + + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + + "=? AND " + TrackPointsColumns.TIME + "=?)"; + String[] selectionArgs = new String[]{ + Long.toString(trackId), Long.toString(location.getTime())}; + cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, + selectionArgs, TrackPointsColumns._ID); + if (cursor != null && cursor.moveToFirst()) { + return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return -1L; + } - if (!cursor.isNull(indexes.longitudeIndex)) { - location.setLongitude(((double) cursor.getInt(indexes.longitudeIndex)) / 1E6); + @Override + public Location getFirstValidTrackPoint(long trackId) { + if (trackId < 0) { + return null; + } + String selection = TrackPointsColumns._ID + "=(select min(" + TrackPointsColumns._ID + ") from " + + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + + TrackPointsColumns.LATITUDE + "<=" + MAX_LATITUDE + ")"; + String[] selectionArgs = new String[]{Long.toString(trackId)}; + return findTrackPointBy(selection, selectionArgs); } - if (!cursor.isNull(indexes.latitudeIndex)) { - location.setLatitude(((double) cursor.getInt(indexes.latitudeIndex)) / 1E6); - } - if (!cursor.isNull(indexes.timeIndex)) { - location.setTime(cursor.getLong(indexes.timeIndex)); - } - if (!cursor.isNull(indexes.altitudeIndex)) { - location.setAltitude(cursor.getFloat(indexes.altitudeIndex)); - } - if (!cursor.isNull(indexes.accuracyIndex)) { - location.setAccuracy(cursor.getFloat(indexes.accuracyIndex)); - } - if (!cursor.isNull(indexes.speedIndex)) { - location.setSpeed(cursor.getFloat(indexes.speedIndex)); - } - if (!cursor.isNull(indexes.bearingIndex)) { - location.setBearing(cursor.getFloat(indexes.bearingIndex)); - } - if (location instanceof SensorDataSetLocation) { - SensorDataSetLocation sensorDataSetLocation = (SensorDataSetLocation) location; - float heartRate = cursor.isNull(indexes.sensorHeartRateIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorHeartRateIndex); - float cadence = cursor.isNull(indexes.sensorCadenceIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorCadenceIndex); - float power = cursor.isNull(indexes.sensorPowerIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorPowerIndex); - - sensorDataSetLocation.setSensorDataSet(new SensorDataSet(heartRate, cadence, power, SensorDataSet.DATA_UNAVAILABLE, location.getTime())); + @Override + public Location getLastValidTrackPoint(long trackId) { + if (trackId < 0) { + return null; + } + String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID + ") from " + + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + + TrackPointsColumns.LATITUDE + "<=" + MAX_LATITUDE + ")"; + String[] selectionArgs = new String[]{Long.toString(trackId)}; + return findTrackPointBy(selection, selectionArgs); } - } - private Location findTrackPointBy(String selection, String[] selectionArgs) { - try (Cursor cursor = getTrackPointCursor(null, selection, selectionArgs, TrackPointsColumns._ID)) { - if (cursor != null && cursor.moveToNext()) { - return createTrackPoint(cursor); - } + @Override + public Location getLastValidTrackPoint() { + String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID + ") from " + + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.LATITUDE + "<=" + + MAX_LATITUDE + ")"; + return findTrackPointBy(selection, null); } - return null; - } - /** - * Gets a track point cursor. - * - * @param projection the projection - * @param selection the selection - * @param selectionArgs the selection arguments - * @param sortOrder the sort order - */ - private Cursor getTrackPointCursor( - String[] projection, String selection, String[] selectionArgs, String sortOrder) { - return contentResolver.query( - TrackPointsColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder); - } + @Override + public Cursor getTrackPointCursor( + long trackId, long startTrackPointId, int maxLocations, boolean descending) { + if (trackId < 0) { + return null; + } - /** - * A cache of track points indexes. - */ - private static class CachedTrackPointsIndexes { - final int idIndex; - final int longitudeIndex; - final int latitudeIndex; - final int timeIndex; - final int altitudeIndex; - final int accuracyIndex; - final int speedIndex; - final int bearingIndex; - final int sensorHeartRateIndex; - final int sensorCadenceIndex; - final int sensorPowerIndex; + String selection; + String[] selectionArgs; + if (startTrackPointId >= 0) { + String comparison = descending ? "<=" : ">="; + selection = TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns._ID + comparison + + "?"; + selectionArgs = new String[]{Long.toString(trackId), Long.toString(startTrackPointId)}; + } else { + selection = TrackPointsColumns.TRACKID + "=?"; + selectionArgs = new String[]{Long.toString(trackId)}; + } - CachedTrackPointsIndexes(Cursor cursor) { - idIndex = cursor.getColumnIndex(TrackPointsColumns._ID); - longitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE); - latitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE); - timeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.TIME); - altitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE); - accuracyIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ACCURACY); - speedIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SPEED); - bearingIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.BEARING); - sensorHeartRateIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE); - sensorCadenceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_CADENCE); - sensorPowerIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_POWER); + String sortOrder = TrackPointsColumns._ID; + if (descending) { + sortOrder += " DESC"; + } + if (maxLocations >= 0) { + sortOrder += " LIMIT " + maxLocations; + } + return getTrackPointCursor(null, selection, selectionArgs, sortOrder); } - } - /** - * Sets the default cursor batch size. For testing purpose. - * - * @param defaultCursorBatchSize the default cursor batch size - */ - void setDefaultCursorBatchSize(int defaultCursorBatchSize) { - this.defaultCursorBatchSize = defaultCursorBatchSize; - } + @Override + public LocationIterator getTrackPointLocationIterator(final long trackId, + final long startTrackPointId, final boolean descending, + final LocationFactory locationFactory) { + if (locationFactory == null) { + throw new IllegalArgumentException("locationFactory is null"); + } + return new LocationIterator() { + private long lastTrackPointId = -1L; + private Cursor cursor = getCursor(startTrackPointId); + private final CachedTrackPointsIndexes + indexes = cursor != null ? new CachedTrackPointsIndexes(cursor) + : null; + + /** + * Gets the track point cursor. + * + * @param trackPointId the starting track point id + */ + private Cursor getCursor(long trackPointId) { + return getTrackPointCursor(trackId, trackPointId, defaultCursorBatchSize, descending); + } + + /** + * Advances the cursor to the next batch. Returns true if successful. + */ + private boolean advanceCursorToNextBatch() { + long trackPointId = lastTrackPointId == -1L ? -1L + : lastTrackPointId + (descending ? -1 : 1); + Log.d(TAG, "Advancing track point id: " + trackPointId); + cursor.close(); + cursor = getCursor(trackPointId); + return cursor != null; + } + + @Override + public long getLocationId() { + return lastTrackPointId; + } + + @Override + public boolean hasNext() { + if (cursor == null) { + return false; + } + if (cursor.isAfterLast()) { + return false; + } + if (cursor.isLast()) { + if (cursor.getCount() != defaultCursorBatchSize) { + return false; + } + return advanceCursorToNextBatch() && !cursor.isAfterLast(); + } + return true; + } + + @Override + public Location next() { + if (cursor == null) { + throw new NoSuchElementException(); + } + if (!cursor.moveToNext()) { + if (!advanceCursorToNextBatch() || !cursor.moveToNext()) { + throw new NoSuchElementException(); + } + } + lastTrackPointId = cursor.getLong(indexes.idIndex); + Location location = locationFactory.createLocation(); + fillTrackPoint(cursor, indexes, location); + return location; + } + + @Override + public void close() { + if (cursor != null) { + cursor.close(); + cursor = null; + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public Uri insertTrackPoint(Location location, long trackId) { + return contentResolver.insert( + TrackPointsColumns.CONTENT_URI, createContentValues(location, trackId)); + } + + /** + * Creates the {@link ContentValues} for a {@link Location}. + * + * @param location the location + * @param trackId the track id + */ + private ContentValues createContentValues(Location location, long trackId) { + ContentValues values = new ContentValues(); + values.put(TrackPointsColumns.TRACKID, trackId); + values.put(TrackPointsColumns.LONGITUDE, (int) (location.getLongitude() * 1E6)); + values.put(TrackPointsColumns.LATITUDE, (int) (location.getLatitude() * 1E6)); + + // Hack for Samsung phones that don't properly populate the time field + long time = location.getTime(); + if (time == 0) { + time = System.currentTimeMillis(); + } + values.put(TrackPointsColumns.TIME, time); + if (location.hasAltitude()) { + values.put(TrackPointsColumns.ALTITUDE, location.getAltitude()); + } + if (location.hasAccuracy()) { + values.put(TrackPointsColumns.ACCURACY, location.getAccuracy()); + } + if (location.hasSpeed()) { + values.put(TrackPointsColumns.SPEED, location.getSpeed()); + } + if (location.hasBearing()) { + values.put(TrackPointsColumns.BEARING, location.getBearing()); + } + + //SensorData + if (location instanceof SensorDataSetLocation) { + SensorDataSetLocation sensorDataSetLocation = (SensorDataSetLocation) location; + SensorDataSet sensorDataSet = sensorDataSetLocation.getSensorDataSet(); + if (sensorDataSet != null && sensorDataSet.hasHeartRate()) { + values.put(TrackPointsColumns.SENSOR_HEARTRATE, sensorDataSetLocation.getSensorDataSet().getHeartRate()); + } + if (sensorDataSet != null && sensorDataSet.hasCadence()) { + values.put(TrackPointsColumns.SENSOR_CADENCE, sensorDataSetLocation.getSensorDataSet().getCadence()); + } + if (sensorDataSet != null && sensorDataSet.hasPower()) { + values.put(TrackPointsColumns.SENSOR_POWER, sensorDataSetLocation.getSensorDataSet().getPower()); + } + } + return values; + } + + /** + * Fills a track point from a cursor. + * + * @param cursor the cursor pointing to a location. + * @param indexes the cached track points indexes + * @param location the track point + */ + private void fillTrackPoint(Cursor cursor, CachedTrackPointsIndexes indexes, Location location) { + location.reset(); + + if (!cursor.isNull(indexes.longitudeIndex)) { + location.setLongitude(((double) cursor.getInt(indexes.longitudeIndex)) / 1E6); + } + if (!cursor.isNull(indexes.latitudeIndex)) { + location.setLatitude(((double) cursor.getInt(indexes.latitudeIndex)) / 1E6); + } + if (!cursor.isNull(indexes.timeIndex)) { + location.setTime(cursor.getLong(indexes.timeIndex)); + } + if (!cursor.isNull(indexes.altitudeIndex)) { + location.setAltitude(cursor.getFloat(indexes.altitudeIndex)); + } + if (!cursor.isNull(indexes.accuracyIndex)) { + location.setAccuracy(cursor.getFloat(indexes.accuracyIndex)); + } + if (!cursor.isNull(indexes.speedIndex)) { + location.setSpeed(cursor.getFloat(indexes.speedIndex)); + } + if (!cursor.isNull(indexes.bearingIndex)) { + location.setBearing(cursor.getFloat(indexes.bearingIndex)); + } + if (location instanceof SensorDataSetLocation) { + SensorDataSetLocation sensorDataSetLocation = (SensorDataSetLocation) location; + + float heartRate = cursor.isNull(indexes.sensorHeartRateIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorHeartRateIndex); + float cadence = cursor.isNull(indexes.sensorCadenceIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorCadenceIndex); + float power = cursor.isNull(indexes.sensorPowerIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorPowerIndex); + + sensorDataSetLocation.setSensorDataSet(new SensorDataSet(heartRate, cadence, power, SensorDataSet.DATA_UNAVAILABLE, location.getTime())); + } + } + + private Location findTrackPointBy(String selection, String[] selectionArgs) { + try (Cursor cursor = getTrackPointCursor(null, selection, selectionArgs, TrackPointsColumns._ID)) { + if (cursor != null && cursor.moveToNext()) { + return createTrackPoint(cursor); + } + } + return null; + } + + /** + * Gets a track point cursor. + * + * @param projection the projection + * @param selection the selection + * @param selectionArgs the selection arguments + * @param sortOrder the sort order + */ + private Cursor getTrackPointCursor( + String[] projection, String selection, String[] selectionArgs, String sortOrder) { + return contentResolver.query( + TrackPointsColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder); + } + + /** + * Sets the default cursor batch size. For testing purpose. + * + * @param defaultCursorBatchSize the default cursor batch size + */ + void setDefaultCursorBatchSize(int defaultCursorBatchSize) { + this.defaultCursorBatchSize = defaultCursorBatchSize; + } + + /** + * A cache of track points indexes. + */ + private static class CachedTrackPointsIndexes { + final int idIndex; + final int longitudeIndex; + final int latitudeIndex; + final int timeIndex; + final int altitudeIndex; + final int accuracyIndex; + final int speedIndex; + final int bearingIndex; + final int sensorHeartRateIndex; + final int sensorCadenceIndex; + final int sensorPowerIndex; + + CachedTrackPointsIndexes(Cursor cursor) { + idIndex = cursor.getColumnIndex(TrackPointsColumns._ID); + longitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE); + latitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE); + timeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.TIME); + altitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE); + accuracyIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ACCURACY); + speedIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SPEED); + bearingIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.BEARING); + sensorHeartRateIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE); + sensorCadenceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_CADENCE); + sensorPowerIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_POWER); + } + } } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/content/ContentTypeIds.java b/src/main/java/com/google/android/apps/mytracks/content/ContentTypeIds.java index 72c4020ea..a788154d7 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/ContentTypeIds.java +++ b/src/main/java/com/google/android/apps/mytracks/content/ContentTypeIds.java @@ -18,18 +18,18 @@ package com.google.android.apps.mytracks.content; /** * Utilities for serializing primitive types. - * + * * @author Rodrigo Damazio */ public class ContentTypeIds { - private ContentTypeIds() {} - - public static final byte BOOLEAN_TYPE_ID = 0; - public static final byte LONG_TYPE_ID = 1; - public static final byte INT_TYPE_ID = 2; - public static final byte FLOAT_TYPE_ID = 3; - public static final byte DOUBLE_TYPE_ID = 4; - public static final byte STRING_TYPE_ID = 5; - public static final byte BLOB_TYPE_ID = 6; + public static final byte BOOLEAN_TYPE_ID = 0; + public static final byte LONG_TYPE_ID = 1; + public static final byte INT_TYPE_ID = 2; + public static final byte FLOAT_TYPE_ID = 3; + public static final byte DOUBLE_TYPE_ID = 4; + public static final byte STRING_TYPE_ID = 5; + public static final byte BLOB_TYPE_ID = 6; + private ContentTypeIds() { + } } diff --git a/src/main/java/com/google/android/apps/mytracks/content/CustomContentProvider.java b/src/main/java/com/google/android/apps/mytracks/content/CustomContentProvider.java index d18f4964d..066c727bf 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/CustomContentProvider.java +++ b/src/main/java/com/google/android/apps/mytracks/content/CustomContentProvider.java @@ -44,375 +44,372 @@ import java.io.FileNotFoundException; /** * A {@link ContentProvider} that handles access to track points, tracks, and * waypoints tables. - * + * * @author Leif Hendrik Wilden */ public class CustomContentProvider extends ContentProvider { - private static final String TAG = CustomContentProvider.class.getSimpleName(); - - @VisibleForTesting - static final int DATABASE_VERSION = 23; - - @VisibleForTesting - static final String DATABASE_NAME = "database.db"; - - /** - * Database helper for creating and upgrading the database. - */ - @VisibleForTesting - static class DatabaseHelper extends SQLiteOpenHelper { - - public DatabaseHelper(Context context) { - this(context, DATABASE_NAME); - } - @VisibleForTesting - public DatabaseHelper(Context context, String databaseName) { - super(context, databaseName, null, DATABASE_VERSION); + static final int DATABASE_VERSION = 23; + @VisibleForTesting + static final String DATABASE_NAME = "database.db"; + private static final String TAG = CustomContentProvider.class.getSimpleName(); + private final UriMatcher uriMatcher; + private SQLiteDatabase db; + + public CustomContentProvider() { + uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + uriMatcher.addURI(ContentProviderUtils.AUTHORITY, TrackPointsColumns.TABLE_NAME, UrlType.TRACKPOINTS.ordinal()); + uriMatcher.addURI(ContentProviderUtils.AUTHORITY, TrackPointsColumns.TABLE_NAME + "/#", UrlType.TRACKPOINTS_ID.ordinal()); + uriMatcher.addURI(ContentProviderUtils.AUTHORITY, TracksColumns.TABLE_NAME, UrlType.TRACKS.ordinal()); + uriMatcher.addURI(ContentProviderUtils.AUTHORITY, TracksColumns.TABLE_NAME + "/#", UrlType.TRACKS_ID.ordinal()); + uriMatcher.addURI(ContentProviderUtils.AUTHORITY, WaypointsColumns.TABLE_NAME, UrlType.WAYPOINTS.ordinal()); + uriMatcher.addURI(ContentProviderUtils.AUTHORITY, WaypointsColumns.TABLE_NAME + "/#", UrlType.WAYPOINTS_ID.ordinal()); } @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL(TrackPointsColumns.CREATE_TABLE); - db.execSQL(TracksColumns.CREATE_TABLE); - db.execSQL(WaypointsColumns.CREATE_TABLE); + public boolean onCreate() { + return onCreate(getContext()); + } + + /** + * Helper method to make onCreate is testable. + * + * @param context context to creates database + * @return true means run successfully + */ + @VisibleForTesting + boolean onCreate(Context context) { + DatabaseHelper databaseHelper = new DatabaseHelper(context); + try { + db = databaseHelper.getWritableDatabase(); + } catch (SQLiteException e) { + Log.e(TAG, "Unable to open database for writing.", e); + } + return db != null; } @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - } - } - - /** - * Types of url. - * - * @author Jimmy Shih - */ - @VisibleForTesting - enum UrlType { - TRACKPOINTS, TRACKPOINTS_ID, TRACKS, TRACKS_ID, WAYPOINTS, WAYPOINTS_ID - } - - private final UriMatcher uriMatcher; - private SQLiteDatabase db; - - public CustomContentProvider() { - uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); - uriMatcher.addURI(ContentProviderUtils.AUTHORITY, TrackPointsColumns.TABLE_NAME, UrlType.TRACKPOINTS.ordinal()); - uriMatcher.addURI(ContentProviderUtils.AUTHORITY, TrackPointsColumns.TABLE_NAME + "/#", UrlType.TRACKPOINTS_ID.ordinal()); - uriMatcher.addURI(ContentProviderUtils.AUTHORITY, TracksColumns.TABLE_NAME, UrlType.TRACKS.ordinal()); - uriMatcher.addURI(ContentProviderUtils.AUTHORITY, TracksColumns.TABLE_NAME + "/#", UrlType.TRACKS_ID.ordinal()); - uriMatcher.addURI(ContentProviderUtils.AUTHORITY, WaypointsColumns.TABLE_NAME, UrlType.WAYPOINTS.ordinal()); - uriMatcher.addURI(ContentProviderUtils.AUTHORITY, WaypointsColumns.TABLE_NAME + "/#", UrlType.WAYPOINTS_ID.ordinal()); - } - - @Override - public boolean onCreate() { - return onCreate(getContext()); - } - - /** - * Helper method to make onCreate is testable. - * - * @param context context to creates database - * @return true means run successfully - */ - @VisibleForTesting - boolean onCreate(Context context) { - DatabaseHelper databaseHelper = new DatabaseHelper(context); - try { - db = databaseHelper.getWritableDatabase(); - } catch (SQLiteException e) { - Log.e(TAG, "Unable to open database for writing.", e); - } - return db != null; - } - - @Override - public int delete(@NonNull Uri url, String where, String[] selectionArgs) { - String table; - boolean shouldVacuum = false; - switch (getUrlType(url)) { - case TRACKPOINTS: - table = TrackPointsColumns.TABLE_NAME; - break; - case TRACKS: - table = TracksColumns.TABLE_NAME; - shouldVacuum = true; - break; - case WAYPOINTS: - table = WaypointsColumns.TABLE_NAME; - break; - default: - throw new IllegalArgumentException("Unknown URL " + url); - } - - Log.w(TAG, "Deleting table " + table); - int count; - try { - db.beginTransaction(); - count = db.delete(table, where, selectionArgs); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - getContext().getContentResolver().notifyChange(url, null, false); - - if (shouldVacuum) { - // If a potentially large amount of data was deleted, reclaim its space. - Log.i(TAG, "Vacuuming the database."); - db.execSQL("VACUUM"); - } - return count; - } - - @Override - public String getType(@NonNull Uri url) { - switch (getUrlType(url)) { - case TRACKPOINTS: - return TrackPointsColumns.CONTENT_TYPE; - case TRACKPOINTS_ID: - return TrackPointsColumns.CONTENT_ITEMTYPE; - case TRACKS: - return TracksColumns.CONTENT_TYPE; - case TRACKS_ID: - return TracksColumns.CONTENT_ITEMTYPE; - case WAYPOINTS: - return WaypointsColumns.CONTENT_TYPE; - case WAYPOINTS_ID: - return WaypointsColumns.CONTENT_ITEMTYPE; - default: - throw new IllegalArgumentException("Unknown URL " + url); - } - } - - @Override - public Uri insert(@NonNull Uri url, ContentValues initialValues) { - if (initialValues == null) { - initialValues = new ContentValues(); - } - Uri result; - try { - db.beginTransaction(); - result = insertContentValues(url, getUrlType(url), initialValues); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - getContext().getContentResolver().notifyChange(url, null, false); - return result; - } - - @Override - public int bulkInsert(@NonNull Uri url, @NonNull ContentValues[] valuesBulk) { - int numInserted; - try { - // Use a transaction in order to make the insertions run as a single batch - db.beginTransaction(); - - UrlType urlType = getUrlType(url); - for (numInserted = 0; numInserted < valuesBulk.length; numInserted++) { - ContentValues contentValues = valuesBulk[numInserted]; - if (contentValues == null) { - contentValues = new ContentValues(); + public int delete(@NonNull Uri url, String where, String[] selectionArgs) { + String table; + boolean shouldVacuum = false; + switch (getUrlType(url)) { + case TRACKPOINTS: + table = TrackPointsColumns.TABLE_NAME; + break; + case TRACKS: + table = TracksColumns.TABLE_NAME; + shouldVacuum = true; + break; + case WAYPOINTS: + table = WaypointsColumns.TABLE_NAME; + break; + default: + throw new IllegalArgumentException("Unknown URL " + url); } - insertContentValues(url, urlType, contentValues); - } - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - getContext().getContentResolver().notifyChange(url, null, false); - return numInserted; - } - @Override - public Cursor query(@NonNull Uri url, String[] projection, String selection, String[] selectionArgs, String sort) { - SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); - String sortOrder = null; - switch (getUrlType(url)) { - case TRACKPOINTS: - queryBuilder.setTables(TrackPointsColumns.TABLE_NAME); - sortOrder = sort != null ? sort : TrackPointsColumns.DEFAULT_SORT_ORDER; - break; - case TRACKPOINTS_ID: - queryBuilder.setTables(TrackPointsColumns.TABLE_NAME); - queryBuilder.appendWhere("_id=" + url.getPathSegments().get(1)); - break; - case TRACKS: - queryBuilder.setTables(TracksColumns.TABLE_NAME); - sortOrder = sort != null ? sort : TracksColumns.DEFAULT_SORT_ORDER; - break; - case TRACKS_ID: - queryBuilder.setTables(TracksColumns.TABLE_NAME); - queryBuilder.appendWhere("_id=" + url.getPathSegments().get(1)); - break; - case WAYPOINTS: - queryBuilder.setTables(WaypointsColumns.TABLE_NAME); - sortOrder = sort != null ? sort : WaypointsColumns.DEFAULT_SORT_ORDER; - break; - case WAYPOINTS_ID: - queryBuilder.setTables(WaypointsColumns.TABLE_NAME); - queryBuilder.appendWhere("_id=" + url.getPathSegments().get(1)); - break; - default: - throw new IllegalArgumentException("Unknown url " + url); - } - Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); - cursor.setNotificationUri(getContext().getContentResolver(), url); - return cursor; - } - - @Override - public int update(@NonNull Uri url, ContentValues values, String where, String[] selectionArgs) { - String table; - String whereClause; - switch (getUrlType(url)) { - case TRACKPOINTS: - table = TrackPointsColumns.TABLE_NAME; - whereClause = where; - break; - case TRACKPOINTS_ID: - table = TrackPointsColumns.TABLE_NAME; - whereClause = TrackPointsColumns._ID + "=" + url.getPathSegments().get(1); - if (!TextUtils.isEmpty(where)) { - whereClause += " AND (" + where + ")"; + Log.w(TAG, "Deleting table " + table); + int count; + try { + db.beginTransaction(); + count = db.delete(table, where, selectionArgs); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); } - break; - case TRACKS: - table = TracksColumns.TABLE_NAME; - whereClause = where; - break; - case TRACKS_ID: - table = TracksColumns.TABLE_NAME; - whereClause = TracksColumns._ID + "=" + url.getPathSegments().get(1); - if (!TextUtils.isEmpty(where)) { - whereClause += " AND (" + where + ")"; + getContext().getContentResolver().notifyChange(url, null, false); + + if (shouldVacuum) { + // If a potentially large amount of data was deleted, reclaim its space. + Log.i(TAG, "Vacuuming the database."); + db.execSQL("VACUUM"); } - break; - case WAYPOINTS: - table = WaypointsColumns.TABLE_NAME; - whereClause = where; - break; - case WAYPOINTS_ID: - table = WaypointsColumns.TABLE_NAME; - whereClause = WaypointsColumns._ID + "=" + url.getPathSegments().get(1); - if (!TextUtils.isEmpty(where)) { - whereClause += " AND (" + where + ")"; + return count; + } + + @Override + public String getType(@NonNull Uri url) { + switch (getUrlType(url)) { + case TRACKPOINTS: + return TrackPointsColumns.CONTENT_TYPE; + case TRACKPOINTS_ID: + return TrackPointsColumns.CONTENT_ITEMTYPE; + case TRACKS: + return TracksColumns.CONTENT_TYPE; + case TRACKS_ID: + return TracksColumns.CONTENT_ITEMTYPE; + case WAYPOINTS: + return WaypointsColumns.CONTENT_TYPE; + case WAYPOINTS_ID: + return WaypointsColumns.CONTENT_ITEMTYPE; + default: + throw new IllegalArgumentException("Unknown URL " + url); } - break; - default: - throw new IllegalArgumentException("Unknown url " + url); } - int count; - try { - db.beginTransaction(); - count = db.update(table, values, whereClause, selectionArgs); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - getContext().getContentResolver().notifyChange(url, null, false); - return count; - } - @Override - public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { - try { - File file = new File(uri.getPath()); - if (file.exists()) { - return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); - } - throw new FileNotFoundException(uri.getPath()); - } finally { - File dir = new File(getContext().getCacheDir(), FileUtils.PLAY_TRACKS_DIR); - for (File file : dir.listFiles()) { - file.delete(); - } - getContext().revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + @Override + public Uri insert(@NonNull Uri url, ContentValues initialValues) { + if (initialValues == null) { + initialValues = new ContentValues(); + } + Uri result; + try { + db.beginTransaction(); + result = insertContentValues(url, getUrlType(url), initialValues); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + getContext().getContentResolver().notifyChange(url, null, false); + return result; } - } - /** - * Gets the {@link UrlType} for a url. - * - * @param url the url - */ - private UrlType getUrlType(Uri url) { - return UrlType.values()[uriMatcher.match(url)]; - } + @Override + public int bulkInsert(@NonNull Uri url, @NonNull ContentValues[] valuesBulk) { + int numInserted; + try { + // Use a transaction in order to make the insertions run as a single batch + db.beginTransaction(); - /** - * Inserts a content based on the url type. - * - * @param url the content url - * @param urlType the url type - * @param contentValues the content values - */ - private Uri insertContentValues(Uri url, UrlType urlType, ContentValues contentValues) { - switch (urlType) { - case TRACKPOINTS: - return insertTrackPoint(url, contentValues); - case TRACKS: - return insertTrack(url, contentValues); - case WAYPOINTS: - return insertWaypoint(url, contentValues); - default: - throw new IllegalArgumentException("Unknown url " + url); + UrlType urlType = getUrlType(url); + for (numInserted = 0; numInserted < valuesBulk.length; numInserted++) { + ContentValues contentValues = valuesBulk[numInserted]; + if (contentValues == null) { + contentValues = new ContentValues(); + } + insertContentValues(url, urlType, contentValues); + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + getContext().getContentResolver().notifyChange(url, null, false); + return numInserted; } - } - /** - * Inserts a track point. - * - * @param url the content url - * @param values the content values - */ - private Uri insertTrackPoint(Uri url, ContentValues values) { - boolean hasLatitude = values.containsKey(TrackPointsColumns.LATITUDE); - boolean hasLongitude = values.containsKey(TrackPointsColumns.LONGITUDE); - boolean hasTime = values.containsKey(TrackPointsColumns.TIME); - if (!hasLatitude || !hasLongitude || !hasTime) { - throw new IllegalArgumentException("Latitude, longitude, and time values are required."); + @Override + public Cursor query(@NonNull Uri url, String[] projection, String selection, String[] selectionArgs, String sort) { + SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); + String sortOrder = null; + switch (getUrlType(url)) { + case TRACKPOINTS: + queryBuilder.setTables(TrackPointsColumns.TABLE_NAME); + sortOrder = sort != null ? sort : TrackPointsColumns.DEFAULT_SORT_ORDER; + break; + case TRACKPOINTS_ID: + queryBuilder.setTables(TrackPointsColumns.TABLE_NAME); + queryBuilder.appendWhere("_id=" + url.getPathSegments().get(1)); + break; + case TRACKS: + queryBuilder.setTables(TracksColumns.TABLE_NAME); + sortOrder = sort != null ? sort : TracksColumns.DEFAULT_SORT_ORDER; + break; + case TRACKS_ID: + queryBuilder.setTables(TracksColumns.TABLE_NAME); + queryBuilder.appendWhere("_id=" + url.getPathSegments().get(1)); + break; + case WAYPOINTS: + queryBuilder.setTables(WaypointsColumns.TABLE_NAME); + sortOrder = sort != null ? sort : WaypointsColumns.DEFAULT_SORT_ORDER; + break; + case WAYPOINTS_ID: + queryBuilder.setTables(WaypointsColumns.TABLE_NAME); + queryBuilder.appendWhere("_id=" + url.getPathSegments().get(1)); + break; + default: + throw new IllegalArgumentException("Unknown url " + url); + } + Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); + cursor.setNotificationUri(getContext().getContentResolver(), url); + return cursor; } - long rowId = db.insert(TrackPointsColumns.TABLE_NAME, TrackPointsColumns._ID, values); - if (rowId >= 0) { - return ContentUris.appendId(TrackPointsColumns.CONTENT_URI.buildUpon(), rowId).build(); - } - throw new SQLiteException("Failed to insert a track point " + url); - } - /** - * Inserts a track. - * - * @param url the content url - * @param contentValues the content values - */ - private Uri insertTrack(Uri url, ContentValues contentValues) { - boolean hasStartTime = contentValues.containsKey(TracksColumns.STARTTIME); - boolean hasStartId = contentValues.containsKey(TracksColumns.STARTID); - if (!hasStartTime || !hasStartId) { - throw new IllegalArgumentException("Both start time and start id values are required."); + @Override + public int update(@NonNull Uri url, ContentValues values, String where, String[] selectionArgs) { + String table; + String whereClause; + switch (getUrlType(url)) { + case TRACKPOINTS: + table = TrackPointsColumns.TABLE_NAME; + whereClause = where; + break; + case TRACKPOINTS_ID: + table = TrackPointsColumns.TABLE_NAME; + whereClause = TrackPointsColumns._ID + "=" + url.getPathSegments().get(1); + if (!TextUtils.isEmpty(where)) { + whereClause += " AND (" + where + ")"; + } + break; + case TRACKS: + table = TracksColumns.TABLE_NAME; + whereClause = where; + break; + case TRACKS_ID: + table = TracksColumns.TABLE_NAME; + whereClause = TracksColumns._ID + "=" + url.getPathSegments().get(1); + if (!TextUtils.isEmpty(where)) { + whereClause += " AND (" + where + ")"; + } + break; + case WAYPOINTS: + table = WaypointsColumns.TABLE_NAME; + whereClause = where; + break; + case WAYPOINTS_ID: + table = WaypointsColumns.TABLE_NAME; + whereClause = WaypointsColumns._ID + "=" + url.getPathSegments().get(1); + if (!TextUtils.isEmpty(where)) { + whereClause += " AND (" + where + ")"; + } + break; + default: + throw new IllegalArgumentException("Unknown url " + url); + } + int count; + try { + db.beginTransaction(); + count = db.update(table, values, whereClause, selectionArgs); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + getContext().getContentResolver().notifyChange(url, null, false); + return count; } - long rowId = db.insert(TracksColumns.TABLE_NAME, TracksColumns._ID, contentValues); - if (rowId >= 0) { - return ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), rowId).build(); - } - throw new SQLException("Failed to insert a track " + url); - } - /** - * Inserts a waypoint. - * - * @param url the content url - * @param contentValues the content values - */ - private Uri insertWaypoint(Uri url, ContentValues contentValues) { - long rowId = db.insert(WaypointsColumns.TABLE_NAME, WaypointsColumns._ID, contentValues); - if (rowId >= 0) { - return ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), rowId).build(); + @Override + public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { + try { + File file = new File(uri.getPath()); + if (file.exists()) { + return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); + } + throw new FileNotFoundException(uri.getPath()); + } finally { + File dir = new File(getContext().getCacheDir(), FileUtils.PLAY_TRACKS_DIR); + for (File file : dir.listFiles()) { + file.delete(); + } + getContext().revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + } + + /** + * Gets the {@link UrlType} for a url. + * + * @param url the url + */ + private UrlType getUrlType(Uri url) { + return UrlType.values()[uriMatcher.match(url)]; + } + + /** + * Inserts a content based on the url type. + * + * @param url the content url + * @param urlType the url type + * @param contentValues the content values + */ + private Uri insertContentValues(Uri url, UrlType urlType, ContentValues contentValues) { + switch (urlType) { + case TRACKPOINTS: + return insertTrackPoint(url, contentValues); + case TRACKS: + return insertTrack(url, contentValues); + case WAYPOINTS: + return insertWaypoint(url, contentValues); + default: + throw new IllegalArgumentException("Unknown url " + url); + } + } + + /** + * Inserts a track point. + * + * @param url the content url + * @param values the content values + */ + private Uri insertTrackPoint(Uri url, ContentValues values) { + boolean hasLatitude = values.containsKey(TrackPointsColumns.LATITUDE); + boolean hasLongitude = values.containsKey(TrackPointsColumns.LONGITUDE); + boolean hasTime = values.containsKey(TrackPointsColumns.TIME); + if (!hasLatitude || !hasLongitude || !hasTime) { + throw new IllegalArgumentException("Latitude, longitude, and time values are required."); + } + long rowId = db.insert(TrackPointsColumns.TABLE_NAME, TrackPointsColumns._ID, values); + if (rowId >= 0) { + return ContentUris.appendId(TrackPointsColumns.CONTENT_URI.buildUpon(), rowId).build(); + } + throw new SQLiteException("Failed to insert a track point " + url); + } + + /** + * Inserts a track. + * + * @param url the content url + * @param contentValues the content values + */ + private Uri insertTrack(Uri url, ContentValues contentValues) { + boolean hasStartTime = contentValues.containsKey(TracksColumns.STARTTIME); + boolean hasStartId = contentValues.containsKey(TracksColumns.STARTID); + if (!hasStartTime || !hasStartId) { + throw new IllegalArgumentException("Both start time and start id values are required."); + } + long rowId = db.insert(TracksColumns.TABLE_NAME, TracksColumns._ID, contentValues); + if (rowId >= 0) { + return ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), rowId).build(); + } + throw new SQLException("Failed to insert a track " + url); + } + + /** + * Inserts a waypoint. + * + * @param url the content url + * @param contentValues the content values + */ + private Uri insertWaypoint(Uri url, ContentValues contentValues) { + long rowId = db.insert(WaypointsColumns.TABLE_NAME, WaypointsColumns._ID, contentValues); + if (rowId >= 0) { + return ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), rowId).build(); + } + throw new SQLException("Failed to insert a waypoint " + url); + } + + /** + * Types of url. + * + * @author Jimmy Shih + */ + @VisibleForTesting + enum UrlType { + TRACKPOINTS, TRACKPOINTS_ID, TRACKS, TRACKS_ID, WAYPOINTS, WAYPOINTS_ID + } + + /** + * Database helper for creating and upgrading the database. + */ + @VisibleForTesting + static class DatabaseHelper extends SQLiteOpenHelper { + + public DatabaseHelper(Context context) { + this(context, DATABASE_NAME); + } + + @VisibleForTesting + public DatabaseHelper(Context context, String databaseName) { + super(context, databaseName, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL(TrackPointsColumns.CREATE_TABLE); + db.execSQL(TracksColumns.CREATE_TABLE); + db.execSQL(WaypointsColumns.CREATE_TABLE); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + } } - throw new SQLException("Failed to insert a waypoint " + url); - } } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/content/DataSource.java b/src/main/java/com/google/android/apps/mytracks/content/DataSource.java index b6b2842da..dcd80c9b0 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/DataSource.java +++ b/src/main/java/com/google/android/apps/mytracks/content/DataSource.java @@ -1,12 +1,12 @@ /* * Copyright 2011 Google Inc. - * + * * 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 @@ -16,8 +16,6 @@ package com.google.android.apps.mytracks.content; -import com.google.android.apps.mytracks.Constants; - import android.content.ContentResolver; import android.content.Context; import android.content.SharedPreferences; @@ -25,56 +23,58 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.database.ContentObserver; import android.net.Uri; +import com.google.android.apps.mytracks.Constants; + /** * Data source on the phone. - * + * * @author Rodrigo Damazio */ public class DataSource { - private final ContentResolver contentResolver; - private final SharedPreferences sharedPreferences; - - public DataSource(Context context) { - contentResolver = context.getContentResolver(); - sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); - } + private final ContentResolver contentResolver; + private final SharedPreferences sharedPreferences; - /** - * Registers a content observer. - * - * @param uri the uri - * @param observer the observer - */ - public void registerContentObserver(Uri uri, ContentObserver observer) { - contentResolver.registerContentObserver(uri, false, observer); - } + public DataSource(Context context) { + contentResolver = context.getContentResolver(); + sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); + } - /** - * Unregisters a content observer. - * - * @param observer the observer - */ - public void unregisterContentObserver(ContentObserver observer) { - contentResolver.unregisterContentObserver(observer); - } + /** + * Registers a content observer. + * + * @param uri the uri + * @param observer the observer + */ + public void registerContentObserver(Uri uri, ContentObserver observer) { + contentResolver.registerContentObserver(uri, false, observer); + } - /** - * Registers a shared preference change listener. - * - * @param listener the listener - */ - public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { - sharedPreferences.registerOnSharedPreferenceChangeListener(listener); - } + /** + * Unregisters a content observer. + * + * @param observer the observer + */ + public void unregisterContentObserver(ContentObserver observer) { + contentResolver.unregisterContentObserver(observer); + } - /** - * Unregisters a shared preference change listener. - * - * @param listener the listener - */ - public void unregisterOnSharedPreferenceChangeListener( - OnSharedPreferenceChangeListener listener) { - sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener); - } + /** + * Registers a shared preference change listener. + * + * @param listener the listener + */ + public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { + sharedPreferences.registerOnSharedPreferenceChangeListener(listener); + } + + /** + * Unregisters a shared preference change listener. + * + * @param listener the listener + */ + public void unregisterOnSharedPreferenceChangeListener( + OnSharedPreferenceChangeListener listener) { + sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener); + } } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/content/DataSourceListener.java b/src/main/java/com/google/android/apps/mytracks/content/DataSourceListener.java index e71bf1849..1cf4d9953 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/DataSourceListener.java +++ b/src/main/java/com/google/android/apps/mytracks/content/DataSourceListener.java @@ -1,12 +1,12 @@ /* * Copyright 2011 Google Inc. - * + * * 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 @@ -19,30 +19,30 @@ package com.google.android.apps.mytracks.content; /** * Listener to be invoked when {@link DataSource} changes. - * + * * @author Jimmy Shih */ public interface DataSourceListener { - /** - * Notifies when the tracks table is updated. - */ - void notifyTracksTableUpdated(); + /** + * Notifies when the tracks table is updated. + */ + void notifyTracksTableUpdated(); - /** - * Notifies when the waypoints table is updated. - */ - void notifyWaypointsTableUpdated(); + /** + * Notifies when the waypoints table is updated. + */ + void notifyWaypointsTableUpdated(); - /** - * Notifies when the track points table is updated. - */ - void notifyTrackPointsTableUpdated(); + /** + * Notifies when the track points table is updated. + */ + void notifyTrackPointsTableUpdated(); - /** - * Notifies when a preference changes. - * - * @param key the preference key - */ - void notifyPreferenceChanged(String key); + /** + * Notifies when a preference changes. + * + * @param key the preference key + */ + void notifyPreferenceChanged(String key); } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/content/DataSourceManager.java b/src/main/java/com/google/android/apps/mytracks/content/DataSourceManager.java index be17a981e..f12f338db 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/DataSourceManager.java +++ b/src/main/java/com/google/android/apps/mytracks/content/DataSourceManager.java @@ -1,12 +1,12 @@ /* * Copyright 2011 Google Inc. - * + * * 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 @@ -29,203 +29,199 @@ import java.util.Set; * Data source manager. Creates observers/listeners and manages their * registration with {@link DataSource}. The observers/listeners calls * {@link DataSourceListener} when data changes. - * + * * @author Rodrigo Damazio */ public class DataSourceManager { - private static final String TAG = DataSourceManager.class.getSimpleName(); - - /** - * Observer when the tracks table is updated. - * - * @author Jimmy Shih - */ - private class TracksTableObserver extends ContentObserver { + private static final String TAG = DataSourceManager.class.getSimpleName(); + private final DataSource dataSource; + private final DataSourceListener dataSourceListener; + // Registered listeners + private final Set registeredListeners = EnumSet.noneOf(TrackDataType.class); + private final Handler handler; + private final TracksTableObserver tracksTableObserver; + private final WaypointsTableObserver waypointsTableObserver; + private final TrackPointsTableObserver trackPointsTableObserver; + private final PreferenceListener preferenceListener; + public DataSourceManager(DataSource dataSource, DataSourceListener dataSourceListener) { + this.dataSource = dataSource; + this.dataSourceListener = dataSourceListener; - public TracksTableObserver() { - super(handler); + handler = new Handler(); + tracksTableObserver = new TracksTableObserver(); + waypointsTableObserver = new WaypointsTableObserver(); + trackPointsTableObserver = new TrackPointsTableObserver(); + preferenceListener = new PreferenceListener(); } - @Override - public void onChange(boolean selfChange) { - dataSourceListener.notifyTracksTableUpdated(); - } - } - - /** - * Observer when the waypoints table is updated. - * - * @author Jimmy Shih - */ - private class WaypointsTableObserver extends ContentObserver { - - public WaypointsTableObserver() { - super(handler); - } - - @Override - public void onChange(boolean selfChange) { - dataSourceListener.notifyWaypointsTableUpdated(); - } - } - - /** - * Observer when the track points table is updated. - * - * @author Jimmy Shih - */ - private class TrackPointsTableObserver extends ContentObserver { - - public TrackPointsTableObserver() { - super(handler); - } - - @Override - public void onChange(boolean selfChange) { - dataSourceListener.notifyTrackPointsTableUpdated(); - } - } - - /** - * Listener for preference changes. - * - * @author Jimmy Shih - */ - private class PreferenceListener implements OnSharedPreferenceChangeListener { - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - dataSourceListener.notifyPreferenceChanged(key); - } - } - - private final DataSource dataSource; - private final DataSourceListener dataSourceListener; - - // Registered listeners - private final Set registeredListeners = EnumSet.noneOf(TrackDataType.class); - - private final Handler handler; - private final TracksTableObserver tracksTableObserver; - private final WaypointsTableObserver waypointsTableObserver; - private final TrackPointsTableObserver trackPointsTableObserver; - private final PreferenceListener preferenceListener; - - public DataSourceManager(DataSource dataSource, DataSourceListener dataSourceListener) { - this.dataSource = dataSource; - this.dataSourceListener = dataSourceListener; - - handler = new Handler(); - tracksTableObserver = new TracksTableObserver(); - waypointsTableObserver = new WaypointsTableObserver(); - trackPointsTableObserver = new TrackPointsTableObserver(); - preferenceListener = new PreferenceListener(); - } - - /** - * Updates listeners with data source. - * - * @param listeners the listeners - */ - public void updateListeners(EnumSet listeners) { - EnumSet neededListeners = EnumSet.copyOf(listeners); - - /* - * Map SAMPLED_OUT_POINT_UPDATES to POINT_UPDATES since they correspond to - * the same internal listener + /** + * Updates listeners with data source. + * + * @param listeners the listeners */ - if (neededListeners.contains(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE)) { - neededListeners.remove(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE); - neededListeners.add(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE); + public void updateListeners(EnumSet listeners) { + EnumSet neededListeners = EnumSet.copyOf(listeners); + + /* + * Map SAMPLED_OUT_POINT_UPDATES to POINT_UPDATES since they correspond to + * the same internal listener + */ + if (neededListeners.contains(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE)) { + neededListeners.remove(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE); + neededListeners.add(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE); + } + + Log.d(TAG, "Updating listeners " + neededListeners); + + // Unnecessary = registered - needed + Set unnecessaryListeners = EnumSet.copyOf(registeredListeners); + unnecessaryListeners.removeAll(neededListeners); + + // Missing = needed - registered + Set missingListeners = EnumSet.copyOf(neededListeners); + missingListeners.removeAll(registeredListeners); + + // Remove unnecessary listeners + for (TrackDataType trackDataType : unnecessaryListeners) { + unregisterListener(trackDataType); + } + + // Add missing listeners + for (TrackDataType trackDataType : missingListeners) { + registerListener(trackDataType); + } + + // Update registered listeners + registeredListeners.clear(); + registeredListeners.addAll(neededListeners); } - Log.d(TAG, "Updating listeners " + neededListeners); - - // Unnecessary = registered - needed - Set unnecessaryListeners = EnumSet.copyOf(registeredListeners); - unnecessaryListeners.removeAll(neededListeners); - - // Missing = needed - registered - Set missingListeners = EnumSet.copyOf(neededListeners); - missingListeners.removeAll(registeredListeners); - - // Remove unnecessary listeners - for (TrackDataType trackDataType : unnecessaryListeners) { - unregisterListener(trackDataType); + /** + * Registers a listener with data source. + * + * @param trackDataType the listener data type + */ + private void registerListener(TrackDataType trackDataType) { + switch (trackDataType) { + case TRACKS_TABLE: + dataSource.registerContentObserver(TracksColumns.CONTENT_URI, tracksTableObserver); + break; + case WAYPOINTS_TABLE: + dataSource.registerContentObserver(WaypointsColumns.CONTENT_URI, waypointsTableObserver); + break; + case SAMPLED_IN_TRACK_POINTS_TABLE: + dataSource.registerContentObserver( + TrackPointsColumns.CONTENT_URI, trackPointsTableObserver); + break; + case SAMPLED_OUT_TRACK_POINTS_TABLE: + // Do nothing. SAMPLED_OUT_POINT_UPDATES is mapped to POINT_UPDATES. + break; + case PREFERENCE: + dataSource.registerOnSharedPreferenceChangeListener(preferenceListener); + break; + default: + break; + } } - // Add missing listeners - for (TrackDataType trackDataType : missingListeners) { - registerListener(trackDataType); + /** + * Unregisters a listener with data source. + * + * @param trackDataType listener data type + */ + private void unregisterListener(TrackDataType trackDataType) { + switch (trackDataType) { + case TRACKS_TABLE: + dataSource.unregisterContentObserver(tracksTableObserver); + break; + case WAYPOINTS_TABLE: + dataSource.unregisterContentObserver(waypointsTableObserver); + break; + case SAMPLED_IN_TRACK_POINTS_TABLE: + dataSource.unregisterContentObserver(trackPointsTableObserver); + break; + case SAMPLED_OUT_TRACK_POINTS_TABLE: + // Do nothing. SAMPLED_OUT_POINT_UPDATES is mapped to POINT_UPDATES. + break; + case PREFERENCE: + dataSource.unregisterOnSharedPreferenceChangeListener(preferenceListener); + break; + default: + break; + } } - // Update registered listeners - registeredListeners.clear(); - registeredListeners.addAll(neededListeners); - } - - /** - * Registers a listener with data source. - * - * @param trackDataType the listener data type - */ - private void registerListener(TrackDataType trackDataType) { - switch (trackDataType) { - case TRACKS_TABLE: - dataSource.registerContentObserver(TracksColumns.CONTENT_URI, tracksTableObserver); - break; - case WAYPOINTS_TABLE: - dataSource.registerContentObserver(WaypointsColumns.CONTENT_URI, waypointsTableObserver); - break; - case SAMPLED_IN_TRACK_POINTS_TABLE: - dataSource.registerContentObserver( - TrackPointsColumns.CONTENT_URI, trackPointsTableObserver); - break; - case SAMPLED_OUT_TRACK_POINTS_TABLE: - // Do nothing. SAMPLED_OUT_POINT_UPDATES is mapped to POINT_UPDATES. - break; - case PREFERENCE: - dataSource.registerOnSharedPreferenceChangeListener(preferenceListener); - break; - default: - break; + /** + * Unregisters all listeners with data source. + */ + public void unregisterAllListeners() { + for (TrackDataType trackDataType : TrackDataType.values()) { + unregisterListener(trackDataType); + } } - } - /** - * Unregisters a listener with data source. - * - * @param trackDataType listener data type - */ - private void unregisterListener(TrackDataType trackDataType) { - switch (trackDataType) { - case TRACKS_TABLE: - dataSource.unregisterContentObserver(tracksTableObserver); - break; - case WAYPOINTS_TABLE: - dataSource.unregisterContentObserver(waypointsTableObserver); - break; - case SAMPLED_IN_TRACK_POINTS_TABLE: - dataSource.unregisterContentObserver(trackPointsTableObserver); - break; - case SAMPLED_OUT_TRACK_POINTS_TABLE: - // Do nothing. SAMPLED_OUT_POINT_UPDATES is mapped to POINT_UPDATES. - break; - case PREFERENCE: - dataSource.unregisterOnSharedPreferenceChangeListener(preferenceListener); - break; - default: - break; - } - } + /** + * Observer when the tracks table is updated. + * + * @author Jimmy Shih + */ + private class TracksTableObserver extends ContentObserver { - /** - * Unregisters all listeners with data source. - */ - public void unregisterAllListeners() { - for (TrackDataType trackDataType : TrackDataType.values()) { - unregisterListener(trackDataType); + public TracksTableObserver() { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + dataSourceListener.notifyTracksTableUpdated(); + } + } + + /** + * Observer when the waypoints table is updated. + * + * @author Jimmy Shih + */ + private class WaypointsTableObserver extends ContentObserver { + + public WaypointsTableObserver() { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + dataSourceListener.notifyWaypointsTableUpdated(); + } + } + + /** + * Observer when the track points table is updated. + * + * @author Jimmy Shih + */ + private class TrackPointsTableObserver extends ContentObserver { + + public TrackPointsTableObserver() { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + dataSourceListener.notifyTrackPointsTableUpdated(); + } + } + + /** + * Listener for preference changes. + * + * @author Jimmy Shih + */ + private class PreferenceListener implements OnSharedPreferenceChangeListener { + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + dataSourceListener.notifyPreferenceChanged(key); + } } - } } diff --git a/src/main/java/com/google/android/apps/mytracks/content/DescriptionGenerator.java b/src/main/java/com/google/android/apps/mytracks/content/DescriptionGenerator.java index e17d582b2..885fddf61 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/DescriptionGenerator.java +++ b/src/main/java/com/google/android/apps/mytracks/content/DescriptionGenerator.java @@ -1,12 +1,12 @@ /* * Copyright 2010 Google Inc. - * + * * 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 @@ -27,21 +27,21 @@ import java.util.Vector; */ public interface DescriptionGenerator { - /** - * Generates a track description. - * - * @param track the track - * @param distances a vector of distances to generate the elevation chart - * @param elevations a vector of elevations to generate the elevation chart - * @param html true to output html, false to output plain text - */ - String generateTrackDescription( - Track track, Vector distances, Vector elevations, boolean html); + /** + * Generates a track description. + * + * @param track the track + * @param distances a vector of distances to generate the elevation chart + * @param elevations a vector of elevations to generate the elevation chart + * @param html true to output html, false to output plain text + */ + String generateTrackDescription( + Track track, Vector distances, Vector elevations, boolean html); - /** - * Generate a waypoint description from a trip statistics. - * - * @param tripStatistics the trip statistics - */ - String generateWaypointDescription(TripStatistics tripStatistics); + /** + * Generate a waypoint description from a trip statistics. + * + * @param tripStatistics the trip statistics + */ + String generateWaypointDescription(TripStatistics tripStatistics); } diff --git a/src/main/java/com/google/android/apps/mytracks/content/DescriptionGeneratorImpl.java b/src/main/java/com/google/android/apps/mytracks/content/DescriptionGeneratorImpl.java index ecb26c298..c1d57c814 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/DescriptionGeneratorImpl.java +++ b/src/main/java/com/google/android/apps/mytracks/content/DescriptionGeneratorImpl.java @@ -17,6 +17,7 @@ package com.google.android.apps.mytracks.content; import android.content.Context; + import androidx.annotation.VisibleForTesting; import com.google.android.apps.mytracks.stats.TripStatistics; @@ -31,228 +32,228 @@ import java.util.Vector; */ public class DescriptionGeneratorImpl implements DescriptionGenerator { - private static final String HTML_LINE_BREAK = "
"; - private static final String HTML_PARAGRAPH_SEPARATOR = "

"; - private static final String TEXT_LINE_BREAK = "\n"; - private static final String TEXT_PARAGRAPH_SEPARATOR = "\n\n"; + private static final String HTML_LINE_BREAK = "
"; + private static final String HTML_PARAGRAPH_SEPARATOR = "

"; + private static final String TEXT_LINE_BREAK = "\n"; + private static final String TEXT_PARAGRAPH_SEPARATOR = "\n\n"; - private Context context; + private Context context; - public DescriptionGeneratorImpl(Context context) { - this.context = context; - } - - @Override - public String generateTrackDescription(Track track, Vector distances, Vector elevations, boolean html) { - String paragraphSeparator = html ? HTML_PARAGRAPH_SEPARATOR : TEXT_PARAGRAPH_SEPARATOR; - String lineBreak = html ? HTML_LINE_BREAK : TEXT_LINE_BREAK; - StringBuilder builder = new StringBuilder(); - - // Created by - String creator = html - ? "" + context.getString(R.string.app_name) + "" - : context.getString(R.string.app_name); - builder.append(creator); - - builder.append(paragraphSeparator); - - writeString(track.getName(), builder, R.string.generic_name_line, lineBreak); - writeString(track.getCategory(), builder, R.string.description_activity_type, lineBreak); - writeString(track.getDescription(), builder, R.string.generic_description_line, lineBreak); - builder.append(generateTripStatisticsDescription(track.getTripStatistics(), html)); - - return builder.toString(); - } - - @Override - public String generateWaypointDescription(TripStatistics tripStatistics) { - return generateTripStatisticsDescription(tripStatistics, false); - } - - /** - * Writes a string to a string builder. - * - * @param text the string - * @param builder the string builder - * @param resId the resource id containing one string placeholder - * @param lineBreak the line break - */ - private void writeString(String text, StringBuilder builder, int resId, String lineBreak) { - if (text == null || text.length() == 0) { - text = context.getString(R.string.value_unknown); - } - builder.append(context.getString(resId, text)); - builder.append(lineBreak); - } - - /** - * Generates a description for a {@link TripStatistics}. - * - * @param stats the trip statistics - * @param html true to use "
" for line break instead of "\n" - */ - private String generateTripStatisticsDescription(TripStatistics stats, boolean html) { - String lineBreak = html ? HTML_LINE_BREAK : TEXT_LINE_BREAK; - StringBuilder builder = new StringBuilder(); - - // Total distance - writeDistance( - stats.getTotalDistance(), builder, R.string.description_total_distance, lineBreak); - - // Total time - writeTime(stats.getTotalTime(), builder, R.string.description_total_time, lineBreak); - - // Moving time - writeTime(stats.getMovingTime(), builder, R.string.description_moving_time, lineBreak); - - // Average speed - writeSpeed(stats.getAverageSpeed(), builder, R.string.description_average_speed, lineBreak); - - // Average moving speed - writeSpeed(stats.getAverageMovingSpeed(), builder, R.string.description_average_moving_speed, - lineBreak); - - // Max speed - writeSpeed(stats.getMaxSpeed(), builder, R.string.description_max_speed, lineBreak); - - // Average pace - writePace( - stats.getAverageSpeed(), builder, R.string.description_average_pace_in_minute, lineBreak); - - // Average moving pace - writePace(stats.getAverageMovingSpeed(), builder, - R.string.description_average_moving_pace_in_minute, lineBreak); - - // Fastest pace - writePace(stats.getMaxSpeed(), builder, R.string.description_fastest_pace_in_minute, lineBreak); - - // Max elevation - writeElevation(stats.getMaxElevation(), builder, R.string.description_max_elevation, lineBreak); - - // Min elevation - writeElevation(stats.getMinElevation(), builder, R.string.description_min_elevation, lineBreak); - - // Elevation gain - writeElevation( - stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak); - - // Max grade - writeGrade(stats.getMaxGrade(), builder, R.string.description_max_grade, lineBreak); - - // Min grade - writeGrade(stats.getMinGrade(), builder, R.string.description_min_grade, lineBreak); - - // Recorded time - builder.append( - context.getString(R.string.description_recorded_time, - StringUtils.formatDateTime(context, stats.getStartTime()))); - builder.append(lineBreak); - - return builder.toString(); - } - - /** - * Writes distance. - * - * @param distance distance in meters - * @param builder StringBuilder to append distance - * @param resId resource id of distance string - * @param lineBreak line break string - */ - @VisibleForTesting - void writeDistance(double distance, StringBuilder builder, int resId, String lineBreak) { - double distanceInKm = distance * UnitConversions.M_TO_KM; - double distanceInMi = distanceInKm * UnitConversions.KM_TO_MI; - builder.append(context.getString(resId, distanceInKm, distanceInMi)); - builder.append(lineBreak); - } - - /** - * Writes time. - * - * @param time time in milliseconds. - * @param builder StringBuilder to append time - * @param resId resource id of time string - * @param lineBreak line break string - */ - @VisibleForTesting - void writeTime(long time, StringBuilder builder, int resId, String lineBreak) { - builder.append(context.getString(resId, StringUtils.formatElapsedTime(time))); - builder.append(lineBreak); - } - - /** - * Writes speed. - * - * @param speed speed in meters per second - * @param builder StringBuilder to append speed - * @param resId resource id of speed string - * @param lineBreak line break string - */ - @VisibleForTesting - void writeSpeed( - double speed, StringBuilder builder, int resId, String lineBreak) { - double speedInKmHr = speed * UnitConversions.MS_TO_KMH; - double speedInMiHr = speedInKmHr * UnitConversions.KM_TO_MI; - builder.append(context.getString(resId, speedInKmHr, speedInMiHr)); - builder.append(lineBreak); - } - - /** - * Writes pace. - * - * @param speed speed in meters per second - * @param builder StringBuilder to append pace - * @param resId resource id of pace string - * @param lineBreak line break string - */ - @VisibleForTesting - void writePace(double speed, StringBuilder builder, int resId, String lineBreak) { - String[] paceInMetrics = StringUtils.getSpeedParts(context, speed, true, false); - String[] paceInImperial = StringUtils.getSpeedParts(context, speed, false, false); - - if (paceInMetrics[0] == null) { - paceInMetrics[0] = context.getString(R.string.value_unknown); + public DescriptionGeneratorImpl(Context context) { + this.context = context; } - if (paceInImperial[0] == null) { - paceInImperial[0] = context.getString(R.string.value_unknown); + @Override + public String generateTrackDescription(Track track, Vector distances, Vector elevations, boolean html) { + String paragraphSeparator = html ? HTML_PARAGRAPH_SEPARATOR : TEXT_PARAGRAPH_SEPARATOR; + String lineBreak = html ? HTML_LINE_BREAK : TEXT_LINE_BREAK; + StringBuilder builder = new StringBuilder(); + + // Created by + String creator = html + ? "" + context.getString(R.string.app_name) + "" + : context.getString(R.string.app_name); + builder.append(creator); + + builder.append(paragraphSeparator); + + writeString(track.getName(), builder, R.string.generic_name_line, lineBreak); + writeString(track.getCategory(), builder, R.string.description_activity_type, lineBreak); + writeString(track.getDescription(), builder, R.string.generic_description_line, lineBreak); + builder.append(generateTripStatisticsDescription(track.getTripStatistics(), html)); + + return builder.toString(); } - builder.append(context.getString(resId, paceInMetrics[0], paceInImperial[0])); - builder.append(lineBreak); - } + @Override + public String generateWaypointDescription(TripStatistics tripStatistics) { + return generateTripStatisticsDescription(tripStatistics, false); + } - /** - * Writes elevation. - * - * @param elevation elevation in meters - * @param builder StringBuilder to append elevation - * @param resId resource id of elevation string - * @param lineBreak line break string - */ - @VisibleForTesting - void writeElevation( - double elevation, StringBuilder builder, int resId, String lineBreak) { - long elevationInM = Math.round(elevation); - long elevationInFt = Math.round(elevation * UnitConversions.M_TO_FT); - builder.append(context.getString(resId, elevationInM, elevationInFt)); - builder.append(lineBreak); - } + /** + * Writes a string to a string builder. + * + * @param text the string + * @param builder the string builder + * @param resId the resource id containing one string placeholder + * @param lineBreak the line break + */ + private void writeString(String text, StringBuilder builder, int resId, String lineBreak) { + if (text == null || text.length() == 0) { + text = context.getString(R.string.value_unknown); + } + builder.append(context.getString(resId, text)); + builder.append(lineBreak); + } - /** - * Writes grade. - * - * @param grade grade in fraction - * @param builder StringBuilder to append grade - * @param resId resource id grade string - * @param lineBreak line break string - */ - @VisibleForTesting - void writeGrade(double grade, StringBuilder builder, int resId, String lineBreak) { - long gradeInPercent = Double.isNaN(grade) || Double.isInfinite(grade) ? 0L - : Math.round(grade * 100); - builder.append(context.getString(resId, gradeInPercent)); - builder.append(lineBreak); - } + /** + * Generates a description for a {@link TripStatistics}. + * + * @param stats the trip statistics + * @param html true to use "
" for line break instead of "\n" + */ + private String generateTripStatisticsDescription(TripStatistics stats, boolean html) { + String lineBreak = html ? HTML_LINE_BREAK : TEXT_LINE_BREAK; + StringBuilder builder = new StringBuilder(); + + // Total distance + writeDistance( + stats.getTotalDistance(), builder, R.string.description_total_distance, lineBreak); + + // Total time + writeTime(stats.getTotalTime(), builder, R.string.description_total_time, lineBreak); + + // Moving time + writeTime(stats.getMovingTime(), builder, R.string.description_moving_time, lineBreak); + + // Average speed + writeSpeed(stats.getAverageSpeed(), builder, R.string.description_average_speed, lineBreak); + + // Average moving speed + writeSpeed(stats.getAverageMovingSpeed(), builder, R.string.description_average_moving_speed, + lineBreak); + + // Max speed + writeSpeed(stats.getMaxSpeed(), builder, R.string.description_max_speed, lineBreak); + + // Average pace + writePace( + stats.getAverageSpeed(), builder, R.string.description_average_pace_in_minute, lineBreak); + + // Average moving pace + writePace(stats.getAverageMovingSpeed(), builder, + R.string.description_average_moving_pace_in_minute, lineBreak); + + // Fastest pace + writePace(stats.getMaxSpeed(), builder, R.string.description_fastest_pace_in_minute, lineBreak); + + // Max elevation + writeElevation(stats.getMaxElevation(), builder, R.string.description_max_elevation, lineBreak); + + // Min elevation + writeElevation(stats.getMinElevation(), builder, R.string.description_min_elevation, lineBreak); + + // Elevation gain + writeElevation( + stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak); + + // Max grade + writeGrade(stats.getMaxGrade(), builder, R.string.description_max_grade, lineBreak); + + // Min grade + writeGrade(stats.getMinGrade(), builder, R.string.description_min_grade, lineBreak); + + // Recorded time + builder.append( + context.getString(R.string.description_recorded_time, + StringUtils.formatDateTime(context, stats.getStartTime()))); + builder.append(lineBreak); + + return builder.toString(); + } + + /** + * Writes distance. + * + * @param distance distance in meters + * @param builder StringBuilder to append distance + * @param resId resource id of distance string + * @param lineBreak line break string + */ + @VisibleForTesting + void writeDistance(double distance, StringBuilder builder, int resId, String lineBreak) { + double distanceInKm = distance * UnitConversions.M_TO_KM; + double distanceInMi = distanceInKm * UnitConversions.KM_TO_MI; + builder.append(context.getString(resId, distanceInKm, distanceInMi)); + builder.append(lineBreak); + } + + /** + * Writes time. + * + * @param time time in milliseconds. + * @param builder StringBuilder to append time + * @param resId resource id of time string + * @param lineBreak line break string + */ + @VisibleForTesting + void writeTime(long time, StringBuilder builder, int resId, String lineBreak) { + builder.append(context.getString(resId, StringUtils.formatElapsedTime(time))); + builder.append(lineBreak); + } + + /** + * Writes speed. + * + * @param speed speed in meters per second + * @param builder StringBuilder to append speed + * @param resId resource id of speed string + * @param lineBreak line break string + */ + @VisibleForTesting + void writeSpeed( + double speed, StringBuilder builder, int resId, String lineBreak) { + double speedInKmHr = speed * UnitConversions.MS_TO_KMH; + double speedInMiHr = speedInKmHr * UnitConversions.KM_TO_MI; + builder.append(context.getString(resId, speedInKmHr, speedInMiHr)); + builder.append(lineBreak); + } + + /** + * Writes pace. + * + * @param speed speed in meters per second + * @param builder StringBuilder to append pace + * @param resId resource id of pace string + * @param lineBreak line break string + */ + @VisibleForTesting + void writePace(double speed, StringBuilder builder, int resId, String lineBreak) { + String[] paceInMetrics = StringUtils.getSpeedParts(context, speed, true, false); + String[] paceInImperial = StringUtils.getSpeedParts(context, speed, false, false); + + if (paceInMetrics[0] == null) { + paceInMetrics[0] = context.getString(R.string.value_unknown); + } + + if (paceInImperial[0] == null) { + paceInImperial[0] = context.getString(R.string.value_unknown); + } + + builder.append(context.getString(resId, paceInMetrics[0], paceInImperial[0])); + builder.append(lineBreak); + } + + /** + * Writes elevation. + * + * @param elevation elevation in meters + * @param builder StringBuilder to append elevation + * @param resId resource id of elevation string + * @param lineBreak line break string + */ + @VisibleForTesting + void writeElevation( + double elevation, StringBuilder builder, int resId, String lineBreak) { + long elevationInM = Math.round(elevation); + long elevationInFt = Math.round(elevation * UnitConversions.M_TO_FT); + builder.append(context.getString(resId, elevationInM, elevationInFt)); + builder.append(lineBreak); + } + + /** + * Writes grade. + * + * @param grade grade in fraction + * @param builder StringBuilder to append grade + * @param resId resource id grade string + * @param lineBreak line break string + */ + @VisibleForTesting + void writeGrade(double grade, StringBuilder builder, int resId, String lineBreak) { + long gradeInPercent = Double.isNaN(grade) || Double.isInfinite(grade) ? 0L + : Math.round(grade * 100); + builder.append(context.getString(resId, gradeInPercent)); + builder.append(lineBreak); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/content/SearchEngine.java b/src/main/java/com/google/android/apps/mytracks/content/SearchEngine.java index 28cf7c9e6..8aa09cd1e 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/SearchEngine.java +++ b/src/main/java/com/google/android/apps/mytracks/content/SearchEngine.java @@ -38,364 +38,383 @@ import java.util.TreeSet; */ public class SearchEngine { - /** WHERE query to get tracks by name. */ - private static final String TRACK_SELECTION_QUERY = - TracksColumns.NAME + " LIKE ? OR " + - TracksColumns.DESCRIPTION + " LIKE ? OR " + - TracksColumns.CATEGORY + " LIKE ?"; + /** + * WHERE query to get tracks by name. + */ + private static final String TRACK_SELECTION_QUERY = + TracksColumns.NAME + " LIKE ? OR " + + TracksColumns.DESCRIPTION + " LIKE ? OR " + + TracksColumns.CATEGORY + " LIKE ?"; - /** WHERE query to get waypoints by name. */ - private static final String WAYPOINT_SELECTION_QUERY = - WaypointsColumns.NAME + " LIKE ? OR " + - WaypointsColumns.DESCRIPTION + " LIKE ? OR " + - WaypointsColumns.CATEGORY + " LIKE ?"; + /** + * WHERE query to get waypoints by name. + */ + private static final String WAYPOINT_SELECTION_QUERY = + WaypointsColumns.NAME + " LIKE ? OR " + + WaypointsColumns.DESCRIPTION + " LIKE ? OR " + + WaypointsColumns.CATEGORY + " LIKE ?"; - /** Order of track results. */ - private static final String TRACK_SELECTION_ORDER = TracksColumns._ID + " DESC LIMIT 1000"; + /** + * Order of track results. + */ + private static final String TRACK_SELECTION_ORDER = TracksColumns._ID + " DESC LIMIT 1000"; - /** Order of waypoint results. */ - private static final String WAYPOINT_SELECTION_ORDER = WaypointsColumns._ID + " DESC"; + /** + * Order of waypoint results. + */ + private static final String WAYPOINT_SELECTION_ORDER = WaypointsColumns._ID + " DESC"; - /** How much we promote a match in the track category. */ - private static final double TRACK_CATEGORY_PROMOTION = 2.0; + /** + * How much we promote a match in the track category. + */ + private static final double TRACK_CATEGORY_PROMOTION = 2.0; - /** How much we promote a match in the track description. */ - private static final double TRACK_DESCRIPTION_PROMOTION = 8.0; + /** + * How much we promote a match in the track description. + */ + private static final double TRACK_DESCRIPTION_PROMOTION = 8.0; - /** How much we promote a match in the track name. */ - private static final double TRACK_NAME_PROMOTION = 16.0; + /** + * How much we promote a match in the track name. + */ + private static final double TRACK_NAME_PROMOTION = 16.0; - /** How much we promote a waypoint result if it's in the currently-selected track. */ - private static final double CURRENT_TRACK_WAYPOINT_PROMOTION = 2.0; + /** + * How much we promote a waypoint result if it's in the currently-selected track. + */ + private static final double CURRENT_TRACK_WAYPOINT_PROMOTION = 2.0; - /** How much we promote a track result if it's the currently-selected track. */ - private static final double CURRENT_TRACK_DEMOTION = 0.5; + /** + * How much we promote a track result if it's the currently-selected track. + */ + private static final double CURRENT_TRACK_DEMOTION = 0.5; - /** Maximum number of waypoints which will be retrieved and scored. */ - private static final int MAX_SCORED_WAYPOINTS = 100; + /** + * Maximum number of waypoints which will be retrieved and scored. + */ + private static final int MAX_SCORED_WAYPOINTS = 100; - /** Oldest timestamp for which we rank based on time (2000-01-01 00:00:00.000) */ - private static final long OLDEST_ALLOWED_TIMESTAMP = 946692000000L; + /** + * Oldest timestamp for which we rank based on time (2000-01-01 00:00:00.000) + */ + private static final long OLDEST_ALLOWED_TIMESTAMP = 946692000000L; + /** + * Comparador for scored results. + */ + private static final Comparator SCORED_RESULT_COMPARATOR = + new Comparator() { + @Override + public int compare(ScoredResult r1, ScoredResult r2) { + // Score ordering. + int scoreDiff = Double.compare(r2.score, r1.score); + if (scoreDiff != 0) { + return scoreDiff; + } - /** - * Description of a search query, along with all contextual data needed to execute it. - */ - public static class SearchQuery { - public SearchQuery(String textQuery, Location currentLocation, long currentTrackId, - long currentTimestamp) { - this.textQuery = textQuery.toLowerCase(Locale.getDefault()); - this.currentLocation = currentLocation; - this.currentTrackId = currentTrackId; - this.currentTimestamp = currentTimestamp; + // Make tracks come before waypoints. + if (r1.waypoint != null && r2.track != null) { + return 1; + } else if (r1.track != null && r2.waypoint != null) { + return -1; + } + + // Finally, use arbitrary ordering, by ID. + long id1 = r1.track != null ? r1.track.getId() : r1.waypoint.getId(); + long id2 = r2.track != null ? r2.track.getId() : r2.waypoint.getId(); + long idDiff = id2 - id1; + return Long.signum(idDiff); + } + }; + private final ContentProviderUtils providerUtils; + + public SearchEngine(ContentProviderUtils providerUtils) { + this.providerUtils = providerUtils; } - public final String textQuery; - public final Location currentLocation; - public final long currentTrackId; - public final long currentTimestamp; - } - - /** - * Description of a search result which has been retrieved and scored. - */ - public static class ScoredResult { - ScoredResult(Track track, double score) { - this.track = track; - this.waypoint = null; - this.score = score; + /** + * Squashes a number by calculating 1 / log (1 + x). + */ + private static double squash(double x) { + return 1.0 / Math.log1p(x); } - ScoredResult(Waypoint waypoint, double score) { - this.track = null; - this.waypoint = waypoint; - this.score = score; + /** + * Executes a search query and returns a set of sorted results. + * + * @param query the query to execute + * @return a set of results, sorted according to their score + */ + public SortedSet search(SearchQuery query) { + ArrayList tracks = new ArrayList<>(); + ArrayList waypoints = new ArrayList<>(); + TreeSet scoredResults = new TreeSet<>(SCORED_RESULT_COMPARATOR); + + retrieveTracks(query, tracks); + retrieveWaypoints(query, waypoints); + + scoreTrackResults(tracks, query, scoredResults); + scoreWaypointResults(waypoints, query, scoredResults); + + return scoredResults; } - public final Track track; - public final Waypoint waypoint; - public final double score; + /** + * Retrieves tracks matching the given query from the database. + * + * @param query the query to retrieve for + * @param tracks list to fill with the resulting tracks + */ + private void retrieveTracks(SearchQuery query, ArrayList tracks) { + String queryLikeSelection = "%" + query.textQuery + "%"; + String[] trackSelectionArgs = new String[]{ + queryLikeSelection, + queryLikeSelection, + queryLikeSelection}; - @NonNull - @Override - public String toString() { - return "ScoredResult [" - + (track != null ? ("trackId=" + track.getId() + ", ") : "") - + (waypoint != null ? ("wptId=" + waypoint.getId() + ", ") : "") - + "score=" + score + "]"; + try (Cursor cursor = providerUtils.getTrackCursor( + TRACK_SELECTION_QUERY, trackSelectionArgs, TRACK_SELECTION_ORDER)) { + if (cursor != null) { + tracks.ensureCapacity(cursor.getCount()); + while (cursor.moveToNext()) { + tracks.add(providerUtils.createTrack(cursor)); + } + } + } } - } - /** Comparador for scored results. */ - private static final Comparator SCORED_RESULT_COMPARATOR = - new Comparator() { + /** + * Retrieves waypoints matching the given query from the database. + * + * @param query the query to retrieve for + * @param waypoints list to fill with the resulting waypoints + */ + private void retrieveWaypoints(SearchQuery query, ArrayList waypoints) { + String queryLikeSelection2 = "%" + query.textQuery + "%"; + String[] waypointSelectionArgs = new String[]{ + queryLikeSelection2, + queryLikeSelection2, + queryLikeSelection2}; + try (Cursor cursor = providerUtils.getWaypointCursor(WAYPOINT_SELECTION_QUERY, waypointSelectionArgs, + WAYPOINT_SELECTION_ORDER, MAX_SCORED_WAYPOINTS)) { + if (cursor != null) { + waypoints.ensureCapacity(cursor.getCount()); + while (cursor.moveToNext()) { + Waypoint waypoint = providerUtils.createWaypoint(cursor); + if (LocationUtils.isValidLocation(waypoint.getLocation())) { + waypoints.add(waypoint); + } + } + } + } + } + + /** + * Scores a collection of track results. + * + * @param tracks the results to score + * @param query the query to score for + * @param output the collection to fill with scored results + */ + private void scoreTrackResults(Collection tracks, SearchQuery query, Collection output) { + for (Track track : tracks) { + // Calculate the score. + double score = scoreTrackResult(query, track); + + // Add to the output. + output.add(new ScoredResult(track, score)); + } + } + + /** + * Scores a single track result. + * + * @param query the query to score for + * @param track the results to score + * @return the score for the track + */ + private double scoreTrackResult(SearchQuery query, Track track) { + double score = 1.0; + + score *= getTitleBoost(query, track.getName(), track.getDescription(), track.getCategory()); + + TripStatistics statistics = track.getTripStatistics(); + // TODO: Also boost for proximity to the currently-centered position on the map. + score *= getDistanceBoost(query, statistics.getMeanLatitude(), statistics.getMeanLongitude()); + + long meanTimestamp = (statistics.getStartTime() + statistics.getStopTime()) / 2L; + score *= getTimeBoost(query, meanTimestamp); + + // Score the currently-selected track lower (user is already there, wouldn't be searching for it). + if (track.getId() == query.currentTrackId) { + score *= CURRENT_TRACK_DEMOTION; + } + + return score; + } + + /** + * Scores a collection of waypoint results. + * + * @param waypoints the results to score + * @param query the query to score for + * @param output the collection to fill with scored results + */ + private void scoreWaypointResults(Collection waypoints, SearchQuery query, Collection output) { + for (Waypoint waypoint : waypoints) { + // Calculate the score. + double score = scoreWaypointResult(query, waypoint); + + // Add to the output. + output.add(new ScoredResult(waypoint, score)); + } + } + + /** + * Scores a single waypoint result. + * + * @param query the query to score for + * @param waypoint the results to score + * @return the score for the waypoint + */ + private double scoreWaypointResult(SearchQuery query, Waypoint waypoint) { + double score = 1.0; + + Location location = waypoint.getLocation(); + score *= getTitleBoost(query, waypoint.getName(), waypoint.getDescription(), waypoint.getCategory()); + // TODO: Also boost for proximity to the currently-centered position on the map. + score *= getDistanceBoost(query, location.getLatitude(), location.getLongitude()); + score *= getTimeBoost(query, location.getTime()); + + // Score waypoints in the currently-selected track higher (searching inside the current track). + if (query.currentTrackId != -1 && waypoint.getTrackId() == query.currentTrackId) { + score *= CURRENT_TRACK_WAYPOINT_PROMOTION; + } + + return score; + } + + /** + * Calculates the boosting of the score due to the field(s) in which the match occured. + * + * @param query the query to boost for + * @param name the name of the track or waypoint + * @param description the description of the track or waypoint + * @param category the category of the track or waypoint + * @return the total boost to be applied to the result + */ + private double getTitleBoost(SearchQuery query, + String name, String description, String category) { + // Title boost: track name > description > category. + double boost = 1.0; + if (name.toLowerCase(Locale.getDefault()).contains(query.textQuery)) { + boost *= TRACK_NAME_PROMOTION; + } + if (description.toLowerCase(Locale.getDefault()).contains(query.textQuery)) { + boost *= TRACK_DESCRIPTION_PROMOTION; + } + if (category.toLowerCase(Locale.getDefault()).contains(query.textQuery)) { + boost *= TRACK_CATEGORY_PROMOTION; + } + return boost; + } + + /** + * Calculates the boosting of the score due to the recency of the matched entity. + * + * @param query the query to boost for + * @param timestamp the timestamp to calculate the boost for + * @return the total boost to be applied to the result + */ + private double getTimeBoost(SearchQuery query, long timestamp) { + if (timestamp < OLDEST_ALLOWED_TIMESTAMP) { + // Safety: if timestamp is too old or invalid, don't rank based on time. + return 1.0; + } + + // Score recent tracks higher. + long timeAgoHours = (long) ((query.currentTimestamp - timestamp) * UnitConversions.MS_TO_S + * UnitConversions.S_TO_MIN * UnitConversions.MIN_TO_HR); + if (timeAgoHours > 0L) { + return squash(timeAgoHours); + } else { + // Should rarely happen (track recorded in the last hour). + return Double.POSITIVE_INFINITY; + } + } + + /** + * Calculates the boosting of the score due to proximity to a location. + * + * @param query the query to boost for + * @param latitude the latitude to calculate the boost for + * @param longitude the longitude to calculate the boost for + * @return the total boost to be applied to the result + */ + private double getDistanceBoost(SearchQuery query, double latitude, double longitude) { + if (query.currentLocation == null) { + return 1.0; + } + + float[] distanceResults = new float[1]; + + Location.distanceBetween( + latitude, longitude, + query.currentLocation.getLatitude(), query.currentLocation.getLongitude(), + distanceResults); + + // Score tracks close to the current location higher. + double distanceKm = distanceResults[0] * UnitConversions.M_TO_KM; + if (distanceKm > 0.0) { + // Use the inverse of the amortized distance. + return squash(distanceKm); + } else { + // Should rarely happen (distance is exactly 0). + return Double.POSITIVE_INFINITY; + } + } + + /** + * Description of a search query, along with all contextual data needed to execute it. + */ + public static class SearchQuery { + public final String textQuery; + public final Location currentLocation; + public final long currentTrackId; + public final long currentTimestamp; + public SearchQuery(String textQuery, Location currentLocation, long currentTrackId, + long currentTimestamp) { + this.textQuery = textQuery.toLowerCase(Locale.getDefault()); + this.currentLocation = currentLocation; + this.currentTrackId = currentTrackId; + this.currentTimestamp = currentTimestamp; + } + } + + /** + * Description of a search result which has been retrieved and scored. + */ + public static class ScoredResult { + public final Track track; + public final Waypoint waypoint; + public final double score; + ScoredResult(Track track, double score) { + this.track = track; + this.waypoint = null; + this.score = score; + } + ScoredResult(Waypoint waypoint, double score) { + this.track = null; + this.waypoint = waypoint; + this.score = score; + } + + @NonNull @Override - public int compare(ScoredResult r1, ScoredResult r2) { - // Score ordering. - int scoreDiff = Double.compare(r2.score, r1.score); - if (scoreDiff != 0) { - return scoreDiff; - } - - // Make tracks come before waypoints. - if (r1.waypoint != null && r2.track != null) { - return 1; - } else if (r1.track != null && r2.waypoint != null) { - return -1; - } - - // Finally, use arbitrary ordering, by ID. - long id1 = r1.track != null ? r1.track.getId() : r1.waypoint.getId(); - long id2 = r2.track != null ? r2.track.getId() : r2.waypoint.getId(); - long idDiff = id2 - id1; - return Long.signum(idDiff); + public String toString() { + return "ScoredResult [" + + (track != null ? ("trackId=" + track.getId() + ", ") : "") + + (waypoint != null ? ("wptId=" + waypoint.getId() + ", ") : "") + + "score=" + score + "]"; } - }; - - private final ContentProviderUtils providerUtils; - - public SearchEngine(ContentProviderUtils providerUtils) { - this.providerUtils = providerUtils; - } - - /** - * Executes a search query and returns a set of sorted results. - * - * @param query the query to execute - * @return a set of results, sorted according to their score - */ - public SortedSet search(SearchQuery query) { - ArrayList tracks = new ArrayList<>(); - ArrayList waypoints = new ArrayList<>(); - TreeSet scoredResults = new TreeSet<>(SCORED_RESULT_COMPARATOR); - - retrieveTracks(query, tracks); - retrieveWaypoints(query, waypoints); - - scoreTrackResults(tracks, query, scoredResults); - scoreWaypointResults(waypoints, query, scoredResults); - - return scoredResults; - } - - /** - * Retrieves tracks matching the given query from the database. - * - * @param query the query to retrieve for - * @param tracks list to fill with the resulting tracks - */ - private void retrieveTracks(SearchQuery query, ArrayList tracks) { - String queryLikeSelection = "%" + query.textQuery + "%"; - String[] trackSelectionArgs = new String[] { - queryLikeSelection, - queryLikeSelection, - queryLikeSelection }; - - try (Cursor cursor = providerUtils.getTrackCursor( - TRACK_SELECTION_QUERY, trackSelectionArgs, TRACK_SELECTION_ORDER)) { - if (cursor != null) { - tracks.ensureCapacity(cursor.getCount()); - while (cursor.moveToNext()) { - tracks.add(providerUtils.createTrack(cursor)); - } - } } - } - - /** - * Retrieves waypoints matching the given query from the database. - * - * @param query the query to retrieve for - * @param waypoints list to fill with the resulting waypoints - */ - private void retrieveWaypoints(SearchQuery query, ArrayList waypoints) { - String queryLikeSelection2 = "%" + query.textQuery + "%"; - String[] waypointSelectionArgs = new String[] { - queryLikeSelection2, - queryLikeSelection2, - queryLikeSelection2 }; - try (Cursor cursor = providerUtils.getWaypointCursor(WAYPOINT_SELECTION_QUERY, waypointSelectionArgs, - WAYPOINT_SELECTION_ORDER, MAX_SCORED_WAYPOINTS)) { - if (cursor != null) { - waypoints.ensureCapacity(cursor.getCount()); - while (cursor.moveToNext()) { - Waypoint waypoint = providerUtils.createWaypoint(cursor); - if (LocationUtils.isValidLocation(waypoint.getLocation())) { - waypoints.add(waypoint); - } - } - } - } - } - - /** - * Scores a collection of track results. - * - * @param tracks the results to score - * @param query the query to score for - * @param output the collection to fill with scored results - */ - private void scoreTrackResults(Collection tracks, SearchQuery query, Collection output) { - for (Track track : tracks) { - // Calculate the score. - double score = scoreTrackResult(query, track); - - // Add to the output. - output.add(new ScoredResult(track, score)); - } - } - - /** - * Scores a single track result. - * - * @param query the query to score for - * @param track the results to score - * @return the score for the track - */ - private double scoreTrackResult(SearchQuery query, Track track) { - double score = 1.0; - - score *= getTitleBoost(query, track.getName(), track.getDescription(), track.getCategory()); - - TripStatistics statistics = track.getTripStatistics(); - // TODO: Also boost for proximity to the currently-centered position on the map. - score *= getDistanceBoost(query, statistics.getMeanLatitude(), statistics.getMeanLongitude()); - - long meanTimestamp = (statistics.getStartTime() + statistics.getStopTime()) / 2L; - score *= getTimeBoost(query, meanTimestamp); - - // Score the currently-selected track lower (user is already there, wouldn't be searching for it). - if (track.getId() == query.currentTrackId) { - score *= CURRENT_TRACK_DEMOTION; - } - - return score; - } - - /** - * Scores a collection of waypoint results. - * - * @param waypoints the results to score - * @param query the query to score for - * @param output the collection to fill with scored results - */ - private void scoreWaypointResults(Collection waypoints, SearchQuery query, Collection output) { - for (Waypoint waypoint : waypoints) { - // Calculate the score. - double score = scoreWaypointResult(query, waypoint); - - // Add to the output. - output.add(new ScoredResult(waypoint, score)); - } - } - - /** - * Scores a single waypoint result. - * - * @param query the query to score for - * @param waypoint the results to score - * @return the score for the waypoint - */ - private double scoreWaypointResult(SearchQuery query, Waypoint waypoint) { - double score = 1.0; - - Location location = waypoint.getLocation(); - score *= getTitleBoost(query, waypoint.getName(), waypoint.getDescription(), waypoint.getCategory()); - // TODO: Also boost for proximity to the currently-centered position on the map. - score *= getDistanceBoost(query, location.getLatitude(), location.getLongitude()); - score *= getTimeBoost(query, location.getTime()); - - // Score waypoints in the currently-selected track higher (searching inside the current track). - if (query.currentTrackId != -1 && waypoint.getTrackId() == query.currentTrackId) { - score *= CURRENT_TRACK_WAYPOINT_PROMOTION; - } - - return score; - } - - /** - * Calculates the boosting of the score due to the field(s) in which the match occured. - * - * @param query the query to boost for - * @param name the name of the track or waypoint - * @param description the description of the track or waypoint - * @param category the category of the track or waypoint - * @return the total boost to be applied to the result - */ - private double getTitleBoost(SearchQuery query, - String name, String description, String category) { - // Title boost: track name > description > category. - double boost = 1.0; - if (name.toLowerCase(Locale.getDefault()).contains(query.textQuery)) { - boost *= TRACK_NAME_PROMOTION; - } - if (description.toLowerCase(Locale.getDefault()).contains(query.textQuery)) { - boost *= TRACK_DESCRIPTION_PROMOTION; - } - if (category.toLowerCase(Locale.getDefault()).contains(query.textQuery)) { - boost *= TRACK_CATEGORY_PROMOTION; - } - return boost; - } - - /** - * Calculates the boosting of the score due to the recency of the matched entity. - * - * @param query the query to boost for - * @param timestamp the timestamp to calculate the boost for - * @return the total boost to be applied to the result - */ - private double getTimeBoost(SearchQuery query, long timestamp) { - if (timestamp < OLDEST_ALLOWED_TIMESTAMP) { - // Safety: if timestamp is too old or invalid, don't rank based on time. - return 1.0; - } - - // Score recent tracks higher. - long timeAgoHours = (long) ((query.currentTimestamp - timestamp) * UnitConversions.MS_TO_S - * UnitConversions.S_TO_MIN * UnitConversions.MIN_TO_HR); - if (timeAgoHours > 0L) { - return squash(timeAgoHours); - } else { - // Should rarely happen (track recorded in the last hour). - return Double.POSITIVE_INFINITY; - } - } - - /** - * Calculates the boosting of the score due to proximity to a location. - * - * @param query the query to boost for - * @param latitude the latitude to calculate the boost for - * @param longitude the longitude to calculate the boost for - * @return the total boost to be applied to the result - */ - private double getDistanceBoost(SearchQuery query, double latitude, double longitude) { - if (query.currentLocation == null) { - return 1.0; - } - - float[] distanceResults = new float[1]; - - Location.distanceBetween( - latitude, longitude, - query.currentLocation.getLatitude(), query.currentLocation.getLongitude(), - distanceResults); - - // Score tracks close to the current location higher. - double distanceKm = distanceResults[0] * UnitConversions.M_TO_KM; - if (distanceKm > 0.0) { - // Use the inverse of the amortized distance. - return squash(distanceKm); - } else { - // Should rarely happen (distance is exactly 0). - return Double.POSITIVE_INFINITY; - } - } - - /** - * Squashes a number by calculating 1 / log (1 + x). - */ - private static double squash(double x) { - return 1.0 / Math.log1p(x); - } } diff --git a/src/main/java/com/google/android/apps/mytracks/content/SearchEngineProvider.java b/src/main/java/com/google/android/apps/mytracks/content/SearchEngineProvider.java index ae7c64fe1..fb215e538 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/SearchEngineProvider.java +++ b/src/main/java/com/google/android/apps/mytracks/content/SearchEngineProvider.java @@ -26,19 +26,19 @@ import android.provider.SearchRecentSuggestions; */ public class SearchEngineProvider extends SearchRecentSuggestionsProvider { - private static final String AUTHORITY = "com.google.android.maps.mytracks.search"; - private static final int MODE = DATABASE_MODE_QUERIES; + private static final String AUTHORITY = "com.google.android.maps.mytracks.search"; + private static final int MODE = DATABASE_MODE_QUERIES; - public SearchEngineProvider() { - setupSuggestions(AUTHORITY, MODE); - } + public SearchEngineProvider() { + setupSuggestions(AUTHORITY, MODE); + } - // TODO: Also add suggestions from the database. + // TODO: Also add suggestions from the database. - /** - * Creates and returns a helper for adding recent queries or clearing the recent query history. - */ - public static SearchRecentSuggestions newHelper(Context context) { - return new SearchRecentSuggestions(context, AUTHORITY, MODE); - } + /** + * Creates and returns a helper for adding recent queries or clearing the recent query history. + */ + public static SearchRecentSuggestions newHelper(Context context) { + return new SearchRecentSuggestions(context, AUTHORITY, MODE); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/content/SensorDataSetLocation.java b/src/main/java/com/google/android/apps/mytracks/content/SensorDataSetLocation.java index 9d352faaa..a2867ad6f 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/SensorDataSetLocation.java +++ b/src/main/java/com/google/android/apps/mytracks/content/SensorDataSetLocation.java @@ -26,41 +26,41 @@ import com.google.android.apps.mytracks.content.sensor.SensorDataSet; */ public class SensorDataSetLocation extends Location { - private SensorDataSet sensorDataSet = null; + private SensorDataSet sensorDataSet = null; - /** - * The id of this location from the provider. - */ - private int id = -1; + /** + * The id of this location from the provider. + */ + private int id = -1; - public SensorDataSetLocation(Location location, SensorDataSet sensorDataSet) { - super(location); - this.sensorDataSet = sensorDataSet; - } + public SensorDataSetLocation(Location location, SensorDataSet sensorDataSet) { + super(location); + this.sensorDataSet = sensorDataSet; + } - public SensorDataSetLocation(String provider) { - super(provider); - } + public SensorDataSetLocation(String provider) { + super(provider); + } - public SensorDataSet getSensorDataSet() { - return sensorDataSet; - } + public SensorDataSet getSensorDataSet() { + return sensorDataSet; + } - public void setSensorDataSet(SensorDataSet sensorDataSet) { - this.sensorDataSet = sensorDataSet; - } + public void setSensorDataSet(SensorDataSet sensorDataSet) { + this.sensorDataSet = sensorDataSet; + } - public int getId() { - return id; - } + public int getId() { + return id; + } - public void setId(int id) { - this.id = id; - } - - public void reset() { - super.reset(); - sensorDataSet = null; - id = -1; - } + public void setId(int id) { + this.id = id; + } + + public void reset() { + super.reset(); + sensorDataSet = null; + id = -1; + } } diff --git a/src/main/java/com/google/android/apps/mytracks/content/Track.java b/src/main/java/com/google/android/apps/mytracks/content/Track.java index f3d0018e5..5e234c08d 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/Track.java +++ b/src/main/java/com/google/android/apps/mytracks/content/Track.java @@ -16,188 +16,185 @@ package com.google.android.apps.mytracks.content; -import com.google.android.apps.mytracks.stats.TripStatistics; - import android.location.Location; import android.os.Parcel; import android.os.Parcelable; +import com.google.android.apps.mytracks.stats.TripStatistics; + import java.util.ArrayList; /** * A track. - * + * * @author Leif Hendrik Wilden * @author Rodrigo Damazio */ public class Track implements Parcelable { - private long id = -1L; - private String name = ""; - private String description = ""; - private String category = ""; - private long startId = -1L; - private long stopId = -1L; + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public Track createFromParcel(Parcel in) { + return new Track(in); + } - /* - * The number of location points (present even if the points themselves are - * not loaded) - */ - private int numberOfPoints = 0; - private String icon = ""; - private long modifiedTime = -1L; + @Override + public Track[] newArray(int size) { + return new Track[size]; + } + }; + private long id = -1L; + private String name = ""; + private String description = ""; + private String category = ""; + private long startId = -1L; + private long stopId = -1L; + /* + * The number of location points (present even if the points themselves are + * not loaded) + */ + private int numberOfPoints = 0; + private String icon = ""; + private long modifiedTime = -1L; + private TripStatistics tripStatistics = new TripStatistics(); + // Location points (which may not have been loaded) + private ArrayList locations = new ArrayList<>(); - private TripStatistics tripStatistics = new TripStatistics(); - - // Location points (which may not have been loaded) - private ArrayList locations = new ArrayList<>(); - - public Track() {} - - private Track(Parcel in) { - id = in.readLong(); - name = in.readString(); - description = in.readString(); - category = in.readString(); - startId = in.readLong(); - stopId = in.readLong(); - numberOfPoints = in.readInt(); - icon = in.readString(); - modifiedTime = in.readLong(); - - ClassLoader classLoader = getClass().getClassLoader(); - tripStatistics = in.readParcelable(classLoader); - - for (int i = 0; i < numberOfPoints; ++i) { - Location location = in.readParcelable(classLoader); - locations.add(location); - } - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeLong(id); - dest.writeString(name); - dest.writeString(description); - dest.writeString(category); - dest.writeLong(startId); - dest.writeLong(stopId); - dest.writeInt(numberOfPoints); - dest.writeString(icon); - dest.writeLong(modifiedTime); - - dest.writeParcelable(tripStatistics, 0); - for (int i = 0; i < numberOfPoints; ++i) { - dest.writeParcelable(locations.get(i), 0); - } - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - @Override - public Track createFromParcel(Parcel in) { - return new Track(in); + public Track() { } - @Override - public Track[] newArray(int size) { - return new Track[size]; + private Track(Parcel in) { + id = in.readLong(); + name = in.readString(); + description = in.readString(); + category = in.readString(); + startId = in.readLong(); + stopId = in.readLong(); + numberOfPoints = in.readInt(); + icon = in.readString(); + modifiedTime = in.readLong(); + + ClassLoader classLoader = getClass().getClassLoader(); + tripStatistics = in.readParcelable(classLoader); + + for (int i = 0; i < numberOfPoints; ++i) { + Location location = in.readParcelable(classLoader); + locations.add(location); + } } - }; - public long getId() { - return id; - } + @Override + public int describeContents() { + return 0; + } - public void setId(long id) { - this.id = id; - } + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(id); + dest.writeString(name); + dest.writeString(description); + dest.writeString(category); + dest.writeLong(startId); + dest.writeLong(stopId); + dest.writeInt(numberOfPoints); + dest.writeString(icon); + dest.writeLong(modifiedTime); - public String getName() { - return name; - } + dest.writeParcelable(tripStatistics, 0); + for (int i = 0; i < numberOfPoints; ++i) { + dest.writeParcelable(locations.get(i), 0); + } + } - public void setName(String name) { - this.name = name; - } + public long getId() { + return id; + } - public String getDescription() { - return description; - } + public void setId(long id) { + this.id = id; + } - public void setDescription(String description) { - this.description = description; - } + public String getName() { + return name; + } - public String getCategory() { - return category; - } + public void setName(String name) { + this.name = name; + } - public void setCategory(String category) { - this.category = category; - } + public String getDescription() { + return description; + } - public long getStartId() { - return startId; - } + public void setDescription(String description) { + this.description = description; + } - public void setStartId(long startId) { - this.startId = startId; - } + public String getCategory() { + return category; + } - public long getStopId() { - return stopId; - } + public void setCategory(String category) { + this.category = category; + } - public void setStopId(long stopId) { - this.stopId = stopId; - } + public long getStartId() { + return startId; + } - public int getNumberOfPoints() { - return numberOfPoints; - } + public void setStartId(long startId) { + this.startId = startId; + } - public void setNumberOfPoints(int numberOfPoints) { - this.numberOfPoints = numberOfPoints; - } + public long getStopId() { + return stopId; + } - public String getIcon() { - return icon; - } + public void setStopId(long stopId) { + this.stopId = stopId; + } - public void setIcon(String icon) { - this.icon = icon; - } + public int getNumberOfPoints() { + return numberOfPoints; + } - public long getModifiedTime() { - return modifiedTime; - } + public void setNumberOfPoints(int numberOfPoints) { + this.numberOfPoints = numberOfPoints; + } - public void setModifiedTime(long modifiedTime) { - this.modifiedTime = modifiedTime; - } + public String getIcon() { + return icon; + } - public TripStatistics getTripStatistics() { - return tripStatistics; - } + public void setIcon(String icon) { + this.icon = icon; + } - public void setTripStatistics(TripStatistics tripStatistics) { - this.tripStatistics = tripStatistics; - } + public long getModifiedTime() { + return modifiedTime; + } - public void addLocation(Location location) { - locations.add(location); - } + public void setModifiedTime(long modifiedTime) { + this.modifiedTime = modifiedTime; + } - public ArrayList getLocations() { - return locations; - } + public TripStatistics getTripStatistics() { + return tripStatistics; + } - public void setLocations(ArrayList locations) { - this.locations = locations; - } + public void setTripStatistics(TripStatistics tripStatistics) { + this.tripStatistics = tripStatistics; + } + + public void addLocation(Location location) { + locations.add(location); + } + + public ArrayList getLocations() { + return locations; + } + + public void setLocations(ArrayList locations) { + this.locations = locations; + } } diff --git a/src/main/java/com/google/android/apps/mytracks/content/TrackDataHub.java b/src/main/java/com/google/android/apps/mytracks/content/TrackDataHub.java index 166d4435a..89de4e8ac 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/TrackDataHub.java +++ b/src/main/java/com/google/android/apps/mytracks/content/TrackDataHub.java @@ -37,580 +37,577 @@ import java.util.Set; /** * Track data hub. Receives data from {@link DataSource} and distributes it to * {@link TrackDataListener} after some processing. - * + * * @author Rodrigo Damazio */ public class TrackDataHub implements DataSourceListener { - private static final String TAG = TrackDataHub.class.getSimpleName(); - - /** - * Maximum number of waypoints to displayed. - */ - @VisibleForTesting - static final int MAX_DISPLAYED_WAYPOINTS = 128; - - /** - * Target number of track points displayed by the map overlay. We may display - * more than this number of points. - */ - public static final int TARGET_DISPLAYED_TRACK_POINTS = 5000; - - private final Context context; - private final TrackDataManager trackDataManager; - private final ContentProviderUtils contentProviderUtils; - private final int targetNumPoints; + /** + * Target number of track points displayed by the map overlay. We may display + * more than this number of points. + */ + public static final int TARGET_DISPLAYED_TRACK_POINTS = 5000; + /** + * Maximum number of waypoints to displayed. + */ + @VisibleForTesting + static final int MAX_DISPLAYED_WAYPOINTS = 128; + private static final String TAG = TrackDataHub.class.getSimpleName(); + private final Context context; + private final TrackDataManager trackDataManager; + private final ContentProviderUtils contentProviderUtils; + private final int targetNumPoints; - private boolean started; - private HandlerThread handlerThread; - private Handler handler; - private DataSource dataSource; - private DataSourceManager dataSourceManager; + private boolean started; + private HandlerThread handlerThread; + private Handler handler; + private DataSource dataSource; + private DataSourceManager dataSourceManager; - // Preference values - private long selectedTrackId; - private long recordingTrackId; - private boolean recordingTrackPaused; - private boolean metricUnits; - private boolean reportSpeed; - private int recordingGpsAccuracy; - private int recordingDistanceInterval; + // Preference values + private long selectedTrackId; + private long recordingTrackId; + private boolean recordingTrackPaused; + private boolean metricUnits; + private boolean reportSpeed; + private int recordingGpsAccuracy; + private int recordingDistanceInterval; - // Track points sampling state - private int numLoadedPoints; - private long firstSeenLocationId; - private long lastSeenLocationId; + // Track points sampling state + private int numLoadedPoints; + private long firstSeenLocationId; + private long lastSeenLocationId; - /** - * Creates a new instance. - */ - public synchronized static TrackDataHub newInstance(Context context) { - return new TrackDataHub(context, new TrackDataManager(), ContentProviderUtils.Factory.get( - context), TARGET_DISPLAYED_TRACK_POINTS); - } - - /** - * Constructor. - * - * @param context the context - * @param trackDataManager the track data manager - * @param contentProviderUtils the content tracks provider utils - * @param targetNumPoints the target number of points - */ - @VisibleForTesting - TrackDataHub(Context context, TrackDataManager trackDataManager, - ContentProviderUtils contentProviderUtils, int targetNumPoints) { - this.context = context; - this.trackDataManager = trackDataManager; - this.contentProviderUtils = contentProviderUtils; - this.targetNumPoints = targetNumPoints; - resetSamplingState(); - } - - /** - * Starts. - */ - public void start() { - if (started) { - Log.i(TAG, "TrackDataHub already started, ignoring start."); - return; - } - started = true; - handlerThread = new HandlerThread("TrackDataHubHandlerThread"); - handlerThread.start(); - handler = new Handler(handlerThread.getLooper()); - dataSource = newDataSource(); - dataSourceManager = new DataSourceManager(dataSource, this); - - notifyPreferenceChanged(null); - runInHanderThread(new Runnable() { - @Override - public void run() { - if (dataSourceManager != null) { - dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes()); - loadDataForAll(); - } - } - }); - } - - /** - * Stops. - */ - public void stop() { - if (!started) { - Log.i(TAG, "TrackDataHub not started, ignoring stop."); - return; - } - started = false; - - dataSourceManager.unregisterAllListeners(); - if (handlerThread != null) { - handlerThread.getLooper().quit(); - handlerThread = null; - } - handler = null; - dataSource = null; - dataSourceManager = null; - } - - /** - * Loads a track. - * - * @param trackId the track id - */ - public void loadTrack(final long trackId) { - runInHanderThread(new Runnable() { - @Override - public void run() { - if (trackId == selectedTrackId) { - Log.i(TAG, "Not reloading track " + trackId); - return; - } - selectedTrackId = trackId; - loadDataForAll(); - } - }); - } - - /** - * Registers a {@link TrackDataListener}. - * - * @param trackDataListener the track data listener - * @param trackDataTypes the track data types - */ - public void registerTrackDataListener(final TrackDataListener trackDataListener, final EnumSet trackDataTypes) { - runInHanderThread(new Runnable() { - @Override - public void run() { - trackDataManager.registerListener(trackDataListener, trackDataTypes); - if (dataSourceManager != null) { - dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes()); - loadDataForListener(trackDataListener); - } - } - }); - } - - /** - * Unregisters a {@link TrackDataListener}. - * - * @param trackDataListener the track data listener - */ - public void unregisterTrackDataListener(final TrackDataListener trackDataListener) { - runInHanderThread(new Runnable() { - @Override - public void run() { - trackDataManager.unregisterListener(trackDataListener); - if (dataSourceManager != null) { - dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes()); - } - } - }); - } - - /** - * Reloads data for a {@link TrackDataListener}. - */ - public void reloadDataForListener(final TrackDataListener trackDataListener) { - runInHanderThread(new Runnable() { - @Override - public void run() { - loadDataForListener(trackDataListener); - } - }); - } - - /** - * Returns true if the selected track is recording. - */ - public boolean isSelectedTrackRecording() { - return selectedTrackId == recordingTrackId - && recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; - } - - /** - * Returns true if the selected track is paused. - */ - public boolean isSelectedTrackPaused() { - return selectedTrackId == recordingTrackId && recordingTrackPaused; - } - - @Override - public void notifyTracksTableUpdated() { - runInHanderThread(new Runnable() { - @Override - public void run() { - notifyTracksTableUpdate(trackDataManager.getListeners(TrackDataType.TRACKS_TABLE)); - } - }); - } - - @Override - public void notifyWaypointsTableUpdated() { - runInHanderThread(new Runnable() { - @Override - public void run() { - notifyWaypointsTableUpdate(trackDataManager.getListeners(TrackDataType.WAYPOINTS_TABLE)); - } - }); - } - - @Override - public void notifyTrackPointsTableUpdated() { - runInHanderThread(new Runnable() { - @Override - public void run() { - notifyTrackPointsTableUpdate( - true, trackDataManager.getListeners(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE), - trackDataManager.getListeners(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE)); - } - }); - } - - @Override - public void notifyPreferenceChanged(final String key) { - runInHanderThread(new Runnable() { - @Override - public void run() { - if (key == null - || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_id_key))) { - recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key); - } - if (key == null - || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_paused_key))) { - recordingTrackPaused = PreferencesUtils.getBoolean( - context, R.string.recording_track_paused_key, - PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); - } - if (key == null - || key.equals(PreferencesUtils.getKey(context, R.string.stats_units_key))) { - metricUnits = PreferencesUtils.isMetricUnits(context); - if (key != null) { - for (TrackDataListener trackDataListener : - trackDataManager.getListeners(TrackDataType.PREFERENCE)) { - if (trackDataListener.onMetricUnitsChanged(metricUnits)) { - loadDataForListener(trackDataListener); - } - } - } - } - if (key == null - || key.equals(PreferencesUtils.getKey(context, R.string.stats_rate_key))) { - reportSpeed = PreferencesUtils.isReportSpeed(context); - if (key != null) { - for (TrackDataListener trackDataListener : - trackDataManager.getListeners(TrackDataType.PREFERENCE)) { - if (trackDataListener.onReportSpeedChanged(reportSpeed)) { - loadDataForListener(trackDataListener); - } - } - } - } - if (key == null - || key.equals(PreferencesUtils.getKey(context, R.string.recording_gps_accuracy_key))) { - recordingGpsAccuracy = PreferencesUtils.getInt(context, R.string.recording_gps_accuracy_key, - PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT); - if (key != null) { - for (TrackDataListener trackDataListener : - trackDataManager.getListeners(TrackDataType.PREFERENCE)) { - if (trackDataListener.onRecordingGpsAccuracy(recordingGpsAccuracy)) { - loadDataForListener(trackDataListener); - } - } - } - } - if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_distance_interval_key))) { - recordingDistanceInterval = PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT); - if (key != null) { - for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) { - if (trackDataListener.onRecordingDistanceIntervalChanged(recordingDistanceInterval)) { - loadDataForListener(trackDataListener); - } - } - } - } - } - }); - } - - /** - * Loads data for all listeners. To be run in the {@link #handler} thread. - */ - private void loadDataForAll() { - resetSamplingState(); - if (trackDataManager.getNumberOfListeners() == 0) { - return; - } - - for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) { - trackDataListener.onMetricUnitsChanged(metricUnits); - trackDataListener.onReportSpeedChanged(reportSpeed); - trackDataListener.onRecordingGpsAccuracy(recordingGpsAccuracy); - trackDataListener.onRecordingDistanceIntervalChanged(recordingDistanceInterval); - } - - notifyTracksTableUpdate(trackDataManager.getListeners(TrackDataType.TRACKS_TABLE)); - - for (TrackDataListener listener : trackDataManager.getListeners(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE)) { - listener.clearTrackPoints(); - } - notifyTrackPointsTableUpdate(true, - trackDataManager.getListeners(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE), - trackDataManager.getListeners(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE)); - notifyWaypointsTableUpdate(trackDataManager.getListeners(TrackDataType.WAYPOINTS_TABLE)); - } - - /** - * Loads data for a listener. To be run in the {@link #handler} thread. - * - * @param trackDataListener the track data listener. - */ - private void loadDataForListener(TrackDataListener trackDataListener) { - Set trackDataListeners = Collections.singleton(trackDataListener); - EnumSet trackDataTypes = trackDataManager.getTrackDataTypes(trackDataListener); - - if (trackDataTypes.contains(TrackDataType.PREFERENCE)) { - trackDataListener.onMetricUnitsChanged(metricUnits); - trackDataListener.onReportSpeedChanged(reportSpeed); - trackDataListener.onRecordingGpsAccuracy(recordingGpsAccuracy); - trackDataListener.onRecordingDistanceIntervalChanged(recordingDistanceInterval); - } - - if (trackDataTypes.contains(TrackDataType.TRACKS_TABLE)) { - notifyTracksTableUpdate(trackDataListeners); - } - - boolean hasSampledIn = trackDataTypes.contains(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE); - boolean hasSampledOut = trackDataTypes.contains(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE); - if (hasSampledIn || hasSampledOut) { - trackDataListener.clearTrackPoints(); - boolean isOnlyListener = trackDataManager.getNumberOfListeners() == 1; - if (isOnlyListener) { + /** + * Constructor. + * + * @param context the context + * @param trackDataManager the track data manager + * @param contentProviderUtils the content tracks provider utils + * @param targetNumPoints the target number of points + */ + @VisibleForTesting + TrackDataHub(Context context, TrackDataManager trackDataManager, + ContentProviderUtils contentProviderUtils, int targetNumPoints) { + this.context = context; + this.trackDataManager = trackDataManager; + this.contentProviderUtils = contentProviderUtils; + this.targetNumPoints = targetNumPoints; resetSamplingState(); - } - Set sampledOutListeners = hasSampledOut ? trackDataListeners - : Collections. emptySet(); - notifyTrackPointsTableUpdate(isOnlyListener, trackDataListeners, sampledOutListeners); } - if (trackDataTypes.contains(TrackDataType.WAYPOINTS_TABLE)) { - notifyWaypointsTableUpdate(trackDataListeners); - } - } - - /** - * Notifies track table update. To be run in the {@link #handler} thread. - * - * @param trackDataListeners the track data listeners to notify - */ - private void notifyTracksTableUpdate(Set trackDataListeners) { - if (trackDataListeners.isEmpty()) { - return; - } - Track track = contentProviderUtils.getTrack(selectedTrackId); - for (TrackDataListener trackDataListener : trackDataListeners) { - trackDataListener.onTrackUpdated(track); - } - } - - /** - * Notifies waypoint table update. Currently, reloads all the waypoints up to - * {@link #MAX_DISPLAYED_WAYPOINTS}. To be run in the {@link #handler} - * thread. - * - * @param trackDataListeners the track data listeners to notify - */ - private void notifyWaypointsTableUpdate(Set trackDataListeners) { - if (trackDataListeners.isEmpty()) { - return; + /** + * Creates a new instance. + */ + public synchronized static TrackDataHub newInstance(Context context) { + return new TrackDataHub(context, new TrackDataManager(), ContentProviderUtils.Factory.get( + context), TARGET_DISPLAYED_TRACK_POINTS); } - for (TrackDataListener trackDataListener : trackDataListeners) { - trackDataListener.clearWaypoints(); - } - - try (Cursor cursor = contentProviderUtils.getWaypointCursor( - selectedTrackId, -1L, MAX_DISPLAYED_WAYPOINTS)) { - if (cursor != null && cursor.moveToFirst()) { - do { - Waypoint waypoint = contentProviderUtils.createWaypoint(cursor); - if (!LocationUtils.isValidLocation(waypoint.getLocation())) { - continue; - } - for (TrackDataListener trackDataListener : trackDataListeners) { - trackDataListener.onNewWaypoint(waypoint); - } - } while (cursor.moveToNext()); - } - } - - for (TrackDataListener trackDataListener : trackDataListeners) { - trackDataListener.onNewWaypointsDone(); - } - } - - /** - * Notifies track points table update. To be run in the {@link #handler} - * thread. - * - * @param updateSamplingState true to update the sampling state - * @param sampledInListeners the sampled-in listeners - * @param sampledOutListeners the sampled-out listeners - */ - private void notifyTrackPointsTableUpdate(boolean updateSamplingState, - Set sampledInListeners, Set sampledOutListeners) { - if (sampledInListeners.isEmpty() && sampledOutListeners.isEmpty()) { - return; - } - if (updateSamplingState && numLoadedPoints >= targetNumPoints) { - // Reload and resample the track at a lower frequency. - Log.i(TAG, "Resampling track after " + numLoadedPoints + " points."); - resetSamplingState(); - for (TrackDataListener listener : sampledInListeners) { - listener.clearTrackPoints(); - } - } - - int localNumLoadedPoints = updateSamplingState ? numLoadedPoints : 0; - long localFirstSeenLocationId = updateSamplingState ? firstSeenLocationId : -1L; - long localLastSeenLocationId = updateSamplingState ? lastSeenLocationId : -1L; - long maxPointId = updateSamplingState ? -1L : lastSeenLocationId; - - long lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId); - int samplingFrequency = -1; - boolean includeNextPoint = false; - LocationIterator locationIterator = null; - - try { - locationIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, - localLastSeenLocationId + 1, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); - - while (locationIterator.hasNext()) { - Location location = locationIterator.next(); - long locationId = locationIterator.getLocationId(); - - // Stop if past the last wanted point - if (maxPointId != -1L && locationId > maxPointId) { - break; + /** + * Starts. + */ + public void start() { + if (started) { + Log.i(TAG, "TrackDataHub already started, ignoring start."); + return; } + started = true; + handlerThread = new HandlerThread("TrackDataHubHandlerThread"); + handlerThread.start(); + handler = new Handler(handlerThread.getLooper()); + dataSource = newDataSource(); + dataSourceManager = new DataSourceManager(dataSource, this); - if (localFirstSeenLocationId == -1) { - localFirstSeenLocationId = locationId; - } - - if (samplingFrequency == -1) { - long numTotalPoints = Math.max(0L, lastTrackPointId - localFirstSeenLocationId); - samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints); - } - - if (!LocationUtils.isValidLocation(location)) { - // TODO: also include the last valid point before a split - for (TrackDataListener trackDataListener : sampledInListeners) { - trackDataListener.onSegmentSplit(location); - includeNextPoint = true; - } - } else { - // Also include the last point if the selected track is not recording. - if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0) - || (locationId == lastTrackPointId && !isSelectedTrackRecording())) { - includeNextPoint = false; - for (TrackDataListener trackDataListener : sampledInListeners) { - trackDataListener.onSampledInTrackPoint(location); + notifyPreferenceChanged(null); + runInHanderThread(new Runnable() { + @Override + public void run() { + if (dataSourceManager != null) { + dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes()); + loadDataForAll(); + } } - } else { - for (TrackDataListener trackDataListener : sampledOutListeners) { - trackDataListener.onSampledOutTrackPoint(location); + }); + } + + /** + * Stops. + */ + public void stop() { + if (!started) { + Log.i(TAG, "TrackDataHub not started, ignoring stop."); + return; + } + started = false; + + dataSourceManager.unregisterAllListeners(); + if (handlerThread != null) { + handlerThread.getLooper().quit(); + handlerThread = null; + } + handler = null; + dataSource = null; + dataSourceManager = null; + } + + /** + * Loads a track. + * + * @param trackId the track id + */ + public void loadTrack(final long trackId) { + runInHanderThread(new Runnable() { + @Override + public void run() { + if (trackId == selectedTrackId) { + Log.i(TAG, "Not reloading track " + trackId); + return; + } + selectedTrackId = trackId; + loadDataForAll(); } - } + }); + } + + /** + * Registers a {@link TrackDataListener}. + * + * @param trackDataListener the track data listener + * @param trackDataTypes the track data types + */ + public void registerTrackDataListener(final TrackDataListener trackDataListener, final EnumSet trackDataTypes) { + runInHanderThread(new Runnable() { + @Override + public void run() { + trackDataManager.registerListener(trackDataListener, trackDataTypes); + if (dataSourceManager != null) { + dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes()); + loadDataForListener(trackDataListener); + } + } + }); + } + + /** + * Unregisters a {@link TrackDataListener}. + * + * @param trackDataListener the track data listener + */ + public void unregisterTrackDataListener(final TrackDataListener trackDataListener) { + runInHanderThread(new Runnable() { + @Override + public void run() { + trackDataManager.unregisterListener(trackDataListener); + if (dataSourceManager != null) { + dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes()); + } + } + }); + } + + /** + * Reloads data for a {@link TrackDataListener}. + */ + public void reloadDataForListener(final TrackDataListener trackDataListener) { + runInHanderThread(new Runnable() { + @Override + public void run() { + loadDataForListener(trackDataListener); + } + }); + } + + /** + * Returns true if the selected track is recording. + */ + public boolean isSelectedTrackRecording() { + return selectedTrackId == recordingTrackId + && recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; + } + + /** + * Returns true if the selected track is paused. + */ + public boolean isSelectedTrackPaused() { + return selectedTrackId == recordingTrackId && recordingTrackPaused; + } + + @Override + public void notifyTracksTableUpdated() { + runInHanderThread(new Runnable() { + @Override + public void run() { + notifyTracksTableUpdate(trackDataManager.getListeners(TrackDataType.TRACKS_TABLE)); + } + }); + } + + @Override + public void notifyWaypointsTableUpdated() { + runInHanderThread(new Runnable() { + @Override + public void run() { + notifyWaypointsTableUpdate(trackDataManager.getListeners(TrackDataType.WAYPOINTS_TABLE)); + } + }); + } + + @Override + public void notifyTrackPointsTableUpdated() { + runInHanderThread(new Runnable() { + @Override + public void run() { + notifyTrackPointsTableUpdate( + true, trackDataManager.getListeners(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE), + trackDataManager.getListeners(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE)); + } + }); + } + + @Override + public void notifyPreferenceChanged(final String key) { + runInHanderThread(new Runnable() { + @Override + public void run() { + if (key == null + || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_id_key))) { + recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key); + } + if (key == null + || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_paused_key))) { + recordingTrackPaused = PreferencesUtils.getBoolean( + context, R.string.recording_track_paused_key, + PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); + } + if (key == null + || key.equals(PreferencesUtils.getKey(context, R.string.stats_units_key))) { + metricUnits = PreferencesUtils.isMetricUnits(context); + if (key != null) { + for (TrackDataListener trackDataListener : + trackDataManager.getListeners(TrackDataType.PREFERENCE)) { + if (trackDataListener.onMetricUnitsChanged(metricUnits)) { + loadDataForListener(trackDataListener); + } + } + } + } + if (key == null + || key.equals(PreferencesUtils.getKey(context, R.string.stats_rate_key))) { + reportSpeed = PreferencesUtils.isReportSpeed(context); + if (key != null) { + for (TrackDataListener trackDataListener : + trackDataManager.getListeners(TrackDataType.PREFERENCE)) { + if (trackDataListener.onReportSpeedChanged(reportSpeed)) { + loadDataForListener(trackDataListener); + } + } + } + } + if (key == null + || key.equals(PreferencesUtils.getKey(context, R.string.recording_gps_accuracy_key))) { + recordingGpsAccuracy = PreferencesUtils.getInt(context, R.string.recording_gps_accuracy_key, + PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT); + if (key != null) { + for (TrackDataListener trackDataListener : + trackDataManager.getListeners(TrackDataType.PREFERENCE)) { + if (trackDataListener.onRecordingGpsAccuracy(recordingGpsAccuracy)) { + loadDataForListener(trackDataListener); + } + } + } + } + if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_distance_interval_key))) { + recordingDistanceInterval = PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT); + if (key != null) { + for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) { + if (trackDataListener.onRecordingDistanceIntervalChanged(recordingDistanceInterval)) { + loadDataForListener(trackDataListener); + } + } + } + } + } + }); + } + + /** + * Loads data for all listeners. To be run in the {@link #handler} thread. + */ + private void loadDataForAll() { + resetSamplingState(); + if (trackDataManager.getNumberOfListeners() == 0) { + return; } - localNumLoadedPoints++; - localLastSeenLocationId = locationId; - } - } finally { - if (locationIterator != null) { - locationIterator.close(); - } + for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) { + trackDataListener.onMetricUnitsChanged(metricUnits); + trackDataListener.onReportSpeedChanged(reportSpeed); + trackDataListener.onRecordingGpsAccuracy(recordingGpsAccuracy); + trackDataListener.onRecordingDistanceIntervalChanged(recordingDistanceInterval); + } + + notifyTracksTableUpdate(trackDataManager.getListeners(TrackDataType.TRACKS_TABLE)); + + for (TrackDataListener listener : trackDataManager.getListeners(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE)) { + listener.clearTrackPoints(); + } + notifyTrackPointsTableUpdate(true, + trackDataManager.getListeners(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE), + trackDataManager.getListeners(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE)); + notifyWaypointsTableUpdate(trackDataManager.getListeners(TrackDataType.WAYPOINTS_TABLE)); } - if (updateSamplingState) { - numLoadedPoints = localNumLoadedPoints; - firstSeenLocationId = localFirstSeenLocationId; - lastSeenLocationId = localLastSeenLocationId; + /** + * Loads data for a listener. To be run in the {@link #handler} thread. + * + * @param trackDataListener the track data listener. + */ + private void loadDataForListener(TrackDataListener trackDataListener) { + Set trackDataListeners = Collections.singleton(trackDataListener); + EnumSet trackDataTypes = trackDataManager.getTrackDataTypes(trackDataListener); + + if (trackDataTypes.contains(TrackDataType.PREFERENCE)) { + trackDataListener.onMetricUnitsChanged(metricUnits); + trackDataListener.onReportSpeedChanged(reportSpeed); + trackDataListener.onRecordingGpsAccuracy(recordingGpsAccuracy); + trackDataListener.onRecordingDistanceIntervalChanged(recordingDistanceInterval); + } + + if (trackDataTypes.contains(TrackDataType.TRACKS_TABLE)) { + notifyTracksTableUpdate(trackDataListeners); + } + + boolean hasSampledIn = trackDataTypes.contains(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE); + boolean hasSampledOut = trackDataTypes.contains(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE); + if (hasSampledIn || hasSampledOut) { + trackDataListener.clearTrackPoints(); + boolean isOnlyListener = trackDataManager.getNumberOfListeners() == 1; + if (isOnlyListener) { + resetSamplingState(); + } + Set sampledOutListeners = hasSampledOut ? trackDataListeners + : Collections.emptySet(); + notifyTrackPointsTableUpdate(isOnlyListener, trackDataListeners, sampledOutListeners); + } + + if (trackDataTypes.contains(TrackDataType.WAYPOINTS_TABLE)) { + notifyWaypointsTableUpdate(trackDataListeners); + } } - for (TrackDataListener listener : sampledInListeners) { - listener.onNewTrackPointsDone(); + /** + * Notifies track table update. To be run in the {@link #handler} thread. + * + * @param trackDataListeners the track data listeners to notify + */ + private void notifyTracksTableUpdate(Set trackDataListeners) { + if (trackDataListeners.isEmpty()) { + return; + } + Track track = contentProviderUtils.getTrack(selectedTrackId); + for (TrackDataListener trackDataListener : trackDataListeners) { + trackDataListener.onTrackUpdated(track); + } } - } - /** - * Resets the track points sampling states. - */ - private void resetSamplingState() { - numLoadedPoints = 0; - firstSeenLocationId = -1L; - lastSeenLocationId = -1L; - } + /** + * Notifies waypoint table update. Currently, reloads all the waypoints up to + * {@link #MAX_DISPLAYED_WAYPOINTS}. To be run in the {@link #handler} + * thread. + * + * @param trackDataListeners the track data listeners to notify + */ + private void notifyWaypointsTableUpdate(Set trackDataListeners) { + if (trackDataListeners.isEmpty()) { + return; + } - /** - * Creates a {@link DataSource}. - */ - @VisibleForTesting - protected DataSource newDataSource() { - return new DataSource(context); - } + for (TrackDataListener trackDataListener : trackDataListeners) { + trackDataListener.clearWaypoints(); + } - /** - * Run in the handler thread. - * - * @param runnable the runnable - */ - @VisibleForTesting - protected void runInHanderThread(Runnable runnable) { - if (handler == null) { - // Use a Throwable to ensure the stack trace is logged. - Log.d(TAG, "handler is null.", new Throwable()); - return; + try (Cursor cursor = contentProviderUtils.getWaypointCursor( + selectedTrackId, -1L, MAX_DISPLAYED_WAYPOINTS)) { + if (cursor != null && cursor.moveToFirst()) { + do { + Waypoint waypoint = contentProviderUtils.createWaypoint(cursor); + if (!LocationUtils.isValidLocation(waypoint.getLocation())) { + continue; + } + for (TrackDataListener trackDataListener : trackDataListeners) { + trackDataListener.onNewWaypoint(waypoint); + } + } while (cursor.moveToNext()); + } + } + + for (TrackDataListener trackDataListener : trackDataListeners) { + trackDataListener.onNewWaypointsDone(); + } + } + + /** + * Notifies track points table update. To be run in the {@link #handler} + * thread. + * + * @param updateSamplingState true to update the sampling state + * @param sampledInListeners the sampled-in listeners + * @param sampledOutListeners the sampled-out listeners + */ + private void notifyTrackPointsTableUpdate(boolean updateSamplingState, + Set sampledInListeners, Set sampledOutListeners) { + if (sampledInListeners.isEmpty() && sampledOutListeners.isEmpty()) { + return; + } + if (updateSamplingState && numLoadedPoints >= targetNumPoints) { + // Reload and resample the track at a lower frequency. + Log.i(TAG, "Resampling track after " + numLoadedPoints + " points."); + resetSamplingState(); + for (TrackDataListener listener : sampledInListeners) { + listener.clearTrackPoints(); + } + } + + int localNumLoadedPoints = updateSamplingState ? numLoadedPoints : 0; + long localFirstSeenLocationId = updateSamplingState ? firstSeenLocationId : -1L; + long localLastSeenLocationId = updateSamplingState ? lastSeenLocationId : -1L; + long maxPointId = updateSamplingState ? -1L : lastSeenLocationId; + + long lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId); + int samplingFrequency = -1; + boolean includeNextPoint = false; + LocationIterator locationIterator = null; + + try { + locationIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, + localLastSeenLocationId + 1, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + + while (locationIterator.hasNext()) { + Location location = locationIterator.next(); + long locationId = locationIterator.getLocationId(); + + // Stop if past the last wanted point + if (maxPointId != -1L && locationId > maxPointId) { + break; + } + + if (localFirstSeenLocationId == -1) { + localFirstSeenLocationId = locationId; + } + + if (samplingFrequency == -1) { + long numTotalPoints = Math.max(0L, lastTrackPointId - localFirstSeenLocationId); + samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints); + } + + if (!LocationUtils.isValidLocation(location)) { + // TODO: also include the last valid point before a split + for (TrackDataListener trackDataListener : sampledInListeners) { + trackDataListener.onSegmentSplit(location); + includeNextPoint = true; + } + } else { + // Also include the last point if the selected track is not recording. + if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0) + || (locationId == lastTrackPointId && !isSelectedTrackRecording())) { + includeNextPoint = false; + for (TrackDataListener trackDataListener : sampledInListeners) { + trackDataListener.onSampledInTrackPoint(location); + } + } else { + for (TrackDataListener trackDataListener : sampledOutListeners) { + trackDataListener.onSampledOutTrackPoint(location); + } + } + } + + localNumLoadedPoints++; + localLastSeenLocationId = locationId; + } + } finally { + if (locationIterator != null) { + locationIterator.close(); + } + } + + if (updateSamplingState) { + numLoadedPoints = localNumLoadedPoints; + firstSeenLocationId = localFirstSeenLocationId; + lastSeenLocationId = localLastSeenLocationId; + } + + for (TrackDataListener listener : sampledInListeners) { + listener.onNewTrackPointsDone(); + } + } + + /** + * Resets the track points sampling states. + */ + private void resetSamplingState() { + numLoadedPoints = 0; + firstSeenLocationId = -1L; + lastSeenLocationId = -1L; + } + + /** + * Creates a {@link DataSource}. + */ + @VisibleForTesting + protected DataSource newDataSource() { + return new DataSource(context); + } + + /** + * Run in the handler thread. + * + * @param runnable the runnable + */ + @VisibleForTesting + protected void runInHanderThread(Runnable runnable) { + if (handler == null) { + // Use a Throwable to ensure the stack trace is logged. + Log.d(TAG, "handler is null.", new Throwable()); + return; + } + handler.post(runnable); + } + + /** + * Gets the value selectedTrackId. + * + * @return the selectedTrackId + */ + public long getSelectedTrackId() { + return selectedTrackId; + } + + /** + * Gets the recordingGpsAccuracy. + */ + @VisibleForTesting + int getRecordingGpsAccuracy() { + return recordingGpsAccuracy; + } + + /** + * Gets the metricUnits. + * + * @return the metricUnits + */ + @VisibleForTesting + boolean isMetricUnits() { + return metricUnits; + } + + /** + * Gets the reportSpeed. + * + * @return the reportSpeed + */ + @VisibleForTesting + boolean isReportSpeed() { + return reportSpeed; } - handler.post(runnable); - } - - /** - * Gets the value selectedTrackId. - * - * @return the selectedTrackId - */ - public long getSelectedTrackId() { - return selectedTrackId; - } - - /** - * Gets the recordingGpsAccuracy. - */ - @VisibleForTesting - int getRecordingGpsAccuracy() { - return recordingGpsAccuracy; - } - - /** - * Gets the metricUnits. - * - * @return the metricUnits - */ - @VisibleForTesting - boolean isMetricUnits() { - return metricUnits; - } - - /** - * Gets the reportSpeed. - * - * @return the reportSpeed - */ - @VisibleForTesting - boolean isReportSpeed() { - return reportSpeed; - } } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/content/TrackDataListener.java b/src/main/java/com/google/android/apps/mytracks/content/TrackDataListener.java index 5d25f5f78..be89edb3a 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/TrackDataListener.java +++ b/src/main/java/com/google/android/apps/mytracks/content/TrackDataListener.java @@ -1,12 +1,12 @@ /* * Copyright 2011 Google Inc. - * + * * 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 @@ -20,98 +20,98 @@ import android.location.Location; /** * Listener for track data changes. - * + * * @author Rodrigo Damazio */ public interface TrackDataListener { - /** - * Called when the track or its statistics has been updated. - * - * @param track the track - */ - void onTrackUpdated(Track track); + /** + * Called when the track or its statistics has been updated. + * + * @param track the track + */ + void onTrackUpdated(Track track); - /** - * Called to clear previously-sent track points. - */ - void clearTrackPoints(); + /** + * Called to clear previously-sent track points. + */ + void clearTrackPoints(); - /** - * Called when a sampled in track point is read. - * - * @param location the location - */ - void onSampledInTrackPoint(Location location); + /** + * Called when a sampled in track point is read. + * + * @param location the location + */ + void onSampledInTrackPoint(Location location); - /** - * Called when a sampled out track point is read. - * - * @param location the location - */ - void onSampledOutTrackPoint(Location location); + /** + * Called when a sampled out track point is read. + * + * @param location the location + */ + void onSampledOutTrackPoint(Location location); - /** - * Called when an invalid track point representing a segment split is read. - */ - void onSegmentSplit(Location location); + /** + * Called when an invalid track point representing a segment split is read. + */ + void onSegmentSplit(Location location); - /** - * Called when finish sending new track points. This gets called after every - * batch of calls to {@link #onSampledInTrackPoint(Location)}, - * {@link #onSampledOutTrackPoint(Location)} and - * {@link #onSegmentSplit(Location)}. - */ - void onNewTrackPointsDone(); + /** + * Called when finish sending new track points. This gets called after every + * batch of calls to {@link #onSampledInTrackPoint(Location)}, + * {@link #onSampledOutTrackPoint(Location)} and + * {@link #onSegmentSplit(Location)}. + */ + void onNewTrackPointsDone(); - /** - * Called to clear previously sent waypoints. - */ - void clearWaypoints(); + /** + * Called to clear previously sent waypoints. + */ + void clearWaypoints(); - /** - * Called when a new waypoint is read. - * - * @param waypoint the waypoint - */ - void onNewWaypoint(Waypoint waypoint); + /** + * Called when a new waypoint is read. + * + * @param waypoint the waypoint + */ + void onNewWaypoint(Waypoint waypoint); - /** - * Called when finish sending new waypoints. This gets called after every - * batch of calls to {@link #clearWaypoints()} and - * {@link #onNewWaypoint(Waypoint)}. - */ - void onNewWaypointsDone(); + /** + * Called when finish sending new waypoints. This gets called after every + * batch of calls to {@link #clearWaypoints()} and + * {@link #onNewWaypoint(Waypoint)}. + */ + void onNewWaypointsDone(); - /** - * Called when the metric units preference value is change. - * - * @param metricUnits true to use metric units, false to use imperial units - * @return true to reload all the data, false otherwise. - */ - boolean onMetricUnitsChanged(boolean metricUnits); + /** + * Called when the metric units preference value is change. + * + * @param metricUnits true to use metric units, false to use imperial units + * @return true to reload all the data, false otherwise. + */ + boolean onMetricUnitsChanged(boolean metricUnits); - /** - * Called when the report speed preference value is changed. - * - * @param reportSpeed true to report speed, false to report pace - * @return true to reload all the data, false otherwise. - */ - boolean onReportSpeedChanged(boolean reportSpeed); + /** + * Called when the report speed preference value is changed. + * + * @param reportSpeed true to report speed, false to report pace + * @return true to reload all the data, false otherwise. + */ + boolean onReportSpeedChanged(boolean reportSpeed); - /** - * Called when the recording gps accuracy preference value is changed. - * - * @param recordingGpsAccuracy the new value - * @return true to reload all the data, false otherwise - */ - boolean onRecordingGpsAccuracy(int recordingGpsAccuracy); - - /** - * Called when the recording distance interval preference value is changed. - * - * @param recordingDistanceInterval the new value - * @return true to reload all the data, false otherwise. - */ - boolean onRecordingDistanceIntervalChanged(int recordingDistanceInterval); + /** + * Called when the recording gps accuracy preference value is changed. + * + * @param recordingGpsAccuracy the new value + * @return true to reload all the data, false otherwise + */ + boolean onRecordingGpsAccuracy(int recordingGpsAccuracy); + + /** + * Called when the recording distance interval preference value is changed. + * + * @param recordingDistanceInterval the new value + * @return true to reload all the data, false otherwise. + */ + boolean onRecordingDistanceIntervalChanged(int recordingDistanceInterval); } diff --git a/src/main/java/com/google/android/apps/mytracks/content/TrackDataManager.java b/src/main/java/com/google/android/apps/mytracks/content/TrackDataManager.java index e018d7615..ec54168b3 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/TrackDataManager.java +++ b/src/main/java/com/google/android/apps/mytracks/content/TrackDataManager.java @@ -28,96 +28,96 @@ import java.util.Set; /** * Manages register/unregister {@link TrackDataListener} and keeping the state * for each registered listener. - * + * * @author Rodrigo Damazio */ public class TrackDataManager { - private static final String TAG = TrackDataManager.class.getSimpleName(); - - // Map of listener to its track data types - private final Map> - listenerToTypesMap = new HashMap<>(); + private static final String TAG = TrackDataManager.class.getSimpleName(); - // Map of track data type to listeners - private final Map> - typeToListenersMap = new EnumMap<>(TrackDataType.class); + // Map of listener to its track data types + private final Map> + listenerToTypesMap = new HashMap<>(); - public TrackDataManager() { - for (TrackDataType trackDataType : TrackDataType.values()) { - typeToListenersMap.put(trackDataType, new LinkedHashSet()); - } - } + // Map of track data type to listeners + private final Map> + typeToListenersMap = new EnumMap<>(TrackDataType.class); - /** - * Registers a listener. - * - * @param listener the listener - * @param trackDataTypes the track data types the listener is interested - */ - public void registerListener( - TrackDataListener listener, EnumSet trackDataTypes) { - if (listenerToTypesMap.containsKey(listener)) { - Log.w(TAG, "Tried to register a listener that is already registered. Ignore."); - return; - } - listenerToTypesMap.put(listener, trackDataTypes); - for (TrackDataType trackDataType : trackDataTypes) { - typeToListenersMap.get(trackDataType).add(listener); - } - } - - /** - * Unregisters a listener. - * - * @param listener the listener - */ - public void unregisterListener(TrackDataListener listener) { - EnumSet removedTypes = listenerToTypesMap.remove(listener); - if (removedTypes == null) { - Log.w(TAG, "Tried to unregister a listener that is not registered. Ignore."); - return; + public TrackDataManager() { + for (TrackDataType trackDataType : TrackDataType.values()) { + typeToListenersMap.put(trackDataType, new LinkedHashSet()); + } } - // Remove the listener from the typeToListenersMap - for (TrackDataType trackDataType : removedTypes) { - typeToListenersMap.get(trackDataType).remove(listener); + /** + * Registers a listener. + * + * @param listener the listener + * @param trackDataTypes the track data types the listener is interested + */ + public void registerListener( + TrackDataListener listener, EnumSet trackDataTypes) { + if (listenerToTypesMap.containsKey(listener)) { + Log.w(TAG, "Tried to register a listener that is already registered. Ignore."); + return; + } + listenerToTypesMap.put(listener, trackDataTypes); + for (TrackDataType trackDataType : trackDataTypes) { + typeToListenersMap.get(trackDataType).add(listener); + } } - } - /** - * Gets the number of {@link TrackDataListener}. - */ - public int getNumberOfListeners() { - return listenerToTypesMap.size(); - } + /** + * Unregisters a listener. + * + * @param listener the listener + */ + public void unregisterListener(TrackDataListener listener) { + EnumSet removedTypes = listenerToTypesMap.remove(listener); + if (removedTypes == null) { + Log.w(TAG, "Tried to unregister a listener that is not registered. Ignore."); + return; + } - /** - * Gets the track data types for a listener. - * - * @param listener the listener - */ - public EnumSet getTrackDataTypes(TrackDataListener listener) { - return listenerToTypesMap.get(listener); - } - - /** - * Gets the listeners for a {@link TrackDataType}. - * - * @param type the type - */ - public Set getListeners(TrackDataType type) { - return typeToListenersMap.get(type); - } - - /** - * Gets all the registered {@link TrackDataType}. - */ - public EnumSet getRegisteredTrackDataTypes() { - EnumSet types = EnumSet.noneOf(TrackDataType.class); - for (EnumSet value : listenerToTypesMap.values()) { - types.addAll(value); + // Remove the listener from the typeToListenersMap + for (TrackDataType trackDataType : removedTypes) { + typeToListenersMap.get(trackDataType).remove(listener); + } + } + + /** + * Gets the number of {@link TrackDataListener}. + */ + public int getNumberOfListeners() { + return listenerToTypesMap.size(); + } + + /** + * Gets the track data types for a listener. + * + * @param listener the listener + */ + public EnumSet getTrackDataTypes(TrackDataListener listener) { + return listenerToTypesMap.get(listener); + } + + /** + * Gets the listeners for a {@link TrackDataType}. + * + * @param type the type + */ + public Set getListeners(TrackDataType type) { + return typeToListenersMap.get(type); + } + + /** + * Gets all the registered {@link TrackDataType}. + */ + public EnumSet getRegisteredTrackDataTypes() { + EnumSet types = EnumSet.noneOf(TrackDataType.class); + for (EnumSet value : listenerToTypesMap.values()) { + types.addAll(value); + } + return types; } - return types; - } } diff --git a/src/main/java/com/google/android/apps/mytracks/content/TrackDataType.java b/src/main/java/com/google/android/apps/mytracks/content/TrackDataType.java index 0ae05337d..3c3966b35 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/TrackDataType.java +++ b/src/main/java/com/google/android/apps/mytracks/content/TrackDataType.java @@ -18,13 +18,13 @@ package com.google.android.apps.mytracks.content; /** * Types of track data. - * + * * @author Jimmy Shih */ public enum TrackDataType { - TRACKS_TABLE, // tracks table changes - WAYPOINTS_TABLE, // waypoints table changes - SAMPLED_IN_TRACK_POINTS_TABLE, // sampled-in track points table changes - SAMPLED_OUT_TRACK_POINTS_TABLE, // sampled-out track points table changes - PREFERENCE // preference changes + TRACKS_TABLE, // tracks table changes + WAYPOINTS_TABLE, // waypoints table changes + SAMPLED_IN_TRACK_POINTS_TABLE, // sampled-in track points table changes + SAMPLED_OUT_TRACK_POINTS_TABLE, // sampled-out track points table changes + PREFERENCE // preference changes } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/content/TrackPointsColumns.java b/src/main/java/com/google/android/apps/mytracks/content/TrackPointsColumns.java index bb3f86d0a..05af026d1 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/TrackPointsColumns.java +++ b/src/main/java/com/google/android/apps/mytracks/content/TrackPointsColumns.java @@ -1,12 +1,12 @@ /* * Copyright 2008 Google Inc. - * + * * 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 @@ -26,36 +26,36 @@ import android.provider.BaseColumns; */ public interface TrackPointsColumns extends BaseColumns { - String TABLE_NAME = "trackpoints"; - Uri CONTENT_URI = Uri.parse("content://com.google.android.maps.mytracks/trackpoints"); - String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.google.trackpoint"; - String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.google.trackpoint"; - String DEFAULT_SORT_ORDER = "_id"; + String TABLE_NAME = "trackpoints"; + Uri CONTENT_URI = Uri.parse("content://com.google.android.maps.mytracks/trackpoints"); + String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.google.trackpoint"; + String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.google.trackpoint"; + String DEFAULT_SORT_ORDER = "_id"; - // Columns - String TRACKID = "trackid"; - String LONGITUDE = "longitude"; - String LATITUDE = "latitude"; - String TIME = "time"; - String ALTITUDE = "elevation"; - String ACCURACY = "accuracy"; - String SPEED = "speed"; - String BEARING = "bearing"; - String SENSOR_HEARTRATE = "sensor_heartrate"; - String SENSOR_CADENCE = "sensor_cadence"; - String SENSOR_POWER = "sensor_power"; + // Columns + String TRACKID = "trackid"; + String LONGITUDE = "longitude"; + String LATITUDE = "latitude"; + String TIME = "time"; + String ALTITUDE = "elevation"; + String ACCURACY = "accuracy"; + String SPEED = "speed"; + String BEARING = "bearing"; + String SENSOR_HEARTRATE = "sensor_heartrate"; + String SENSOR_CADENCE = "sensor_cadence"; + String SENSOR_POWER = "sensor_power"; - String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" - + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + TRACKID + " INTEGER, " - + LONGITUDE + " INTEGER, " - + LATITUDE + " INTEGER, " - + TIME + " INTEGER, " - + ALTITUDE + " FLOAT, " - + ACCURACY + " FLOAT, " - + SPEED + " FLOAT, " - + BEARING + " FLOAT, " - + SENSOR_HEARTRATE + " FLOAT, " - + SENSOR_CADENCE + " FLOAT, " - + SENSOR_POWER + " FLOAT);"; + String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + TRACKID + " INTEGER, " + + LONGITUDE + " INTEGER, " + + LATITUDE + " INTEGER, " + + TIME + " INTEGER, " + + ALTITUDE + " FLOAT, " + + ACCURACY + " FLOAT, " + + SPEED + " FLOAT, " + + BEARING + " FLOAT, " + + SENSOR_HEARTRATE + " FLOAT, " + + SENSOR_CADENCE + " FLOAT, " + + SENSOR_POWER + " FLOAT);"; } diff --git a/src/main/java/com/google/android/apps/mytracks/content/TracksColumns.java b/src/main/java/com/google/android/apps/mytracks/content/TracksColumns.java index edb3f8052..39cb01989 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/TracksColumns.java +++ b/src/main/java/com/google/android/apps/mytracks/content/TracksColumns.java @@ -1,12 +1,12 @@ /* * Copyright 2008 Google Inc. - * + * * 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 @@ -21,85 +21,85 @@ import android.provider.BaseColumns; /** * Constants for the tracks table. - * + * * @author Leif Hendrik Wilden */ public interface TracksColumns extends BaseColumns { - String TABLE_NAME = "tracks"; + String TABLE_NAME = "tracks"; - /** - * Tracks provider uri. - */ - Uri CONTENT_URI = Uri.parse("content://com.google.android.maps.mytracks/tracks"); + /** + * Tracks provider uri. + */ + Uri CONTENT_URI = Uri.parse("content://com.google.android.maps.mytracks/tracks"); - /** - * Track content type. - */ - String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.google.track"; + /** + * Track content type. + */ + String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.google.track"; - /** - * Track id content type. - */ - String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.google.track"; + /** + * Track id content type. + */ + String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.google.track"; - /** - * Tracks table default sort order. - */ - String DEFAULT_SORT_ORDER = "_id"; + /** + * Tracks table default sort order. + */ + String DEFAULT_SORT_ORDER = "_id"; - // Columns - String NAME = "name"; // track name - String DESCRIPTION = "description"; // track description - String CATEGORY = "category"; // track activity type - String STARTID = "startid"; // first track point id - String STOPID = "stopid"; // last track point id - String STARTTIME = "starttime"; // track start time - String STOPTIME = "stoptime"; // track stop time - String NUMPOINTS = "numpoints"; // number of track points - String TOTALDISTANCE = "totaldistance"; // total distance - String TOTALTIME = "totaltime"; // total time - String MOVINGTIME = "movingtime"; // moving time - String MINLAT = "minlat"; // minimum latitude - String MAXLAT = "maxlat"; // maximum latitude - String MINLON = "minlon"; // minimum longitude - String MAXLON = "maxlon"; // maximum longitude - String AVGSPEED = "avgspeed"; // average speed + // Columns + String NAME = "name"; // track name + String DESCRIPTION = "description"; // track description + String CATEGORY = "category"; // track activity type + String STARTID = "startid"; // first track point id + String STOPID = "stopid"; // last track point id + String STARTTIME = "starttime"; // track start time + String STOPTIME = "stoptime"; // track stop time + String NUMPOINTS = "numpoints"; // number of track points + String TOTALDISTANCE = "totaldistance"; // total distance + String TOTALTIME = "totaltime"; // total time + String MOVINGTIME = "movingtime"; // moving time + String MINLAT = "minlat"; // minimum latitude + String MAXLAT = "maxlat"; // maximum latitude + String MINLON = "minlon"; // minimum longitude + String MAXLON = "maxlon"; // maximum longitude + String AVGSPEED = "avgspeed"; // average speed - // average moving speed - String AVGMOVINGSPEED = "avgmovingspeed"; - String MAXSPEED = "maxspeed"; // maximum speed - String MINELEVATION = "minelevation"; // minimum elevation - String MAXELEVATION = "maxelevation"; // maximum elevation - String ELEVATIONGAIN = "elevationgain"; // elevation gain - String MINGRADE = "mingrade"; // minimum grade - String MAXGRADE = "maxgrade"; // maximum grade - String ICON = "icon"; // track activity type icon + // average moving speed + String AVGMOVINGSPEED = "avgmovingspeed"; + String MAXSPEED = "maxspeed"; // maximum speed + String MINELEVATION = "minelevation"; // minimum elevation + String MAXELEVATION = "maxelevation"; // maximum elevation + String ELEVATIONGAIN = "elevationgain"; // elevation gain + String MINGRADE = "mingrade"; // minimum grade + String MAXGRADE = "maxgrade"; // maximum grade + String ICON = "icon"; // track activity type icon - String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" // table - + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " // id - + NAME + " TEXT, " // name - + DESCRIPTION + " TEXT, " // description - + CATEGORY + " TEXT, " // category - + STARTID + " INTEGER, " // start id - + STOPID + " INTEGER, " // stop id - + STARTTIME + " INTEGER, " // start time - + STOPTIME + " INTEGER, " // stop time - + NUMPOINTS + " INTEGER, " // num points - + TOTALDISTANCE + " FLOAT, " // total distance - + TOTALTIME + " INTEGER, " // total time - + MOVINGTIME + " INTEGER, " // moving time - + MINLAT + " INTEGER, " // min latitude - + MAXLAT + " INTEGER, " // max latitude - + MINLON + " INTEGER, " // min longitude - + MAXLON + " INTEGER, " // max longitude - + AVGSPEED + " FLOAT, " // average speed - + AVGMOVINGSPEED + " FLOAT, " // average moving speed - + MAXSPEED + " FLOAT, " // max speed - + MINELEVATION + " FLOAT, " // min elevation - + MAXELEVATION + " FLOAT, " // max elevation - + ELEVATIONGAIN + " FLOAT, " // elevation gain - + MINGRADE + " FLOAT, " // min grade - + MAXGRADE + " FLOAT, " // max grade - + ICON + " TEXT);"; // icon + String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" // table + + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " // id + + NAME + " TEXT, " // name + + DESCRIPTION + " TEXT, " // description + + CATEGORY + " TEXT, " // category + + STARTID + " INTEGER, " // start id + + STOPID + " INTEGER, " // stop id + + STARTTIME + " INTEGER, " // start time + + STOPTIME + " INTEGER, " // stop time + + NUMPOINTS + " INTEGER, " // num points + + TOTALDISTANCE + " FLOAT, " // total distance + + TOTALTIME + " INTEGER, " // total time + + MOVINGTIME + " INTEGER, " // moving time + + MINLAT + " INTEGER, " // min latitude + + MAXLAT + " INTEGER, " // max latitude + + MINLON + " INTEGER, " // min longitude + + MAXLON + " INTEGER, " // max longitude + + AVGSPEED + " FLOAT, " // average speed + + AVGMOVINGSPEED + " FLOAT, " // average moving speed + + MAXSPEED + " FLOAT, " // max speed + + MINELEVATION + " FLOAT, " // min elevation + + MAXELEVATION + " FLOAT, " // max elevation + + ELEVATIONGAIN + " FLOAT, " // elevation gain + + MINGRADE + " FLOAT, " // min grade + + MAXGRADE + " FLOAT, " // max grade + + ICON + " TEXT);"; // icon } diff --git a/src/main/java/com/google/android/apps/mytracks/content/Waypoint.java b/src/main/java/com/google/android/apps/mytracks/content/Waypoint.java index 14079856b..443d52936 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/Waypoint.java +++ b/src/main/java/com/google/android/apps/mytracks/content/Waypoint.java @@ -1,12 +1,12 @@ /* * Copyright 2009 Google Inc. - * + * * 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 @@ -25,234 +25,234 @@ import com.google.android.apps.mytracks.stats.TripStatistics; /** * A waypoint. - * + * * @author Leif Hendrik Wilden * @author Rodrigo Damazio */ public final class Waypoint implements Parcelable { - public enum WaypointType { - WAYPOINT, STATISTICS - } + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public Waypoint createFromParcel(Parcel in) { + return new Waypoint(in); + } - private long id = -1L; - private String name = ""; - private String description = ""; - private String category = ""; - private String icon = ""; - private long trackId = -1L; - private WaypointType type = WaypointType.WAYPOINT; - private double length = 0.0; - private long duration = 0; - private long startId = -1L; - private long stopId = -1L; - private Location location = null; - private TripStatistics tripStatistics = null; - private String photoUrl = ""; - - public Waypoint() {} - - public Waypoint(String name, String description, String category, String icon, long trackId, - WaypointType type, double length, long duration, long startId, long stopId, Location location, - TripStatistics tripStatistics, String photoUrl) { - this.name = name; - this.description = description; - this.category = category; - this.icon = icon; - this.trackId = trackId; - this.type = type; - this.length = length; - this.duration = duration; - this.startId = startId; - this.stopId = stopId; - this.location = location; - this.tripStatistics = tripStatistics; - this.photoUrl = photoUrl; - } + @Override + public Waypoint[] newArray(int size) { + return new Waypoint[size]; + } + }; + private long id = -1L; + private String name = ""; + private String description = ""; + private String category = ""; + private String icon = ""; + private long trackId = -1L; + private WaypointType type = WaypointType.WAYPOINT; + private double length = 0.0; + private long duration = 0; + private long startId = -1L; + private long stopId = -1L; + private Location location = null; + private TripStatistics tripStatistics = null; + private String photoUrl = ""; - private Waypoint(Parcel source) { - id = source.readLong(); - name = source.readString(); - description = source.readString(); - category = source.readString(); - icon = source.readString(); - trackId = source.readLong(); - type = WaypointType.values()[source.readInt()]; - length = source.readDouble(); - duration = source.readLong(); - startId = source.readLong(); - stopId = source.readLong(); - - ClassLoader classLoader = getClass().getClassLoader(); - byte hasLocation = source.readByte(); - if (hasLocation > 0) { - location = source.readParcelable(classLoader); - } - byte hasStats = source.readByte(); - if (hasStats > 0) { - tripStatistics = source.readParcelable(classLoader); - } - photoUrl = source.readString(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeLong(id); - dest.writeString(name); - dest.writeString(description); - dest.writeString(category); - dest.writeString(icon); - dest.writeLong(trackId); - dest.writeInt(type.ordinal()); - dest.writeDouble(length); - dest.writeLong(duration); - dest.writeLong(startId); - dest.writeLong(stopId); - dest.writeByte(location == null ? (byte) 0 : (byte) 1); - if (location != null) { - dest.writeParcelable(location, 0); - } - dest.writeByte(tripStatistics == null ? (byte) 0 : (byte) 1); - if (tripStatistics != null) { - dest.writeParcelable(tripStatistics, 0); - } - dest.writeString(photoUrl); - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - @Override - public Waypoint createFromParcel(Parcel in) { - return new Waypoint(in); + public Waypoint() { } - @Override - public Waypoint[] newArray(int size) { - return new Waypoint[size]; + public Waypoint(String name, String description, String category, String icon, long trackId, + WaypointType type, double length, long duration, long startId, long stopId, Location location, + TripStatistics tripStatistics, String photoUrl) { + this.name = name; + this.description = description; + this.category = category; + this.icon = icon; + this.trackId = trackId; + this.type = type; + this.length = length; + this.duration = duration; + this.startId = startId; + this.stopId = stopId; + this.location = location; + this.tripStatistics = tripStatistics; + this.photoUrl = photoUrl; } - }; - public long getId() { - return id; - } + private Waypoint(Parcel source) { + id = source.readLong(); + name = source.readString(); + description = source.readString(); + category = source.readString(); + icon = source.readString(); + trackId = source.readLong(); + type = WaypointType.values()[source.readInt()]; + length = source.readDouble(); + duration = source.readLong(); + startId = source.readLong(); + stopId = source.readLong(); - public void setId(long id) { - this.id = id; - } + ClassLoader classLoader = getClass().getClassLoader(); + byte hasLocation = source.readByte(); + if (hasLocation > 0) { + location = source.readParcelable(classLoader); + } + byte hasStats = source.readByte(); + if (hasStats > 0) { + tripStatistics = source.readParcelable(classLoader); + } + photoUrl = source.readString(); + } - public String getName() { - return name; - } + @Override + public int describeContents() { + return 0; + } - public void setName(String name) { - this.name = name; - } + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(id); + dest.writeString(name); + dest.writeString(description); + dest.writeString(category); + dest.writeString(icon); + dest.writeLong(trackId); + dest.writeInt(type.ordinal()); + dest.writeDouble(length); + dest.writeLong(duration); + dest.writeLong(startId); + dest.writeLong(stopId); + dest.writeByte(location == null ? (byte) 0 : (byte) 1); + if (location != null) { + dest.writeParcelable(location, 0); + } + dest.writeByte(tripStatistics == null ? (byte) 0 : (byte) 1); + if (tripStatistics != null) { + dest.writeParcelable(tripStatistics, 0); + } + dest.writeString(photoUrl); + } - public String getDescription() { - return description; - } + public long getId() { + return id; + } - public void setDescription(String description) { - this.description = description; - } + public void setId(long id) { + this.id = id; + } - public String getCategory() { - return category; - } + public String getName() { + return name; + } - public void setCategory(String category) { - this.category = category; - } + public void setName(String name) { + this.name = name; + } - public String getIcon() { - return icon; - } + public String getDescription() { + return description; + } - public void setIcon(String icon) { - this.icon = icon; - } + public void setDescription(String description) { + this.description = description; + } - public long getTrackId() { - return trackId; - } + public String getCategory() { + return category; + } - public void setTrackId(long trackId) { - this.trackId = trackId; - } + public void setCategory(String category) { + this.category = category; + } - public WaypointType getType() { - return type; - } + public String getIcon() { + return icon; + } - public void setType(WaypointType type) { - this.type = type; - } + public void setIcon(String icon) { + this.icon = icon; + } - public double getLength() { - return length; - } + public long getTrackId() { + return trackId; + } - public void setLength(double length) { - this.length = length; - } + public void setTrackId(long trackId) { + this.trackId = trackId; + } - public long getDuration() { - return duration; - } + public WaypointType getType() { + return type; + } - public void setDuration(long duration) { - this.duration = duration; - } + public void setType(WaypointType type) { + this.type = type; + } - public long getStartId() { - return startId; - } + public double getLength() { + return length; + } - public void setStartId(long startId) { - this.startId = startId; - } + public void setLength(double length) { + this.length = length; + } - public long getStopId() { - return stopId; - } + public long getDuration() { + return duration; + } - public void setStopId(long stopId) { - this.stopId = stopId; - } + public void setDuration(long duration) { + this.duration = duration; + } - public Location getLocation() { - return location; - } + public long getStartId() { + return startId; + } - public void setLocation(Location location) { - this.location = location; - } + public void setStartId(long startId) { + this.startId = startId; + } - public TripStatistics getTripStatistics() { - return tripStatistics; - } + public long getStopId() { + return stopId; + } - public void setTripStatistics(TripStatistics tripStatistics) { - this.tripStatistics = tripStatistics; - } - - public String getPhotoUrl() { - return photoUrl; - } + public void setStopId(long stopId) { + this.stopId = stopId; + } - public Uri getPhotoURI() { - return Uri.parse(photoUrl); - } + public Location getLocation() { + return location; + } - public boolean hasPhoto() { - return photoUrl != null && !"".equals(photoUrl); - } + public void setLocation(Location location) { + this.location = location; + } - public void setPhotoUrl(String photoUrl) { - this.photoUrl = photoUrl; - } + public TripStatistics getTripStatistics() { + return tripStatistics; + } + + public void setTripStatistics(TripStatistics tripStatistics) { + this.tripStatistics = tripStatistics; + } + + public String getPhotoUrl() { + return photoUrl; + } + + public void setPhotoUrl(String photoUrl) { + this.photoUrl = photoUrl; + } + + public Uri getPhotoURI() { + return Uri.parse(photoUrl); + } + + public boolean hasPhoto() { + return photoUrl != null && !"".equals(photoUrl); + } + + public enum WaypointType { + WAYPOINT, STATISTICS + } } diff --git a/src/main/java/com/google/android/apps/mytracks/content/WaypointCreationRequest.java b/src/main/java/com/google/android/apps/mytracks/content/WaypointCreationRequest.java index f0cc0d882..fd4397315 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/WaypointCreationRequest.java +++ b/src/main/java/com/google/android/apps/mytracks/content/WaypointCreationRequest.java @@ -1,12 +1,12 @@ /* * Copyright 2010 Google Inc. - * + * * 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 @@ -15,111 +15,109 @@ */ package com.google.android.apps.mytracks.content; -import com.google.android.apps.mytracks.content.Waypoint.WaypointType; - import android.os.Parcel; import android.os.Parcelable; +import com.google.android.apps.mytracks.content.Waypoint.WaypointType; + /** * A request for the service to create a waypoint at the current location. - * + * * @author Sandor Dornbush */ public class WaypointCreationRequest implements Parcelable { - private WaypointType type; - // true if this marker contains the track statistics - private boolean isTrackStatistics; - private String name; - private String category; - private String description; - private String iconUrl; - private String photoUrl; + public final static WaypointCreationRequest DEFAULT_WAYPOINT = new WaypointCreationRequest( + WaypointType.WAYPOINT, false); + public final static WaypointCreationRequest DEFAULT_STATISTICS = new WaypointCreationRequest( + WaypointType.STATISTICS, false); + public final static WaypointCreationRequest DEFAULT_START_TRACK = new WaypointCreationRequest( + WaypointType.STATISTICS, true); + public static final Creator CREATOR = new Creator(); + private WaypointType type; + // true if this marker contains the track statistics + private boolean isTrackStatistics; + private String name; + private String category; + private String description; + private String iconUrl; + private String photoUrl; - public final static WaypointCreationRequest DEFAULT_WAYPOINT = new WaypointCreationRequest( - WaypointType.WAYPOINT, false); - public final static WaypointCreationRequest DEFAULT_STATISTICS = new WaypointCreationRequest( - WaypointType.STATISTICS, false); - public final static WaypointCreationRequest DEFAULT_START_TRACK = new WaypointCreationRequest( - WaypointType.STATISTICS, true); + private WaypointCreationRequest(WaypointType type, boolean isTrackStatistics) { + this(type, isTrackStatistics, null, null, null, null, null); + } - private WaypointCreationRequest(WaypointType type, boolean isTrackStatistics) { - this(type, isTrackStatistics, null, null, null, null, null); - } - - public WaypointCreationRequest(WaypointType type, boolean isTrackStatistics, String name, - String category, String description, String iconUrl, String photoUrl) { - this.type = type; - this.isTrackStatistics = isTrackStatistics; - this.name = name; - this.category = category; - this.description = description; - this.iconUrl = iconUrl; - this.photoUrl = photoUrl; - } - - public static class Creator implements Parcelable.Creator { + public WaypointCreationRequest(WaypointType type, boolean isTrackStatistics, String name, + String category, String description, String iconUrl, String photoUrl) { + this.type = type; + this.isTrackStatistics = isTrackStatistics; + this.name = name; + this.category = category; + this.description = description; + this.iconUrl = iconUrl; + this.photoUrl = photoUrl; + } @Override - public WaypointCreationRequest createFromParcel(Parcel source) { - return new WaypointCreationRequest( - WaypointType.values()[source.readInt()], - source.readByte() == 1, - source.readString(), - source.readString(), - source.readString(), - source.readString(), - source.readString()); + public int describeContents() { + return 0; } - public WaypointCreationRequest[] newArray(int size) { - return new WaypointCreationRequest[size]; + @Override + public void writeToParcel(Parcel parcel, int arg1) { + parcel.writeInt(type.ordinal()); + parcel.writeByte((byte) (isTrackStatistics ? 1 : 0)); + parcel.writeString(name); + parcel.writeString(category); + parcel.writeString(description); + parcel.writeString(iconUrl); + parcel.writeString(photoUrl); } - } - public static final Creator CREATOR = new Creator(); + public WaypointType getType() { + return type; + } - @Override - public int describeContents() { - return 0; - } + public boolean isTrackStatistics() { + return isTrackStatistics; + } - @Override - public void writeToParcel(Parcel parcel, int arg1) { - parcel.writeInt(type.ordinal()); - parcel.writeByte((byte) (isTrackStatistics ? 1 : 0)); - parcel.writeString(name); - parcel.writeString(category); - parcel.writeString(description); - parcel.writeString(iconUrl); - parcel.writeString(photoUrl); - } + public String getName() { + return name; + } - public WaypointType getType() { - return type; - } + public String getCategory() { + return category; + } - public boolean isTrackStatistics() { - return isTrackStatistics; - } + public String getDescription() { + return description; + } - public String getName() { - return name; - } + public String getIconUrl() { + return iconUrl; + } - public String getCategory() { - return category; - } + public String getPhotoUrl() { + return photoUrl; + } - public String getDescription() { - return description; - } + public static class Creator implements Parcelable.Creator { - public String getIconUrl() { - return iconUrl; - } - - public String getPhotoUrl() { - return photoUrl; - } + @Override + public WaypointCreationRequest createFromParcel(Parcel source) { + return new WaypointCreationRequest( + WaypointType.values()[source.readInt()], + source.readByte() == 1, + source.readString(), + source.readString(), + source.readString(), + source.readString(), + source.readString()); + } + + public WaypointCreationRequest[] newArray(int size) { + return new WaypointCreationRequest[size]; + } + } } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/content/WaypointsColumns.java b/src/main/java/com/google/android/apps/mytracks/content/WaypointsColumns.java index 64e4490be..27f361c93 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/WaypointsColumns.java +++ b/src/main/java/com/google/android/apps/mytracks/content/WaypointsColumns.java @@ -1,12 +1,12 @@ /* * Copyright 2009 Google Inc. - * + * * 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 @@ -21,83 +21,83 @@ import android.provider.BaseColumns; /** * Constants for waypoints table. - * + * * @author Leif Hendrik Wilden */ public interface WaypointsColumns extends BaseColumns { - String TABLE_NAME = "waypoints"; - Uri CONTENT_URI = Uri.parse("content://com.google.android.maps.mytracks/waypoints"); - String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.google.waypoint"; - String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.google.waypoint"; - String DEFAULT_SORT_ORDER = "_id"; + String TABLE_NAME = "waypoints"; + Uri CONTENT_URI = Uri.parse("content://com.google.android.maps.mytracks/waypoints"); + String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.google.waypoint"; + String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.google.waypoint"; + String DEFAULT_SORT_ORDER = "_id"; - // Columns - String NAME = "name"; // waypoint name - String DESCRIPTION = "description"; // waypoint description - String CATEGORY = "category"; // waypoint category - String ICON = "icon"; // waypoint icon - String TRACKID = "trackid"; // track id - String TYPE = "type"; // type - String LENGTH = "length"; // length of the track (without smoothing) - String DURATION = "duration"; // total duration of the track (not from last waypoint) - String STARTTIME = "starttime"; // start time of the trip statistics - String STARTID = "startid"; // start track point id - String STOPID = "stopid"; // stop track point id + // Columns + String NAME = "name"; // waypoint name + String DESCRIPTION = "description"; // waypoint description + String CATEGORY = "category"; // waypoint category + String ICON = "icon"; // waypoint icon + String TRACKID = "trackid"; // track id + String TYPE = "type"; // type + String LENGTH = "length"; // length of the track (without smoothing) + String DURATION = "duration"; // total duration of the track (not from last waypoint) + String STARTTIME = "starttime"; // start time of the trip statistics + String STARTID = "startid"; // start track point id + String STOPID = "stopid"; // stop track point id - String LONGITUDE = "longitude"; // longitude - String LATITUDE = "latitude"; // latitude - String TIME = "time"; // time - String ALTITUDE = "elevation"; // altitude - String ACCURACY = "accuracy"; // accuracy - String SPEED = "speed"; // speed - String BEARING = "bearing"; // bearing + String LONGITUDE = "longitude"; // longitude + String LATITUDE = "latitude"; // latitude + String TIME = "time"; // time + String ALTITUDE = "elevation"; // altitude + String ACCURACY = "accuracy"; // accuracy + String SPEED = "speed"; // speed + String BEARING = "bearing"; // bearing - String TOTALDISTANCE = "totaldistance"; // total distance - String TOTALTIME = "totaltime"; // total time - String MOVINGTIME = "movingtime"; // moving time - String AVGSPEED = "avgspeed"; // average speed - String AVGMOVINGSPEED = "avgmovingspeed"; // average moving speed - String MAXSPEED = "maxspeed"; // max speed - String MINELEVATION = "minelevation"; // min elevation - String MAXELEVATION = "maxelevation"; // max elevation - String ELEVATIONGAIN = "elevationgain"; // elevation gain - String MINGRADE = "mingrade"; // min grade - String MAXGRADE = "maxgrade"; // max grade + String TOTALDISTANCE = "totaldistance"; // total distance + String TOTALTIME = "totaltime"; // total time + String MOVINGTIME = "movingtime"; // moving time + String AVGSPEED = "avgspeed"; // average speed + String AVGMOVINGSPEED = "avgmovingspeed"; // average moving speed + String MAXSPEED = "maxspeed"; // max speed + String MINELEVATION = "minelevation"; // min elevation + String MAXELEVATION = "maxelevation"; // max elevation + String ELEVATIONGAIN = "elevationgain"; // elevation gain + String MINGRADE = "mingrade"; // min grade + String MAXGRADE = "maxgrade"; // max grade - String PHOTOURL = "photoUrl"; // url for the photo - - String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" - + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + NAME + " TEXT, " - + DESCRIPTION + " TEXT, " - + CATEGORY + " TEXT, " - + ICON + " TEXT, " - + TRACKID + " INTEGER, " - + TYPE + " INTEGER, " - + LENGTH + " FLOAT, " - + DURATION + " INTEGER, " - + STARTTIME + " INTEGER, " - + STARTID + " INTEGER, " - + STOPID + " INTEGER, " - + LONGITUDE + " INTEGER, " - + LATITUDE + " INTEGER, " - + TIME + " INTEGER, " - + ALTITUDE + " FLOAT, " - + ACCURACY + " FLOAT, " - + SPEED + " FLOAT, " - + BEARING + " FLOAT, " - + TOTALDISTANCE + " FLOAT, " - + TOTALTIME + " INTEGER, " - + MOVINGTIME + " INTEGER, " - + AVGSPEED + " FLOAT, " - + AVGMOVINGSPEED + " FLOAT, " - + MAXSPEED + " FLOAT, " - + MINELEVATION + " FLOAT, " - + MAXELEVATION + " FLOAT, " - + ELEVATIONGAIN + " FLOAT, " - + MINGRADE + " FLOAT, " - + MAXGRADE + " FLOAT, " - + PHOTOURL + " TEXT" - + ");"; + String PHOTOURL = "photoUrl"; // url for the photo + + String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + NAME + " TEXT, " + + DESCRIPTION + " TEXT, " + + CATEGORY + " TEXT, " + + ICON + " TEXT, " + + TRACKID + " INTEGER, " + + TYPE + " INTEGER, " + + LENGTH + " FLOAT, " + + DURATION + " INTEGER, " + + STARTTIME + " INTEGER, " + + STARTID + " INTEGER, " + + STOPID + " INTEGER, " + + LONGITUDE + " INTEGER, " + + LATITUDE + " INTEGER, " + + TIME + " INTEGER, " + + ALTITUDE + " FLOAT, " + + ACCURACY + " FLOAT, " + + SPEED + " FLOAT, " + + BEARING + " FLOAT, " + + TOTALDISTANCE + " FLOAT, " + + TOTALTIME + " INTEGER, " + + MOVINGTIME + " INTEGER, " + + AVGSPEED + " FLOAT, " + + AVGMOVINGSPEED + " FLOAT, " + + MAXSPEED + " FLOAT, " + + MINELEVATION + " FLOAT, " + + MAXELEVATION + " FLOAT, " + + ELEVATIONGAIN + " FLOAT, " + + MINGRADE + " FLOAT, " + + MAXGRADE + " FLOAT, " + + PHOTOURL + " TEXT" + + ");"; } diff --git a/src/main/java/com/google/android/apps/mytracks/content/sensor/SensorDataSet.java b/src/main/java/com/google/android/apps/mytracks/content/sensor/SensorDataSet.java index ddc3b257e..285fb5bd7 100644 --- a/src/main/java/com/google/android/apps/mytracks/content/sensor/SensorDataSet.java +++ b/src/main/java/com/google/android/apps/mytracks/content/sensor/SensorDataSet.java @@ -5,16 +5,14 @@ public final class SensorDataSet { public static final float DATA_UNAVAILABLE = Integer.MAX_VALUE; //TODO It might be necessary to consider: sensor if sensor was connected as well. - + public String sensorName; + public String sensorAddress; private float heartRate; private float cadence; private float power; private float batteryLevel; private long creationTimestamp; - public String sensorName; - public String sensorAddress; - public SensorDataSet(float heartRate, float cadence, float power, float batteryLevel, long creationTimestamp) { this.heartRate = heartRate; this.cadence = cadence; @@ -71,6 +69,7 @@ public final class SensorDataSet { /** * Is the data recent considering the current time. + * * @param maxAge the maximal age in milliseconds. */ public boolean isRecent(long maxAge) { diff --git a/src/main/java/com/google/android/apps/mytracks/fragments/AbstractDialogFragment.java b/src/main/java/com/google/android/apps/mytracks/fragments/AbstractDialogFragment.java index f01f7de71..4e72b5873 100644 --- a/src/main/java/com/google/android/apps/mytracks/fragments/AbstractDialogFragment.java +++ b/src/main/java/com/google/android/apps/mytracks/fragments/AbstractDialogFragment.java @@ -16,33 +16,33 @@ package com.google.android.apps.mytracks.fragments; -import com.google.android.apps.mytracks.util.DialogUtils; - import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; + import androidx.annotation.NonNull; import androidx.fragment.app.DialogFragment; -/** +import com.google.android.apps.mytracks.util.DialogUtils; +/** * @author Jimmy Shih */ public abstract class AbstractDialogFragment extends DialogFragment { - @NonNull - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Dialog dialog = createDialog(); - dialog.setOnShowListener(new DialogInterface.OnShowListener() { + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Dialog dialog = createDialog(); + dialog.setOnShowListener(new DialogInterface.OnShowListener() { - @Override - public void onShow(DialogInterface dialogInterface) { - DialogUtils.setDialogTitleDivider(getActivity(), dialog); - } - }); - return dialog; - } + @Override + public void onShow(DialogInterface dialogInterface) { + DialogUtils.setDialogTitleDivider(getActivity(), dialog); + } + }); + return dialog; + } - protected abstract Dialog createDialog(); + protected abstract Dialog createDialog(); } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/fragments/ChartFragment.java b/src/main/java/com/google/android/apps/mytracks/fragments/ChartFragment.java index b2e7b14ab..bb6b0cc68 100644 --- a/src/main/java/com/google/android/apps/mytracks/fragments/ChartFragment.java +++ b/src/main/java/com/google/android/apps/mytracks/fragments/ChartFragment.java @@ -50,472 +50,472 @@ import java.util.EnumSet; /** * A fragment to display track chart to the user. - * + * * @author Sandor Dornbush * @author Rodrigo Damazio */ public class ChartFragment extends Fragment implements TrackDataListener { - public static final String CHART_FRAGMENT_TAG = "chartFragment"; + public static final String CHART_FRAGMENT_TAG = "chartFragment"; - private final ArrayList pendingPoints = new ArrayList<>(); + private final ArrayList pendingPoints = new ArrayList<>(); - private TrackDataHub trackDataHub; + private TrackDataHub trackDataHub; - // Stats gathered from the received data - private TripStatisticsUpdater tripStatisticsUpdater; - private long startTime; + // Stats gathered from the received data + private TripStatisticsUpdater tripStatisticsUpdater; + private long startTime; - private boolean metricUnits = true; - private boolean reportSpeed = true; - private int recordingDistanceInterval = PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT; + private boolean metricUnits = true; + private boolean reportSpeed = true; + private int recordingDistanceInterval = PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT; - // Modes of operation - private boolean chartByDistance = true; - private boolean[] chartShow = new boolean[] { true, true, true, true, true, true }; + // Modes of operation + private boolean chartByDistance = true; + private boolean[] chartShow = new boolean[]{true, true, true, true, true, true}; - // UI elements - private ChartView chartView; - private ZoomControls zoomControls; + // UI elements + private ChartView chartView; + private ZoomControls zoomControls; - /** - * A runnable that will enable/disable zoom controls and orange pointer as - * appropriate and redraw. - */ - private final Runnable updateChart = new Runnable() { - @Override - public void run() { - if (!isResumed() || trackDataHub == null) { - return; - } - - zoomControls.setIsZoomInEnabled(chartView.canZoomIn()); - zoomControls.setIsZoomOutEnabled(chartView.canZoomOut()); - chartView.setShowPointer(isSelectedTrackRecording()); - chartView.invalidate(); - } - }; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - /* - * Create a chartView here to store data thus won't need to reload all the - * data on every onStart or onResume. + /** + * A runnable that will enable/disable zoom controls and orange pointer as + * appropriate and redraw. */ - chartView = new ChartView(getActivity()); - } - - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.chart, container, false); - zoomControls = view.findViewById(R.id.chart_zoom_controls); - zoomControls.setOnZoomInClickListener(new View.OnClickListener() { + private final Runnable updateChart = new Runnable() { @Override - public void onClick(View v) { - zoomIn(); - } - }); - zoomControls.setOnZoomOutClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - zoomOut(); - } - }); - return view; - } - - @Override - public void onStart() { - super.onStart(); - ViewGroup layout = getActivity().findViewById(R.id.chart_view_layout); - LayoutParams layoutParams = new LayoutParams( - LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); - layout.addView(chartView, layoutParams); - } - - @Override - public void onResume() { - super.onResume(); - resumeTrackDataHub(); - checkChartSettings(); - getActivity().runOnUiThread(updateChart); - } - - @Override - public void onPause() { - super.onPause(); - pauseTrackDataHub(); - } - - @Override - public void onStop() { - super.onStop(); - ViewGroup layout = getActivity().findViewById(R.id.chart_view_layout); - layout.removeView(chartView); - } - - @Override - public void onTrackUpdated(Track track) { - if (isResumed()) { - if (track == null || track.getTripStatistics() == null) { - startTime = -1L; - return; - } - startTime = track.getTripStatistics().getStartTime(); - } - } - - @Override - public void clearTrackPoints() { - if (isResumed()) { - tripStatisticsUpdater = startTime != -1L ? new TripStatisticsUpdater(startTime) : null; - pendingPoints.clear(); - chartView.reset(); - runOnUiThread(new Runnable() { - @Override public void run() { - if (isResumed()) { - chartView.resetScroll(); - } + if (!isResumed() || trackDataHub == null) { + return; + } + + zoomControls.setIsZoomInEnabled(chartView.canZoomIn()); + zoomControls.setIsZoomOutEnabled(chartView.canZoomOut()); + chartView.setShowPointer(isSelectedTrackRecording()); + chartView.invalidate(); } - }); - } - } + }; - @Override - public void onSampledInTrackPoint(Location location) { - if (isResumed()) { - double[] data = new double[ChartView.NUM_SERIES + 1]; - fillDataPoint(location, data); - pendingPoints.add(data); - } - } + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - @Override - public void onSampledOutTrackPoint(Location location) { - if (isResumed()) { - fillDataPoint(location, null); + /* + * Create a chartView here to store data thus won't need to reload all the + * data on every onStart or onResume. + */ + chartView = new ChartView(getActivity()); } - } - @Override - public void onSegmentSplit(Location location) { - if (isResumed()) { - fillDataPoint(location, null); + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.chart, container, false); + zoomControls = view.findViewById(R.id.chart_zoom_controls); + zoomControls.setOnZoomInClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + zoomIn(); + } + }); + zoomControls.setOnZoomOutClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + zoomOut(); + } + }); + return view; } - } - @Override - public void onNewTrackPointsDone() { - if (isResumed()) { - chartView.addDataPoints(pendingPoints); - pendingPoints.clear(); - runOnUiThread(updateChart); + @Override + public void onStart() { + super.onStart(); + ViewGroup layout = getActivity().findViewById(R.id.chart_view_layout); + LayoutParams layoutParams = new LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + layout.addView(chartView, layoutParams); } - } - @Override - public void clearWaypoints() { - if (isResumed()) { - chartView.clearWaypoints(); + @Override + public void onResume() { + super.onResume(); + resumeTrackDataHub(); + checkChartSettings(); + getActivity().runOnUiThread(updateChart); } - } - @Override - public void onNewWaypoint(Waypoint waypoint) { - if (isResumed() && waypoint != null && LocationUtils.isValidLocation(waypoint.getLocation())) { - chartView.addWaypoint(waypoint); + @Override + public void onPause() { + super.onPause(); + pauseTrackDataHub(); } - } - @Override - public void onNewWaypointsDone() { - if (isResumed()) { - runOnUiThread(updateChart); + @Override + public void onStop() { + super.onStop(); + ViewGroup layout = getActivity().findViewById(R.id.chart_view_layout); + layout.removeView(chartView); } - } - @Override - public boolean onMetricUnitsChanged(boolean metric) { - if (isResumed()) { - if (metricUnits == metric) { + @Override + public void onTrackUpdated(Track track) { + if (isResumed()) { + if (track == null || track.getTripStatistics() == null) { + startTime = -1L; + return; + } + startTime = track.getTripStatistics().getStartTime(); + } + } + + @Override + public void clearTrackPoints() { + if (isResumed()) { + tripStatisticsUpdater = startTime != -1L ? new TripStatisticsUpdater(startTime) : null; + pendingPoints.clear(); + chartView.reset(); + runOnUiThread(new Runnable() { + @Override + public void run() { + if (isResumed()) { + chartView.resetScroll(); + } + } + }); + } + } + + @Override + public void onSampledInTrackPoint(Location location) { + if (isResumed()) { + double[] data = new double[ChartView.NUM_SERIES + 1]; + fillDataPoint(location, data); + pendingPoints.add(data); + } + } + + @Override + public void onSampledOutTrackPoint(Location location) { + if (isResumed()) { + fillDataPoint(location, null); + } + } + + @Override + public void onSegmentSplit(Location location) { + if (isResumed()) { + fillDataPoint(location, null); + } + } + + @Override + public void onNewTrackPointsDone() { + if (isResumed()) { + chartView.addDataPoints(pendingPoints); + pendingPoints.clear(); + runOnUiThread(updateChart); + } + } + + @Override + public void clearWaypoints() { + if (isResumed()) { + chartView.clearWaypoints(); + } + } + + @Override + public void onNewWaypoint(Waypoint waypoint) { + if (isResumed() && waypoint != null && LocationUtils.isValidLocation(waypoint.getLocation())) { + chartView.addWaypoint(waypoint); + } + } + + @Override + public void onNewWaypointsDone() { + if (isResumed()) { + runOnUiThread(updateChart); + } + } + + @Override + public boolean onMetricUnitsChanged(boolean metric) { + if (isResumed()) { + if (metricUnits == metric) { + return false; + } + metricUnits = metric; + chartView.setMetricUnits(metricUnits); + runOnUiThread(new Runnable() { + @Override + public void run() { + if (isResumed()) { + chartView.requestLayout(); + } + } + }); + return true; + } return false; - } - metricUnits = metric; - chartView.setMetricUnits(metricUnits); - runOnUiThread(new Runnable() { - @Override - public void run() { - if (isResumed()) { - chartView.requestLayout(); - } - } - }); - return true; } - return false; - } - @Override - public boolean onReportSpeedChanged(boolean speed) { - if (isResumed()) { - if (reportSpeed == speed) { + @Override + public boolean onReportSpeedChanged(boolean speed) { + if (isResumed()) { + if (reportSpeed == speed) { + return false; + } + reportSpeed = speed; + chartView.setReportSpeed(reportSpeed); + boolean chartShowSpeed = PreferencesUtils.getBoolean( + getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT); + setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed); + setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed); + runOnUiThread(new Runnable() { + @Override + public void run() { + if (isResumed()) { + chartView.requestLayout(); + } + } + }); + return true; + } return false; - } - reportSpeed = speed; - chartView.setReportSpeed(reportSpeed); - boolean chartShowSpeed = PreferencesUtils.getBoolean( - getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT); - setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed); - setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed); - runOnUiThread(new Runnable() { - @Override - public void run() { - if (isResumed()) { - chartView.requestLayout(); - } - } - }); - return true; } - return false; - } - @Override - public boolean onRecordingGpsAccuracy(int minRequiredAccuracy) { - // We don't care. - return false; - } - - @Override - public boolean onRecordingDistanceIntervalChanged(int value) { - if (isResumed()) { - if (recordingDistanceInterval == value) { + @Override + public boolean onRecordingGpsAccuracy(int minRequiredAccuracy) { + // We don't care. return false; - } - recordingDistanceInterval = value; - return true; - } - return false; - } - - /** - * Checks the chart settings. - */ - private void checkChartSettings() { - boolean needUpdate = false; - if (chartByDistance != PreferencesUtils.isChartByDistance(getActivity())) { - chartByDistance = !chartByDistance; - chartView.setChartByDistance(chartByDistance); - reloadTrackDataHub(); - needUpdate = true; - } - if (setSeriesEnabled(ChartView.ELEVATION_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_elevation_key, PreferencesUtils.CHART_SHOW_ELEVATION_DEFAULT))) { - needUpdate = true; } - boolean chartShowSpeed = PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT); - if (setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed)) { - needUpdate = true; - } - if (setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed)) { - needUpdate = true; - } - if (setSeriesEnabled(ChartView.POWER_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_power_key, PreferencesUtils.CHART_SHOW_POWER_DEFAULT))) { - needUpdate = true; - } - if (setSeriesEnabled(ChartView.CADENCE_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_cadence_key, PreferencesUtils.CHART_SHOW_CADENCE_DEFAULT))) { - needUpdate = true; - } - if (setSeriesEnabled(ChartView.HEART_RATE_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_heart_rate_key, PreferencesUtils.CHART_SHOW_HEART_RATE_DEFAULT))) { - needUpdate = true; - } - if (needUpdate) { - chartView.postInvalidate(); - } - } - - /** - * Sets the series enabled value. - * - * @param index the series index - * @param value the value - * @return true if changed - */ - private boolean setSeriesEnabled(int index, boolean value) { - if (chartShow[index] != value) { - chartShow[index] = value; - chartView.setChartValueSeriesEnabled(index, value); - return true; - } else { - return false; - } - } - - /** - * Resumes the trackDataHub. Needs to be synchronized because trackDataHub can - * be accessed by multiple threads. - */ - private synchronized void resumeTrackDataHub() { - trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub(); - trackDataHub.registerTrackDataListener(this, EnumSet.of(TrackDataType.TRACKS_TABLE, - TrackDataType.WAYPOINTS_TABLE, TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE, - TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE, TrackDataType.PREFERENCE)); - } - - /** - * Pauses the trackDataHub. Needs to be synchronized because trackDataHub can - * be accessed by multiple threads. - */ - private synchronized void pauseTrackDataHub() { - trackDataHub.unregisterTrackDataListener(this); - trackDataHub = null; - } - - /** - * Returns true if the selected track is recording. Needs to be synchronized - * because trackDataHub can be accessed by multiple threads. - */ - private synchronized boolean isSelectedTrackRecording() { - return trackDataHub != null && trackDataHub.isSelectedTrackRecording(); - } - - /** - * Reloads the trackDataHub. Needs to be synchronized because trackDataHub can - * be accessed by multiple threads. - */ - private synchronized void reloadTrackDataHub() { - if (trackDataHub != null) { - trackDataHub.reloadDataForListener(this); - } - } - - /** - * To zoom in. - */ - private void zoomIn() { - chartView.zoomIn(); - zoomControls.setIsZoomInEnabled(chartView.canZoomIn()); - zoomControls.setIsZoomOutEnabled(chartView.canZoomOut()); - } - - /** - * To zoom out. - */ - private void zoomOut() { - chartView.zoomOut(); - zoomControls.setIsZoomInEnabled(chartView.canZoomIn()); - zoomControls.setIsZoomOutEnabled(chartView.canZoomOut()); - } - - /** - * Runs a runnable on the UI thread if possible. - * - * @param runnable the runnable - */ - private void runOnUiThread(Runnable runnable) { - FragmentActivity fragmentActivity = getActivity(); - if (fragmentActivity != null) { - fragmentActivity.runOnUiThread(runnable); - } - } - - /** - * Given a location, fill in a data point, an array of double[].
- * data[0] = time/distance
- * data[1] = elevation
- * data[2] = speed
- * data[3] = pace
- * data[4] = heart rate
- * data[5] = cadence
- * data[6] = power
- * - * @param location the location - * @param data the data point to fill in, can be null - */ - @VisibleForTesting - void fillDataPoint(Location location, double[] data) { - double timeOrDistance = Double.NaN; - double elevation = Double.NaN; - double speed = Double.NaN; - double pace = Double.NaN; - double heartRate = Double.NaN; - double cadence = Double.NaN; - double power = Double.NaN; - - if (tripStatisticsUpdater != null) { - tripStatisticsUpdater.addLocation(location, recordingDistanceInterval); - TripStatistics tripStatistics = tripStatisticsUpdater.getTripStatistics(); - if (chartByDistance) { - double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM; - if (!metricUnits) { - distance *= UnitConversions.KM_TO_MI; + @Override + public boolean onRecordingDistanceIntervalChanged(int value) { + if (isResumed()) { + if (recordingDistanceInterval == value) { + return false; + } + recordingDistanceInterval = value; + return true; } - timeOrDistance = distance; - } else { - timeOrDistance = tripStatistics.getTotalTime(); - } - - elevation = tripStatisticsUpdater.getSmoothedElevation(); - if (!metricUnits) { - elevation *= UnitConversions.M_TO_FT; - } - - speed = tripStatisticsUpdater.getSmoothedSpeed() * UnitConversions.MS_TO_KMH; - if (!metricUnits) { - speed *= UnitConversions.KM_TO_MI; - } - pace = speed == 0 ? 0.0 : 60.0 / speed; - } - if (location instanceof SensorDataSetLocation && ((SensorDataSetLocation) location).getSensorDataSet() != null) { - SensorDataSet sensorDataSet = ((SensorDataSetLocation) location).getSensorDataSet(); - if (sensorDataSet.hasHeartRate()) { - heartRate = sensorDataSet.getHeartRate(); - } - if (sensorDataSet.hasCadence()) { - cadence = sensorDataSet.getCadence(); - } - if (sensorDataSet.hasPower()) { - power = sensorDataSet.getPower(); - } + return false; } - if (data != null) { - data[0] = timeOrDistance; - data[1] = elevation; - data[2] = speed; - data[3] = pace; - data[4] = heartRate; - data[5] = cadence; - data[6] = power; + /** + * Checks the chart settings. + */ + private void checkChartSettings() { + boolean needUpdate = false; + if (chartByDistance != PreferencesUtils.isChartByDistance(getActivity())) { + chartByDistance = !chartByDistance; + chartView.setChartByDistance(chartByDistance); + reloadTrackDataHub(); + needUpdate = true; + } + if (setSeriesEnabled(ChartView.ELEVATION_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_elevation_key, PreferencesUtils.CHART_SHOW_ELEVATION_DEFAULT))) { + needUpdate = true; + } + + boolean chartShowSpeed = PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT); + if (setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed)) { + needUpdate = true; + } + if (setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed)) { + needUpdate = true; + } + if (setSeriesEnabled(ChartView.POWER_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_power_key, PreferencesUtils.CHART_SHOW_POWER_DEFAULT))) { + needUpdate = true; + } + if (setSeriesEnabled(ChartView.CADENCE_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_cadence_key, PreferencesUtils.CHART_SHOW_CADENCE_DEFAULT))) { + needUpdate = true; + } + if (setSeriesEnabled(ChartView.HEART_RATE_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_heart_rate_key, PreferencesUtils.CHART_SHOW_HEART_RATE_DEFAULT))) { + needUpdate = true; + } + if (needUpdate) { + chartView.postInvalidate(); + } } - } - @VisibleForTesting - void setTripStatisticsUpdater(long time) { - tripStatisticsUpdater = new TripStatisticsUpdater(time); - } + /** + * Sets the series enabled value. + * + * @param index the series index + * @param value the value + * @return true if changed + */ + private boolean setSeriesEnabled(int index, boolean value) { + if (chartShow[index] != value) { + chartShow[index] = value; + chartView.setChartValueSeriesEnabled(index, value); + return true; + } else { + return false; + } + } - @VisibleForTesting - void setChartView(ChartView view) { - chartView = view; - } + /** + * Resumes the trackDataHub. Needs to be synchronized because trackDataHub can + * be accessed by multiple threads. + */ + private synchronized void resumeTrackDataHub() { + trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub(); + trackDataHub.registerTrackDataListener(this, EnumSet.of(TrackDataType.TRACKS_TABLE, + TrackDataType.WAYPOINTS_TABLE, TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE, + TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE, TrackDataType.PREFERENCE)); + } - @VisibleForTesting - void setMetricUnits(boolean value) { - metricUnits = value; - } + /** + * Pauses the trackDataHub. Needs to be synchronized because trackDataHub can + * be accessed by multiple threads. + */ + private synchronized void pauseTrackDataHub() { + trackDataHub.unregisterTrackDataListener(this); + trackDataHub = null; + } - @VisibleForTesting - void setReportSpeed(boolean value) { - reportSpeed = value; - } + /** + * Returns true if the selected track is recording. Needs to be synchronized + * because trackDataHub can be accessed by multiple threads. + */ + private synchronized boolean isSelectedTrackRecording() { + return trackDataHub != null && trackDataHub.isSelectedTrackRecording(); + } - @VisibleForTesting - void setChartByDistance(boolean value) { - chartByDistance = value; - } + /** + * Reloads the trackDataHub. Needs to be synchronized because trackDataHub can + * be accessed by multiple threads. + */ + private synchronized void reloadTrackDataHub() { + if (trackDataHub != null) { + trackDataHub.reloadDataForListener(this); + } + } + + /** + * To zoom in. + */ + private void zoomIn() { + chartView.zoomIn(); + zoomControls.setIsZoomInEnabled(chartView.canZoomIn()); + zoomControls.setIsZoomOutEnabled(chartView.canZoomOut()); + } + + /** + * To zoom out. + */ + private void zoomOut() { + chartView.zoomOut(); + zoomControls.setIsZoomInEnabled(chartView.canZoomIn()); + zoomControls.setIsZoomOutEnabled(chartView.canZoomOut()); + } + + /** + * Runs a runnable on the UI thread if possible. + * + * @param runnable the runnable + */ + private void runOnUiThread(Runnable runnable) { + FragmentActivity fragmentActivity = getActivity(); + if (fragmentActivity != null) { + fragmentActivity.runOnUiThread(runnable); + } + } + + /** + * Given a location, fill in a data point, an array of double[].
+ * data[0] = time/distance
+ * data[1] = elevation
+ * data[2] = speed
+ * data[3] = pace
+ * data[4] = heart rate
+ * data[5] = cadence
+ * data[6] = power
+ * + * @param location the location + * @param data the data point to fill in, can be null + */ + @VisibleForTesting + void fillDataPoint(Location location, double[] data) { + double timeOrDistance = Double.NaN; + double elevation = Double.NaN; + double speed = Double.NaN; + double pace = Double.NaN; + double heartRate = Double.NaN; + double cadence = Double.NaN; + double power = Double.NaN; + + if (tripStatisticsUpdater != null) { + tripStatisticsUpdater.addLocation(location, recordingDistanceInterval); + TripStatistics tripStatistics = tripStatisticsUpdater.getTripStatistics(); + if (chartByDistance) { + double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM; + if (!metricUnits) { + distance *= UnitConversions.KM_TO_MI; + } + timeOrDistance = distance; + } else { + timeOrDistance = tripStatistics.getTotalTime(); + } + + elevation = tripStatisticsUpdater.getSmoothedElevation(); + if (!metricUnits) { + elevation *= UnitConversions.M_TO_FT; + } + + speed = tripStatisticsUpdater.getSmoothedSpeed() * UnitConversions.MS_TO_KMH; + if (!metricUnits) { + speed *= UnitConversions.KM_TO_MI; + } + pace = speed == 0 ? 0.0 : 60.0 / speed; + } + if (location instanceof SensorDataSetLocation && ((SensorDataSetLocation) location).getSensorDataSet() != null) { + SensorDataSet sensorDataSet = ((SensorDataSetLocation) location).getSensorDataSet(); + if (sensorDataSet.hasHeartRate()) { + heartRate = sensorDataSet.getHeartRate(); + } + if (sensorDataSet.hasCadence()) { + cadence = sensorDataSet.getCadence(); + } + if (sensorDataSet.hasPower()) { + power = sensorDataSet.getPower(); + } + } + + if (data != null) { + data[0] = timeOrDistance; + data[1] = elevation; + data[2] = speed; + data[3] = pace; + data[4] = heartRate; + data[5] = cadence; + data[6] = power; + } + } + + @VisibleForTesting + void setTripStatisticsUpdater(long time) { + tripStatisticsUpdater = new TripStatisticsUpdater(time); + } + + @VisibleForTesting + void setChartView(ChartView view) { + chartView = view; + } + + @VisibleForTesting + void setMetricUnits(boolean value) { + metricUnits = value; + } + + @VisibleForTesting + void setReportSpeed(boolean value) { + reportSpeed = value; + } + + @VisibleForTesting + void setChartByDistance(boolean value) { + chartByDistance = value; + } } diff --git a/src/main/java/com/google/android/apps/mytracks/fragments/ChooseActivityTypeDialogFragment.java b/src/main/java/com/google/android/apps/mytracks/fragments/ChooseActivityTypeDialogFragment.java index 52732fdc0..cffe2d876 100644 --- a/src/main/java/com/google/android/apps/mytracks/fragments/ChooseActivityTypeDialogFragment.java +++ b/src/main/java/com/google/android/apps/mytracks/fragments/ChooseActivityTypeDialogFragment.java @@ -41,117 +41,115 @@ import java.util.List; /** * A DialogFragment to choose an activity type. - * + * * @author apoorvn */ public class ChooseActivityTypeDialogFragment extends DialogFragment { - /** - * Interface for caller of this dialog fragment. - * - * @author apoorvn - */ - public interface ChooseActivityTypeCaller { + public static final String CHOOSE_ACTIVITY_TYPE_DIALOG_TAG = "chooseActivityType"; + private static final String KEY_CATEGORY = "category"; + private ChooseActivityTypeCaller caller; + + public static ChooseActivityTypeDialogFragment newInstance(String category) { + Bundle bundle = new Bundle(); + bundle.putString(KEY_CATEGORY, category); + + ChooseActivityTypeDialogFragment fragment = new ChooseActivityTypeDialogFragment(); + fragment.setArguments(bundle); + return fragment; + } + + public static Dialog getDialog(final Activity activity, final String category, final ChooseActivityTypeCaller caller) { + View view = activity.getLayoutInflater().inflate(R.layout.choose_activity_type, null); + GridView gridView = view.findViewById(R.id.choose_activity_type_grid_view); + + List imageIds = new ArrayList<>(); + for (String iconValue : TrackIconUtils.getAllIconValues()) { + imageIds.add(TrackIconUtils.getIconDrawable(iconValue)); + } + + Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeResource(activity.getResources(), R.drawable.ic_track_airplane, options); + int padding = 32; + int width = options.outWidth + 2 * padding; + int height = options.outHeight + 2 * padding; + gridView.setColumnWidth(width); + + final ChooseActivityTypeImageAdapter imageAdapter = new ChooseActivityTypeImageAdapter(activity, imageIds, width, height, padding); + gridView.setAdapter(imageAdapter); + + final AlertDialog alertDialog = new AlertDialog.Builder(activity).setTitle(R.string.track_edit_activity_type_hint).setView(view).create(); + alertDialog.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialog) { + int position = getPosition(activity, category); + if (position != -1) { + imageAdapter.setSelected(position); + imageAdapter.notifyDataSetChanged(); + } + DialogUtils.setDialogTitleDivider(activity, alertDialog); + } + }); + + gridView.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View v, int position, long id) { + caller.onChooseActivityTypeDone(TrackIconUtils.getAllIconValues().get(position)); + alertDialog.dismiss(); + } + }); + return alertDialog; + } + + private static int getPosition(Activity activity, String category) { + if (category == null) { + return -1; + } + String iconValue = TrackIconUtils.getIconValue(activity, category); + if (iconValue.equals("")) { + return -1; + } + List iconValues = TrackIconUtils.getAllIconValues(); + for (int i = 0; i < iconValues.size(); i++) { + if (iconValues.get(i).equals(iconValue)) { + return i; + } + } + return -1; + } + + private static void updateWeightContainer(View weightContainer, int position) { + boolean showWeight = position == 0 || position == 1 || position == 2; + weightContainer.setVisibility(showWeight ? View.VISIBLE : View.GONE); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + try { + caller = (ChooseActivityTypeCaller) context; + } catch (ClassCastException e) { + throw new ClassCastException(context + " must implement " + ChooseActivityTypeCaller.class.getSimpleName()); + } + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return getDialog(getActivity(), getArguments().getString(KEY_CATEGORY), caller); + } /** - * Called when choose activity type is done. + * Interface for caller of this dialog fragment. + * + * @author apoorvn */ - void onChooseActivityTypeDone(String iconValue); - } + public interface ChooseActivityTypeCaller { - public static final String CHOOSE_ACTIVITY_TYPE_DIALOG_TAG = "chooseActivityType"; - - private static final String KEY_CATEGORY = "category"; - - private ChooseActivityTypeCaller caller; - - public static ChooseActivityTypeDialogFragment newInstance(String category) { - Bundle bundle = new Bundle(); - bundle.putString(KEY_CATEGORY, category); - - ChooseActivityTypeDialogFragment fragment = new ChooseActivityTypeDialogFragment(); - fragment.setArguments(bundle); - return fragment; - } - - @Override - public void onAttach(Context context) { - super.onAttach(context); - try { - caller = (ChooseActivityTypeCaller) context; - } catch (ClassCastException e) { - throw new ClassCastException(context + " must implement " + ChooseActivityTypeCaller.class.getSimpleName()); + /** + * Called when choose activity type is done. + */ + void onChooseActivityTypeDone(String iconValue); } - } - - @NonNull - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - return getDialog(getActivity(), getArguments().getString(KEY_CATEGORY), caller); - } - - public static Dialog getDialog(final Activity activity, final String category, final ChooseActivityTypeCaller caller) { - View view = activity.getLayoutInflater().inflate(R.layout.choose_activity_type, null); - GridView gridView = view.findViewById(R.id.choose_activity_type_grid_view); - - List imageIds = new ArrayList<>(); - for (String iconValue : TrackIconUtils.getAllIconValues()) { - imageIds.add(TrackIconUtils.getIconDrawable(iconValue)); - } - - Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeResource(activity.getResources(), R.drawable.ic_track_airplane, options); - int padding = 32; - int width = options.outWidth + 2 * padding; - int height = options.outHeight + 2 * padding; - gridView.setColumnWidth(width); - - final ChooseActivityTypeImageAdapter imageAdapter = new ChooseActivityTypeImageAdapter(activity, imageIds, width, height, padding); - gridView.setAdapter(imageAdapter); - - final AlertDialog alertDialog = new AlertDialog.Builder(activity).setTitle(R.string.track_edit_activity_type_hint).setView(view).create(); - alertDialog.setOnShowListener(new DialogInterface.OnShowListener() { - @Override - public void onShow(DialogInterface dialog) { - int position = getPosition(activity, category); - if (position != -1) { - imageAdapter.setSelected(position); - imageAdapter.notifyDataSetChanged(); - } - DialogUtils.setDialogTitleDivider(activity, alertDialog); - } - }); - - gridView.setOnItemClickListener(new OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View v, int position, long id) { - caller.onChooseActivityTypeDone(TrackIconUtils.getAllIconValues().get(position)); - alertDialog.dismiss(); - } - }); - return alertDialog; - } - - private static int getPosition(Activity activity, String category) { - if (category == null) { - return -1; - } - String iconValue = TrackIconUtils.getIconValue(activity, category); - if (iconValue.equals("")) { - return -1; - } - List iconValues = TrackIconUtils.getAllIconValues(); - for (int i = 0; i < iconValues.size(); i++) { - if (iconValues.get(i).equals(iconValue)) { - return i; - } - } - return -1; - } - - private static void updateWeightContainer(View weightContainer, int position) { - boolean showWeight = position == 0 || position == 1 || position == 2; - weightContainer.setVisibility(showWeight ? View.VISIBLE : View.GONE); - } } diff --git a/src/main/java/com/google/android/apps/mytracks/fragments/ChooseActivityTypeImageAdapter.java b/src/main/java/com/google/android/apps/mytracks/fragments/ChooseActivityTypeImageAdapter.java index 33a1942c0..874e0c1cf 100644 --- a/src/main/java/com/google/android/apps/mytracks/fragments/ChooseActivityTypeImageAdapter.java +++ b/src/main/java/com/google/android/apps/mytracks/fragments/ChooseActivityTypeImageAdapter.java @@ -16,8 +16,6 @@ package com.google.android.apps.mytracks.fragments; -import com.google.android.maps.mytracks.R; - import android.content.Context; import android.graphics.Color; import android.view.View; @@ -25,6 +23,8 @@ import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; +import com.google.android.maps.mytracks.R; + import java.util.List; /** @@ -34,62 +34,62 @@ import java.util.List; */ public class ChooseActivityTypeImageAdapter extends BaseAdapter { - private final Context context; - private final List imageIds; - private final int width; - private final int height; - private final int padding; - private int selected = -1; + private final Context context; + private final List imageIds; + private final int width; + private final int height; + private final int padding; + private int selected = -1; - public ChooseActivityTypeImageAdapter( - Context context, List imageIds, int width, int height, int padding) { - this.context = context; - this.imageIds = imageIds; - this.width = width; - this.height = height; - this.padding = padding; - } - - @Override - public int getCount() { - return imageIds.size(); - } - - @Override - public Object getItem(int position) { - return null; - } - - @Override - public long getItemId(int position) { - return 0; - } - - public int getSelected() { - return selected; - } - - public void setSelected(int position) { - selected = position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - ImageView imageView; - if (convertView == null) { - imageView = new ImageView(context); - } else { - imageView = (ImageView) convertView; + public ChooseActivityTypeImageAdapter( + Context context, List imageIds, int width, int height, int padding) { + this.context = context; + this.imageIds = imageIds; + this.width = width; + this.height = height; + this.padding = padding; } - if (position == selected) { - imageView.setBackgroundResource(R.drawable.list_selector_background_transition_holo_dark); - } else { - imageView.setBackgroundColor(Color.TRANSPARENT); + + @Override + public int getCount() { + return imageIds.size(); + } + + @Override + public Object getItem(int position) { + return null; + } + + @Override + public long getItemId(int position) { + return 0; + } + + public int getSelected() { + return selected; + } + + public void setSelected(int position) { + selected = position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ImageView imageView; + if (convertView == null) { + imageView = new ImageView(context); + } else { + imageView = (ImageView) convertView; + } + if (position == selected) { + imageView.setBackgroundResource(R.drawable.list_selector_background_transition_holo_dark); + } else { + imageView.setBackgroundColor(Color.TRANSPARENT); + } + imageView.setImageResource(imageIds.get(position)); + imageView.setMinimumHeight(height); + imageView.setMinimumWidth(width); + imageView.setPadding(padding, padding, padding, padding); + return imageView; } - imageView.setImageResource(imageIds.get(position)); - imageView.setMinimumHeight(height); - imageView.setMinimumWidth(width); - imageView.setPadding(padding, padding, padding, padding); - return imageView; - } } diff --git a/src/main/java/com/google/android/apps/mytracks/fragments/ConfirmDeleteDialogFragment.java b/src/main/java/com/google/android/apps/mytracks/fragments/ConfirmDeleteDialogFragment.java index e63587346..b2a8f68eb 100644 --- a/src/main/java/com/google/android/apps/mytracks/fragments/ConfirmDeleteDialogFragment.java +++ b/src/main/java/com/google/android/apps/mytracks/fragments/ConfirmDeleteDialogFragment.java @@ -26,72 +26,71 @@ import com.google.android.maps.mytracks.R; /** * A DialogFragment to confirm delete. - * + * * @author Jimmy Shih */ public class ConfirmDeleteDialogFragment extends AbstractDialogFragment { - /** - * Interface for caller of this dialog fragment. - * - * @author Jimmy Shih - */ - public interface ConfirmDeleteCaller { + public static final String CONFIRM_DELETE_DIALOG_TAG = "confirmDeleteDialog"; + private static final String KEY_TRACK_IDS = "trackIds"; + private ConfirmDeleteCaller caller; /** - * Called when confirm delete is done. - * + * Create a new instance. + * * @param trackIds list of track ids to delete. To delete all, set to size 1 - * with trackIds[0] == -1L + * with trackIds[0] == -1L */ - void onConfirmDeleteDone(long[] trackIds); - } + public static ConfirmDeleteDialogFragment newInstance(long[] trackIds) { + Bundle bundle = new Bundle(); + bundle.putLongArray(KEY_TRACK_IDS, trackIds); - public static final String CONFIRM_DELETE_DIALOG_TAG = "confirmDeleteDialog"; - private static final String KEY_TRACK_IDS = "trackIds"; - - /** - * Create a new instance. - * - * @param trackIds list of track ids to delete. To delete all, set to size 1 - * with trackIds[0] == -1L - */ - public static ConfirmDeleteDialogFragment newInstance(long[] trackIds) { - Bundle bundle = new Bundle(); - bundle.putLongArray(KEY_TRACK_IDS, trackIds); - - ConfirmDeleteDialogFragment deleteTrackDialogFragment = new ConfirmDeleteDialogFragment(); - deleteTrackDialogFragment.setArguments(bundle); - return deleteTrackDialogFragment; - } - - private ConfirmDeleteCaller caller; - - @Override - public void onAttach(Context context) { - super.onAttach(context); - try { - caller = (ConfirmDeleteCaller) context; - } catch (ClassCastException e) { - throw new ClassCastException(context + " must implement " + ConfirmDeleteCaller.class.getSimpleName()); + ConfirmDeleteDialogFragment deleteTrackDialogFragment = new ConfirmDeleteDialogFragment(); + deleteTrackDialogFragment.setArguments(bundle); + return deleteTrackDialogFragment; } - } - @Override - protected Dialog createDialog() { - final long[] trackIds = getArguments().getLongArray(KEY_TRACK_IDS); - int titleId; - int messageId; - titleId = trackIds.length > 1 ? R.string.generic_delete_selected_confirm_title - : R.string.track_delete_one_confirm_title; - messageId = trackIds.length > 1 ? R.string.track_delete_multiple_confirm_message - : R.string.track_delete_one_confirm_message; - return DialogUtils.createConfirmationDialog( - getActivity(), titleId, getString(messageId), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - caller.onConfirmDeleteDone(trackIds); - } - }); - } + @Override + public void onAttach(Context context) { + super.onAttach(context); + try { + caller = (ConfirmDeleteCaller) context; + } catch (ClassCastException e) { + throw new ClassCastException(context + " must implement " + ConfirmDeleteCaller.class.getSimpleName()); + } + } + + @Override + protected Dialog createDialog() { + final long[] trackIds = getArguments().getLongArray(KEY_TRACK_IDS); + int titleId; + int messageId; + titleId = trackIds.length > 1 ? R.string.generic_delete_selected_confirm_title + : R.string.track_delete_one_confirm_title; + messageId = trackIds.length > 1 ? R.string.track_delete_multiple_confirm_message + : R.string.track_delete_one_confirm_message; + return DialogUtils.createConfirmationDialog( + getActivity(), titleId, getString(messageId), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + caller.onConfirmDeleteDone(trackIds); + } + }); + } + + /** + * Interface for caller of this dialog fragment. + * + * @author Jimmy Shih + */ + public interface ConfirmDeleteCaller { + + /** + * Called when confirm delete is done. + * + * @param trackIds list of track ids to delete. To delete all, set to size 1 + * with trackIds[0] == -1L + */ + void onConfirmDeleteDone(long[] trackIds); + } } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/fragments/DeleteMarkerDialogFragment.java b/src/main/java/com/google/android/apps/mytracks/fragments/DeleteMarkerDialogFragment.java index c8c11d437..79bf41428 100644 --- a/src/main/java/com/google/android/apps/mytracks/fragments/DeleteMarkerDialogFragment.java +++ b/src/main/java/com/google/android/apps/mytracks/fragments/DeleteMarkerDialogFragment.java @@ -23,87 +23,86 @@ import android.os.Bundle; import androidx.fragment.app.FragmentActivity; -import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl; import com.google.android.apps.mytracks.content.ContentProviderUtils; +import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl; import com.google.android.apps.mytracks.util.DialogUtils; import com.google.android.maps.mytracks.R; /** * A DialogFragment to delete marker. - * + * * @author Jimmy Shih */ public class DeleteMarkerDialogFragment extends AbstractDialogFragment { - /** - * Interface for caller of this dialog fragment. - * - * @author Jimmy Shih - */ - public interface DeleteMarkerCaller { + public static final String DELETE_MARKER_DIALOG_TAG = "deleteMarkerDialog"; + private static final String KEY_MARKER_IDS = "markerIds"; + private DeleteMarkerCaller caller; + + public static DeleteMarkerDialogFragment newInstance(long[] markerIds) { + Bundle bundle = new Bundle(); + bundle.putLongArray(KEY_MARKER_IDS, markerIds); + + DeleteMarkerDialogFragment deleteMarkerDialogFragment = new DeleteMarkerDialogFragment(); + deleteMarkerDialogFragment.setArguments(bundle); + return deleteMarkerDialogFragment; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + try { + caller = (DeleteMarkerCaller) context; + } catch (ClassCastException e) { + throw new ClassCastException(context + " must implement " + DeleteMarkerCaller.class.getSimpleName()); + } + } + + @Override + protected Dialog createDialog() { + final FragmentActivity fragmentActivity = getActivity(); + final long[] markerIds = getArguments().getLongArray(KEY_MARKER_IDS); + int titleId; + int messageId; + if (markerIds.length == 1 && markerIds[0] == -1L) { + titleId = R.string.generic_delete_all_confirm_title; + messageId = R.string.marker_delete_all_confirm_message; + } else { + titleId = markerIds.length > 1 ? R.string.generic_delete_selected_confirm_title + : R.string.marker_delete_one_confirm_title; + messageId = markerIds.length > 1 ? R.string.marker_delete_multiple_confirm_message + : R.string.marker_delete_one_confirm_message; + } + return DialogUtils.createConfirmationDialog( + fragmentActivity, titleId, getString(messageId), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + new Thread(new Runnable() { + @Override + public void run() { + ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get( + fragmentActivity); + for (long markerId : markerIds) { + contentProviderUtils.deleteWaypoint(fragmentActivity, + markerId, new DescriptionGeneratorImpl(fragmentActivity)); + } + caller.onDeleteMarkerDone(); + } + }).start(); + } + }); + } /** - * Called when delete marker is done. + * Interface for caller of this dialog fragment. + * + * @author Jimmy Shih */ - void onDeleteMarkerDone(); - } + public interface DeleteMarkerCaller { - public static final String DELETE_MARKER_DIALOG_TAG = "deleteMarkerDialog"; - private static final String KEY_MARKER_IDS = "markerIds"; - - public static DeleteMarkerDialogFragment newInstance(long[] markerIds) { - Bundle bundle = new Bundle(); - bundle.putLongArray(KEY_MARKER_IDS, markerIds); - - DeleteMarkerDialogFragment deleteMarkerDialogFragment = new DeleteMarkerDialogFragment(); - deleteMarkerDialogFragment.setArguments(bundle); - return deleteMarkerDialogFragment; - } - - private DeleteMarkerCaller caller; - - @Override - public void onAttach(Context context) { - super.onAttach(context); - try { - caller = (DeleteMarkerCaller) context; - } catch (ClassCastException e) { - throw new ClassCastException(context + " must implement " + DeleteMarkerCaller.class.getSimpleName()); + /** + * Called when delete marker is done. + */ + void onDeleteMarkerDone(); } - } - - @Override - protected Dialog createDialog() { - final FragmentActivity fragmentActivity = getActivity(); - final long[] markerIds = getArguments().getLongArray(KEY_MARKER_IDS); - int titleId; - int messageId; - if (markerIds.length == 1 && markerIds[0] == -1L) { - titleId = R.string.generic_delete_all_confirm_title; - messageId = R.string.marker_delete_all_confirm_message; - } else { - titleId = markerIds.length > 1 ? R.string.generic_delete_selected_confirm_title - : R.string.marker_delete_one_confirm_title; - messageId = markerIds.length > 1 ? R.string.marker_delete_multiple_confirm_message - : R.string.marker_delete_one_confirm_message; - } - return DialogUtils.createConfirmationDialog( - fragmentActivity, titleId, getString(messageId), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - new Thread(new Runnable() { - @Override - public void run() { - ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get( - fragmentActivity); - for (long markerId : markerIds) { - contentProviderUtils.deleteWaypoint(fragmentActivity, - markerId, new DescriptionGeneratorImpl(fragmentActivity)); - } - caller.onDeleteMarkerDone(); - } - }).start(); - } - }); - } } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/fragments/FileTypeDialogFragment.java b/src/main/java/com/google/android/apps/mytracks/fragments/FileTypeDialogFragment.java index 6766633c6..f1d0bb5b1 100644 --- a/src/main/java/com/google/android/apps/mytracks/fragments/FileTypeDialogFragment.java +++ b/src/main/java/com/google/android/apps/mytracks/fragments/FileTypeDialogFragment.java @@ -29,72 +29,70 @@ import com.google.android.maps.mytracks.R; /** * A DialogFragment to select a file type, gpx, kml, etc. - * + * * @author Jimmy Shih */ public class FileTypeDialogFragment extends AbstractDialogFragment { - /** - * Interface for caller of this dialog fragment. - * - * @author Jimmy Shih - */ - public interface FileTypeCaller { + public static final String FILE_TYPE_DIALOG_TAG = "fileType"; + private static final String KEY_MENU_ID = "menuId"; + private static final String KEY_TITLE_ID = "titleId"; + private static final String KEY_OPTION_ID = "optionId"; + private FileTypeCaller caller; + + public static FileTypeDialogFragment newInstance(int menuId, int titleId, int optionId) { + Bundle bundle = new Bundle(); + bundle.putInt(KEY_MENU_ID, menuId); + bundle.putInt(KEY_TITLE_ID, titleId); + bundle.putInt(KEY_OPTION_ID, optionId); + + FileTypeDialogFragment fileTypeDialogFragment = new FileTypeDialogFragment(); + fileTypeDialogFragment.setArguments(bundle); + return fileTypeDialogFragment; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + try { + caller = (FileTypeCaller) context; + } catch (ClassCastException e) { + throw new ClassCastException(context + " must implement " + FileTypeCaller.class.getSimpleName()); + } + } + + @Override + protected Dialog createDialog() { + int optionId = getArguments().getInt(KEY_OPTION_ID); + final int titleId = getArguments().getInt(KEY_TITLE_ID); + final int menuId = getArguments().getInt(KEY_MENU_ID); + TrackFileFormat[] trackFileFormats = TrackFileFormat.values(); + String[] choices = new String[trackFileFormats.length]; + for (int i = 0; i < choices.length; i++) { + TrackFileFormat trackFileFormat = trackFileFormats[i]; + choices[i] = getString(optionId, trackFileFormat.name(), + FileUtils.getPathDisplayName(trackFileFormat.getExtension())); + } + return new AlertDialog.Builder(getActivity()).setNegativeButton(R.string.generic_cancel, null) + .setPositiveButton(R.string.generic_ok, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + int position = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); + caller.onFileTypeDone(menuId, TrackFileFormat.values()[position]); + } + }).setSingleChoiceItems(choices, 0, null).setTitle(titleId).create(); + } /** - * Called when file type selection is done. + * Interface for caller of this dialog fragment. + * + * @author Jimmy Shih */ - void onFileTypeDone(int menuId, TrackFileFormat trackFileFormat); - } + public interface FileTypeCaller { - public static final String FILE_TYPE_DIALOG_TAG = "fileType"; - - private static final String KEY_MENU_ID = "menuId"; - private static final String KEY_TITLE_ID = "titleId"; - private static final String KEY_OPTION_ID = "optionId"; - - public static FileTypeDialogFragment newInstance(int menuId, int titleId, int optionId) { - Bundle bundle = new Bundle(); - bundle.putInt(KEY_MENU_ID, menuId); - bundle.putInt(KEY_TITLE_ID, titleId); - bundle.putInt(KEY_OPTION_ID, optionId); - - FileTypeDialogFragment fileTypeDialogFragment = new FileTypeDialogFragment(); - fileTypeDialogFragment.setArguments(bundle); - return fileTypeDialogFragment; - } - - private FileTypeCaller caller; - - @Override - public void onAttach(Context context) { - super.onAttach(context); - try { - caller = (FileTypeCaller) context; - } catch (ClassCastException e) { - throw new ClassCastException(context + " must implement " + FileTypeCaller.class.getSimpleName()); + /** + * Called when file type selection is done. + */ + void onFileTypeDone(int menuId, TrackFileFormat trackFileFormat); } - } - - @Override - protected Dialog createDialog() { - int optionId = getArguments().getInt(KEY_OPTION_ID); - final int titleId = getArguments().getInt(KEY_TITLE_ID); - final int menuId = getArguments().getInt(KEY_MENU_ID); - TrackFileFormat[] trackFileFormats = TrackFileFormat.values(); - String[] choices = new String[trackFileFormats.length]; - for (int i = 0; i < choices.length; i++) { - TrackFileFormat trackFileFormat = trackFileFormats[i]; - choices[i] = getString(optionId, trackFileFormat.name(), - FileUtils.getPathDisplayName(trackFileFormat.getExtension())); - } - return new AlertDialog.Builder(getActivity()).setNegativeButton(R.string.generic_cancel, null) - .setPositiveButton(R.string.generic_ok, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - int position = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); - caller.onFileTypeDone(menuId, TrackFileFormat.values()[position]); - } - }).setSingleChoiceItems(choices, 0, null).setTitle(titleId).create(); - } } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/fragments/FrequencyDialogFragment.java b/src/main/java/com/google/android/apps/mytracks/fragments/FrequencyDialogFragment.java index 8cefb527a..53ca2281c 100644 --- a/src/main/java/com/google/android/apps/mytracks/fragments/FrequencyDialogFragment.java +++ b/src/main/java/com/google/android/apps/mytracks/fragments/FrequencyDialogFragment.java @@ -16,91 +16,92 @@ package com.google.android.apps.mytracks.fragments; -import com.google.android.apps.mytracks.util.PreferencesUtils; -import com.google.android.apps.mytracks.util.StringUtils; -import com.google.android.maps.mytracks.R; - import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; + import androidx.fragment.app.FragmentActivity; +import com.google.android.apps.mytracks.util.PreferencesUtils; +import com.google.android.apps.mytracks.util.StringUtils; +import com.google.android.maps.mytracks.R; + /** * A DialogFragment to configure frequency. - * + * * @author Jimmy Shih */ public class FrequencyDialogFragment extends AbstractDialogFragment { - public static final String FREQUENCY_DIALOG_TAG = "frequencyDialog"; + public static final String FREQUENCY_DIALOG_TAG = "frequencyDialog"; - private static final String KEY_PREFERENCE_ID = "preferenceId"; - private static final String KEY_DEFAULT_VALUE = "defaultValue"; - private static final String KEY_TITLE_ID = "titleId"; + private static final String KEY_PREFERENCE_ID = "preferenceId"; + private static final String KEY_DEFAULT_VALUE = "defaultValue"; + private static final String KEY_TITLE_ID = "titleId"; - public static FrequencyDialogFragment newInstance( - int preferenceId, int defaultValue, int titleId) { - Bundle bundle = new Bundle(); - bundle.putInt(KEY_PREFERENCE_ID, preferenceId); - bundle.putInt(KEY_DEFAULT_VALUE, defaultValue); - bundle.putInt(KEY_TITLE_ID, titleId); + public static FrequencyDialogFragment newInstance( + int preferenceId, int defaultValue, int titleId) { + Bundle bundle = new Bundle(); + bundle.putInt(KEY_PREFERENCE_ID, preferenceId); + bundle.putInt(KEY_DEFAULT_VALUE, defaultValue); + bundle.putInt(KEY_TITLE_ID, titleId); - FrequencyDialogFragment frequencyDialogFragment = new FrequencyDialogFragment(); - frequencyDialogFragment.setArguments(bundle); - return frequencyDialogFragment; - } - - @Override - protected Dialog createDialog() { - FragmentActivity fragmentActivity = getActivity(); - - final int preferenceId = getArguments().getInt(KEY_PREFERENCE_ID); - int defaultValue = getArguments().getInt(KEY_DEFAULT_VALUE); - int titleId = getArguments().getInt(KEY_TITLE_ID); - int frequencyValue = PreferencesUtils.getInt(fragmentActivity, preferenceId, defaultValue); - - return new AlertDialog.Builder(fragmentActivity).setPositiveButton( - R.string.generic_ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - int listIndex = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); - PreferencesUtils.setInt(getActivity(), preferenceId, getFrequencyValue(listIndex)); - } - }).setSingleChoiceItems( - getFrequencyDisplayOptions(fragmentActivity), getListIndex(frequencyValue), null) - .setTitle(titleId).create(); - } - - /** - * Gets the frequency display options. - */ - private String[] getFrequencyDisplayOptions(FragmentActivity fragmentActivity) { - boolean metricUnits = PreferencesUtils.isMetricUnits(fragmentActivity); - return StringUtils.getFrequencyOptions(fragmentActivity, metricUnits); - } - - /** - * Gets the list index for a frequency value. Returns 0 if the value is not on - * the list. - */ - private int getListIndex(int frequencyValue) { - String[] values = getResources().getStringArray(R.array.frequency_values); - for (int i = 0; i < values.length; i++) { - if (frequencyValue == Integer.parseInt(values[i])) { - return i; - } + FrequencyDialogFragment frequencyDialogFragment = new FrequencyDialogFragment(); + frequencyDialogFragment.setArguments(bundle); + return frequencyDialogFragment; } - return 0; - } - /** - * Gets the frequency value from a list index. - * - * @param listIndex the list index - */ - private int getFrequencyValue(int listIndex) { - String[] values = getResources().getStringArray(R.array.frequency_values); - return Integer.parseInt(values[listIndex]); - } + @Override + protected Dialog createDialog() { + FragmentActivity fragmentActivity = getActivity(); + + final int preferenceId = getArguments().getInt(KEY_PREFERENCE_ID); + int defaultValue = getArguments().getInt(KEY_DEFAULT_VALUE); + int titleId = getArguments().getInt(KEY_TITLE_ID); + int frequencyValue = PreferencesUtils.getInt(fragmentActivity, preferenceId, defaultValue); + + return new AlertDialog.Builder(fragmentActivity).setPositiveButton( + R.string.generic_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + int listIndex = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); + PreferencesUtils.setInt(getActivity(), preferenceId, getFrequencyValue(listIndex)); + } + }).setSingleChoiceItems( + getFrequencyDisplayOptions(fragmentActivity), getListIndex(frequencyValue), null) + .setTitle(titleId).create(); + } + + /** + * Gets the frequency display options. + */ + private String[] getFrequencyDisplayOptions(FragmentActivity fragmentActivity) { + boolean metricUnits = PreferencesUtils.isMetricUnits(fragmentActivity); + return StringUtils.getFrequencyOptions(fragmentActivity, metricUnits); + } + + /** + * Gets the list index for a frequency value. Returns 0 if the value is not on + * the list. + */ + private int getListIndex(int frequencyValue) { + String[] values = getResources().getStringArray(R.array.frequency_values); + for (int i = 0; i < values.length; i++) { + if (frequencyValue == Integer.parseInt(values[i])) { + return i; + } + } + return 0; + } + + /** + * Gets the frequency value from a list index. + * + * @param listIndex the list index + */ + private int getFrequencyValue(int listIndex) { + String[] values = getResources().getStringArray(R.array.frequency_values); + return Integer.parseInt(values[listIndex]); + } } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/fragments/MarkerDetailFragment.java b/src/main/java/com/google/android/apps/mytracks/fragments/MarkerDetailFragment.java index 68e14bbdc..a3de73a5d 100644 --- a/src/main/java/com/google/android/apps/mytracks/fragments/MarkerDetailFragment.java +++ b/src/main/java/com/google/android/apps/mytracks/fragments/MarkerDetailFragment.java @@ -52,236 +52,236 @@ import com.google.android.maps.mytracks.R; /** * A fragment to show marker details. - * + * * @author Jimmy Shih */ public class MarkerDetailFragment extends Fragment { - public static MarkerDetailFragment newInstance(long markerId, String title) { - Bundle bundle = new Bundle(); - bundle.putLong(KEY_MARKER_ID, markerId); - bundle.putString(KEY_TITLE, title); - - MarkerDetailFragment fragment = new MarkerDetailFragment(); - fragment.setArguments(bundle); - return fragment; - } - - private static final String TAG = MarkerDetailFragment.class.getSimpleName(); - private static final String KEY_MARKER_ID = "markerId"; - private static final String KEY_TITLE = "title"; - private static final long HIDE_TEXT_DELAY = 4000L; // 4 seconds - - private ContentProviderUtils contentProviderUtils; - private Handler handler; - private ImageView photo; - private ImageView textGradient; - private LinearLayout waypointInfo; - private Waypoint waypoint; - - private Runnable hideText = new Runnable() { - @Override - public void run() { - Animation animation = AnimationUtils.loadAnimation(getActivity(), R.anim.fadeout); - animation.setAnimationListener(new AnimationListener() { - - @Override - public void onAnimationStart(Animation anim) {} - - @Override - public void onAnimationRepeat(Animation anim) {} - - @Override - public void onAnimationEnd(Animation anim) { - textGradient.setVisibility(View.GONE); - waypointInfo.setVisibility(View.GONE); - } - }); - textGradient.startAnimation(animation); - waypointInfo.startAnimation(animation); - } - }; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - long markerId = getArguments().getLong(KEY_MARKER_ID); - if (markerId == -1L) { - Log.d(TAG, "invalid marker id"); - getFragmentManager().popBackStack(); - return; - } - contentProviderUtils = ContentProviderUtils.Factory.get(getActivity()); - handler = new Handler(); - setHasOptionsMenu(true); - } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.marker_detail_fragment, container, false); - - getActivity().setTitle(getArguments().getString(KEY_TITLE)); - - photo = view.findViewById(R.id.marker_detail_waypoint_photo); - textGradient = view.findViewById(R.id.marker_detail_waypoint_text_gradient); - waypointInfo = view.findViewById(R.id.marker_detail_waypoint_info); - - photo.setOnClickListener(new View.OnClickListener() { + private static final String TAG = MarkerDetailFragment.class.getSimpleName(); + private static final String KEY_MARKER_ID = "markerId"; + private static final String KEY_TITLE = "title"; + private static final long HIDE_TEXT_DELAY = 4000L; // 4 seconds + private ContentProviderUtils contentProviderUtils; + private Handler handler; + private ImageView photo; + private ImageView textGradient; + private LinearLayout waypointInfo; + private Waypoint waypoint; + private Runnable hideText = new Runnable() { @Override - public void onClick(View v) { - handler.removeCallbacks(hideText); - int visibility = waypointInfo.getVisibility() == View.GONE ? View.VISIBLE : View.GONE; - textGradient.setVisibility(visibility); - waypointInfo.setVisibility(visibility); - if (visibility == View.VISIBLE) { - handler.postDelayed(hideText, HIDE_TEXT_DELAY); + public void run() { + Animation animation = AnimationUtils.loadAnimation(getActivity(), R.anim.fadeout); + animation.setAnimationListener(new AnimationListener() { + + @Override + public void onAnimationStart(Animation anim) { + } + + @Override + public void onAnimationRepeat(Animation anim) { + } + + @Override + public void onAnimationEnd(Animation anim) { + textGradient.setVisibility(View.GONE); + waypointInfo.setVisibility(View.GONE); + } + }); + textGradient.startAnimation(animation); + waypointInfo.startAnimation(animation); } - } - }); - return view; - } + }; + + public static MarkerDetailFragment newInstance(long markerId, String title) { + Bundle bundle = new Bundle(); + bundle.putLong(KEY_MARKER_ID, markerId); + bundle.putString(KEY_TITLE, title); + + MarkerDetailFragment fragment = new MarkerDetailFragment(); + fragment.setArguments(bundle); + return fragment; + } @Override - public void onResume() { - super.onResume(); + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - // Need to update the waypoint in case returning after an edit - updateWaypoint(true); - updateUi(); - } + long markerId = getArguments().getLong(KEY_MARKER_ID); + if (markerId == -1L) { + Log.d(TAG, "invalid marker id"); + getFragmentManager().popBackStack(); + return; + } + contentProviderUtils = ContentProviderUtils.Factory.get(getActivity()); + handler = new Handler(); + setHasOptionsMenu(true); + } - @Override - public void onPause() { - super.onPause(); - handler.removeCallbacks(hideText); - } + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.marker_detail_fragment, container, false); - @Override - public void setMenuVisibility(boolean menuVisible) { - super.setMenuVisibility(menuVisible); - /* - * View pager caches the neighboring fragments in the resumed state. If - * becoming visible from the resumed state, update the UI to display the - * text above the image. - */ - if (isResumed()) { - if (menuVisible) { + getActivity().setTitle(getArguments().getString(KEY_TITLE)); + + photo = view.findViewById(R.id.marker_detail_waypoint_photo); + textGradient = view.findViewById(R.id.marker_detail_waypoint_text_gradient); + waypointInfo = view.findViewById(R.id.marker_detail_waypoint_info); + + photo.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + handler.removeCallbacks(hideText); + int visibility = waypointInfo.getVisibility() == View.GONE ? View.VISIBLE : View.GONE; + textGradient.setVisibility(visibility); + waypointInfo.setVisibility(visibility); + if (visibility == View.VISIBLE) { + handler.postDelayed(hideText, HIDE_TEXT_DELAY); + } + } + }); + return view; + } + + @Override + public void onResume() { + super.onResume(); + + // Need to update the waypoint in case returning after an edit + updateWaypoint(true); updateUi(); - } else { + } + + @Override + public void onPause() { + super.onPause(); handler.removeCallbacks(hideText); - } } - } - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.marker_detail, menu); - - updateWaypoint(false); - - TrackIconUtils.setMenuIconColor(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - long markerId = getArguments().getLong(KEY_MARKER_ID); - FragmentActivity fragmentActivity = getActivity(); - Intent intent; - switch (item.getItemId()) { - case R.id.marker_detail_show_on_map: - intent = IntentUtils.newShowOnMapIntent(waypoint); - startActivity(intent); - return true; - case R.id.marker_detail_edit: - intent = IntentUtils.newIntent(fragmentActivity, MarkerEditActivity.class) - .putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerId); - startActivity(intent); - return true; - case R.id.marker_detail_delete: - DeleteMarkerDialogFragment.newInstance(new long[] { markerId }) - .show(getChildFragmentManager(), DeleteMarkerDialogFragment.DELETE_MARKER_DIALOG_TAG); - return true; - default: - return super.onOptionsItemSelected(item); + @Override + public void setMenuVisibility(boolean menuVisible) { + super.setMenuVisibility(menuVisible); + /* + * View pager caches the neighboring fragments in the resumed state. If + * becoming visible from the resumed state, update the UI to display the + * text above the image. + */ + if (isResumed()) { + if (menuVisible) { + updateUi(); + } else { + handler.removeCallbacks(hideText); + } + } } - } - /** - * Updates the waypoint. - * - * @param refresh true to always update - */ - private void updateWaypoint(boolean refresh) { - if (refresh || waypoint == null) { - waypoint = contentProviderUtils.getWaypoint(getArguments().getLong(KEY_MARKER_ID)); - if (waypoint == null) { - Log.d(TAG, "waypoint is null"); - getFragmentManager().popBackStack(); - } + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.marker_detail, menu); + + updateWaypoint(false); + + TrackIconUtils.setMenuIconColor(menu); } - } - /** - * Updates the UI. - */ - private void updateUi() { - View waypointView = getView().findViewById(R.id.marker_detail_waypoint); - View statisticsView = getView().findViewById(R.id.marker_detail_statistics); - - boolean isWaypoint = waypoint.getType() == WaypointType.WAYPOINT; - waypointView.setVisibility(isWaypoint ? View.VISIBLE : View.GONE); - statisticsView.setVisibility(isWaypoint ? View.GONE : View.VISIBLE); - - if (isWaypoint) { - boolean hasPhoto = waypoint.hasPhoto(); - photo.setVisibility(hasPhoto ? View.VISIBLE : View.GONE); - textGradient.setVisibility(hasPhoto ? View.VISIBLE : View.GONE); - waypointInfo.setVisibility(View.VISIBLE); - - if (hasPhoto) { - handler.removeCallbacks(hideText); - photo.setImageURI(waypoint.getPhotoURI()); - handler.postDelayed(hideText, HIDE_TEXT_DELAY); - } - - setName(R.id.marker_detail_waypoint_name, hasPhoto); - - TextView category = getView().findViewById(R.id.marker_detail_waypoint_category); - ListItemUtils.setTextView(getActivity(), category, StringUtils.getCategory(waypoint.getCategory()), hasPhoto); - - TextView description = getView().findViewById(R.id.marker_detail_waypoint_description); - ListItemUtils.setTextView(getActivity(), description, waypoint.getDescription(), hasPhoto); - - setLocation(R.id.marker_detail_waypoint_location, hasPhoto); - } else { - setName(R.id.marker_detail_statistics_name, false); - - setLocation(R.id.marker_detail_statistics_location, false); - - Track track = contentProviderUtils.getTrack(waypoint.getTrackId()); - StatsUtils.setTripStatisticsValues(getActivity(), null, getView(), waypoint.getTripStatistics(), null); - StatsUtils.setLocationValues(getActivity(), null, getView(), waypoint.getLocation(), false); + @Override + public boolean onOptionsItemSelected(MenuItem item) { + long markerId = getArguments().getLong(KEY_MARKER_ID); + FragmentActivity fragmentActivity = getActivity(); + Intent intent; + switch (item.getItemId()) { + case R.id.marker_detail_show_on_map: + intent = IntentUtils.newShowOnMapIntent(waypoint); + startActivity(intent); + return true; + case R.id.marker_detail_edit: + intent = IntentUtils.newIntent(fragmentActivity, MarkerEditActivity.class) + .putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerId); + startActivity(intent); + return true; + case R.id.marker_detail_delete: + DeleteMarkerDialogFragment.newInstance(new long[]{markerId}) + .show(getChildFragmentManager(), DeleteMarkerDialogFragment.DELETE_MARKER_DIALOG_TAG); + return true; + default: + return super.onOptionsItemSelected(item); + } } - } - private void setName(int resId, boolean addShadow) { - TextView textView = getView().findViewById(resId); - ListItemUtils.setTextView(getActivity(), textView, waypoint.getName(), addShadow); - } - - private void setLocation(int resId, boolean addShadow) { - TextView textView = getView().findViewById(resId); - Location location = waypoint.getLocation(); - String value; - if (location == null) { - value = null; - } else { - value = "[" + getString(R.string.stats_latitude) + " " - + StringUtils.formatCoordinate(location.getLatitude()) + ", " - + getString(R.string.stats_longitude) + " " - + StringUtils.formatCoordinate(location.getLongitude()) + "]"; + /** + * Updates the waypoint. + * + * @param refresh true to always update + */ + private void updateWaypoint(boolean refresh) { + if (refresh || waypoint == null) { + waypoint = contentProviderUtils.getWaypoint(getArguments().getLong(KEY_MARKER_ID)); + if (waypoint == null) { + Log.d(TAG, "waypoint is null"); + getFragmentManager().popBackStack(); + } + } + } + + /** + * Updates the UI. + */ + private void updateUi() { + View waypointView = getView().findViewById(R.id.marker_detail_waypoint); + View statisticsView = getView().findViewById(R.id.marker_detail_statistics); + + boolean isWaypoint = waypoint.getType() == WaypointType.WAYPOINT; + waypointView.setVisibility(isWaypoint ? View.VISIBLE : View.GONE); + statisticsView.setVisibility(isWaypoint ? View.GONE : View.VISIBLE); + + if (isWaypoint) { + boolean hasPhoto = waypoint.hasPhoto(); + photo.setVisibility(hasPhoto ? View.VISIBLE : View.GONE); + textGradient.setVisibility(hasPhoto ? View.VISIBLE : View.GONE); + waypointInfo.setVisibility(View.VISIBLE); + + if (hasPhoto) { + handler.removeCallbacks(hideText); + photo.setImageURI(waypoint.getPhotoURI()); + handler.postDelayed(hideText, HIDE_TEXT_DELAY); + } + + setName(R.id.marker_detail_waypoint_name, hasPhoto); + + TextView category = getView().findViewById(R.id.marker_detail_waypoint_category); + ListItemUtils.setTextView(getActivity(), category, StringUtils.getCategory(waypoint.getCategory()), hasPhoto); + + TextView description = getView().findViewById(R.id.marker_detail_waypoint_description); + ListItemUtils.setTextView(getActivity(), description, waypoint.getDescription(), hasPhoto); + + setLocation(R.id.marker_detail_waypoint_location, hasPhoto); + } else { + setName(R.id.marker_detail_statistics_name, false); + + setLocation(R.id.marker_detail_statistics_location, false); + + Track track = contentProviderUtils.getTrack(waypoint.getTrackId()); + StatsUtils.setTripStatisticsValues(getActivity(), null, getView(), waypoint.getTripStatistics(), null); + StatsUtils.setLocationValues(getActivity(), null, getView(), waypoint.getLocation(), false); + } + } + + private void setName(int resId, boolean addShadow) { + TextView textView = getView().findViewById(resId); + ListItemUtils.setTextView(getActivity(), textView, waypoint.getName(), addShadow); + } + + private void setLocation(int resId, boolean addShadow) { + TextView textView = getView().findViewById(resId); + Location location = waypoint.getLocation(); + String value; + if (location == null) { + value = null; + } else { + value = "[" + getString(R.string.stats_latitude) + " " + + StringUtils.formatCoordinate(location.getLatitude()) + ", " + + getString(R.string.stats_longitude) + " " + + StringUtils.formatCoordinate(location.getLongitude()) + "]"; + } + ListItemUtils.setTextView(getActivity(), textView, value, addShadow); } - ListItemUtils.setTextView(getActivity(), textView, value, addShadow); - } } diff --git a/src/main/java/com/google/android/apps/mytracks/fragments/StatsFragment.java b/src/main/java/com/google/android/apps/mytracks/fragments/StatsFragment.java index 77dd680db..610058be3 100644 --- a/src/main/java/com/google/android/apps/mytracks/fragments/StatsFragment.java +++ b/src/main/java/com/google/android/apps/mytracks/fragments/StatsFragment.java @@ -53,274 +53,272 @@ import java.util.EnumSet; /** * A fragment to display track statistics to the user. - * + * * @author Sandor Dornbush * @author Rodrigo Damazio */ public class StatsFragment extends Fragment implements TrackDataListener { - public static final String STATS_FRAGMENT_TAG = "statsFragment"; + public static final String STATS_FRAGMENT_TAG = "statsFragment"; - private TrackDataHub trackDataHub; - private Handler handlerUpdateUI; + private TrackDataHub trackDataHub; + private Handler handlerUpdateUI; - private TrackRecordingServiceConnection trackRecordingServiceConnection; + private TrackRecordingServiceConnection trackRecordingServiceConnection; - private Location lastLocation = null; - private TripStatistics lastTripStatistics = null; - private SensorDataSet sensorDataSet = null; - private String category = ""; - private int recordingGpsAccuracy = PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT; + private Location lastLocation = null; + private TripStatistics lastTripStatistics = null; + private SensorDataSet sensorDataSet = null; + private final Runnable updateUIeachSecond = new Runnable() { + public void run() { + if (isResumed() && isSelectedTrackRecording()) { + if (!isSelectedTrackPaused() && lastTripStatistics != null) { + StatsUtils.setTotalTimeValue(getActivity(), System.currentTimeMillis() - lastTripStatistics.getStopTime() + lastTripStatistics.getTotalTime()); + updateSensorDataUI(); + } - private final Runnable updateUIeachSecond = new Runnable() { - public void run() { - if (isResumed() && isSelectedTrackRecording()) { - if (!isSelectedTrackPaused() && lastTripStatistics != null) { - StatsUtils.setTotalTimeValue(getActivity(), System.currentTimeMillis() - lastTripStatistics.getStopTime() + lastTripStatistics.getTotalTime()); - updateSensorDataUI(); + handlerUpdateUI.postDelayed(this, UnitConversions.ONE_SECOND); + } } + }; + private String category = ""; + private int recordingGpsAccuracy = PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT; - handlerUpdateUI.postDelayed(this, UnitConversions.ONE_SECOND); - } + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.stats, container, false); } - }; + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + handlerUpdateUI = new Handler(); + trackRecordingServiceConnection = new TrackRecordingServiceConnection(getContext(), null); + TrackRecordingServiceConnectionUtils.startConnection(getContext(), trackRecordingServiceConnection); - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.stats, container, false); - } + Spinner activityTypeIcon = getView().findViewById(R.id.stats_activity_type_icon); + activityTypeIcon.setAdapter(TrackIconUtils.getIconSpinnerAdapter(getActivity(), "")); + activityTypeIcon.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP) { + ((TrackDetailActivity) getActivity()).chooseActivityType(category); + } + return true; + } + }); + activityTypeIcon.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { + ((TrackDetailActivity) getActivity()).chooseActivityType(category); + } + return true; + } + }); + } - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - handlerUpdateUI = new Handler(); - trackRecordingServiceConnection = new TrackRecordingServiceConnection(getContext(), null); - TrackRecordingServiceConnectionUtils.startConnection(getContext(), trackRecordingServiceConnection); + @Override + public void onResume() { + super.onResume(); + resumeTrackDataHub(); + updateUi(getActivity()); + if (isSelectedTrackRecording()) { + handlerUpdateUI.post(updateUIeachSecond); + } + } - Spinner activityTypeIcon = getView().findViewById(R.id.stats_activity_type_icon); - activityTypeIcon.setAdapter(TrackIconUtils.getIconSpinnerAdapter(getActivity(), "")); - activityTypeIcon.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_UP) { - ((TrackDetailActivity) getActivity()).chooseActivityType(category); + @Override + public void onPause() { + super.onPause(); + pauseTrackDataHub(); + handlerUpdateUI.removeCallbacks(updateUIeachSecond); + } + + @Override + public void onStop() { + super.onStop(); + trackRecordingServiceConnection.unbind(); + } + + @Override + public void onTrackUpdated(final Track track) { + if (isResumed()) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + if (isResumed()) { + lastTripStatistics = track != null ? track.getTripStatistics() : null; + category = track != null ? track.getCategory() : ""; + updateUi(getActivity()); + } + } + }); + } + } + + @Override + public void clearTrackPoints() { + lastLocation = null; + } + + @Override + public void onSampledInTrackPoint(Location location) { + lastLocation = location; + } + + @Override + public void onSampledOutTrackPoint(Location location) { + lastLocation = location; + } + + @Override + public void onSegmentSplit(Location location) { + // We don't care. + } + + @Override + public void onNewTrackPointsDone() { + if (isResumed()) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + if (isResumed()) { + if (!isSelectedTrackRecording() || isSelectedTrackPaused()) { + lastLocation = null; + } + + if (lastLocation != null) { + boolean hasFix = !LocationUtils.isLocationOld(lastLocation); + boolean hasGoodFix = lastLocation.hasAccuracy() && lastLocation.getAccuracy() < recordingGpsAccuracy; + + if (!hasFix || !hasGoodFix) { + lastLocation = null; + } + } + StatsUtils.setLocationValues(getActivity(), getActivity(), null, lastLocation, isSelectedTrackRecording()); + } + } + }); + } + } + + @Override + public void clearWaypoints() { + // We don't care. + } + + @Override + public void onNewWaypoint(Waypoint wpt) { + // We don't care. + } + + @Override + public void onNewWaypointsDone() { + // We don't care. + } + + @Override + public boolean onMetricUnitsChanged(final boolean metric) { + if (isResumed()) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + if (isResumed()) { + updateUi(getActivity()); + } + } + }); } return true; - } - }); - activityTypeIcon.setOnKeyListener(new View.OnKeyListener() { - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { - ((TrackDetailActivity) getActivity()).chooseActivityType(category); + } + + @Override + public boolean onReportSpeedChanged(final boolean speed) { + if (isResumed()) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + if (isResumed()) { + updateUi(getActivity()); + } + } + }); } return true; - } - }); - } - - @Override - public void onResume() { - super.onResume(); - resumeTrackDataHub(); - updateUi(getActivity()); - if (isSelectedTrackRecording()) { - handlerUpdateUI.post(updateUIeachSecond); } - } - @Override - public void onPause() { - super.onPause(); - pauseTrackDataHub(); - handlerUpdateUI.removeCallbacks(updateUIeachSecond); - } + @Override + public boolean onRecordingGpsAccuracy(int newValue) { + recordingGpsAccuracy = newValue; + return false; + } - @Override - public void onStop() { - super.onStop(); - trackRecordingServiceConnection.unbind(); - } + @Override + public boolean onRecordingDistanceIntervalChanged(int minRecordingDistance) { + // We don't care. + return false; + } - @Override - public void onTrackUpdated(final Track track) { - if (isResumed()) { - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - if (isResumed()) { - lastTripStatistics = track != null ? track.getTripStatistics() : null; - category = track != null ? track.getCategory() : ""; - updateUi(getActivity()); - } + /** + * Resumes the trackDataHub. Needs to be synchronized because trackDataHub can + * be accessed by multiple threads. + */ + private synchronized void resumeTrackDataHub() { + trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub(); + trackDataHub.registerTrackDataListener(this, EnumSet.of(TrackDataType.TRACKS_TABLE, + TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE, TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE, + TrackDataType.PREFERENCE)); + } + + /** + * Pauses the trackDataHub. Needs to be synchronized because trackDataHub can + * be accessed by multiple threads. + */ + private synchronized void pauseTrackDataHub() { + trackDataHub.unregisterTrackDataListener(this); + trackDataHub = null; + } + + /** + * Returns true if the selected track is recording. Needs to be synchronized + * because trackDataHub can be accessed by multiple threads. + */ + private synchronized boolean isSelectedTrackRecording() { + return trackDataHub != null && trackDataHub.isSelectedTrackRecording(); + } + + /** + * Returns true if the selected track is paused. Needs to be synchronized + * because trackDataHub can be accessed by multiple threads. + */ + private synchronized boolean isSelectedTrackPaused() { + return trackDataHub != null && trackDataHub.isSelectedTrackPaused(); + } + + /** + * Tries to fetch most recent {@link SensorDataSet} {@link com.google.android.apps.mytracks.services.sensors.SensorManager}. + */ + private void updateSensorDataUI() { + ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound(); + if (trackRecordingService == null) { + Log.d(STATS_FRAGMENT_TAG, "Cannot get the track recording service."); + sensorDataSet = null; + } else { + //TODO sensorState = trackRecordingService.getSensorState(); + sensorDataSet = trackRecordingService.getSensorData(); } - }); - } - } - @Override - public void clearTrackPoints() { - lastLocation = null; - } - - @Override - public void onSampledInTrackPoint(Location location) { - lastLocation = location; - } - - @Override - public void onSampledOutTrackPoint(Location location) { - lastLocation = location; - } - - @Override - public void onSegmentSplit(Location location) { - // We don't care. - } - - @Override - public void onNewTrackPointsDone() { - if (isResumed()) { - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - if (isResumed()) { - if (!isSelectedTrackRecording() || isSelectedTrackPaused()) { - lastLocation = null; - } - - if (lastLocation != null) { - boolean hasFix = !LocationUtils.isLocationOld(lastLocation); - boolean hasGoodFix = lastLocation.hasAccuracy() && lastLocation.getAccuracy() < recordingGpsAccuracy; - - if (!hasFix || !hasGoodFix) { - lastLocation = null; - } - } - StatsUtils.setLocationValues(getActivity(), getActivity(), null, lastLocation, isSelectedTrackRecording()); - } - } - }); - } - } - - @Override - public void clearWaypoints() { - // We don't care. - } - - @Override - public void onNewWaypoint(Waypoint wpt) { - // We don't care. - } - - @Override - public void onNewWaypointsDone() { - // We don't care. - } - - @Override - public boolean onMetricUnitsChanged(final boolean metric) { - if (isResumed()) { - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - if (isResumed()) { - updateUi(getActivity()); - } - } - }); - } - return true; - } - - @Override - public boolean onReportSpeedChanged(final boolean speed) { - if (isResumed()) { - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - if (isResumed()) { - updateUi(getActivity()); - } - } - }); - } - return true; - } - - @Override - public boolean onRecordingGpsAccuracy(int newValue) { - recordingGpsAccuracy = newValue; - return false; - } - - @Override - public boolean onRecordingDistanceIntervalChanged(int minRecordingDistance) { - // We don't care. - return false; - } - - /** - * Resumes the trackDataHub. Needs to be synchronized because trackDataHub can - * be accessed by multiple threads. - */ - private synchronized void resumeTrackDataHub() { - trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub(); - trackDataHub.registerTrackDataListener(this, EnumSet.of(TrackDataType.TRACKS_TABLE, - TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE, TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE, - TrackDataType.PREFERENCE)); - } - - /** - * Pauses the trackDataHub. Needs to be synchronized because trackDataHub can - * be accessed by multiple threads. - */ - private synchronized void pauseTrackDataHub() { - trackDataHub.unregisterTrackDataListener(this); - trackDataHub = null; - } - - /** - * Returns true if the selected track is recording. Needs to be synchronized - * because trackDataHub can be accessed by multiple threads. - */ - private synchronized boolean isSelectedTrackRecording() { - return trackDataHub != null && trackDataHub.isSelectedTrackRecording(); - } - - /** - * Returns true if the selected track is paused. Needs to be synchronized - * because trackDataHub can be accessed by multiple threads. - */ - private synchronized boolean isSelectedTrackPaused() { - return trackDataHub != null && trackDataHub.isSelectedTrackPaused(); - } - - /** - * Tries to fetch most recent {@link SensorDataSet} {@link com.google.android.apps.mytracks.services.sensors.SensorManager}. - */ - private void updateSensorDataUI() { - ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound(); - if (trackRecordingService == null) { - Log.d(STATS_FRAGMENT_TAG, "Cannot get the track recording service."); - sensorDataSet = null; - } else { - //TODO sensorState = trackRecordingService.getSensorState(); - sensorDataSet = trackRecordingService.getSensorData(); + StatsUtils.setSensorData(getActivity(), getActivity(), sensorDataSet, isSelectedTrackRecording()); } - StatsUtils.setSensorData(getActivity(), getActivity(), sensorDataSet, isSelectedTrackRecording()); - } - - /** - * Updates the UI. - */ - private void updateUi(FragmentActivity activity) { - String trackIconValue = TrackIconUtils.getIconValue(activity, category); - StatsUtils.setTripStatisticsValues(activity, activity, null, lastTripStatistics, trackIconValue); - StatsUtils.setLocationValues(activity, activity, null, lastLocation, isSelectedTrackRecording()); - updateSensorDataUI(); - } + /** + * Updates the UI. + */ + private void updateUi(FragmentActivity activity) { + String trackIconValue = TrackIconUtils.getIconValue(activity, category); + StatsUtils.setTripStatisticsValues(activity, activity, null, lastTripStatistics, trackIconValue); + StatsUtils.setLocationValues(activity, activity, null, lastLocation, isSelectedTrackRecording()); + updateSensorDataUI(); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/io/file/TrackFileFormat.java b/src/main/java/com/google/android/apps/mytracks/io/file/TrackFileFormat.java index 9cc96b72f..0806a78d1 100644 --- a/src/main/java/com/google/android/apps/mytracks/io/file/TrackFileFormat.java +++ b/src/main/java/com/google/android/apps/mytracks/io/file/TrackFileFormat.java @@ -1,75 +1,75 @@ package com.google.android.apps.mytracks.io.file; +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; + import com.google.android.apps.mytracks.io.file.exporter.GpxTrackWriter; import com.google.android.apps.mytracks.io.file.exporter.KmlTrackWriter; import com.google.android.apps.mytracks.io.file.exporter.TrackWriter; import com.google.android.maps.mytracks.R; -import android.content.Context; -import android.os.Parcel; -import android.os.Parcelable; - import java.util.Locale; /** * Definition of all possible track formats. */ public enum TrackFileFormat implements Parcelable { - KML { - @Override - public TrackWriter newTrackWriter(Context context, boolean multiple, boolean playTrack) { - return new KmlTrackWriter(context, multiple, playTrack); - } - }, - GPX { - @Override - public TrackWriter newTrackWriter(Context context, boolean multiple, boolean playTrack) { - return new GpxTrackWriter(context.getString(R.string.app_name)); - } - }; + KML { + @Override + public TrackWriter newTrackWriter(Context context, boolean multiple, boolean playTrack) { + return new KmlTrackWriter(context, multiple, playTrack); + } + }, + GPX { + @Override + public TrackWriter newTrackWriter(Context context, boolean multiple, boolean playTrack) { + return new GpxTrackWriter(context.getString(R.string.app_name)); + } + }; - @Override - public int describeContents() { - return 0; - } + public static final Creator CREATOR = new Creator() { + @Override + public TrackFileFormat createFromParcel(final Parcel source) { + return TrackFileFormat.values()[source.readInt()]; + } - @Override - public void writeToParcel(final Parcel dest, final int flags) { - dest.writeInt(ordinal()); - } + @Override + public TrackFileFormat[] newArray(final int size) { + return new TrackFileFormat[size]; + } + }; - public static final Creator CREATOR = new Creator() { - @Override - public TrackFileFormat createFromParcel(final Parcel source) { - return TrackFileFormat.values()[source.readInt()]; + @Override + public int describeContents() { + return 0; } - @Override - public TrackFileFormat[] newArray(final int size) { - return new TrackFileFormat[size]; + @Override + public void writeToParcel(final Parcel dest, final int flags) { + dest.writeInt(ordinal()); } - }; - /** - * Creates a new track writer for the format. - * - * @param context the context - * @param multiple true for writing multiple tracks - * @param playTrack true to play track - */ - public abstract TrackWriter newTrackWriter(Context context, boolean multiple, boolean playTrack); + /** + * Creates a new track writer for the format. + * + * @param context the context + * @param multiple true for writing multiple tracks + * @param playTrack true to play track + */ + public abstract TrackWriter newTrackWriter(Context context, boolean multiple, boolean playTrack); - /** - * Returns the mime type for each format. - */ - public String getMimeType() { - return "application/" + getExtension() + "+xml"; - } + /** + * Returns the mime type for each format. + */ + public String getMimeType() { + return "application/" + getExtension() + "+xml"; + } - /** - * Returns the file extension for each format. - */ - public String getExtension() { - return this.name().toLowerCase(Locale.US); - } + /** + * Returns the file extension for each format. + */ + public String getExtension() { + return this.name().toLowerCase(Locale.US); + } } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/io/file/exporter/FileTrackExporter.java b/src/main/java/com/google/android/apps/mytracks/io/file/exporter/FileTrackExporter.java index c9868a406..a84fdfcbc 100644 --- a/src/main/java/com/google/android/apps/mytracks/io/file/exporter/FileTrackExporter.java +++ b/src/main/java/com/google/android/apps/mytracks/io/file/exporter/FileTrackExporter.java @@ -16,219 +16,219 @@ package com.google.android.apps.mytracks.io.file.exporter; -import com.google.android.apps.mytracks.Constants; -import com.google.android.apps.mytracks.content.SensorDataSetLocation; -import com.google.android.apps.mytracks.content.ContentProviderUtils; -import com.google.android.apps.mytracks.content.ContentProviderUtils.LocationIterator; -import com.google.android.apps.mytracks.content.Track; -import com.google.android.apps.mytracks.content.Waypoint; -import com.google.android.apps.mytracks.util.LocationUtils; - import android.database.Cursor; import android.location.Location; import android.util.Log; +import com.google.android.apps.mytracks.Constants; +import com.google.android.apps.mytracks.content.ContentProviderUtils; +import com.google.android.apps.mytracks.content.ContentProviderUtils.LocationIterator; +import com.google.android.apps.mytracks.content.SensorDataSetLocation; +import com.google.android.apps.mytracks.content.Track; +import com.google.android.apps.mytracks.content.Waypoint; +import com.google.android.apps.mytracks.util.LocationUtils; + import java.io.OutputStream; /** * Track Writer for writing tracks to an {@link OutputStream}. - * + * * @author Sandor Dornbush * @author Rodrigo Damazio */ public class FileTrackExporter implements TrackExporter { - private static final String TAG = FileTrackExporter.class.getSimpleName(); + private static final String TAG = FileTrackExporter.class.getSimpleName(); - private final ContentProviderUtils contentProviderUtils; - private final Track[] tracks; - private final TrackWriter trackWriter; - private final TrackExporterListener trackExporterListener; + private final ContentProviderUtils contentProviderUtils; + private final Track[] tracks; + private final TrackWriter trackWriter; + private final TrackExporterListener trackExporterListener; - /** - * Constructor. - * - * @param contentProviderUtils the content provider utils - * @param tracks the tracks - * @param trackWriter the track writer - * @param trackExporterListener the track export listener - */ - public FileTrackExporter(ContentProviderUtils contentProviderUtils, Track[] tracks, - TrackWriter trackWriter, TrackExporterListener trackExporterListener) { - this.contentProviderUtils = contentProviderUtils; - this.tracks = tracks; - this.trackWriter = trackWriter; - this.trackExporterListener = trackExporterListener; - } - - @Override - public boolean writeTrack(OutputStream outputStream) { - try { - trackWriter.prepare(outputStream); - trackWriter.writeHeader(tracks); - for (Track track1 : tracks) { - writeWaypoints(track1); - } - trackWriter.writeBeginTracks(); - long startTime = tracks[0].getTripStatistics().getStartTime(); - for (Track track : tracks) { - long offset = track.getTripStatistics().getStartTime() - startTime; - writeLocations(track, offset); - } - trackWriter.writeEndTracks(); - trackWriter.writeFooter(); - trackWriter.close(); - return true; - } catch (InterruptedException e) { - Log.e(TAG, "Thread interrupted", e); - return false; - } - } - - /** - * Writes the waypoints. - */ - private void writeWaypoints(Track track) throws InterruptedException { - /* - * TODO: Stream through the waypoints in chunks. I am leaving the number of - * waypoints very high which should not be a problem because we don't try to - * load them into objects all at the same time. + /** + * Constructor. + * + * @param contentProviderUtils the content provider utils + * @param tracks the tracks + * @param trackWriter the track writer + * @param trackExporterListener the track export listener */ - boolean hasWaypoints = false; - try (Cursor cursor = contentProviderUtils.getWaypointCursor( - track.getId(), -1L, Constants.MAX_LOADED_WAYPOINTS_POINTS)) { - if (cursor != null && cursor.moveToFirst()) { - /* - * Yes, this will skip the first waypoint and that is intentional as the - * first waypoint holds the stats for the track. - */ - while (cursor.moveToNext()) { - if (Thread.interrupted()) { - throw new InterruptedException(); - } - if (!hasWaypoints) { - trackWriter.writeBeginWaypoints(track); - hasWaypoints = true; - } - Waypoint waypoint = contentProviderUtils.createWaypoint(cursor); - trackWriter.writeWaypoint(waypoint); - } - } + public FileTrackExporter(ContentProviderUtils contentProviderUtils, Track[] tracks, + TrackWriter trackWriter, TrackExporterListener trackExporterListener) { + this.contentProviderUtils = contentProviderUtils; + this.tracks = tracks; + this.trackWriter = trackWriter; + this.trackExporterListener = trackExporterListener; } - if (hasWaypoints) { - trackWriter.writeEndWaypoints(); - } - } - - /** - * Writes the locations. - */ - private void writeLocations(Track track, long offset) throws InterruptedException { - boolean wroteTrack = false; - boolean wroteSegment = false; - boolean isLastLocationValid = false; - TrackWriterLocationFactory locationFactory = new TrackWriterLocationFactory(); - int locationNumber = 0; - LocationIterator locationIterator = null; - - try { - locationIterator = contentProviderUtils.getTrackPointLocationIterator( - track.getId(), -1L, false, locationFactory); - - while (locationIterator.hasNext()) { - if (Thread.interrupted()) { - throw new InterruptedException(); - } - Location location = locationIterator.next(); - - setLocationTime(location, offset); - locationNumber++; - - boolean isLocationValid = LocationUtils.isValidLocation(location); - boolean isSegmentValid = isLocationValid && isLastLocationValid; - if (!wroteTrack && isSegmentValid) { - // Found the first two consecutive locations that are valid - trackWriter.writeBeginTrack(track, locationFactory.lastLocation); - wroteTrack = true; - } - - if (isSegmentValid) { - if (!wroteSegment) { - // Start a segment - trackWriter.writeOpenSegment(); - wroteSegment = true; - - // Write the previous location, which we had previously skipped - trackWriter.writeLocation(locationFactory.lastLocation); - } - - // Write the current location - trackWriter.writeLocation(location); - if (trackExporterListener != null) { - trackExporterListener.onProgressUpdate(locationNumber, track.getNumberOfPoints()); - } - } else { - if (wroteSegment) { - trackWriter.writeCloseSegment(); - wroteSegment = false; - } - } - locationFactory.swapLocations(); - isLastLocationValid = isLocationValid; - } - - if (wroteTrack) { - Location lastValidTrackPoint = contentProviderUtils.getLastValidTrackPoint(track.getId()); - setLocationTime(lastValidTrackPoint, offset); - trackWriter.writeEndTrack(track, lastValidTrackPoint); - } else { - // Write an empty track - trackWriter.writeBeginTrack(track, null); - trackWriter.writeEndTrack(track, null); - } - } finally { - if (locationIterator != null) { - locationIterator.close(); - } - } - } - - /** - * Sets a location time. - * - * @param location the location - * @param offset the time offset - */ - private void setLocationTime(Location location, long offset) { - if (location != null) { - location.setTime(location.getTime() - offset); - } - } - - /** - * Track writer location factory. Keeping the last two locations. - * - * @author Jimmy Shih - */ - private class TrackWriterLocationFactory implements ContentProviderUtils.LocationFactory { - Location currentLocation; - Location lastLocation; @Override - public Location createLocation() { - if (currentLocation == null) { - currentLocation = new SensorDataSetLocation(""); - } - return currentLocation; + public boolean writeTrack(OutputStream outputStream) { + try { + trackWriter.prepare(outputStream); + trackWriter.writeHeader(tracks); + for (Track track1 : tracks) { + writeWaypoints(track1); + } + trackWriter.writeBeginTracks(); + long startTime = tracks[0].getTripStatistics().getStartTime(); + for (Track track : tracks) { + long offset = track.getTripStatistics().getStartTime() - startTime; + writeLocations(track, offset); + } + trackWriter.writeEndTracks(); + trackWriter.writeFooter(); + trackWriter.close(); + return true; + } catch (InterruptedException e) { + Log.e(TAG, "Thread interrupted", e); + return false; + } } - public void swapLocations() { - Location tempLocation = lastLocation; - lastLocation = currentLocation; - currentLocation = tempLocation; - if (currentLocation != null) { - currentLocation.reset(); - } + /** + * Writes the waypoints. + */ + private void writeWaypoints(Track track) throws InterruptedException { + /* + * TODO: Stream through the waypoints in chunks. I am leaving the number of + * waypoints very high which should not be a problem because we don't try to + * load them into objects all at the same time. + */ + boolean hasWaypoints = false; + try (Cursor cursor = contentProviderUtils.getWaypointCursor( + track.getId(), -1L, Constants.MAX_LOADED_WAYPOINTS_POINTS)) { + if (cursor != null && cursor.moveToFirst()) { + /* + * Yes, this will skip the first waypoint and that is intentional as the + * first waypoint holds the stats for the track. + */ + while (cursor.moveToNext()) { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + if (!hasWaypoints) { + trackWriter.writeBeginWaypoints(track); + hasWaypoints = true; + } + Waypoint waypoint = contentProviderUtils.createWaypoint(cursor); + trackWriter.writeWaypoint(waypoint); + } + } + } + if (hasWaypoints) { + trackWriter.writeEndWaypoints(); + } + } + + /** + * Writes the locations. + */ + private void writeLocations(Track track, long offset) throws InterruptedException { + boolean wroteTrack = false; + boolean wroteSegment = false; + boolean isLastLocationValid = false; + TrackWriterLocationFactory locationFactory = new TrackWriterLocationFactory(); + int locationNumber = 0; + LocationIterator locationIterator = null; + + try { + locationIterator = contentProviderUtils.getTrackPointLocationIterator( + track.getId(), -1L, false, locationFactory); + + while (locationIterator.hasNext()) { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + Location location = locationIterator.next(); + + setLocationTime(location, offset); + locationNumber++; + + boolean isLocationValid = LocationUtils.isValidLocation(location); + boolean isSegmentValid = isLocationValid && isLastLocationValid; + if (!wroteTrack && isSegmentValid) { + // Found the first two consecutive locations that are valid + trackWriter.writeBeginTrack(track, locationFactory.lastLocation); + wroteTrack = true; + } + + if (isSegmentValid) { + if (!wroteSegment) { + // Start a segment + trackWriter.writeOpenSegment(); + wroteSegment = true; + + // Write the previous location, which we had previously skipped + trackWriter.writeLocation(locationFactory.lastLocation); + } + + // Write the current location + trackWriter.writeLocation(location); + if (trackExporterListener != null) { + trackExporterListener.onProgressUpdate(locationNumber, track.getNumberOfPoints()); + } + } else { + if (wroteSegment) { + trackWriter.writeCloseSegment(); + wroteSegment = false; + } + } + locationFactory.swapLocations(); + isLastLocationValid = isLocationValid; + } + + if (wroteTrack) { + Location lastValidTrackPoint = contentProviderUtils.getLastValidTrackPoint(track.getId()); + setLocationTime(lastValidTrackPoint, offset); + trackWriter.writeEndTrack(track, lastValidTrackPoint); + } else { + // Write an empty track + trackWriter.writeBeginTrack(track, null); + trackWriter.writeEndTrack(track, null); + } + } finally { + if (locationIterator != null) { + locationIterator.close(); + } + } + } + + /** + * Sets a location time. + * + * @param location the location + * @param offset the time offset + */ + private void setLocationTime(Location location, long offset) { + if (location != null) { + location.setTime(location.getTime() - offset); + } + } + + /** + * Track writer location factory. Keeping the last two locations. + * + * @author Jimmy Shih + */ + private class TrackWriterLocationFactory implements ContentProviderUtils.LocationFactory { + Location currentLocation; + Location lastLocation; + + @Override + public Location createLocation() { + if (currentLocation == null) { + currentLocation = new SensorDataSetLocation(""); + } + return currentLocation; + } + + public void swapLocations() { + Location tempLocation = lastLocation; + lastLocation = currentLocation; + currentLocation = tempLocation; + if (currentLocation != null) { + currentLocation.reset(); + } + } } - } } diff --git a/src/main/java/com/google/android/apps/mytracks/io/file/exporter/GpxTrackWriter.java b/src/main/java/com/google/android/apps/mytracks/io/file/exporter/GpxTrackWriter.java index 09f7bbc61..82c6a3b34 100644 --- a/src/main/java/com/google/android/apps/mytracks/io/file/exporter/GpxTrackWriter.java +++ b/src/main/java/com/google/android/apps/mytracks/io/file/exporter/GpxTrackWriter.java @@ -30,172 +30,173 @@ import java.util.Locale; /** * Write track as GPX to a file. - * + * * @author Sandor Dornbush */ //TODO Can we export Sensor.Data in GPX? public class GpxTrackWriter implements TrackWriter { - private static final NumberFormat ELEVATION_FORMAT = NumberFormat.getInstance(Locale.US); - private static final NumberFormat COORDINATE_FORMAT = NumberFormat.getInstance(Locale.US); - static { - /* - * GPX readers expect to see fractional numbers with US-style punctuation. - * That is, they want periods for decimal points, rather than commas. - */ - ELEVATION_FORMAT.setMaximumFractionDigits(1); - ELEVATION_FORMAT.setGroupingUsed(false); + private static final NumberFormat ELEVATION_FORMAT = NumberFormat.getInstance(Locale.US); + private static final NumberFormat COORDINATE_FORMAT = NumberFormat.getInstance(Locale.US); - COORDINATE_FORMAT.setMaximumFractionDigits(6); - COORDINATE_FORMAT.setMaximumIntegerDigits(3); - COORDINATE_FORMAT.setGroupingUsed(false); - } + static { + /* + * GPX readers expect to see fractional numbers with US-style punctuation. + * That is, they want periods for decimal points, rather than commas. + */ + ELEVATION_FORMAT.setMaximumFractionDigits(1); + ELEVATION_FORMAT.setGroupingUsed(false); - private final String creator; - private PrintWriter printWriter; - - public GpxTrackWriter(String creator) { - this.creator = creator; - } - - @Override - public String getExtension() { - return TrackFileFormat.GPX.getExtension(); - } - - @Override - public void prepare(OutputStream outputStream) { - this.printWriter = new PrintWriter(outputStream); - } - - @Override - public void close() { - if (printWriter != null) { - printWriter.flush(); - printWriter = null; + COORDINATE_FORMAT.setMaximumFractionDigits(6); + COORDINATE_FORMAT.setMaximumIntegerDigits(3); + COORDINATE_FORMAT.setGroupingUsed(false); } - } - @Override - public void writeHeader(Track[] tracks) { - if (printWriter != null) { - printWriter.println(""); - printWriter.println(""); - printWriter.println(""); - Track track = tracks[0]; - printWriter.println("" + StringUtils.formatCData(track.getName()) + ""); - printWriter.println("" + StringUtils.formatCData(track.getDescription()) + ""); - printWriter.println(""); + private final String creator; + private PrintWriter printWriter; + + public GpxTrackWriter(String creator) { + this.creator = creator; } - } - @Override - public void writeFooter() { - if (printWriter != null) { - printWriter.println(""); + @Override + public String getExtension() { + return TrackFileFormat.GPX.getExtension(); } - } - @Override - public void writeBeginWaypoints(Track track) { - // Do nothing - } + @Override + public void prepare(OutputStream outputStream) { + this.printWriter = new PrintWriter(outputStream); + } - @Override - public void writeEndWaypoints() { - // Do nothing - } - - @Override - public void writeWaypoint(Waypoint waypoint) { - if (printWriter != null) { - Location location = waypoint.getLocation(); - if (location != null) { - printWriter.println(""); - if (location.hasAltitude()) { - printWriter.println("" + ELEVATION_FORMAT.format(location.getAltitude()) + ""); + @Override + public void close() { + if (printWriter != null) { + printWriter.flush(); + printWriter = null; } - printWriter.println( - ""); - printWriter.println("" + StringUtils.formatCData(waypoint.getName()) + ""); - printWriter.println("" + StringUtils.formatCData(waypoint.getType().name()) + ""); - printWriter.println( - "" + StringUtils.formatCData(waypoint.getDescription()) + ""); - printWriter.println("" + StringUtils.formatCData(waypoint.getCategory()) + ""); - printWriter.println(""); - } } - } - @Override - public void writeBeginTracks() { - // Do nothing - } - - @Override - public void writeEndTracks() { - // Do nothing - } - - @Override - public void writeBeginTrack(Track track, Location startLocation) { - if (printWriter != null) { - printWriter.println(""); - printWriter.println("" + StringUtils.formatCData(track.getName()) + ""); - printWriter.println("" + StringUtils.formatCData(track.getDescription()) + ""); - printWriter.println("" + StringUtils.formatCData(track.getCategory()) + ""); - printWriter.println("c0c0c0"); + @Override + public void writeHeader(Track[] tracks) { + if (printWriter != null) { + printWriter.println(""); + printWriter.println(""); + printWriter.println(""); + Track track = tracks[0]; + printWriter.println("" + StringUtils.formatCData(track.getName()) + ""); + printWriter.println("" + StringUtils.formatCData(track.getDescription()) + ""); + printWriter.println(""); + } } - } - @Override - public void writeEndTrack(Track track, Location endLocation) { - if (printWriter != null) { - printWriter.println(""); + @Override + public void writeFooter() { + if (printWriter != null) { + printWriter.println(""); + } } - } - @Override - public void writeOpenSegment() { - printWriter.println(""); - } - - @Override - public void writeCloseSegment() { - printWriter.println(""); - } - - @Override - public void writeLocation(Location location) { - if (printWriter != null) { - printWriter.println(""); - if (location.hasAltitude()) { - printWriter.println("" + ELEVATION_FORMAT.format(location.getAltitude()) + ""); - } - printWriter.println( - ""); - printWriter.println(""); + @Override + public void writeBeginWaypoints(Track track) { + // Do nothing } - } - /** - * Formats a location with latitude and longitude coordinates. - * - * @param location the location - */ - private String formatLocation(Location location) { - return "lat=\"" + COORDINATE_FORMAT.format(location.getLatitude()) + "\" lon=\"" - + COORDINATE_FORMAT.format(location.getLongitude()) + "\""; - } + @Override + public void writeEndWaypoints() { + // Do nothing + } + + @Override + public void writeWaypoint(Waypoint waypoint) { + if (printWriter != null) { + Location location = waypoint.getLocation(); + if (location != null) { + printWriter.println(""); + if (location.hasAltitude()) { + printWriter.println("" + ELEVATION_FORMAT.format(location.getAltitude()) + ""); + } + printWriter.println( + ""); + printWriter.println("" + StringUtils.formatCData(waypoint.getName()) + ""); + printWriter.println("" + StringUtils.formatCData(waypoint.getType().name()) + ""); + printWriter.println( + "" + StringUtils.formatCData(waypoint.getDescription()) + ""); + printWriter.println("" + StringUtils.formatCData(waypoint.getCategory()) + ""); + printWriter.println(""); + } + } + } + + @Override + public void writeBeginTracks() { + // Do nothing + } + + @Override + public void writeEndTracks() { + // Do nothing + } + + @Override + public void writeBeginTrack(Track track, Location startLocation) { + if (printWriter != null) { + printWriter.println(""); + printWriter.println("" + StringUtils.formatCData(track.getName()) + ""); + printWriter.println("" + StringUtils.formatCData(track.getDescription()) + ""); + printWriter.println("" + StringUtils.formatCData(track.getCategory()) + ""); + printWriter.println("c0c0c0"); + } + } + + @Override + public void writeEndTrack(Track track, Location endLocation) { + if (printWriter != null) { + printWriter.println(""); + } + } + + @Override + public void writeOpenSegment() { + printWriter.println(""); + } + + @Override + public void writeCloseSegment() { + printWriter.println(""); + } + + @Override + public void writeLocation(Location location) { + if (printWriter != null) { + printWriter.println(""); + if (location.hasAltitude()) { + printWriter.println("" + ELEVATION_FORMAT.format(location.getAltitude()) + ""); + } + printWriter.println( + ""); + printWriter.println(""); + } + } + + /** + * Formats a location with latitude and longitude coordinates. + * + * @param location the location + */ + private String formatLocation(Location location) { + return "lat=\"" + COORDINATE_FORMAT.format(location.getLatitude()) + "\" lon=\"" + + COORDINATE_FORMAT.format(location.getLongitude()) + "\""; + } } diff --git a/src/main/java/com/google/android/apps/mytracks/io/file/exporter/KmlTrackWriter.java b/src/main/java/com/google/android/apps/mytracks/io/file/exporter/KmlTrackWriter.java index 46de69ef0..119a61e0d 100644 --- a/src/main/java/com/google/android/apps/mytracks/io/file/exporter/KmlTrackWriter.java +++ b/src/main/java/com/google/android/apps/mytracks/io/file/exporter/KmlTrackWriter.java @@ -22,10 +22,10 @@ import android.net.Uri; import androidx.annotation.VisibleForTesting; +import com.google.android.apps.mytracks.content.ContentProviderUtils; import com.google.android.apps.mytracks.content.DescriptionGenerator; import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl; import com.google.android.apps.mytracks.content.SensorDataSetLocation; -import com.google.android.apps.mytracks.content.ContentProviderUtils; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.content.Waypoint.WaypointType; @@ -41,420 +41,420 @@ import java.util.ArrayList; /** * Write track as KML to a file. - * + * * @author Leif Hendrik Wilden */ public class KmlTrackWriter implements TrackWriter { - private static final String WAYPOINT_STYLE = "waypoint"; - private static final String STATISTICS_STYLE = "statistics"; - private static final String START_STYLE = "start"; - private static final String END_STYLE = "end"; - private static final String TRACK_STYLE = "track"; - private static final String SCHEMA_ID = "schema"; - private static final String CADENCE = "cadence"; - private static final String HEART_RATE = "heart_rate"; - private static final String POWER = "power"; + private static final String WAYPOINT_STYLE = "waypoint"; + private static final String STATISTICS_STYLE = "statistics"; + private static final String START_STYLE = "start"; + private static final String END_STYLE = "end"; + private static final String TRACK_STYLE = "track"; + private static final String SCHEMA_ID = "schema"; + private static final String CADENCE = "cadence"; + private static final String HEART_RATE = "heart_rate"; + private static final String POWER = "power"; - private static final String - WAYPOINT_ICON = "http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png"; - private static final String - STATISTICS_ICON = "http://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png"; - private static final String - START_ICON = "http://maps.google.com/mapfiles/kml/paddle/grn-circle.png"; - private static final String - END_ICON = "http://maps.google.com/mapfiles/kml/paddle/red-circle.png"; - private static final String - TRACK_ICON = "http://earth.google.com/images/kml-icons/track-directional/track-0.png"; + private static final String + WAYPOINT_ICON = "http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png"; + private static final String + STATISTICS_ICON = "http://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png"; + private static final String + START_ICON = "http://maps.google.com/mapfiles/kml/paddle/grn-circle.png"; + private static final String + END_ICON = "http://maps.google.com/mapfiles/kml/paddle/red-circle.png"; + private static final String + TRACK_ICON = "http://earth.google.com/images/kml-icons/track-directional/track-0.png"; - private final Context context; - private final boolean multiple; - private final boolean playTrack; - private final DescriptionGenerator descriptionGenerator; - private final ContentProviderUtils contentProviderUtils; + private final Context context; + private final boolean multiple; + private final boolean playTrack; + private final DescriptionGenerator descriptionGenerator; + private final ContentProviderUtils contentProviderUtils; - private PrintWriter printWriter; - private ArrayList powerList = new ArrayList<>(); - private ArrayList cadenceList = new ArrayList<>(); - private ArrayList heartRateList = new ArrayList<>(); - private boolean hasPower; - private boolean hasCadence; - private boolean hasHeartRate; + private PrintWriter printWriter; + private ArrayList powerList = new ArrayList<>(); + private ArrayList cadenceList = new ArrayList<>(); + private ArrayList heartRateList = new ArrayList<>(); + private boolean hasPower; + private boolean hasCadence; + private boolean hasHeartRate; - public KmlTrackWriter(Context context, boolean multiple, boolean playTrack) { - this(context, multiple, playTrack, new DescriptionGeneratorImpl(context)); - } - - @VisibleForTesting - KmlTrackWriter(Context context, boolean multiple, boolean playTrack, DescriptionGenerator descriptionGenerator) { - this.context = context; - this.multiple = multiple; - this.playTrack = playTrack; - this.descriptionGenerator = descriptionGenerator; - this.contentProviderUtils = ContentProviderUtils.Factory.get(context); - } - - @Override - public String getExtension() { - return TrackFileFormat.KML.getExtension(); - } - - @Override - public void prepare(OutputStream outputStream) { - this.printWriter = new PrintWriter(outputStream); - } - - @Override - public void close() { - if (printWriter != null) { - printWriter.flush(); - printWriter = null; + public KmlTrackWriter(Context context, boolean multiple, boolean playTrack) { + this(context, multiple, playTrack, new DescriptionGeneratorImpl(context)); } - } - @Override - public void writeHeader(Track[] tracks) { - if (printWriter != null) { - printWriter.println(""); - printWriter.println(""); - printWriter.println(""); - printWriter.println("1"); - printWriter.println("1"); - Track track = tracks[0]; - printWriter.println("" + StringUtils.formatCData(track.getName()) + ""); - printWriter.println("" - + StringUtils.formatCData(context.getString(R.string.app_name)) - + ""); - writeTrackStyle(); - writePlacemarkerStyle(START_STYLE, START_ICON, 32, 1); - writePlacemarkerStyle(END_STYLE, END_ICON, 32, 1); - writePlacemarkerStyle(STATISTICS_STYLE, STATISTICS_ICON, 20, 2); - writePlacemarkerStyle(WAYPOINT_STYLE, WAYPOINT_ICON, 20, 2); - printWriter.println(""); - writeSensorStyle(POWER, context.getString(R.string.description_sensor_power)); - writeSensorStyle(CADENCE, context.getString(R.string.description_sensor_cadence)); - writeSensorStyle(HEART_RATE, context.getString(R.string.description_sensor_heart_rate)); - printWriter.println(""); + @VisibleForTesting + KmlTrackWriter(Context context, boolean multiple, boolean playTrack, DescriptionGenerator descriptionGenerator) { + this.context = context; + this.multiple = multiple; + this.playTrack = playTrack; + this.descriptionGenerator = descriptionGenerator; + this.contentProviderUtils = ContentProviderUtils.Factory.get(context); } - } - @Override - public void writeFooter() { - if (printWriter != null) { - printWriter.println(""); - printWriter.println(""); + @Override + public String getExtension() { + return TrackFileFormat.KML.getExtension(); } - } - @Override - public void writeBeginWaypoints(Track track) { - if (printWriter != null) { - printWriter.println("" - + StringUtils.formatCData(context.getString(R.string.track_markers, track.getName())) - + ""); - printWriter.println("1"); + @Override + public void prepare(OutputStream outputStream) { + this.printWriter = new PrintWriter(outputStream); } - } - @Override - public void writeEndWaypoints() { - if (printWriter != null) { - printWriter.println(""); - } - } - - @Override - public void writeWaypoint(Waypoint waypoint) { - if (printWriter != null) { - String styleName = waypoint.getType() == WaypointType.STATISTICS ? STATISTICS_STYLE : WAYPOINT_STYLE; - if (waypoint.hasPhoto()) { - float heading = getHeading(waypoint.getTrackId(), waypoint.getLocation()); - writePhotoOverlay(waypoint.getName(), waypoint.getCategory(), waypoint.getDescription(), styleName, waypoint.getLocation(), waypoint.getPhotoUrl(), heading); - } else { - writePlacemark(waypoint.getName(), waypoint.getCategory(), waypoint.getDescription(), styleName, waypoint.getLocation()); - } - } - } - - @Override - public void writeBeginTracks() { - if (printWriter != null && multiple) { - printWriter.println(""); - printWriter.println("" + context.getString(R.string.generic_tracks) + ""); - printWriter.println("1"); - } - } - - @Override - public void writeEndTracks() { - if (printWriter != null && multiple) { - printWriter.println(""); - } - } - - @Override - public void writeBeginTrack(Track track, Location startLocation) { - if (printWriter != null) { - String name = context.getString(R.string.marker_label_start, track.getName()); - writePlacemark(name, "", "", START_STYLE, startLocation); - printWriter.println(""); - printWriter.println("" + StringUtils.formatCData(track.getName()) + ""); - printWriter.println( - "" + StringUtils.formatCData(track.getDescription()) + ""); - printWriter.println("#" + TRACK_STYLE + ""); - writeCategory(track.getCategory()); - printWriter.println(""); - printWriter.println("absolute"); - printWriter.println("1"); - } - } - - @Override - public void writeEndTrack(Track track, Location endLocation) { - if (printWriter != null) { - printWriter.println(""); - printWriter.println(""); - String name = context.getString(R.string.marker_label_end, track.getName()); - String description = descriptionGenerator.generateTrackDescription(track, null, null, false); - writePlacemark(name, "", description, END_STYLE, endLocation); - } - } - - @Override - public void writeOpenSegment() { - if (printWriter != null) { - printWriter.println(""); - hasPower = false; - hasCadence = false; - hasHeartRate = false; - powerList.clear(); - cadenceList.clear(); - heartRateList.clear(); - } - } - - @Override - public void writeCloseSegment() { - if (printWriter != null) { - printWriter.println(""); - printWriter.println(""); - if (hasPower) { - writeSensorData(powerList, POWER); - } - if (hasCadence) { - writeSensorData(cadenceList, CADENCE); - } - if (hasHeartRate) { - writeSensorData(heartRateList, HEART_RATE); - } - printWriter.println(""); - printWriter.println(""); - printWriter.println(""); - } - } - - @Override - public void writeLocation(Location location) { - if (printWriter != null) { - printWriter.println( - "" + StringUtils.formatDateTimeIso8601(location.getTime()) + ""); - printWriter.println("" + getCoordinates(location, " ") + ""); - if (location instanceof SensorDataSetLocation) { - SensorDataSet sensorDataSet = ((SensorDataSetLocation) location).getSensorDataSet(); - if (sensorDataSet != null) { - if (sensorDataSet.hasHeartRate()) { - heartRateList.add(sensorDataSet.getHeartRate()); - } - if (sensorDataSet.hasCadence()) { - cadenceList.add(sensorDataSet.getCadence()); - } - if (sensorDataSet.hasPower()) { - powerList.add(sensorDataSet.getPower()); - } + @Override + public void close() { + if (printWriter != null) { + printWriter.flush(); + printWriter = null; } - } } - } - /** - * Writes the sensor data. - * - * @param list a list of sensor data - * @param name the name of the sensor data - */ - private void writeSensorData(ArrayList list, String name) { - printWriter.println(""); - for (int i = 0; i < list.size(); i++) { - printWriter.println("" + list.get(i) + ""); + @Override + public void writeHeader(Track[] tracks) { + if (printWriter != null) { + printWriter.println(""); + printWriter.println(""); + printWriter.println(""); + printWriter.println("1"); + printWriter.println("1"); + Track track = tracks[0]; + printWriter.println("" + StringUtils.formatCData(track.getName()) + ""); + printWriter.println("" + + StringUtils.formatCData(context.getString(R.string.app_name)) + + ""); + writeTrackStyle(); + writePlacemarkerStyle(START_STYLE, START_ICON, 32, 1); + writePlacemarkerStyle(END_STYLE, END_ICON, 32, 1); + writePlacemarkerStyle(STATISTICS_STYLE, STATISTICS_ICON, 20, 2); + writePlacemarkerStyle(WAYPOINT_STYLE, WAYPOINT_ICON, 20, 2); + printWriter.println(""); + writeSensorStyle(POWER, context.getString(R.string.description_sensor_power)); + writeSensorStyle(CADENCE, context.getString(R.string.description_sensor_cadence)); + writeSensorStyle(HEART_RATE, context.getString(R.string.description_sensor_heart_rate)); + printWriter.println(""); + } } - printWriter.println(""); - } - /** - * Writes a placemark. - * - * @param name the name - * @param category the category - * @param description the description - * @param styleName the style name - * @param location the location - */ - private void writePlacemark( - String name, String category, String description, String styleName, Location location) { - if (location != null) { - printWriter.println(""); - printWriter.println("" + StringUtils.formatCData(name) + ""); - printWriter.println( - "" + StringUtils.formatCData(description) + ""); - printWriter.println("" - + StringUtils.formatDateTimeIso8601(location.getTime()) + ""); - printWriter.println("#" + styleName + ""); - writeCategory(category); - printWriter.println(""); - printWriter.println("" + getCoordinates(location, ",") + ""); - printWriter.println(""); - printWriter.println(""); + @Override + public void writeFooter() { + if (printWriter != null) { + printWriter.println(""); + printWriter.println(""); + } } - } - - /** - * Writes a photo overlay. - * - * @param name the name - * @param category the category - * @param description the description - * @param styleName the style name - * @param location the location - * @param photoUrl the photo url - * @param heading the heading - */ - private void writePhotoOverlay(String name, String category, String description, String styleName, - Location location, String photoUrl, float heading) { - if (location != null) { - printWriter.println(""); - printWriter.println("" + StringUtils.formatCData(name) + ""); - printWriter.println( - "" + StringUtils.formatCData(description) + ""); - printWriter.print(""); - printWriter.print("" + location.getLongitude() + ""); - printWriter.print("" + location.getLatitude() + ""); - printWriter.print("20"); - printWriter.print("" + heading + ""); - printWriter.print("90"); - printWriter.println(""); - printWriter.println("" - + StringUtils.formatDateTimeIso8601(location.getTime()) + ""); - printWriter.println("#" + styleName + ""); - writeCategory(category); - if (playTrack) { - printWriter.println("" + Uri.decode(photoUrl) + ""); - } else { - Uri uri = Uri.parse(photoUrl); - printWriter.println("" + KmzTrackExporter.KMZ_IMAGES_DIR + File.separatorChar - + uri.getLastPathSegment() + ""); - } - - printWriter.print(""); - printWriter.print("10"); - printWriter.print("-60"); - printWriter.print("60"); - printWriter.print("-45"); - printWriter.print("45"); - printWriter.println(""); - printWriter.println(""); - printWriter.println("" + getCoordinates(location, ",") + ""); - printWriter.println(""); - printWriter.println(""); - } - } - /** - * Gets the heading to a location. - * - * @param trackId the track id containing the location - * @param location the location - */ - private float getHeading(long trackId, Location location) { - long trackPointId = contentProviderUtils.getTrackPointId(trackId, location); - if (trackPointId == -1L) { - return location.getBearing(); + @Override + public void writeBeginWaypoints(Track track) { + if (printWriter != null) { + printWriter.println("" + + StringUtils.formatCData(context.getString(R.string.track_markers, track.getName())) + + ""); + printWriter.println("1"); + } } - Location viewLocation; - try (Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackPointId, 10, true)) { - if (cursor == null || cursor.getCount() == 0) { - return location.getBearing(); - } - cursor.moveToPosition(cursor.getCount() - 1); - viewLocation = contentProviderUtils.createTrackPoint(cursor); + + @Override + public void writeEndWaypoints() { + if (printWriter != null) { + printWriter.println(""); + } } - return viewLocation.bearingTo(location); - } - - private String getCoordinates(Location location, String separator) { - StringBuilder builder = new StringBuilder(); - builder.append(location.getLongitude()).append(separator).append(location.getLatitude()); - if (location.hasAltitude()) { - builder.append(separator).append(location.getAltitude()); + + @Override + public void writeWaypoint(Waypoint waypoint) { + if (printWriter != null) { + String styleName = waypoint.getType() == WaypointType.STATISTICS ? STATISTICS_STYLE : WAYPOINT_STYLE; + if (waypoint.hasPhoto()) { + float heading = getHeading(waypoint.getTrackId(), waypoint.getLocation()); + writePhotoOverlay(waypoint.getName(), waypoint.getCategory(), waypoint.getDescription(), styleName, waypoint.getLocation(), waypoint.getPhotoUrl(), heading); + } else { + writePlacemark(waypoint.getName(), waypoint.getCategory(), waypoint.getDescription(), styleName, waypoint.getLocation()); + } + } } - return builder.toString(); - } - /** - * Writes the category. - * - * @param category the category - */ - private void writeCategory(String category) { - if (category == null || category.equals("")) { - return; + @Override + public void writeBeginTracks() { + if (printWriter != null && multiple) { + printWriter.println(""); + printWriter.println("" + context.getString(R.string.generic_tracks) + ""); + printWriter.println("1"); + } } - printWriter.println(""); - printWriter.println( - "" + StringUtils.formatCData(category) + ""); - printWriter.println(""); - } - /** - * Writes the track style. - */ - private void writeTrackStyle() { - printWriter.println(""); - } + @Override + public void writeEndTracks() { + if (printWriter != null && multiple) { + printWriter.println(""); + } + } - /** - * Writes a placemarker style. - * - * @param name the name of the style - * @param url the url of the style icon - * @param x the x position of the hotspot - * @param y the y position of the hotspot - */ - private void writePlacemarkerStyle(String name, String url, int x, int y) { - printWriter.println(""); - } + @Override + public void writeBeginTrack(Track track, Location startLocation) { + if (printWriter != null) { + String name = context.getString(R.string.marker_label_start, track.getName()); + writePlacemark(name, "", "", START_STYLE, startLocation); + printWriter.println(""); + printWriter.println("" + StringUtils.formatCData(track.getName()) + ""); + printWriter.println( + "" + StringUtils.formatCData(track.getDescription()) + ""); + printWriter.println("#" + TRACK_STYLE + ""); + writeCategory(track.getCategory()); + printWriter.println(""); + printWriter.println("absolute"); + printWriter.println("1"); + } + } - /** - * Writes a sensor style. - * - * @param name the name of the sesnor - * @param displayName the sensor display name - */ - private void writeSensorStyle(String name, String displayName) { - printWriter.println(""); - printWriter.println("" + StringUtils.formatCData(displayName) + ""); - printWriter.println(""); - } + @Override + public void writeEndTrack(Track track, Location endLocation) { + if (printWriter != null) { + printWriter.println(""); + printWriter.println(""); + String name = context.getString(R.string.marker_label_end, track.getName()); + String description = descriptionGenerator.generateTrackDescription(track, null, null, false); + writePlacemark(name, "", description, END_STYLE, endLocation); + } + } + + @Override + public void writeOpenSegment() { + if (printWriter != null) { + printWriter.println(""); + hasPower = false; + hasCadence = false; + hasHeartRate = false; + powerList.clear(); + cadenceList.clear(); + heartRateList.clear(); + } + } + + @Override + public void writeCloseSegment() { + if (printWriter != null) { + printWriter.println(""); + printWriter.println(""); + if (hasPower) { + writeSensorData(powerList, POWER); + } + if (hasCadence) { + writeSensorData(cadenceList, CADENCE); + } + if (hasHeartRate) { + writeSensorData(heartRateList, HEART_RATE); + } + printWriter.println(""); + printWriter.println(""); + printWriter.println(""); + } + } + + @Override + public void writeLocation(Location location) { + if (printWriter != null) { + printWriter.println( + "" + StringUtils.formatDateTimeIso8601(location.getTime()) + ""); + printWriter.println("" + getCoordinates(location, " ") + ""); + if (location instanceof SensorDataSetLocation) { + SensorDataSet sensorDataSet = ((SensorDataSetLocation) location).getSensorDataSet(); + if (sensorDataSet != null) { + if (sensorDataSet.hasHeartRate()) { + heartRateList.add(sensorDataSet.getHeartRate()); + } + if (sensorDataSet.hasCadence()) { + cadenceList.add(sensorDataSet.getCadence()); + } + if (sensorDataSet.hasPower()) { + powerList.add(sensorDataSet.getPower()); + } + } + } + } + } + + /** + * Writes the sensor data. + * + * @param list a list of sensor data + * @param name the name of the sensor data + */ + private void writeSensorData(ArrayList list, String name) { + printWriter.println(""); + for (int i = 0; i < list.size(); i++) { + printWriter.println("" + list.get(i) + ""); + } + printWriter.println(""); + } + + /** + * Writes a placemark. + * + * @param name the name + * @param category the category + * @param description the description + * @param styleName the style name + * @param location the location + */ + private void writePlacemark( + String name, String category, String description, String styleName, Location location) { + if (location != null) { + printWriter.println(""); + printWriter.println("" + StringUtils.formatCData(name) + ""); + printWriter.println( + "" + StringUtils.formatCData(description) + ""); + printWriter.println("" + + StringUtils.formatDateTimeIso8601(location.getTime()) + ""); + printWriter.println("#" + styleName + ""); + writeCategory(category); + printWriter.println(""); + printWriter.println("" + getCoordinates(location, ",") + ""); + printWriter.println(""); + printWriter.println(""); + } + } + + /** + * Writes a photo overlay. + * + * @param name the name + * @param category the category + * @param description the description + * @param styleName the style name + * @param location the location + * @param photoUrl the photo url + * @param heading the heading + */ + private void writePhotoOverlay(String name, String category, String description, String styleName, + Location location, String photoUrl, float heading) { + if (location != null) { + printWriter.println(""); + printWriter.println("" + StringUtils.formatCData(name) + ""); + printWriter.println( + "" + StringUtils.formatCData(description) + ""); + printWriter.print(""); + printWriter.print("" + location.getLongitude() + ""); + printWriter.print("" + location.getLatitude() + ""); + printWriter.print("20"); + printWriter.print("" + heading + ""); + printWriter.print("90"); + printWriter.println(""); + printWriter.println("" + + StringUtils.formatDateTimeIso8601(location.getTime()) + ""); + printWriter.println("#" + styleName + ""); + writeCategory(category); + if (playTrack) { + printWriter.println("" + Uri.decode(photoUrl) + ""); + } else { + Uri uri = Uri.parse(photoUrl); + printWriter.println("" + KmzTrackExporter.KMZ_IMAGES_DIR + File.separatorChar + + uri.getLastPathSegment() + ""); + } + + printWriter.print(""); + printWriter.print("10"); + printWriter.print("-60"); + printWriter.print("60"); + printWriter.print("-45"); + printWriter.print("45"); + printWriter.println(""); + printWriter.println(""); + printWriter.println("" + getCoordinates(location, ",") + ""); + printWriter.println(""); + printWriter.println(""); + } + } + + /** + * Gets the heading to a location. + * + * @param trackId the track id containing the location + * @param location the location + */ + private float getHeading(long trackId, Location location) { + long trackPointId = contentProviderUtils.getTrackPointId(trackId, location); + if (trackPointId == -1L) { + return location.getBearing(); + } + Location viewLocation; + try (Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackPointId, 10, true)) { + if (cursor == null || cursor.getCount() == 0) { + return location.getBearing(); + } + cursor.moveToPosition(cursor.getCount() - 1); + viewLocation = contentProviderUtils.createTrackPoint(cursor); + } + return viewLocation.bearingTo(location); + } + + private String getCoordinates(Location location, String separator) { + StringBuilder builder = new StringBuilder(); + builder.append(location.getLongitude()).append(separator).append(location.getLatitude()); + if (location.hasAltitude()) { + builder.append(separator).append(location.getAltitude()); + } + return builder.toString(); + } + + /** + * Writes the category. + * + * @param category the category + */ + private void writeCategory(String category) { + if (category == null || category.equals("")) { + return; + } + printWriter.println(""); + printWriter.println( + "" + StringUtils.formatCData(category) + ""); + printWriter.println(""); + } + + /** + * Writes the track style. + */ + private void writeTrackStyle() { + printWriter.println(""); + } + + /** + * Writes a placemarker style. + * + * @param name the name of the style + * @param url the url of the style icon + * @param x the x position of the hotspot + * @param y the y position of the hotspot + */ + private void writePlacemarkerStyle(String name, String url, int x, int y) { + printWriter.println(""); + } + + /** + * Writes a sensor style. + * + * @param name the name of the sesnor + * @param displayName the sensor display name + */ + private void writeSensorStyle(String name, String displayName) { + printWriter.println(""); + printWriter.println("" + StringUtils.formatCData(displayName) + ""); + printWriter.println(""); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/io/file/exporter/KmzTrackExporter.java b/src/main/java/com/google/android/apps/mytracks/io/file/exporter/KmzTrackExporter.java index 6cd06cf24..4012fb6b4 100644 --- a/src/main/java/com/google/android/apps/mytracks/io/file/exporter/KmzTrackExporter.java +++ b/src/main/java/com/google/android/apps/mytracks/io/file/exporter/KmzTrackExporter.java @@ -16,12 +16,6 @@ package com.google.android.apps.mytracks.io.file.exporter; -import com.google.android.apps.mytracks.content.ContentProviderUtils; -import com.google.android.apps.mytracks.content.Track; -import com.google.android.apps.mytracks.content.Waypoint; -import com.google.android.apps.mytracks.util.PreferencesUtils; -import com.google.android.maps.mytracks.R; - import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; @@ -29,6 +23,12 @@ import android.graphics.BitmapFactory; import android.net.Uri; import android.util.Log; +import com.google.android.apps.mytracks.content.ContentProviderUtils; +import com.google.android.apps.mytracks.content.Track; +import com.google.android.apps.mytracks.content.Waypoint; +import com.google.android.apps.mytracks.util.PreferencesUtils; +import com.google.android.maps.mytracks.R; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -38,148 +38,148 @@ import java.util.zip.ZipOutputStream; /** * KMZ track exporter. - * + * * @author Jimmy Shih */ public class KmzTrackExporter implements TrackExporter { - public static final String KMZ_EXTENSION = "kmz"; - public static final String KMZ_IMAGES_DIR = "images"; - public static final String KMZ_KML_FILE = "doc.kml"; + public static final String KMZ_EXTENSION = "kmz"; + public static final String KMZ_IMAGES_DIR = "images"; + public static final String KMZ_KML_FILE = "doc.kml"; - private static final String TAG = KmzTrackExporter.class.getSimpleName(); - private static final int BUFFER_SIZE = 4096; + private static final String TAG = KmzTrackExporter.class.getSimpleName(); + private static final int BUFFER_SIZE = 4096; - private final ContentProviderUtils contentProviderUtils; - private final FileTrackExporter fileTrackExporter; - private final Track[] tracks; - private final long photoSize; + private final ContentProviderUtils contentProviderUtils; + private final FileTrackExporter fileTrackExporter; + private final Track[] tracks; + private final long photoSize; - /** - * Constructor. - * - * @param contentProviderUtils the content provider utils - * @param fileTrackExporter the file track exporter - * @param tracks the tracks to export - * @param context the context - */ - public KmzTrackExporter(ContentProviderUtils contentProviderUtils, - FileTrackExporter fileTrackExporter, Track[] tracks, Context context) { - this.contentProviderUtils = contentProviderUtils; - this.fileTrackExporter = fileTrackExporter; - this.tracks = tracks; - this.photoSize = PreferencesUtils.getInt( - context, R.string.photo_size_key, PreferencesUtils.PHOTO_SIZE_DEFAULT); - } + /** + * Constructor. + * + * @param contentProviderUtils the content provider utils + * @param fileTrackExporter the file track exporter + * @param tracks the tracks to export + * @param context the context + */ + public KmzTrackExporter(ContentProviderUtils contentProviderUtils, + FileTrackExporter fileTrackExporter, Track[] tracks, Context context) { + this.contentProviderUtils = contentProviderUtils; + this.fileTrackExporter = fileTrackExporter; + this.tracks = tracks; + this.photoSize = PreferencesUtils.getInt( + context, R.string.photo_size_key, PreferencesUtils.PHOTO_SIZE_DEFAULT); + } - @Override - public boolean writeTrack(OutputStream outputStream) { - ZipOutputStream zipOutputStream = null; - try { - zipOutputStream = new ZipOutputStream(outputStream); - - // Add kml file - ZipEntry zipEntry = new ZipEntry(KMZ_KML_FILE); - zipOutputStream.putNextEntry(zipEntry); - - boolean success = fileTrackExporter.writeTrack(zipOutputStream); - zipOutputStream.closeEntry(); - if (!success) { - Log.e(TAG, "Unable to write kml in kmz"); - return false; - } - - // Add photos - addImages(zipOutputStream); - return true; - } catch (InterruptedException e) { - Log.e(TAG, "Unable to write track", e); - return false; - } catch (IOException e) { - Log.e(TAG, "Unable to write track", e); - return false; - } finally { - if (zipOutputStream != null) { + @Override + public boolean writeTrack(OutputStream outputStream) { + ZipOutputStream zipOutputStream = null; try { - zipOutputStream.close(); + zipOutputStream = new ZipOutputStream(outputStream); + + // Add kml file + ZipEntry zipEntry = new ZipEntry(KMZ_KML_FILE); + zipOutputStream.putNextEntry(zipEntry); + + boolean success = fileTrackExporter.writeTrack(zipOutputStream); + zipOutputStream.closeEntry(); + if (!success) { + Log.e(TAG, "Unable to write kml in kmz"); + return false; + } + + // Add photos + addImages(zipOutputStream); + return true; + } catch (InterruptedException e) { + Log.e(TAG, "Unable to write track", e); + return false; } catch (IOException e) { - Log.e(TAG, "Unable to close zip input stream", e); - } - } - } - } - - private void addImages(ZipOutputStream zipOutputStream) throws InterruptedException, IOException { - for (Track track : tracks) { - try (Cursor cursor = contentProviderUtils.getWaypointCursor(track.getId(), -1L, -1)) { - if (cursor != null && cursor.moveToFirst()) { - /* - * Yes, this will skip the first waypoint and that is intentional as - * the first waypoint holds the stats for the track. - */ - while (cursor.moveToNext()) { - if (Thread.interrupted()) { - throw new InterruptedException(); + Log.e(TAG, "Unable to write track", e); + return false; + } finally { + if (zipOutputStream != null) { + try { + zipOutputStream.close(); + } catch (IOException e) { + Log.e(TAG, "Unable to close zip input stream", e); + } } - Waypoint waypoint = contentProviderUtils.createWaypoint(cursor); - if (waypoint.hasPhoto()) { - addImage(zipOutputStream, waypoint.getPhotoUrl()); - } - } } - } - } - } - - private void addImage(ZipOutputStream zipOutputStream, String photoUrl) throws IOException { - Uri uri = Uri.parse(photoUrl); - File file = new File(uri.getPath()); - if (!file.exists()) { - Log.e(TAG, "file not found " + photoUrl); - return; } - ZipEntry zipEntry = new ZipEntry( - KMZ_IMAGES_DIR + File.separatorChar + uri.getLastPathSegment()); - zipOutputStream.putNextEntry(zipEntry); - - int sampleSize; - if (photoSize == -1) { - sampleSize = 1; - } else { - long size = file.length(); - // Convert from kilobytes to bytes. - long limit = photoSize * 1024; - sampleSize = size > limit ? (int) Math.ceil(size / limit) : 1; + private void addImages(ZipOutputStream zipOutputStream) throws InterruptedException, IOException { + for (Track track : tracks) { + try (Cursor cursor = contentProviderUtils.getWaypointCursor(track.getId(), -1L, -1)) { + if (cursor != null && cursor.moveToFirst()) { + /* + * Yes, this will skip the first waypoint and that is intentional as + * the first waypoint holds the stats for the track. + */ + while (cursor.moveToNext()) { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + Waypoint waypoint = contentProviderUtils.createWaypoint(cursor); + if (waypoint.hasPhoto()) { + addImage(zipOutputStream, waypoint.getPhotoUrl()); + } + } + } + } + } } - if (sampleSize == 1) { - readFromFile(zipOutputStream, uri); - } else { - readFromScaledBitmap(zipOutputStream, uri, sampleSize); - } - zipOutputStream.closeEntry(); - } - private void readFromScaledBitmap(ZipOutputStream zipOutputStream, Uri uri, int sampleSize) { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = false; - options.inSampleSize = sampleSize; + private void addImage(ZipOutputStream zipOutputStream, String photoUrl) throws IOException { + Uri uri = Uri.parse(photoUrl); + File file = new File(uri.getPath()); + if (!file.exists()) { + Log.e(TAG, "file not found " + photoUrl); + return; + } - Bitmap bitmap = BitmapFactory.decodeFile(uri.getPath(), options); - if (bitmap == null) { - return; - } - bitmap.compress(Bitmap.CompressFormat.JPEG, 100, zipOutputStream); - bitmap.recycle(); - } + ZipEntry zipEntry = new ZipEntry( + KMZ_IMAGES_DIR + File.separatorChar + uri.getLastPathSegment()); + zipOutputStream.putNextEntry(zipEntry); - private void readFromFile(ZipOutputStream zipOutputStream, Uri uri) throws IOException { - try (FileInputStream fileInputStream = new FileInputStream(new File(uri.getPath()))) { - byte[] buffer = new byte[BUFFER_SIZE]; - int byteCount; - while ((byteCount = fileInputStream.read(buffer)) != -1) { - zipOutputStream.write(buffer, 0, byteCount); - } + int sampleSize; + if (photoSize == -1) { + sampleSize = 1; + } else { + long size = file.length(); + // Convert from kilobytes to bytes. + long limit = photoSize * 1024; + sampleSize = size > limit ? (int) Math.ceil(size / limit) : 1; + } + if (sampleSize == 1) { + readFromFile(zipOutputStream, uri); + } else { + readFromScaledBitmap(zipOutputStream, uri, sampleSize); + } + zipOutputStream.closeEntry(); + } + + private void readFromScaledBitmap(ZipOutputStream zipOutputStream, Uri uri, int sampleSize) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = false; + options.inSampleSize = sampleSize; + + Bitmap bitmap = BitmapFactory.decodeFile(uri.getPath(), options); + if (bitmap == null) { + return; + } + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, zipOutputStream); + bitmap.recycle(); + } + + private void readFromFile(ZipOutputStream zipOutputStream, Uri uri) throws IOException { + try (FileInputStream fileInputStream = new FileInputStream(new File(uri.getPath()))) { + byte[] buffer = new byte[BUFFER_SIZE]; + int byteCount; + while ((byteCount = fileInputStream.read(buffer)) != -1) { + zipOutputStream.write(buffer, 0, byteCount); + } + } } - } } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/io/file/exporter/SaveActivity.java b/src/main/java/com/google/android/apps/mytracks/io/file/exporter/SaveActivity.java index e64731030..e18b71445 100644 --- a/src/main/java/com/google/android/apps/mytracks/io/file/exporter/SaveActivity.java +++ b/src/main/java/com/google/android/apps/mytracks/io/file/exporter/SaveActivity.java @@ -16,12 +16,6 @@ package com.google.android.apps.mytracks.io.file.exporter; -import com.google.android.apps.mytracks.io.file.TrackFileFormat; -import com.google.android.apps.mytracks.util.DialogUtils; -import com.google.android.apps.mytracks.util.FileUtils; -import com.google.android.apps.mytracks.util.IntentUtils; -import com.google.android.maps.mytracks.R; - import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; @@ -31,197 +25,203 @@ import android.content.Intent; import android.os.Bundle; import android.widget.Toast; +import com.google.android.apps.mytracks.io.file.TrackFileFormat; +import com.google.android.apps.mytracks.util.DialogUtils; +import com.google.android.apps.mytracks.util.FileUtils; +import com.google.android.apps.mytracks.util.IntentUtils; +import com.google.android.maps.mytracks.R; + import java.io.File; /** * An activity for saving tracks to the external storage. If saving a specific * track, option to save it to a temp directory and play the track afterward. - * + * * @author Rodrigo Damazio */ public class SaveActivity extends Activity { - public static final String EXTRA_TRACK_FILE_FORMAT = "track_file_format"; - public static final String EXTRA_TRACK_IDS = "track_ids"; + public static final String EXTRA_TRACK_FILE_FORMAT = "track_file_format"; + public static final String EXTRA_TRACK_IDS = "track_ids"; - @Deprecated //TODO Seems to be a left over from Google Earth integration and can be removed. - public static final String EXTRA_PLAY_TRACK = "play_track"; + @Deprecated //TODO Seems to be a left over from Google Earth integration and can be removed. + public static final String EXTRA_PLAY_TRACK = "play_track"; - private static final int DIALOG_PROGRESS_ID = 0; - private static final int DIALOG_RESULT_ID = 1; + private static final int DIALOG_PROGRESS_ID = 0; + private static final int DIALOG_RESULT_ID = 1; - private TrackFileFormat trackFileFormat; - private long[] trackIds; - private boolean playTrack; - private String directoryDisplayName; + private TrackFileFormat trackFileFormat; + private long[] trackIds; + private boolean playTrack; + private String directoryDisplayName; - private SaveAsyncTask saveAsyncTask; - private ProgressDialog progressDialog; + private SaveAsyncTask saveAsyncTask; + private ProgressDialog progressDialog; - // the number of tracks successfully saved - private int successCount; + // the number of tracks successfully saved + private int successCount; - // the number of tracks to save - private int totalCount; + // the number of tracks to save + private int totalCount; - // the last successfully saved path - private String savedPath; + // the last successfully saved path + private String savedPath; - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - Intent intent = getIntent(); - trackFileFormat = intent.getParcelableExtra(EXTRA_TRACK_FILE_FORMAT); - trackIds = intent.getLongArrayExtra(EXTRA_TRACK_IDS); - playTrack = intent.getBooleanExtra(EXTRA_PLAY_TRACK, false); + Intent intent = getIntent(); + trackFileFormat = intent.getParcelableExtra(EXTRA_TRACK_FILE_FORMAT); + trackIds = intent.getLongArrayExtra(EXTRA_TRACK_IDS); + playTrack = intent.getBooleanExtra(EXTRA_PLAY_TRACK, false); - if (!FileUtils.isExternalStorageWriteable()) { - Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show(); - finish(); - return; - } + if (!FileUtils.isExternalStorageWriteable()) { + Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show(); + finish(); + return; + } - File directory = playTrack ? new File(getCacheDir(), FileUtils.PLAY_TRACKS_DIR) - : new File(FileUtils.getPath(trackFileFormat.getExtension())); - if (!FileUtils.ensureDirectoryExists(directory)) { - Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show(); - finish(); - return; - } + File directory = playTrack ? new File(getCacheDir(), FileUtils.PLAY_TRACKS_DIR) + : new File(FileUtils.getPath(trackFileFormat.getExtension())); + if (!FileUtils.ensureDirectoryExists(directory)) { + Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show(); + finish(); + return; + } - if (playTrack) { - for (File file : directory.listFiles()) { - file.delete(); - } - } + if (playTrack) { + for (File file : directory.listFiles()) { + file.delete(); + } + } - directoryDisplayName = playTrack ? directory.getName() - : FileUtils.getPathDisplayName(trackFileFormat.getExtension()); + directoryDisplayName = playTrack ? directory.getName() + : FileUtils.getPathDisplayName(trackFileFormat.getExtension()); - Object retained = getLastNonConfigurationInstance(); - if (retained instanceof SaveAsyncTask) { - saveAsyncTask = (SaveAsyncTask) retained; - saveAsyncTask.setActivity(this); - } else { - saveAsyncTask = new SaveAsyncTask(this, trackIds, trackFileFormat, playTrack, directory); - saveAsyncTask.execute(); - } - } - - @Override - public Object onRetainNonConfigurationInstance() { - saveAsyncTask.setActivity(null); - return saveAsyncTask; - } - - @Override - protected Dialog onCreateDialog(int id) { - switch (id) { - case DIALOG_PROGRESS_ID: - progressDialog = DialogUtils.createHorizontalProgressDialog(this, - R.string.export_external_storage_progress_message, - new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - saveAsyncTask.cancel(true); - dialog.dismiss(); - } - }, directoryDisplayName); - return progressDialog; - case DIALOG_RESULT_ID: - int iconId; - int titleId; - String message; - String totalTracks = getResources() - .getQuantityString(R.plurals.tracks, totalCount, totalCount); - if (successCount == totalCount) { - iconId = R.drawable.ic_dialog_success; - titleId = R.string.generic_success_title; - message = getString( - R.string.export_external_storage_success, totalTracks, directoryDisplayName); + Object retained = getLastNonConfigurationInstance(); + if (retained instanceof SaveAsyncTask) { + saveAsyncTask = (SaveAsyncTask) retained; + saveAsyncTask.setActivity(this); } else { - iconId = android.R.drawable.ic_dialog_alert; - titleId = R.string.generic_error_title; - message = getString(R.string.export_external_storage_error, successCount, totalTracks, - directoryDisplayName); + saveAsyncTask = new SaveAsyncTask(this, trackIds, trackFileFormat, playTrack, directory); + saveAsyncTask.execute(); } - AlertDialog.Builder builder = new AlertDialog.Builder(this).setCancelable(true) - .setIcon(iconId).setMessage(message) - .setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - dialog.dismiss(); - finish(); - } - }).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int arg1) { - dialog.dismiss(); - finish(); - } - }).setTitle(titleId); - if (!playTrack && trackIds.length == 1 && trackIds[0] != -1L && successCount == totalCount - && savedPath != null) { - builder.setNegativeButton( - R.string.share_track_share_file, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Intent intent = IntentUtils.newShareFileIntent( - SaveActivity.this, trackIds[0], savedPath, trackFileFormat); - startActivity( - Intent.createChooser(intent, getString(R.string.share_track_picker_title))); - finish(); + } + + @Override + public Object onRetainNonConfigurationInstance() { + saveAsyncTask.setActivity(null); + return saveAsyncTask; + } + + @Override + protected Dialog onCreateDialog(int id) { + switch (id) { + case DIALOG_PROGRESS_ID: + progressDialog = DialogUtils.createHorizontalProgressDialog(this, + R.string.export_external_storage_progress_message, + new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + saveAsyncTask.cancel(true); + dialog.dismiss(); + } + }, directoryDisplayName); + return progressDialog; + case DIALOG_RESULT_ID: + int iconId; + int titleId; + String message; + String totalTracks = getResources() + .getQuantityString(R.plurals.tracks, totalCount, totalCount); + if (successCount == totalCount) { + iconId = R.drawable.ic_dialog_success; + titleId = R.string.generic_success_title; + message = getString( + R.string.export_external_storage_success, totalTracks, directoryDisplayName); + } else { + iconId = android.R.drawable.ic_dialog_alert; + titleId = R.string.generic_error_title; + message = getString(R.string.export_external_storage_error, successCount, totalTracks, + directoryDisplayName); } - }); + AlertDialog.Builder builder = new AlertDialog.Builder(this).setCancelable(true) + .setIcon(iconId).setMessage(message) + .setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + dialog.dismiss(); + finish(); + } + }).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int arg1) { + dialog.dismiss(); + finish(); + } + }).setTitle(titleId); + if (!playTrack && trackIds.length == 1 && trackIds[0] != -1L && successCount == totalCount + && savedPath != null) { + builder.setNegativeButton( + R.string.share_track_share_file, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intent = IntentUtils.newShareFileIntent( + SaveActivity.this, trackIds[0], savedPath, trackFileFormat); + startActivity( + Intent.createChooser(intent, getString(R.string.share_track_picker_title))); + finish(); + } + }); + } + final Dialog dialog = builder.create(); + dialog.setOnShowListener(new DialogInterface.OnShowListener() { + + @Override + public void onShow(DialogInterface dialogInterface) { + DialogUtils.setDialogTitleDivider(SaveActivity.this, dialog); + } + }); + return dialog; + default: + return null; } - final Dialog dialog = builder.create(); - dialog.setOnShowListener(new DialogInterface.OnShowListener() { - - @Override - public void onShow(DialogInterface dialogInterface) { - DialogUtils.setDialogTitleDivider(SaveActivity.this, dialog); - } - }); - return dialog; - default: - return null; } - } - /** - * Invokes when the associated AsyncTask completes. - * - * @param aSuccessCount the number of tracks successfully saved - * @param aTotalCount the number of tracks to save - * @param aSavedPath the last successfully saved path - */ - public void onAsyncTaskCompleted(int aSuccessCount, int aTotalCount, String aSavedPath) { - successCount = aSuccessCount; - totalCount = aTotalCount; - savedPath = aSavedPath; - removeDialog(DIALOG_PROGRESS_ID); - showDialog(DIALOG_RESULT_ID); - } - - /** - * Shows the progress dialog. - */ - public void showProgressDialog() { - showDialog(DIALOG_PROGRESS_ID); - } - - /** - * Sets the progress dialog value. - * - * @param number the number of points saved - * @param max the maximum number of points - */ - public void setProgressDialogValue(int number, int max) { - if (progressDialog != null) { - progressDialog.setIndeterminate(false); - progressDialog.setMax(max); - progressDialog.setProgress(Math.min(number, max)); + /** + * Invokes when the associated AsyncTask completes. + * + * @param aSuccessCount the number of tracks successfully saved + * @param aTotalCount the number of tracks to save + * @param aSavedPath the last successfully saved path + */ + public void onAsyncTaskCompleted(int aSuccessCount, int aTotalCount, String aSavedPath) { + successCount = aSuccessCount; + totalCount = aTotalCount; + savedPath = aSavedPath; + removeDialog(DIALOG_PROGRESS_ID); + showDialog(DIALOG_RESULT_ID); + } + + /** + * Shows the progress dialog. + */ + public void showProgressDialog() { + showDialog(DIALOG_PROGRESS_ID); + } + + /** + * Sets the progress dialog value. + * + * @param number the number of points saved + * @param max the maximum number of points + */ + public void setProgressDialogValue(int number, int max) { + if (progressDialog != null) { + progressDialog.setIndeterminate(false); + progressDialog.setMax(max); + progressDialog.setProgress(Math.min(number, max)); + } } - } } diff --git a/src/main/java/com/google/android/apps/mytracks/io/file/exporter/SaveAsyncTask.java b/src/main/java/com/google/android/apps/mytracks/io/file/exporter/SaveAsyncTask.java index 2fd5bea7b..c9cc09089 100644 --- a/src/main/java/com/google/android/apps/mytracks/io/file/exporter/SaveAsyncTask.java +++ b/src/main/java/com/google/android/apps/mytracks/io/file/exporter/SaveAsyncTask.java @@ -38,220 +38,218 @@ import java.io.IOException; /** * Async Task to save tracks to the external storage. - * + * * @author Jimmy Shih */ public class SaveAsyncTask extends AsyncTask { - private static final String TAG = SaveAsyncTask.class.getSimpleName(); + private static final String TAG = SaveAsyncTask.class.getSimpleName(); + private final long[] trackIds; + private final TrackFileFormat trackFileFormat; + @Deprecated //TODO Seems to be a left over from Google Earth integration and can be removed. + private final boolean playTrack; + private final File directory; + private final Context context; + private final ContentProviderUtils contentProviderUtils; + private SaveActivity saveActivity; + private WakeLock wakeLock; - private SaveActivity saveActivity; - private final long[] trackIds; - private final TrackFileFormat trackFileFormat; - @Deprecated //TODO Seems to be a left over from Google Earth integration and can be removed. - private final boolean playTrack; - private final File directory; - private final Context context; - private final ContentProviderUtils contentProviderUtils; + // true if the AsyncTask has completed + private boolean completed; - private WakeLock wakeLock; + // the number of tracks successfully saved + private int successCount; - // true if the AsyncTask has completed - private boolean completed; + // the number of tracks to save + private int totalCount; - // the number of tracks successfully saved - private int successCount; + // the last successfully saved path + private String savedPath; - // the number of tracks to save - private int totalCount; + /** + * Creates an AsyncTask. + * + * @param saveActivity the activity currently associated with this task + * @param trackIds the track ids to save. To save all, set to size 1 with + * trackIds[0] == -1L + * @param trackFileFormat the track file format + * @param playTrack true to play track + * @param directory the directory to write the file + */ + public SaveAsyncTask(SaveActivity saveActivity, long[] trackIds, TrackFileFormat trackFileFormat, boolean playTrack, File directory) { + this.saveActivity = saveActivity; + this.trackIds = trackIds; + this.trackFileFormat = trackFileFormat; + this.playTrack = playTrack; + this.directory = directory; + context = saveActivity.getApplicationContext(); + contentProviderUtils = ContentProviderUtils.Factory.get(context); - // the last successfully saved path - private String savedPath; - - /** - * Creates an AsyncTask. - * - * @param saveActivity the activity currently associated with this task - * @param trackIds the track ids to save. To save all, set to size 1 with - * trackIds[0] == -1L - * @param trackFileFormat the track file format - * @param playTrack true to play track - * @param directory the directory to write the file - */ - public SaveAsyncTask(SaveActivity saveActivity, long[] trackIds, TrackFileFormat trackFileFormat, boolean playTrack, File directory) { - this.saveActivity = saveActivity; - this.trackIds = trackIds; - this.trackFileFormat = trackFileFormat; - this.playTrack = playTrack; - this.directory = directory; - context = saveActivity.getApplicationContext(); - contentProviderUtils = ContentProviderUtils.Factory.get(context); - - completed = false; - successCount = 0; - totalCount = 0; - savedPath = null; - } - - /** - * Sets the current activity associated with this AsyncTask. - * - * @param saveActivity the current activity, can be null - */ - public void setActivity(SaveActivity saveActivity) { - this.saveActivity = saveActivity; - if (completed && saveActivity != null) { - saveActivity.onAsyncTaskCompleted(successCount, totalCount, savedPath); + completed = false; + successCount = 0; + totalCount = 0; + savedPath = null; } - } - @Override - protected void onPreExecute() { - if (saveActivity != null) { - saveActivity.showProgressDialog(); - } - } - - @Override - protected Boolean doInBackground(Void... params) { - try { - Thread.currentThread().setPriority(Thread.MAX_PRIORITY); - boolean isRecording = PreferencesUtils.getLong(saveActivity, R.string.recording_track_id_key) != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; - boolean isPaused = PreferencesUtils.getBoolean(saveActivity, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); - // Get the wake lock if not recording or paused - if (!isRecording || isPaused) { - wakeLock = SystemUtils.acquireWakeLock(saveActivity, wakeLock); - } - if (trackIds.length == 1 && trackIds[0] == -1L) { - return saveAllTracks(); - } else { - totalCount = 1; - Track[] tracks = new Track[trackIds.length]; - for (int i = 0; i < trackIds.length; i++) { - tracks[i] = contentProviderUtils.getTrack(trackIds[i]); - if (tracks[i] == null) { - Log.d(TAG, "No track for " + trackIds[i]); - return false; - } + /** + * Sets the current activity associated with this AsyncTask. + * + * @param saveActivity the current activity, can be null + */ + public void setActivity(SaveActivity saveActivity) { + this.saveActivity = saveActivity; + if (completed && saveActivity != null) { + saveActivity.onAsyncTaskCompleted(successCount, totalCount, savedPath); } - if (saveTracks(tracks)) { - successCount = 1; - return true; - } else { - return false; + } + + @Override + protected void onPreExecute() { + if (saveActivity != null) { + saveActivity.showProgressDialog(); } - } - } finally { - if (wakeLock != null && wakeLock.isHeld()) { - wakeLock.release(); - } } - } - @Override - protected void onProgressUpdate(Integer... values) { - if (saveActivity != null) { - saveActivity.setProgressDialogValue(values[0], values[1]); - } - } - - @Override - protected void onPostExecute(Boolean result) { - completed = true; - if (saveActivity != null) { - saveActivity.onAsyncTaskCompleted(successCount, totalCount, savedPath); - } - } - - @Override - protected void onCancelled() { - completed = true; - if (saveActivity != null) { - saveActivity.onAsyncTaskCompleted(successCount, totalCount, null); - } - } - - /** - * Saves tracks to one file. - * - * @param tracks the tracks - */ - private Boolean saveTracks(Track[] tracks) { - if (tracks.length == 0) { - return false; - } - - Track track = tracks[0]; - boolean useKmz = trackFileFormat == TrackFileFormat.KML && !playTrack; - String extension = useKmz ? KmzTrackExporter.KMZ_EXTENSION : trackFileFormat.getExtension(); - FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, tracks, - trackFileFormat.newTrackWriter(context, tracks.length > 1, playTrack), - new TrackExporterListener() { - - @Override - public void onProgressUpdate(int number, int max) { - /* - * If only saving one track, update the progress dialog once every - * 500 points - */ - if (trackIds.length == 1 && trackIds[0] != -1L && number % 500 == 0) { - publishProgress(number, max); - } - } - }); - - TrackExporter trackExporter = useKmz ? new KmzTrackExporter(contentProviderUtils, fileTrackExporter, tracks, context) : fileTrackExporter; - - String fileName = FileUtils.buildUniqueFileName(directory, track.getName(), extension); - File file = new File(directory, fileName); - FileOutputStream fileOutputStream = null; - try { - fileOutputStream = new FileOutputStream(file); - if (trackExporter.writeTrack(fileOutputStream)) { - savedPath = file.getAbsolutePath(); - return true; - } else { - if (!file.delete()) { - Log.d(TAG, "Unable to delete file"); - } - Log.e(TAG, "Unable to export track"); - return false; - } - } catch (FileNotFoundException e) { - Log.e(TAG, "Unable to open file " + file.getName(), e); - return false; - } finally { - if (fileOutputStream != null) { + @Override + protected Boolean doInBackground(Void... params) { try { - fileOutputStream.close(); - } catch (IOException e) { - Log.e(TAG, "Unable to close file output stream", e); + Thread.currentThread().setPriority(Thread.MAX_PRIORITY); + boolean isRecording = PreferencesUtils.getLong(saveActivity, R.string.recording_track_id_key) != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; + boolean isPaused = PreferencesUtils.getBoolean(saveActivity, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); + // Get the wake lock if not recording or paused + if (!isRecording || isPaused) { + wakeLock = SystemUtils.acquireWakeLock(saveActivity, wakeLock); + } + if (trackIds.length == 1 && trackIds[0] == -1L) { + return saveAllTracks(); + } else { + totalCount = 1; + Track[] tracks = new Track[trackIds.length]; + for (int i = 0; i < trackIds.length; i++) { + tracks[i] = contentProviderUtils.getTrack(trackIds[i]); + if (tracks[i] == null) { + Log.d(TAG, "No track for " + trackIds[i]); + return false; + } + } + if (saveTracks(tracks)) { + successCount = 1; + return true; + } else { + return false; + } + } + } finally { + if (wakeLock != null && wakeLock.isHeld()) { + wakeLock.release(); + } } - } } - } - /** - * Saves all the tracks. - */ - private Boolean saveAllTracks() { - try (Cursor cursor = contentProviderUtils.getTrackCursor(null, null, TracksColumns._ID)) { - if (cursor == null) { - return false; - } - totalCount = cursor.getCount(); - for (int i = 0; i < totalCount; i++) { - if (isCancelled()) { - return false; - } - cursor.moveToPosition(i); - Track track = contentProviderUtils.createTrack(cursor); - if (track != null && saveTracks(new Track[]{track})) { - successCount++; - } - publishProgress(i + 1, totalCount); - } - return true; - } - } + @Override + protected void onProgressUpdate(Integer... values) { + if (saveActivity != null) { + saveActivity.setProgressDialogValue(values[0], values[1]); + } + } + + @Override + protected void onPostExecute(Boolean result) { + completed = true; + if (saveActivity != null) { + saveActivity.onAsyncTaskCompleted(successCount, totalCount, savedPath); + } + } + + @Override + protected void onCancelled() { + completed = true; + if (saveActivity != null) { + saveActivity.onAsyncTaskCompleted(successCount, totalCount, null); + } + } + + /** + * Saves tracks to one file. + * + * @param tracks the tracks + */ + private Boolean saveTracks(Track[] tracks) { + if (tracks.length == 0) { + return false; + } + + Track track = tracks[0]; + boolean useKmz = trackFileFormat == TrackFileFormat.KML && !playTrack; + String extension = useKmz ? KmzTrackExporter.KMZ_EXTENSION : trackFileFormat.getExtension(); + FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, tracks, + trackFileFormat.newTrackWriter(context, tracks.length > 1, playTrack), + new TrackExporterListener() { + + @Override + public void onProgressUpdate(int number, int max) { + /* + * If only saving one track, update the progress dialog once every + * 500 points + */ + if (trackIds.length == 1 && trackIds[0] != -1L && number % 500 == 0) { + publishProgress(number, max); + } + } + }); + + TrackExporter trackExporter = useKmz ? new KmzTrackExporter(contentProviderUtils, fileTrackExporter, tracks, context) : fileTrackExporter; + + String fileName = FileUtils.buildUniqueFileName(directory, track.getName(), extension); + File file = new File(directory, fileName); + FileOutputStream fileOutputStream = null; + try { + fileOutputStream = new FileOutputStream(file); + if (trackExporter.writeTrack(fileOutputStream)) { + savedPath = file.getAbsolutePath(); + return true; + } else { + if (!file.delete()) { + Log.d(TAG, "Unable to delete file"); + } + Log.e(TAG, "Unable to export track"); + return false; + } + } catch (FileNotFoundException e) { + Log.e(TAG, "Unable to open file " + file.getName(), e); + return false; + } finally { + if (fileOutputStream != null) { + try { + fileOutputStream.close(); + } catch (IOException e) { + Log.e(TAG, "Unable to close file output stream", e); + } + } + } + } + + /** + * Saves all the tracks. + */ + private Boolean saveAllTracks() { + try (Cursor cursor = contentProviderUtils.getTrackCursor(null, null, TracksColumns._ID)) { + if (cursor == null) { + return false; + } + totalCount = cursor.getCount(); + for (int i = 0; i < totalCount; i++) { + if (isCancelled()) { + return false; + } + cursor.moveToPosition(i); + Track track = contentProviderUtils.createTrack(cursor); + if (track != null && saveTracks(new Track[]{track})) { + successCount++; + } + publishProgress(i + 1, totalCount); + } + return true; + } + } } diff --git a/src/main/java/com/google/android/apps/mytracks/io/file/exporter/TrackExporter.java b/src/main/java/com/google/android/apps/mytracks/io/file/exporter/TrackExporter.java index 7254e8eff..83207bfa4 100644 --- a/src/main/java/com/google/android/apps/mytracks/io/file/exporter/TrackExporter.java +++ b/src/main/java/com/google/android/apps/mytracks/io/file/exporter/TrackExporter.java @@ -20,15 +20,15 @@ import java.io.OutputStream; /** * Track exporting for exporting track to an {@link OutputStream}. - * + * * @author Jimmy Shih */ public interface TrackExporter { - - /** - * Write track to an output stream - * - * @param outputStream the output stream - */ - boolean writeTrack(OutputStream outputStream); + + /** + * Write track to an output stream + * + * @param outputStream the output stream + */ + boolean writeTrack(OutputStream outputStream); } diff --git a/src/main/java/com/google/android/apps/mytracks/io/file/exporter/TrackExporterListener.java b/src/main/java/com/google/android/apps/mytracks/io/file/exporter/TrackExporterListener.java index 86106f903..0c164b78b 100644 --- a/src/main/java/com/google/android/apps/mytracks/io/file/exporter/TrackExporterListener.java +++ b/src/main/java/com/google/android/apps/mytracks/io/file/exporter/TrackExporterListener.java @@ -17,17 +17,17 @@ package com.google.android.apps.mytracks.io.file.exporter; /** * Listener for {@link TrackExporter} progress. - * + * * @author Jimmy Shih */ public interface TrackExporterListener { - /** - * Called to update progress. - * - * @param number the number of locations written - * @param max the maximum number of locations in a track, for calculation of - * completion percentage - */ - void onProgressUpdate(int number, int max); + /** + * Called to update progress. + * + * @param number the number of locations written + * @param max the maximum number of locations in a track, for calculation of + * completion percentage + */ + void onProgressUpdate(int number, int max); } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/io/file/exporter/TrackWriter.java b/src/main/java/com/google/android/apps/mytracks/io/file/exporter/TrackWriter.java index e4bbbaced..f2627d1dc 100644 --- a/src/main/java/com/google/android/apps/mytracks/io/file/exporter/TrackWriter.java +++ b/src/main/java/com/google/android/apps/mytracks/io/file/exporter/TrackWriter.java @@ -15,16 +15,16 @@ */ package com.google.android.apps.mytracks.io.file.exporter; +import android.location.Location; + import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Waypoint; -import android.location.Location; - import java.io.OutputStream; /** * Interface for writing tracks to a file. The expected sequence of calls is: - * + * *

  * {@link #prepare(OutputStream)}
  * {@link #writeHeader(Track[])}
@@ -46,99 +46,99 @@ import java.io.OutputStream;
  * {@link #writeFooter()}
  * {@link #close()}
  * 
- * + * * @author Rodrigo Damazio */ public interface TrackWriter { - /** - * Gets the file extension (e.g, gpx, kml, ...). - */ - String getExtension(); + /** + * Gets the file extension (e.g, gpx, kml, ...). + */ + String getExtension(); - /** - * Prepares the output stream. - * - * @param outputStream the output stream - */ - void prepare(OutputStream outputStream); + /** + * Prepares the output stream. + * + * @param outputStream the output stream + */ + void prepare(OutputStream outputStream); - /** - * Closes the output stream. - */ - void close(); + /** + * Closes the output stream. + */ + void close(); - /** - * Writes the header - * - * @param tracks the tracks - */ - void writeHeader(Track[] tracks); + /** + * Writes the header + * + * @param tracks the tracks + */ + void writeHeader(Track[] tracks); - /** - * Writes the footer. - */ - void writeFooter(); + /** + * Writes the footer. + */ + void writeFooter(); - /** - * Writes the beginning of the waypoints. - * - * @param track the track - */ - void writeBeginWaypoints(Track track); + /** + * Writes the beginning of the waypoints. + * + * @param track the track + */ + void writeBeginWaypoints(Track track); - /** - * Writes the end of the waypoints. - */ - void writeEndWaypoints(); + /** + * Writes the end of the waypoints. + */ + void writeEndWaypoints(); - /** - * Writes a waypoint. - * - * @param waypoint the waypoint - */ - void writeWaypoint(Waypoint waypoint); + /** + * Writes a waypoint. + * + * @param waypoint the waypoint + */ + void writeWaypoint(Waypoint waypoint); - /** - * Writes the beginning of the tracks. - */ - void writeBeginTracks(); + /** + * Writes the beginning of the tracks. + */ + void writeBeginTracks(); - /** - * Writes the end of the tracks, - */ - void writeEndTracks(); + /** + * Writes the end of the tracks, + */ + void writeEndTracks(); - /** - * Writes the beginning of a track. - * - * @param track the track - * @param startLocation the start location - */ - void writeBeginTrack(Track track, Location startLocation); + /** + * Writes the beginning of a track. + * + * @param track the track + * @param startLocation the start location + */ + void writeBeginTrack(Track track, Location startLocation); - /** - * Writes the end of a track. - * - * @param track the track - * @param endLocation the end location - */ - void writeEndTrack(Track track, Location endLocation); + /** + * Writes the end of a track. + * + * @param track the track + * @param endLocation the end location + */ + void writeEndTrack(Track track, Location endLocation); - /** - * Writes open segment. - */ - void writeOpenSegment(); + /** + * Writes open segment. + */ + void writeOpenSegment(); - /** - * Writes close segment. - */ - void writeCloseSegment(); + /** + * Writes close segment. + */ + void writeCloseSegment(); - /** - * Writes a location. - * - * @param location the location - */ - void writeLocation(Location location); + /** + * Writes a location. + * + * @param location the location + */ + void writeLocation(Location location); } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/io/file/importer/AbstractFileTrackImporter.java b/src/main/java/com/google/android/apps/mytracks/io/file/importer/AbstractFileTrackImporter.java index b5c4bd806..ab2db798d 100644 --- a/src/main/java/com/google/android/apps/mytracks/io/file/importer/AbstractFileTrackImporter.java +++ b/src/main/java/com/google/android/apps/mytracks/io/file/importer/AbstractFileTrackImporter.java @@ -22,9 +22,9 @@ import android.location.LocationManager; import android.net.Uri; import android.util.Log; -import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl; import com.google.android.apps.mytracks.content.ContentProviderUtils; import com.google.android.apps.mytracks.content.ContentProviderUtils.LocationIterator; +import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.content.Waypoint.WaypointType; @@ -57,563 +57,556 @@ import javax.xml.parsers.SAXParserFactory; /** * Abstract class for various file track importers like {@link GpxFileTrackImporter} and * {@link KmlFileTrackImporter}. - * + * * @author Jimmy Shih */ abstract class AbstractFileTrackImporter extends DefaultHandler implements TrackImporter { - /** - * Data for the current track. - * - * @author Jimmy Shih - */ - private class TrackData { - - // The current track - Track track = new Track(); - - // The number of segments processed for the current track - int numberOfSegments = 0; - - /* - * The last location in the current segment. Null if the current segment - * doesn't have a last location. + private static final String TAG = AbstractFileTrackImporter.class.getSimpleName(); + // The maximum number of buffered locations for bulk-insertion + private static final int MAX_BUFFERED_LOCATIONS = 512; + private final Context context; + private final long importTrackId; + private final ContentProviderUtils contentProviderUtils; + private final int recordingDistanceInterval; + private final List trackIds; + private final List waypoints; + // The current element content + protected String content; + protected String name; + protected String description; + protected String category; + protected String latitude; + protected String longitude; + protected String altitude; + protected String time; + protected String waypointType; + protected String photoUrl; + // The current track data + private TrackData trackData; + // The SAX locator to get the current line information + private Locator locator; + /** + * Constructor. + * + * @param context the context + * @param importTrackId the track id to import to. -1L to import to a new + * track. */ - Location lastLocationInCurrentSegment; - - // The number of locations processed for the current track - int numberOfLocations = 0; - - // The trip statistics updater for the current track - TripStatisticsUpdater tripStatisticsUpdater; - - // The import time of the track. - long importTime = System.currentTimeMillis(); - - // The buffered locations - Location[] bufferedLocations = new Location[MAX_BUFFERED_LOCATIONS]; - - // The number of buffered locations - int numBufferedLocations = 0; - } - - private static final String TAG = AbstractFileTrackImporter.class.getSimpleName(); - - // The maximum number of buffered locations for bulk-insertion - private static final int MAX_BUFFERED_LOCATIONS = 512; - - private final Context context; - private final long importTrackId; - private final ContentProviderUtils contentProviderUtils; - private final int recordingDistanceInterval; - private final List trackIds; - private final List waypoints; - - // The current track data - private TrackData trackData; - - // The SAX locator to get the current line information - private Locator locator; - - // The current element content - protected String content; - - protected String name; - protected String description; - protected String category; - protected String latitude; - protected String longitude; - protected String altitude; - protected String time; - protected String waypointType; - protected String photoUrl; - - /** - * Constructor. - * - * @param context the context - * @param importTrackId the track id to import to. -1L to import to a new - * track. - */ - AbstractFileTrackImporter( - Context context, long importTrackId, ContentProviderUtils contentProviderUtils) { - this.context = context; - this.importTrackId = importTrackId; - this.contentProviderUtils = contentProviderUtils; - this.recordingDistanceInterval = PreferencesUtils.getInt(context, - R.string.recording_distance_interval_key, - PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT); - trackIds = new ArrayList<>(); - waypoints = new ArrayList<>(); - } - - @Override - public void setDocumentLocator(Locator locator) { - this.locator = locator; - } - - @Override - public void characters(char[] ch, int start, int length) { - String newContent = new String(ch, start, length); - if (content == null) { - content = newContent; - } else { - /* - * In 99% of the cases, a single call to this method will be made for each - * sequence of characters we're interested in, so we'll rarely be - * concatenating strings, thus not justifying the use of a StringBuilder. - */ - content += newContent; - } - } - - @Override - public long importFile(InputStream inputStream) { - try { - SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser(); - long start = System.currentTimeMillis(); - - saxParser.parse(inputStream, this); - Log.d(TAG, "Total import time: " + (System.currentTimeMillis() - start) + "ms"); - if (trackIds.size() != 1) { - Log.d(TAG, trackIds.size() + " tracks imported"); - cleanImport(); - return -1L; - } - return trackIds.get(0); - } catch (IOException e) { - Log.e(TAG, "Unable to import file", e); - cleanImport(); - return -1L; - } catch (ParserConfigurationException e) { - Log.e(TAG, "Unable to import file", e); - cleanImport(); - return -1L; - } catch (SAXException e) { - Log.e(TAG, "Unable to import file", e); - cleanImport(); - return -1L; - } - } - - /** - * On file end. - */ - protected void onFileEnd() { - // Add waypoints to the last imported track - int size = trackIds.size(); - if (size == 0) { - return; - } - long trackId = trackIds.get(size - 1); - Track track = contentProviderUtils.getTrack(trackId); - if (track == null) { - return; + AbstractFileTrackImporter( + Context context, long importTrackId, ContentProviderUtils contentProviderUtils) { + this.context = context; + this.importTrackId = importTrackId; + this.contentProviderUtils = contentProviderUtils; + this.recordingDistanceInterval = PreferencesUtils.getInt(context, + R.string.recording_distance_interval_key, + PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT); + trackIds = new ArrayList<>(); + waypoints = new ArrayList<>(); } - int waypointPosition = -1; - Waypoint waypoint = null; - Location location = null; - TripStatisticsUpdater trackTripStatisticstrackUpdater = new TripStatisticsUpdater( - track.getTripStatistics().getStartTime()); - TripStatisticsUpdater markerTripStatisticsUpdater = new TripStatisticsUpdater( - track.getTripStatistics().getStartTime()); - LocationIterator locationIterator = null; + @Override + public void setDocumentLocator(Locator locator) { + this.locator = locator; + } - try { - locationIterator = contentProviderUtils.getTrackPointLocationIterator( - track.getId(), -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); - - while (true) { - if (waypoint == null) { - waypointPosition++; - waypoint = waypointPosition < waypoints.size() ? waypoints.get(waypointPosition) : null; - if (waypoint == null) { - // No more waypoints - return; - } - } - if (location == null) { - if (!locationIterator.hasNext()) { - // No more track points. Ignore the rest of the waypoints. - return; - } - location = locationIterator.next(); - trackTripStatisticstrackUpdater.addLocation(location, recordingDistanceInterval); - markerTripStatisticsUpdater.addLocation(location, recordingDistanceInterval); - } - if (waypoint.getLocation().getTime() > location.getTime()) { - location = null; - } else if (waypoint.getLocation().getTime() < location.getTime()) { - waypoint = null; + @Override + public void characters(char[] ch, int start, int length) { + String newContent = new String(ch, start, length); + if (content == null) { + content = newContent; } else { - // The waypoint location time matches the track point time - - if (!LocationUtils.isValidLocation(location)) { - // Invalid location, load the next location - location = null; - continue; - } - - // Valid location - if (location.getLatitude() == waypoint.getLocation().getLatitude() - && location.getLongitude() == waypoint.getLocation().getLongitude()) { - - // Get tripStatistics, description, and icon - TripStatistics tripStatistics; - String waypointDescription; - String icon; - if (waypoint.getType() == WaypointType.STATISTICS) { - tripStatistics = markerTripStatisticsUpdater.getTripStatistics(); - markerTripStatisticsUpdater = new TripStatisticsUpdater(location.getTime()); - waypointDescription = new DescriptionGeneratorImpl(context) - .generateWaypointDescription(tripStatistics); - icon = context.getString(R.string.marker_statistics_icon_url); - } else { - tripStatistics = null; - waypointDescription = waypoint.getDescription(); - icon = context.getString(R.string.marker_waypoint_icon_url); - } - - // Get length and duration - double length = trackTripStatisticstrackUpdater.getTripStatistics().getTotalDistance(); - long duration = trackTripStatisticstrackUpdater.getTripStatistics().getTotalTime(); - - // Insert waypoint - Waypoint newWaypoint = new Waypoint(waypoint.getName(), waypointDescription, - waypoint.getCategory(), icon, track.getId(), waypoint.getType(), length, duration, - -1L, -1L, location, tripStatistics, waypoint.getPhotoUrl()); - contentProviderUtils.insertWaypoint(newWaypoint); - } - - // Load the next waypoint - waypoint = null; + /* + * In 99% of the cases, a single call to this method will be made for each + * sequence of characters we're interested in, so we'll rarely be + * concatenating strings, thus not justifying the use of a StringBuilder. + */ + content += newContent; } - } - } finally { - if (locationIterator != null) { - locationIterator.close(); - } } - } - /** - * On track start. - */ - protected void onTrackStart() throws SAXException { - trackData = new TrackData(); - long trackId; - if (importTrackId == -1L) { - Uri uri = contentProviderUtils.insertTrack(trackData.track); - trackId = Long.parseLong(uri.getLastPathSegment()); - } else { - if (trackIds.size() > 0) { - throw new SAXException(createErrorMessage( - "Cannot import more than one track to an existing track " + importTrackId)); - } - trackId = importTrackId; - contentProviderUtils.clearTrack(context, trackId); - } - trackIds.add(trackId); - trackData.track.setId(trackId); - } + @Override + public long importFile(InputStream inputStream) { + try { + SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser(); + long start = System.currentTimeMillis(); - /** - * On track end. - */ - protected void onTrackEnd() { - flushLocations(trackData); - if (name != null) { - trackData.track.setName(name); + saxParser.parse(inputStream, this); + Log.d(TAG, "Total import time: " + (System.currentTimeMillis() - start) + "ms"); + if (trackIds.size() != 1) { + Log.d(TAG, trackIds.size() + " tracks imported"); + cleanImport(); + return -1L; + } + return trackIds.get(0); + } catch (IOException e) { + Log.e(TAG, "Unable to import file", e); + cleanImport(); + return -1L; + } catch (ParserConfigurationException e) { + Log.e(TAG, "Unable to import file", e); + cleanImport(); + return -1L; + } catch (SAXException e) { + Log.e(TAG, "Unable to import file", e); + cleanImport(); + return -1L; + } } - if (description != null) { - trackData.track.setDescription(description); - } - if (category != null) { - trackData.track.setCategory(category); - trackData.track.setIcon(TrackIconUtils.getIconValue(context, category)); - } - if (trackData.tripStatisticsUpdater == null) { - trackData.tripStatisticsUpdater = new TripStatisticsUpdater(trackData.importTime); - trackData.tripStatisticsUpdater.updateTime(trackData.importTime); - } - trackData.track.setTripStatistics(trackData.tripStatisticsUpdater.getTripStatistics()); - trackData.track.setNumberOfPoints(trackData.numberOfLocations); - contentProviderUtils.updateTrack(trackData.track); - insertFirstWaypoint(trackData.track); - } - /** - * On track segment start. - */ - protected void onTrackSegmentStart() { - trackData.numberOfSegments++; - - /* - * If not the first segment, add a pause separator if there is at least one - * location in the last segment. + /** + * On file end. */ - if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment != null) { - insertLocation(createLocation(TrackRecordingService.PAUSE_LATITUDE, 0.0, 0.0, - trackData.lastLocationInCurrentSegment.getTime())); - } - trackData.lastLocationInCurrentSegment = null; - } + protected void onFileEnd() { + // Add waypoints to the last imported track + int size = trackIds.size(); + if (size == 0) { + return; + } + long trackId = trackIds.get(size - 1); + Track track = contentProviderUtils.getTrack(trackId); + if (track == null) { + return; + } - /** - * Adds a waypoint. - * - * @param type the waypoint type - */ - protected void addWaypoint(WaypointType type) throws SAXException { - // Waypoint must have a time, else cannot match to the track points - if (time == null) { - return; + int waypointPosition = -1; + Waypoint waypoint = null; + Location location = null; + TripStatisticsUpdater trackTripStatisticstrackUpdater = new TripStatisticsUpdater( + track.getTripStatistics().getStartTime()); + TripStatisticsUpdater markerTripStatisticsUpdater = new TripStatisticsUpdater( + track.getTripStatistics().getStartTime()); + LocationIterator locationIterator = null; + + try { + locationIterator = contentProviderUtils.getTrackPointLocationIterator( + track.getId(), -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + + while (true) { + if (waypoint == null) { + waypointPosition++; + waypoint = waypointPosition < waypoints.size() ? waypoints.get(waypointPosition) : null; + if (waypoint == null) { + // No more waypoints + return; + } + } + if (location == null) { + if (!locationIterator.hasNext()) { + // No more track points. Ignore the rest of the waypoints. + return; + } + location = locationIterator.next(); + trackTripStatisticstrackUpdater.addLocation(location, recordingDistanceInterval); + markerTripStatisticsUpdater.addLocation(location, recordingDistanceInterval); + } + if (waypoint.getLocation().getTime() > location.getTime()) { + location = null; + } else if (waypoint.getLocation().getTime() < location.getTime()) { + waypoint = null; + } else { + // The waypoint location time matches the track point time + + if (!LocationUtils.isValidLocation(location)) { + // Invalid location, load the next location + location = null; + continue; + } + + // Valid location + if (location.getLatitude() == waypoint.getLocation().getLatitude() + && location.getLongitude() == waypoint.getLocation().getLongitude()) { + + // Get tripStatistics, description, and icon + TripStatistics tripStatistics; + String waypointDescription; + String icon; + if (waypoint.getType() == WaypointType.STATISTICS) { + tripStatistics = markerTripStatisticsUpdater.getTripStatistics(); + markerTripStatisticsUpdater = new TripStatisticsUpdater(location.getTime()); + waypointDescription = new DescriptionGeneratorImpl(context) + .generateWaypointDescription(tripStatistics); + icon = context.getString(R.string.marker_statistics_icon_url); + } else { + tripStatistics = null; + waypointDescription = waypoint.getDescription(); + icon = context.getString(R.string.marker_waypoint_icon_url); + } + + // Get length and duration + double length = trackTripStatisticstrackUpdater.getTripStatistics().getTotalDistance(); + long duration = trackTripStatisticstrackUpdater.getTripStatistics().getTotalTime(); + + // Insert waypoint + Waypoint newWaypoint = new Waypoint(waypoint.getName(), waypointDescription, + waypoint.getCategory(), icon, track.getId(), waypoint.getType(), length, duration, + -1L, -1L, location, tripStatistics, waypoint.getPhotoUrl()); + contentProviderUtils.insertWaypoint(newWaypoint); + } + + // Load the next waypoint + waypoint = null; + } + } + } finally { + if (locationIterator != null) { + locationIterator.close(); + } + } } - Waypoint waypoint = new Waypoint(); - Location location = createLocation(); - - if (!LocationUtils.isValidLocation(location)) { - throw new SAXException(createErrorMessage("Invalid location detected: " + location)); + /** + * On track start. + */ + protected void onTrackStart() throws SAXException { + trackData = new TrackData(); + long trackId; + if (importTrackId == -1L) { + Uri uri = contentProviderUtils.insertTrack(trackData.track); + trackId = Long.parseLong(uri.getLastPathSegment()); + } else { + if (trackIds.size() > 0) { + throw new SAXException(createErrorMessage( + "Cannot import more than one track to an existing track " + importTrackId)); + } + trackId = importTrackId; + contentProviderUtils.clearTrack(context, trackId); + } + trackIds.add(trackId); + trackData.track.setId(trackId); } - waypoint.setLocation(location); - if (name != null) { - waypoint.setName(name); + /** + * On track end. + */ + protected void onTrackEnd() { + flushLocations(trackData); + if (name != null) { + trackData.track.setName(name); + } + if (description != null) { + trackData.track.setDescription(description); + } + if (category != null) { + trackData.track.setCategory(category); + trackData.track.setIcon(TrackIconUtils.getIconValue(context, category)); + } + if (trackData.tripStatisticsUpdater == null) { + trackData.tripStatisticsUpdater = new TripStatisticsUpdater(trackData.importTime); + trackData.tripStatisticsUpdater.updateTime(trackData.importTime); + } + trackData.track.setTripStatistics(trackData.tripStatisticsUpdater.getTripStatistics()); + trackData.track.setNumberOfPoints(trackData.numberOfLocations); + contentProviderUtils.updateTrack(trackData.track); + insertFirstWaypoint(trackData.track); } - if (description != null) { - waypoint.setDescription(description); - } - if (category != null) { - waypoint.setCategory(category); - } - waypoint.setType(type); - - if (photoUrl != null) { - waypoint.setPhotoUrl(photoUrl); - } - waypoints.add(waypoint); - } - /** - * Gets a track point. - */ - protected Location getTrackPoint() throws SAXException { - Location location = createLocation(); - - // Calculate derived attributes from the previous point - if (trackData.lastLocationInCurrentSegment != null && trackData.lastLocationInCurrentSegment.getTime() != 0) { - long timeDifference = location.getTime() - trackData.lastLocationInCurrentSegment.getTime(); - - // Check for negative time change - if (timeDifference <= 0) { - Log.w(TAG, "Time difference not postive."); - } else { + /** + * On track segment start. + */ + protected void onTrackSegmentStart() { + trackData.numberOfSegments++; /* - * We don't have a speed and bearing in GPX, make something up from the - * last two points. GPS points tend to have some inherent imprecision, - * speed and bearing will likely be off, so the statistics for things - * like max speed will also be off. - */ - double duration = timeDifference * UnitConversions.MS_TO_S; - double speed = trackData.lastLocationInCurrentSegment.distanceTo(location) / duration; - location.setSpeed((float) speed); - } - location.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(location)); + * If not the first segment, add a pause separator if there is at least one + * location in the last segment. + */ + if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment != null) { + insertLocation(createLocation(TrackRecordingService.PAUSE_LATITUDE, 0.0, 0.0, + trackData.lastLocationInCurrentSegment.getTime())); + } + trackData.lastLocationInCurrentSegment = null; } - if (!LocationUtils.isValidLocation(location)) { - throw new SAXException(createErrorMessage("Invalid location detected: " + location)); + /** + * Adds a waypoint. + * + * @param type the waypoint type + */ + protected void addWaypoint(WaypointType type) throws SAXException { + // Waypoint must have a time, else cannot match to the track points + if (time == null) { + return; + } + + Waypoint waypoint = new Waypoint(); + Location location = createLocation(); + + if (!LocationUtils.isValidLocation(location)) { + throw new SAXException(createErrorMessage("Invalid location detected: " + location)); + } + waypoint.setLocation(location); + + if (name != null) { + waypoint.setName(name); + } + if (description != null) { + waypoint.setDescription(description); + } + if (category != null) { + waypoint.setCategory(category); + } + waypoint.setType(type); + + if (photoUrl != null) { + waypoint.setPhotoUrl(photoUrl); + } + waypoints.add(waypoint); } - if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment == null) { - /* - * If not the first segment, add a resume separator before adding the - * first location. - */ - insertLocation( - createLocation(TrackRecordingService.RESUME_LATITUDE, 0.0, 0.0, location.getTime())); - } - trackData.lastLocationInCurrentSegment = location; - return location; - } + /** + * Gets a track point. + */ + protected Location getTrackPoint() throws SAXException { + Location location = createLocation(); - /** - * Inserts a track point. - * - * @param location the location - */ - protected void insertTrackPoint(Location location) { - insertLocation(location); + // Calculate derived attributes from the previous point + if (trackData.lastLocationInCurrentSegment != null && trackData.lastLocationInCurrentSegment.getTime() != 0) { + long timeDifference = location.getTime() - trackData.lastLocationInCurrentSegment.getTime(); - if (trackData.track.getStartId() == -1L) { - // Flush the location to set the track start id and the track end id - flushLocations(trackData); - } - } + // Check for negative time change + if (timeDifference <= 0) { + Log.w(TAG, "Time difference not postive."); + } else { - /** - * Creates an error message. - * - * @param message the message - */ - protected String createErrorMessage(String message) { - return String.format(Locale.US, "Parsing error at line: %d column: %d. %s", - locator.getLineNumber(), locator.getColumnNumber(), message); - } + /* + * We don't have a speed and bearing in GPX, make something up from the + * last two points. GPS points tend to have some inherent imprecision, + * speed and bearing will likely be off, so the statistics for things + * like max speed will also be off. + */ + double duration = timeDifference * UnitConversions.MS_TO_S; + double speed = trackData.lastLocationInCurrentSegment.distanceTo(location) / duration; + location.setSpeed((float) speed); + } + location.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(location)); + } - /** - * Gets the photo url for a file. - * - * @param fileName the file name - */ - protected String getPhotoUrl(String fileName) { - if (importTrackId == -1L) { - return null; - } - File dir = FileUtils.getPhotoDir(importTrackId); - File file = new File(dir, fileName); - return Uri.fromFile(file).toString(); - } + if (!LocationUtils.isValidLocation(location)) { + throw new SAXException(createErrorMessage("Invalid location detected: " + location)); + } - /** - * Creates a location. - */ - private Location createLocation() throws SAXException { - if (latitude == null || longitude == null) { - return null; + if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment == null) { + /* + * If not the first segment, add a resume separator before adding the + * first location. + */ + insertLocation( + createLocation(TrackRecordingService.RESUME_LATITUDE, 0.0, 0.0, location.getTime())); + } + trackData.lastLocationInCurrentSegment = location; + return location; } - double latitudeValue; - double longitudeValue; - try { - latitudeValue = Double.parseDouble(latitude); - longitudeValue = Double.parseDouble(longitude); - } catch (NumberFormatException e) { - throw new SAXException(createErrorMessage(String.format( - Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e); - } - Double altitudeValue = null; - if (altitude != null) { - try { - altitudeValue = Double.parseDouble(altitude); - } catch (NumberFormatException e) { - throw new SAXException( - createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)), - e); - } - } - - long timeValue; - if (time == null) { - timeValue = trackData.importTime; - } else { - try { - timeValue = StringUtils.getTime(time); - } catch (IllegalArgumentException e) { - throw new SAXException( - createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e); - } - } - return createLocation(latitudeValue, longitudeValue, altitudeValue, timeValue); - } - /** - * Creates a location. - * - * @param latitudeValue the latitude value - * @param longitudeValue the longitude value - * @param altitudeValue the altitude value - * @param timeValue the time value - */ - private Location createLocation( - double latitudeValue, double longitudeValue, Double altitudeValue, long timeValue) { - Location location = new Location(LocationManager.GPS_PROVIDER); - location.setLatitude(latitudeValue); - location.setLongitude(longitudeValue); - if (altitudeValue != null) { - location.setAltitude(altitudeValue); - } else { - location.removeAltitude(); - } - location.setTime(timeValue); - location.removeAccuracy(); - location.removeBearing(); - location.removeSpeed(); - return location; - } + /** + * Inserts a track point. + * + * @param location the location + */ + protected void insertTrackPoint(Location location) { + insertLocation(location); - /** - * Inserts a location. - * - * @param location the location - */ - private void insertLocation(Location location) { - if (trackData.tripStatisticsUpdater == null) { - trackData.tripStatisticsUpdater = new TripStatisticsUpdater( - location.getTime() != -1L ? location.getTime() : trackData.importTime); + if (trackData.track.getStartId() == -1L) { + // Flush the location to set the track start id and the track end id + flushLocations(trackData); + } } - trackData.tripStatisticsUpdater.addLocation(location, recordingDistanceInterval); - trackData.bufferedLocations[trackData.numBufferedLocations] = location; - trackData.numBufferedLocations++; - trackData.numberOfLocations++; - - if (trackData.numBufferedLocations >= MAX_BUFFERED_LOCATIONS) { - flushLocations(trackData); + /** + * Creates an error message. + * + * @param message the message + */ + protected String createErrorMessage(String message) { + return String.format(Locale.US, "Parsing error at line: %d column: %d. %s", + locator.getLineNumber(), locator.getColumnNumber(), message); } - } - /** - * Flushes the locations to the database. - * - * @param data the track data - */ - private void flushLocations(TrackData data) { - if (data.numBufferedLocations <= 0) { - return; + /** + * Gets the photo url for a file. + * + * @param fileName the file name + */ + protected String getPhotoUrl(String fileName) { + if (importTrackId == -1L) { + return null; + } + File dir = FileUtils.getPhotoDir(importTrackId); + File file = new File(dir, fileName); + return Uri.fromFile(file).toString(); } - contentProviderUtils.bulkInsertTrackPoint( - data.bufferedLocations, data.numBufferedLocations, data.track.getId()); - data.numBufferedLocations = 0; - if (data.track.getStartId() == -1L) { - data.track.setStartId(contentProviderUtils.getFirstTrackPointId(data.track.getId())); - } - data.track.setStopId(contentProviderUtils.getLastTrackPointId(data.track.getId())); - } - /** - * Inserts the first waypoint, the track statistics waypoint. - * - * @param track the track - */ - private void insertFirstWaypoint(Track track) { - String waypointName = context.getString(R.string.marker_split_name_format, 0); - String waypointCategory = ""; - TripStatisticsUpdater updater = new TripStatisticsUpdater( - track.getTripStatistics().getStartTime()); - TripStatistics tripStatistics = updater.getTripStatistics(); - String waypointDescription = new DescriptionGeneratorImpl(context).generateWaypointDescription( - tripStatistics); - String icon = context.getString(R.string.marker_statistics_icon_url); - double length = 0.0; - long duration = 0L; - Location waypointLocation = new Location(""); - waypointLocation.setLatitude(100); - waypointLocation.setLongitude(180); - Waypoint waypoint = new Waypoint(waypointName, waypointDescription, waypointCategory, icon, - track.getId(), WaypointType.STATISTICS, length, duration, -1L, -1L, waypointLocation, - tripStatistics, ""); - contentProviderUtils.insertWaypoint(waypoint); - } + /** + * Creates a location. + */ + private Location createLocation() throws SAXException { + if (latitude == null || longitude == null) { + return null; + } + double latitudeValue; + double longitudeValue; + try { + latitudeValue = Double.parseDouble(latitude); + longitudeValue = Double.parseDouble(longitude); + } catch (NumberFormatException e) { + throw new SAXException(createErrorMessage(String.format( + Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e); + } + Double altitudeValue = null; + if (altitude != null) { + try { + altitudeValue = Double.parseDouble(altitude); + } catch (NumberFormatException e) { + throw new SAXException( + createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)), + e); + } + } - /** - * Cleans up import. - */ - private void cleanImport() { - for (long trackId : trackIds) { - contentProviderUtils.deleteTrack(context, trackId); + long timeValue; + if (time == null) { + timeValue = trackData.importTime; + } else { + try { + timeValue = StringUtils.getTime(time); + } catch (IllegalArgumentException e) { + throw new SAXException( + createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e); + } + } + return createLocation(latitudeValue, longitudeValue, altitudeValue, timeValue); + } + + /** + * Creates a location. + * + * @param latitudeValue the latitude value + * @param longitudeValue the longitude value + * @param altitudeValue the altitude value + * @param timeValue the time value + */ + private Location createLocation( + double latitudeValue, double longitudeValue, Double altitudeValue, long timeValue) { + Location location = new Location(LocationManager.GPS_PROVIDER); + location.setLatitude(latitudeValue); + location.setLongitude(longitudeValue); + if (altitudeValue != null) { + location.setAltitude(altitudeValue); + } else { + location.removeAltitude(); + } + location.setTime(timeValue); + location.removeAccuracy(); + location.removeBearing(); + location.removeSpeed(); + return location; + } + + /** + * Inserts a location. + * + * @param location the location + */ + private void insertLocation(Location location) { + if (trackData.tripStatisticsUpdater == null) { + trackData.tripStatisticsUpdater = new TripStatisticsUpdater( + location.getTime() != -1L ? location.getTime() : trackData.importTime); + } + trackData.tripStatisticsUpdater.addLocation(location, recordingDistanceInterval); + + trackData.bufferedLocations[trackData.numBufferedLocations] = location; + trackData.numBufferedLocations++; + trackData.numberOfLocations++; + + if (trackData.numBufferedLocations >= MAX_BUFFERED_LOCATIONS) { + flushLocations(trackData); + } + } + + /** + * Flushes the locations to the database. + * + * @param data the track data + */ + private void flushLocations(TrackData data) { + if (data.numBufferedLocations <= 0) { + return; + } + contentProviderUtils.bulkInsertTrackPoint( + data.bufferedLocations, data.numBufferedLocations, data.track.getId()); + data.numBufferedLocations = 0; + if (data.track.getStartId() == -1L) { + data.track.setStartId(contentProviderUtils.getFirstTrackPointId(data.track.getId())); + } + data.track.setStopId(contentProviderUtils.getLastTrackPointId(data.track.getId())); + } + + /** + * Inserts the first waypoint, the track statistics waypoint. + * + * @param track the track + */ + private void insertFirstWaypoint(Track track) { + String waypointName = context.getString(R.string.marker_split_name_format, 0); + String waypointCategory = ""; + TripStatisticsUpdater updater = new TripStatisticsUpdater( + track.getTripStatistics().getStartTime()); + TripStatistics tripStatistics = updater.getTripStatistics(); + String waypointDescription = new DescriptionGeneratorImpl(context).generateWaypointDescription( + tripStatistics); + String icon = context.getString(R.string.marker_statistics_icon_url); + double length = 0.0; + long duration = 0L; + Location waypointLocation = new Location(""); + waypointLocation.setLatitude(100); + waypointLocation.setLongitude(180); + Waypoint waypoint = new Waypoint(waypointName, waypointDescription, waypointCategory, icon, + track.getId(), WaypointType.STATISTICS, length, duration, -1L, -1L, waypointLocation, + tripStatistics, ""); + contentProviderUtils.insertWaypoint(waypoint); + } + + /** + * Cleans up import. + */ + private void cleanImport() { + for (long trackId : trackIds) { + contentProviderUtils.deleteTrack(context, trackId); + } + } + + /** + * Data for the current track. + * + * @author Jimmy Shih + */ + private class TrackData { + + // The current track + Track track = new Track(); + + // The number of segments processed for the current track + int numberOfSegments = 0; + + /* + * The last location in the current segment. Null if the current segment + * doesn't have a last location. + */ + Location lastLocationInCurrentSegment; + + // The number of locations processed for the current track + int numberOfLocations = 0; + + // The trip statistics updater for the current track + TripStatisticsUpdater tripStatisticsUpdater; + + // The import time of the track. + long importTime = System.currentTimeMillis(); + + // The buffered locations + Location[] bufferedLocations = new Location[MAX_BUFFERED_LOCATIONS]; + + // The number of buffered locations + int numBufferedLocations = 0; } - } } diff --git a/src/main/java/com/google/android/apps/mytracks/io/file/importer/GpxFileTrackImporter.java b/src/main/java/com/google/android/apps/mytracks/io/file/importer/GpxFileTrackImporter.java index 909e2dbad..3784b0950 100644 --- a/src/main/java/com/google/android/apps/mytracks/io/file/importer/GpxFileTrackImporter.java +++ b/src/main/java/com/google/android/apps/mytracks/io/file/importer/GpxFileTrackImporter.java @@ -18,6 +18,7 @@ package com.google.android.apps.mytracks.io.file.importer; import android.content.Context; import android.location.Location; + import androidx.annotation.VisibleForTesting; import com.google.android.apps.mytracks.content.ContentProviderUtils; @@ -28,163 +29,163 @@ import org.xml.sax.SAXException; /** * Imports a GPX file. - * + * * @author Jimmy Shih */ public class GpxFileTrackImporter extends AbstractFileTrackImporter { - private static final String TAG_DESCRIPTION = "desc"; - private static final String TAG_COMMENT = "cmt"; - private static final String TAG_ELEVATION = "ele"; - private static final String TAG_GPX = "gpx"; - private static final String TAG_NAME = "name"; - private static final String TAG_TIME = "time"; - private static final String TAG_TRACK = "trk"; - private static final String TAG_TRACK_POINT = "trkpt"; - private static final String TAG_TRACK_SEGMENT = "trkseg"; - private static final String TAG_TYPE = "type"; - private static final String TAG_WAYPOINT = "wpt"; + private static final String TAG_DESCRIPTION = "desc"; + private static final String TAG_COMMENT = "cmt"; + private static final String TAG_ELEVATION = "ele"; + private static final String TAG_GPX = "gpx"; + private static final String TAG_NAME = "name"; + private static final String TAG_TIME = "time"; + private static final String TAG_TRACK = "trk"; + private static final String TAG_TRACK_POINT = "trkpt"; + private static final String TAG_TRACK_SEGMENT = "trkseg"; + private static final String TAG_TYPE = "type"; + private static final String TAG_WAYPOINT = "wpt"; - private static final String ATTRIBUTE_LAT = "lat"; - private static final String ATTRIBUTE_LON = "lon"; - - /** - * Constructor. - * - * @param context the context - */ - public GpxFileTrackImporter(Context context) { - this(context, ContentProviderUtils.Factory.get(context)); - } + private static final String ATTRIBUTE_LAT = "lat"; + private static final String ATTRIBUTE_LON = "lon"; - @VisibleForTesting - GpxFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils) { - super(context, -1L, contentProviderUtils); - } - - @Override - public void startElement(String uri, String localName, String tag, Attributes attributes) - throws SAXException { - switch (tag) { - case TAG_WAYPOINT: - onWaypointStart(attributes); - break; - case TAG_TRACK: - onTrackStart(); - break; - case TAG_TRACK_SEGMENT: - onTrackSegmentStart(); - break; - case TAG_TRACK_POINT: - onTrackPointStart(attributes); - break; - } - } - - @Override - public void endElement(String uri, String localName, String tag) throws SAXException { - switch (tag) { - case TAG_GPX: - onFileEnd(); - break; - case TAG_WAYPOINT: - onWaypointEnd(); - break; - case TAG_TRACK: - onTrackEnd(); - break; - case TAG_TRACK_POINT: - onTrackPointEnd(); - break; - case TAG_NAME: - if (content != null) { - name = content.trim(); - } - break; - case TAG_DESCRIPTION: - if (content != null) { - description = content.trim(); - } - break; - case TAG_TYPE: - if (content != null) { - category = content.trim(); - } - break; - case TAG_TIME: - if (content != null) { - time = content.trim(); - } - break; - case TAG_ELEVATION: - if (content != null) { - altitude = content.trim(); - } - break; - case TAG_COMMENT: - if (content != null) { - waypointType = content.trim(); - } - break; - } - - // Reset element content - content = null; - } - - @Override - protected void onTrackStart() throws SAXException { - super.onTrackStart(); - name = null; - description = null; - category = null; - } - - /** - * On track point start. - * - * @param attributes the attributes - */ - private void onTrackPointStart(Attributes attributes) { - latitude = attributes.getValue(ATTRIBUTE_LAT); - longitude = attributes.getValue(ATTRIBUTE_LON); - altitude = null; - time = null; - } - - /** - * On track point end. - */ - private void onTrackPointEnd() throws SAXException { - Location location = getTrackPoint(); - if (location == null) { - return; + /** + * Constructor. + * + * @param context the context + */ + public GpxFileTrackImporter(Context context) { + this(context, ContentProviderUtils.Factory.get(context)); } - insertTrackPoint(location); - } - /** - * On waypoint start. - * - * @param attributes the attributes - */ - private void onWaypointStart(Attributes attributes) { - name = null; - description = null; - category = null; - photoUrl = null; - latitude = attributes.getValue(ATTRIBUTE_LAT); - longitude = attributes.getValue(ATTRIBUTE_LON); - altitude = null; - time = null; - waypointType = null; - } + @VisibleForTesting + GpxFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils) { + super(context, -1L, contentProviderUtils); + } - /** - * On waypoint end. - */ - private void onWaypointEnd() throws SAXException { - addWaypoint(WaypointType.STATISTICS.name().equals(waypointType) ? WaypointType.STATISTICS - : WaypointType.WAYPOINT); - } + @Override + public void startElement(String uri, String localName, String tag, Attributes attributes) + throws SAXException { + switch (tag) { + case TAG_WAYPOINT: + onWaypointStart(attributes); + break; + case TAG_TRACK: + onTrackStart(); + break; + case TAG_TRACK_SEGMENT: + onTrackSegmentStart(); + break; + case TAG_TRACK_POINT: + onTrackPointStart(attributes); + break; + } + } + + @Override + public void endElement(String uri, String localName, String tag) throws SAXException { + switch (tag) { + case TAG_GPX: + onFileEnd(); + break; + case TAG_WAYPOINT: + onWaypointEnd(); + break; + case TAG_TRACK: + onTrackEnd(); + break; + case TAG_TRACK_POINT: + onTrackPointEnd(); + break; + case TAG_NAME: + if (content != null) { + name = content.trim(); + } + break; + case TAG_DESCRIPTION: + if (content != null) { + description = content.trim(); + } + break; + case TAG_TYPE: + if (content != null) { + category = content.trim(); + } + break; + case TAG_TIME: + if (content != null) { + time = content.trim(); + } + break; + case TAG_ELEVATION: + if (content != null) { + altitude = content.trim(); + } + break; + case TAG_COMMENT: + if (content != null) { + waypointType = content.trim(); + } + break; + } + + // Reset element content + content = null; + } + + @Override + protected void onTrackStart() throws SAXException { + super.onTrackStart(); + name = null; + description = null; + category = null; + } + + /** + * On track point start. + * + * @param attributes the attributes + */ + private void onTrackPointStart(Attributes attributes) { + latitude = attributes.getValue(ATTRIBUTE_LAT); + longitude = attributes.getValue(ATTRIBUTE_LON); + altitude = null; + time = null; + } + + /** + * On track point end. + */ + private void onTrackPointEnd() throws SAXException { + Location location = getTrackPoint(); + if (location == null) { + return; + } + insertTrackPoint(location); + } + + /** + * On waypoint start. + * + * @param attributes the attributes + */ + private void onWaypointStart(Attributes attributes) { + name = null; + description = null; + category = null; + photoUrl = null; + latitude = attributes.getValue(ATTRIBUTE_LAT); + longitude = attributes.getValue(ATTRIBUTE_LON); + altitude = null; + time = null; + waypointType = null; + } + + /** + * On waypoint end. + */ + private void onWaypointEnd() throws SAXException { + addWaypoint(WaypointType.STATISTICS.name().equals(waypointType) ? WaypointType.STATISTICS + : WaypointType.WAYPOINT); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/io/file/importer/ImportActivity.java b/src/main/java/com/google/android/apps/mytracks/io/file/importer/ImportActivity.java index edf878200..e804a6fbd 100644 --- a/src/main/java/com/google/android/apps/mytracks/io/file/importer/ImportActivity.java +++ b/src/main/java/com/google/android/apps/mytracks/io/file/importer/ImportActivity.java @@ -41,199 +41,199 @@ import java.io.File; /** * An activity to import files from the external storage. Optionally to import * one specific file. - * + * * @author Rodrigo Damazio */ public class ImportActivity extends Activity { - public static final String EXTRA_IMPORT_ALL = "import_all"; - public static final String EXTRA_TRACK_FILE_FORMAT = "track_file_format"; + public static final String EXTRA_IMPORT_ALL = "import_all"; + public static final String EXTRA_TRACK_FILE_FORMAT = "track_file_format"; - private static final String TAG = ImportActivity.class.getSimpleName(); - private static final int DIALOG_PROGRESS_ID = 0; - private static final int DIALOG_RESULT_ID = 1; + private static final String TAG = ImportActivity.class.getSimpleName(); + private static final int DIALOG_PROGRESS_ID = 0; + private static final int DIALOG_RESULT_ID = 1; - private ImportAsyncTask importAsyncTask; - private ProgressDialog progressDialog; + private ImportAsyncTask importAsyncTask; + private ProgressDialog progressDialog; - private boolean importAll; + private boolean importAll; - // the path on the external storage to import - private String directoryDisplayName; + // the path on the external storage to import + private String directoryDisplayName; - // the number of files successfully imported - private int successCount; + // the number of files successfully imported + private int successCount; - // the number of files to import - private int totalCount; + // the number of files to import + private int totalCount; - // the last successfully imported track id - private long trackId; + // the last successfully imported track id + private long trackId; - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - Intent intent = getIntent(); - importAll = intent.getBooleanExtra(EXTRA_IMPORT_ALL, false); - TrackFileFormat trackFileFormat = intent.getParcelableExtra(EXTRA_TRACK_FILE_FORMAT); - if (trackFileFormat == null) { - trackFileFormat = TrackFileFormat.GPX; - } - - if (!FileUtils.isExternalStorageAvailable()) { - Toast.makeText(this, R.string.external_storage_not_available, Toast.LENGTH_LONG).show(); - finish(); - return; - } - String directoryPath; - if (importAll) { - directoryDisplayName = FileUtils.getPathDisplayName(trackFileFormat.getExtension()); - directoryPath = FileUtils.getPath(trackFileFormat.getExtension()); - if (!FileUtils.isDirectory(new File(directoryPath))) { - Toast.makeText( - this, getString(R.string.import_no_directory, directoryDisplayName), Toast.LENGTH_LONG) - .show(); - finish(); - return; - } - } else { - String action = intent.getAction(); - if (!(Intent.ACTION_ATTACH_DATA.equals(action) || Intent.ACTION_VIEW.equals(action))) { - Log.d(TAG, "Invalid action: " + intent); - finish(); - return; - } - - Uri data = intent.getData(); - if ("file".equals(data.getScheme())) { - Log.d(TAG, "Invalid data: " + intent); - finish(); - return; - } - directoryDisplayName = data.getPath(); - directoryPath = data.getPath(); - } - - Object retained = getLastNonConfigurationInstance(); - if (retained instanceof ImportAsyncTask) { - importAsyncTask = (ImportAsyncTask) retained; - importAsyncTask.setActivity(this); - } else { - importAsyncTask = new ImportAsyncTask(this, importAll, trackFileFormat, directoryPath); - importAsyncTask.execute(); - } - } - - @Override - public Object onRetainNonConfigurationInstance() { - importAsyncTask.setActivity(null); - return importAsyncTask; - } - - @Override - protected Dialog onCreateDialog(int id) { - switch (id) { - case DIALOG_PROGRESS_ID: - progressDialog = DialogUtils.createHorizontalProgressDialog( - this, R.string.import_progress_message, new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - importAsyncTask.cancel(true); - dialog.dismiss(); - } - }, directoryDisplayName); - return progressDialog; - case DIALOG_RESULT_ID: - int iconId; - int titleId; - String message; - String totalFiles = getResources() - .getQuantityString(R.plurals.files, totalCount, totalCount); - if (successCount == totalCount) { - if (totalCount == 0) { - iconId = android.R.drawable.ic_dialog_info; - titleId = R.string.import_no_file_title; - message = getString(R.string.import_no_file, directoryDisplayName); - } else { - iconId = R.drawable.ic_dialog_success; - titleId = R.string.generic_success_title; - message = getString(R.string.import_success, totalFiles, directoryDisplayName); - } - } else { - iconId = android.R.drawable.ic_dialog_alert; - titleId = R.string.generic_error_title; - message = getString( - R.string.import_error, successCount, totalFiles, directoryDisplayName); + Intent intent = getIntent(); + importAll = intent.getBooleanExtra(EXTRA_IMPORT_ALL, false); + TrackFileFormat trackFileFormat = intent.getParcelableExtra(EXTRA_TRACK_FILE_FORMAT); + if (trackFileFormat == null) { + trackFileFormat = TrackFileFormat.GPX; } - final Dialog dialog = new AlertDialog.Builder(this).setCancelable(true).setIcon(iconId) - .setMessage(message).setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialogInterface) { - dialogInterface.dismiss(); + + if (!FileUtils.isExternalStorageAvailable()) { + Toast.makeText(this, R.string.external_storage_not_available, Toast.LENGTH_LONG).show(); + finish(); + return; + } + String directoryPath; + if (importAll) { + directoryDisplayName = FileUtils.getPathDisplayName(trackFileFormat.getExtension()); + directoryPath = FileUtils.getPath(trackFileFormat.getExtension()); + if (!FileUtils.isDirectory(new File(directoryPath))) { + Toast.makeText( + this, getString(R.string.import_no_directory, directoryDisplayName), Toast.LENGTH_LONG) + .show(); finish(); - } - }).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int which) { - if (successCount == totalCount && !importAll && trackId != -1L) { - Intent intent = IntentUtils.newIntent( - ImportActivity.this, TrackDetailActivity.class) - .putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId); - TaskStackBuilder.create(ImportActivity.this) - .addParentStack(TrackDetailActivity.class).addNextIntent(intent) - .startActivities(); + return; + } + } else { + String action = intent.getAction(); + if (!(Intent.ACTION_ATTACH_DATA.equals(action) || Intent.ACTION_VIEW.equals(action))) { + Log.d(TAG, "Invalid action: " + intent); + finish(); + return; + } + + Uri data = intent.getData(); + if ("file".equals(data.getScheme())) { + Log.d(TAG, "Invalid data: " + intent); + finish(); + return; + } + directoryDisplayName = data.getPath(); + directoryPath = data.getPath(); + } + + Object retained = getLastNonConfigurationInstance(); + if (retained instanceof ImportAsyncTask) { + importAsyncTask = (ImportAsyncTask) retained; + importAsyncTask.setActivity(this); + } else { + importAsyncTask = new ImportAsyncTask(this, importAll, trackFileFormat, directoryPath); + importAsyncTask.execute(); + } + } + + @Override + public Object onRetainNonConfigurationInstance() { + importAsyncTask.setActivity(null); + return importAsyncTask; + } + + @Override + protected Dialog onCreateDialog(int id) { + switch (id) { + case DIALOG_PROGRESS_ID: + progressDialog = DialogUtils.createHorizontalProgressDialog( + this, R.string.import_progress_message, new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + importAsyncTask.cancel(true); + dialog.dismiss(); + } + }, directoryDisplayName); + return progressDialog; + case DIALOG_RESULT_ID: + int iconId; + int titleId; + String message; + String totalFiles = getResources() + .getQuantityString(R.plurals.files, totalCount, totalCount); + if (successCount == totalCount) { + if (totalCount == 0) { + iconId = android.R.drawable.ic_dialog_info; + titleId = R.string.import_no_file_title; + message = getString(R.string.import_no_file, directoryDisplayName); + } else { + iconId = R.drawable.ic_dialog_success; + titleId = R.string.generic_success_title; + message = getString(R.string.import_success, totalFiles, directoryDisplayName); + } + } else { + iconId = android.R.drawable.ic_dialog_alert; + titleId = R.string.generic_error_title; + message = getString( + R.string.import_error, successCount, totalFiles, directoryDisplayName); } - dialogInterface.dismiss(); - finish(); - } - }).setTitle(titleId).create(); - dialog.setOnShowListener(new DialogInterface.OnShowListener() { + final Dialog dialog = new AlertDialog.Builder(this).setCancelable(true).setIcon(iconId) + .setMessage(message).setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialogInterface) { + dialogInterface.dismiss(); + finish(); + } + }).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int which) { + if (successCount == totalCount && !importAll && trackId != -1L) { + Intent intent = IntentUtils.newIntent( + ImportActivity.this, TrackDetailActivity.class) + .putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId); + TaskStackBuilder.create(ImportActivity.this) + .addParentStack(TrackDetailActivity.class).addNextIntent(intent) + .startActivities(); + } + dialogInterface.dismiss(); + finish(); + } + }).setTitle(titleId).create(); + dialog.setOnShowListener(new DialogInterface.OnShowListener() { - @Override - public void onShow(DialogInterface dialogInterface) { - DialogUtils.setDialogTitleDivider(ImportActivity.this, dialog); - } - }); - return dialog; - default: - return null; + @Override + public void onShow(DialogInterface dialogInterface) { + DialogUtils.setDialogTitleDivider(ImportActivity.this, dialog); + } + }); + return dialog; + default: + return null; + } } - } - /** - * Invokes when the associated AsyncTask completes. - * - * @param aSuccessCount the number of files successfully imported - * @param aTotalCount the number of files to import - * @param aTrackId the last successfully imported track id - */ - public void onAsyncTaskCompleted(int aSuccessCount, int aTotalCount, long aTrackId) { - successCount = aSuccessCount; - totalCount = aTotalCount; - trackId = aTrackId; - removeDialog(DIALOG_PROGRESS_ID); - showDialog(DIALOG_RESULT_ID); - } - - /** - * Shows the progress dialog. - */ - public void showProgressDialog() { - showDialog(DIALOG_PROGRESS_ID); - } - - /** - * Sets the progress dialog value. - * - * @param number the number of files imported - * @param max the maximum number of files - */ - public void setProgressDialogValue(int number, int max) { - if (progressDialog != null) { - progressDialog.setIndeterminate(false); - progressDialog.setMax(max); - progressDialog.setProgress(Math.min(number, max)); + /** + * Invokes when the associated AsyncTask completes. + * + * @param aSuccessCount the number of files successfully imported + * @param aTotalCount the number of files to import + * @param aTrackId the last successfully imported track id + */ + public void onAsyncTaskCompleted(int aSuccessCount, int aTotalCount, long aTrackId) { + successCount = aSuccessCount; + totalCount = aTotalCount; + trackId = aTrackId; + removeDialog(DIALOG_PROGRESS_ID); + showDialog(DIALOG_RESULT_ID); + } + + /** + * Shows the progress dialog. + */ + public void showProgressDialog() { + showDialog(DIALOG_PROGRESS_ID); + } + + /** + * Sets the progress dialog value. + * + * @param number the number of files imported + * @param max the maximum number of files + */ + public void setProgressDialogValue(int number, int max) { + if (progressDialog != null) { + progressDialog.setIndeterminate(false); + progressDialog.setMax(max); + progressDialog.setProgress(Math.min(number, max)); + } } - } } diff --git a/src/main/java/com/google/android/apps/mytracks/io/file/importer/ImportAsyncTask.java b/src/main/java/com/google/android/apps/mytracks/io/file/importer/ImportAsyncTask.java index be97713f7..4c9abed6d 100644 --- a/src/main/java/com/google/android/apps/mytracks/io/file/importer/ImportAsyncTask.java +++ b/src/main/java/com/google/android/apps/mytracks/io/file/importer/ImportAsyncTask.java @@ -16,6 +16,12 @@ package com.google.android.apps.mytracks.io.file.importer; +import android.content.Context; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.PowerManager.WakeLock; +import android.util.Log; + import com.google.android.apps.mytracks.content.ContentProviderUtils; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.io.file.TrackFileFormat; @@ -25,12 +31,6 @@ import com.google.android.apps.mytracks.util.PreferencesUtils; import com.google.android.apps.mytracks.util.SystemUtils; import com.google.android.maps.mytracks.R; -import android.content.Context; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.PowerManager.WakeLock; -import android.util.Log; - import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -40,202 +40,201 @@ import java.util.List; /** * AsyncTask to import files from the external storage. - * + * * @author Jimmy Shih */ public class ImportAsyncTask extends AsyncTask { - private static final String TAG = ImportAsyncTask.class.getSimpleName(); + private static final String TAG = ImportAsyncTask.class.getSimpleName(); + private final boolean importAll; + private final TrackFileFormat trackFileFormat; + private final String path; + private final Context context; + private ImportActivity importActivity; + private WakeLock wakeLock; - private ImportActivity importActivity; - private final boolean importAll; - private final TrackFileFormat trackFileFormat; - private final String path; - private final Context context; - private WakeLock wakeLock; + // true if the AsyncTask has completed + private boolean completed; - // true if the AsyncTask has completed - private boolean completed; + // the number of files successfully imported + private int successCount; - // the number of files successfully imported - private int successCount; + // the number of files to import + private int totalCount; - // the number of files to import - private int totalCount; + // the last successfully imported track id + private long trackId; - // the last successfully imported track id - private long trackId; + /** + * Creates an AsyncTask. + * + * @param importActivity the activity currently associated with this AsyncTask + * @param importAll true to import all GPX files + * @param trackFileFormat the track file format + * @param path path to import GPX files + */ + public ImportAsyncTask(ImportActivity importActivity, boolean importAll, + TrackFileFormat trackFileFormat, String path) { + this.importActivity = importActivity; + this.importAll = importAll; + this.trackFileFormat = trackFileFormat; + this.path = path; + context = importActivity.getApplicationContext(); - /** - * Creates an AsyncTask. - * - * @param importActivity the activity currently associated with this AsyncTask - * @param importAll true to import all GPX files - * @param trackFileFormat the track file format - * @param path path to import GPX files - */ - public ImportAsyncTask(ImportActivity importActivity, boolean importAll, - TrackFileFormat trackFileFormat, String path) { - this.importActivity = importActivity; - this.importAll = importAll; - this.trackFileFormat = trackFileFormat; - this.path = path; - context = importActivity.getApplicationContext(); - - completed = false; - successCount = 0; - totalCount = 0; - trackId = -1L; - } - - /** - * Sets the current {@link ImportActivity} associated with this AyncTask. - * - * @param importActivity the current {@link ImportActivity}, can be null - */ - public void setActivity(ImportActivity importActivity) { - this.importActivity = importActivity; - if (completed && importActivity != null) { - importActivity.onAsyncTaskCompleted(successCount, totalCount, trackId); + completed = false; + successCount = 0; + totalCount = 0; + trackId = -1L; } - } - @Override - protected void onPreExecute() { - if (importActivity != null) { - importActivity.showProgressDialog(); - } - } - - @Override - protected Boolean doInBackground(Void... params) { - try { - Thread.currentThread().setPriority(Thread.MAX_PRIORITY); - // Get the wake lock if not recording or paused - boolean isRecording = - PreferencesUtils.getLong(importActivity, R.string.recording_track_id_key) - != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; - boolean isPaused = PreferencesUtils.getBoolean(importActivity, - R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); - if (!isRecording || isPaused) { - wakeLock = SystemUtils.acquireWakeLock(importActivity, wakeLock); - } - - List files = getFiles(); - totalCount = files.size(); - if (totalCount == 0) { - return true; - } - - for (int i = 0; i < totalCount; i++) { - if (isCancelled()) { - // If cancelled, return true to show the number of files imported - return true; + /** + * Sets the current {@link ImportActivity} associated with this AyncTask. + * + * @param importActivity the current {@link ImportActivity}, can be null + */ + public void setActivity(ImportActivity importActivity) { + this.importActivity = importActivity; + if (completed && importActivity != null) { + importActivity.onAsyncTaskCompleted(successCount, totalCount, trackId); } - if (importFile(files.get(i))) { - successCount++; + } + + @Override + protected void onPreExecute() { + if (importActivity != null) { + importActivity.showProgressDialog(); } - publishProgress(i + 1, totalCount); - } - return true; - } finally { - if (wakeLock != null && wakeLock.isHeld()) { - wakeLock.release(); - } } - } - @Override - protected void onProgressUpdate(Integer... values) { - if (importActivity != null) { - importActivity.setProgressDialogValue(values[0], values[1]); - } - } - - @Override - protected void onPostExecute(Boolean result) { - completed = true; - if (importActivity != null) { - importActivity.onAsyncTaskCompleted(successCount, totalCount, trackId); - } - } - - @Override - protected void onCancelled() { - completed = true; - if (importActivity != null) { - importActivity.onAsyncTaskCompleted(successCount, totalCount, trackId); - } - } - - /** - * Imports a file. - * - * @param file the file - */ - private boolean importFile(final File file) { - FileInputStream fileInputStream = null; - try { - TrackImporter trackImporter; - if (trackFileFormat == TrackFileFormat.KML) { - String extension = FileUtils.getExtension(file.getName()); - if (TrackFileFormat.KML.getExtension().equals(extension)) { - trackImporter = new KmlFileTrackImporter(context, -1L); - } else { - ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context); - Uri uri = contentProviderUtils.insertTrack(new Track()); - long newId = Long.parseLong(uri.getLastPathSegment()); - - trackImporter = new KmzTrackImporter(context, newId); - } - } else { - trackImporter = new GpxFileTrackImporter(context); - } - fileInputStream = new FileInputStream(file); - trackId = trackImporter.importFile(fileInputStream); - return trackId != -1L; - } catch (FileNotFoundException e) { - Log.e(TAG, "Unable to import file", e); - return false; - } finally { - if (fileInputStream != null) { + @Override + protected Boolean doInBackground(Void... params) { try { - fileInputStream.close(); - } catch (IOException e) { - Log.e(TAG, "Unable to close file input stream", e); - } - } - } - } - - /** - * Gets a list of files. If importAll is true, returns a list of the files - * under the path directory. If importAll is false, returns a list containing - * just the path file. - */ - private List getFiles() { - List files = new ArrayList<>(); - File file = new File(path); - if (importAll) { - File[] candidates = file.listFiles(); - if (candidates != null) { - for (File candidate : candidates) { - if (!FileUtils.isDirectory(candidate)) { - String extension = FileUtils.getExtension(candidate.getName()); - if (trackFileFormat == TrackFileFormat.KML && ( - TrackFileFormat.KML.getExtension().equals(extension) - || KmzTrackExporter.KMZ_EXTENSION.equals(extension))) { - files.add(candidate); - } else if (trackFileFormat == TrackFileFormat.GPX - && TrackFileFormat.GPX.getExtension().equals(extension)) { - files.add(candidate); + Thread.currentThread().setPriority(Thread.MAX_PRIORITY); + // Get the wake lock if not recording or paused + boolean isRecording = + PreferencesUtils.getLong(importActivity, R.string.recording_track_id_key) + != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; + boolean isPaused = PreferencesUtils.getBoolean(importActivity, + R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); + if (!isRecording || isPaused) { + wakeLock = SystemUtils.acquireWakeLock(importActivity, wakeLock); + } + + List files = getFiles(); + totalCount = files.size(); + if (totalCount == 0) { + return true; + } + + for (int i = 0; i < totalCount; i++) { + if (isCancelled()) { + // If cancelled, return true to show the number of files imported + return true; + } + if (importFile(files.get(i))) { + successCount++; + } + publishProgress(i + 1, totalCount); + } + return true; + } finally { + if (wakeLock != null && wakeLock.isHeld()) { + wakeLock.release(); } - } } - } - } else { - files.add(file); } - return files; - } + + @Override + protected void onProgressUpdate(Integer... values) { + if (importActivity != null) { + importActivity.setProgressDialogValue(values[0], values[1]); + } + } + + @Override + protected void onPostExecute(Boolean result) { + completed = true; + if (importActivity != null) { + importActivity.onAsyncTaskCompleted(successCount, totalCount, trackId); + } + } + + @Override + protected void onCancelled() { + completed = true; + if (importActivity != null) { + importActivity.onAsyncTaskCompleted(successCount, totalCount, trackId); + } + } + + /** + * Imports a file. + * + * @param file the file + */ + private boolean importFile(final File file) { + FileInputStream fileInputStream = null; + try { + TrackImporter trackImporter; + if (trackFileFormat == TrackFileFormat.KML) { + String extension = FileUtils.getExtension(file.getName()); + if (TrackFileFormat.KML.getExtension().equals(extension)) { + trackImporter = new KmlFileTrackImporter(context, -1L); + } else { + ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context); + Uri uri = contentProviderUtils.insertTrack(new Track()); + long newId = Long.parseLong(uri.getLastPathSegment()); + + trackImporter = new KmzTrackImporter(context, newId); + } + } else { + trackImporter = new GpxFileTrackImporter(context); + } + fileInputStream = new FileInputStream(file); + trackId = trackImporter.importFile(fileInputStream); + return trackId != -1L; + } catch (FileNotFoundException e) { + Log.e(TAG, "Unable to import file", e); + return false; + } finally { + if (fileInputStream != null) { + try { + fileInputStream.close(); + } catch (IOException e) { + Log.e(TAG, "Unable to close file input stream", e); + } + } + } + } + + /** + * Gets a list of files. If importAll is true, returns a list of the files + * under the path directory. If importAll is false, returns a list containing + * just the path file. + */ + private List getFiles() { + List files = new ArrayList<>(); + File file = new File(path); + if (importAll) { + File[] candidates = file.listFiles(); + if (candidates != null) { + for (File candidate : candidates) { + if (!FileUtils.isDirectory(candidate)) { + String extension = FileUtils.getExtension(candidate.getName()); + if (trackFileFormat == TrackFileFormat.KML && ( + TrackFileFormat.KML.getExtension().equals(extension) + || KmzTrackExporter.KMZ_EXTENSION.equals(extension))) { + files.add(candidate); + } else if (trackFileFormat == TrackFileFormat.GPX + && TrackFileFormat.GPX.getExtension().equals(extension)) { + files.add(candidate); + } + } + } + } + } else { + files.add(file); + } + return files; + } } diff --git a/src/main/java/com/google/android/apps/mytracks/io/file/importer/KmlFileTrackImporter.java b/src/main/java/com/google/android/apps/mytracks/io/file/importer/KmlFileTrackImporter.java index 00c97cadb..4afdb028e 100644 --- a/src/main/java/com/google/android/apps/mytracks/io/file/importer/KmlFileTrackImporter.java +++ b/src/main/java/com/google/android/apps/mytracks/io/file/importer/KmlFileTrackImporter.java @@ -22,8 +22,8 @@ import android.net.Uri; import androidx.annotation.VisibleForTesting; -import com.google.android.apps.mytracks.content.SensorDataSetLocation; import com.google.android.apps.mytracks.content.ContentProviderUtils; +import com.google.android.apps.mytracks.content.SensorDataSetLocation; import com.google.android.apps.mytracks.content.Waypoint.WaypointType; import com.google.android.apps.mytracks.content.sensor.SensorDataSet; @@ -34,278 +34,278 @@ import java.util.ArrayList; /** * Imports a KML file. - * + * * @author Jimmy Shih */ public class KmlFileTrackImporter extends AbstractFileTrackImporter { - private static final String CADENCE = "cadence"; - private static final String HEART_RATE = "heart_rate"; - private static final String POWER = "power"; + private static final String CADENCE = "cadence"; + private static final String HEART_RATE = "heart_rate"; + private static final String POWER = "power"; - private static final String STATISTICS_STYLE = "#statistics"; - private static final String WAYPOINT_STYLE = "#waypoint"; + private static final String STATISTICS_STYLE = "#statistics"; + private static final String WAYPOINT_STYLE = "#waypoint"; - private static final String TAG_COORDINATES = "coordinates"; - private static final String TAG_DESCRIPTION = "description"; - private static final String TAG_GX_COORD = "gx:coord"; - private static final String TAG_GX_MULTI_TRACK = "gx:MultiTrack"; - private static final String TAG_GX_SIMPLE_ARRAY_DATA = "gx:SimpleArrayData"; - private static final String TAG_GX_TRACK = "gx:Track"; - private static final String TAG_GX_VALUE = "gx:value"; - private static final String TAG_HREF = "href"; - private static final String TAG_KML = "kml"; - private static final String TAG_NAME = "name"; - private static final String TAG_PHOTO_OVERLAY = "PhotoOverlay"; - private static final String TAG_PLACEMARK = "Placemark"; - private static final String TAG_STYLE_URL = "styleUrl"; - private static final String TAG_VALUE = "value"; - private static final String TAG_WHEN = "when"; + private static final String TAG_COORDINATES = "coordinates"; + private static final String TAG_DESCRIPTION = "description"; + private static final String TAG_GX_COORD = "gx:coord"; + private static final String TAG_GX_MULTI_TRACK = "gx:MultiTrack"; + private static final String TAG_GX_SIMPLE_ARRAY_DATA = "gx:SimpleArrayData"; + private static final String TAG_GX_TRACK = "gx:Track"; + private static final String TAG_GX_VALUE = "gx:value"; + private static final String TAG_HREF = "href"; + private static final String TAG_KML = "kml"; + private static final String TAG_NAME = "name"; + private static final String TAG_PHOTO_OVERLAY = "PhotoOverlay"; + private static final String TAG_PLACEMARK = "Placemark"; + private static final String TAG_STYLE_URL = "styleUrl"; + private static final String TAG_VALUE = "value"; + private static final String TAG_WHEN = "when"; - private static final String ATTRIBUTE_NAME = "name"; + private static final String ATTRIBUTE_NAME = "name"; - private boolean trackStarted = false; - private String sensorName; - private ArrayList locationList; - private ArrayList cadenceList; - private ArrayList heartRateList; - private ArrayList powerList; + private boolean trackStarted = false; + private String sensorName; + private ArrayList locationList; + private ArrayList cadenceList; + private ArrayList heartRateList; + private ArrayList powerList; - /** - * Constructor. - * - * @param context the context - * @param importTrackId track id to import to. -1L to import to a new track. - */ - public KmlFileTrackImporter(Context context, long importTrackId) { - this(context, importTrackId, ContentProviderUtils.Factory.get(context)); - } + /** + * Constructor. + * + * @param context the context + * @param importTrackId track id to import to. -1L to import to a new track. + */ + public KmlFileTrackImporter(Context context, long importTrackId) { + this(context, importTrackId, ContentProviderUtils.Factory.get(context)); + } - @VisibleForTesting - KmlFileTrackImporter( - Context context, long importTrackId, ContentProviderUtils contentProviderUtils) { - super(context, importTrackId, contentProviderUtils); - } + @VisibleForTesting + KmlFileTrackImporter( + Context context, long importTrackId, ContentProviderUtils contentProviderUtils) { + super(context, importTrackId, contentProviderUtils); + } - @Override - public void startElement(String uri, String localName, String tag, Attributes attributes) - throws SAXException { - switch (tag) { - case TAG_PLACEMARK: - case TAG_PHOTO_OVERLAY: - /* - * Note that a track is contained in a Placemark, calling onWaypointStart - * will clear various track variables like name, category, and - * description. - */ - onWaypointStart(); - break; - case TAG_GX_MULTI_TRACK: - trackStarted = true; - onTrackStart(); - break; - case TAG_GX_TRACK: - if (!trackStarted) { - throw new SAXException("No " + TAG_GX_MULTI_TRACK); + @Override + public void startElement(String uri, String localName, String tag, Attributes attributes) + throws SAXException { + switch (tag) { + case TAG_PLACEMARK: + case TAG_PHOTO_OVERLAY: + /* + * Note that a track is contained in a Placemark, calling onWaypointStart + * will clear various track variables like name, category, and + * description. + */ + onWaypointStart(); + break; + case TAG_GX_MULTI_TRACK: + trackStarted = true; + onTrackStart(); + break; + case TAG_GX_TRACK: + if (!trackStarted) { + throw new SAXException("No " + TAG_GX_MULTI_TRACK); + } + onTrackSegmentStart(); + break; + case TAG_GX_SIMPLE_ARRAY_DATA: + onSensorDataStart(attributes); + break; } - onTrackSegmentStart(); - break; - case TAG_GX_SIMPLE_ARRAY_DATA: - onSensorDataStart(attributes); - break; - } - } - - @Override - public void endElement(String uri, String localName, String tag) throws SAXException { - if (tag.equals(TAG_KML)) { - onFileEnd(); - } else if (tag.equals(TAG_PLACEMARK) || tag.equals(TAG_PHOTO_OVERLAY)) { - /* - * Note that a track is contained in a Placemark, calling onWaypointend is - * save since waypointType is not set for a track. - */ - onWaypointEnd(); - } else if (localName.equals(TAG_COORDINATES)) { - onWaypointLocationEnd(); - } else if (tag.equals(TAG_GX_MULTI_TRACK)) { - onTrackEnd(); - } else if (tag.equals(TAG_GX_TRACK)) { - onTrackSegmentEnd(); - } else if (tag.equals(TAG_GX_COORD)) { - onTrackPointEnd(); - } else if (tag.equals(TAG_GX_VALUE)) { - onSensorValueEnd(); - } else if (tag.equals(TAG_NAME)) { - if (content != null) { - name = content.trim(); - } - } else if (localName.equals(TAG_DESCRIPTION)) { - if (content != null) { - description = content.trim(); - } - } else if (localName.equals(TAG_VALUE)) { - if (content != null) { - category = content.trim(); - } - } else if (localName.equals(TAG_WHEN)) { - if (content != null) { - time = content.trim(); - } - } else if (localName.equals(TAG_STYLE_URL)) { - if (content != null) { - waypointType = content.trim(); - } - } else if (localName.equals(TAG_HREF)) { - if (content != null) { - photoUrl = content.trim(); - } } - // Reset element content - content = null; - } + @Override + public void endElement(String uri, String localName, String tag) throws SAXException { + if (tag.equals(TAG_KML)) { + onFileEnd(); + } else if (tag.equals(TAG_PLACEMARK) || tag.equals(TAG_PHOTO_OVERLAY)) { + /* + * Note that a track is contained in a Placemark, calling onWaypointend is + * save since waypointType is not set for a track. + */ + onWaypointEnd(); + } else if (localName.equals(TAG_COORDINATES)) { + onWaypointLocationEnd(); + } else if (tag.equals(TAG_GX_MULTI_TRACK)) { + onTrackEnd(); + } else if (tag.equals(TAG_GX_TRACK)) { + onTrackSegmentEnd(); + } else if (tag.equals(TAG_GX_COORD)) { + onTrackPointEnd(); + } else if (tag.equals(TAG_GX_VALUE)) { + onSensorValueEnd(); + } else if (tag.equals(TAG_NAME)) { + if (content != null) { + name = content.trim(); + } + } else if (localName.equals(TAG_DESCRIPTION)) { + if (content != null) { + description = content.trim(); + } + } else if (localName.equals(TAG_VALUE)) { + if (content != null) { + category = content.trim(); + } + } else if (localName.equals(TAG_WHEN)) { + if (content != null) { + time = content.trim(); + } + } else if (localName.equals(TAG_STYLE_URL)) { + if (content != null) { + waypointType = content.trim(); + } + } else if (localName.equals(TAG_HREF)) { + if (content != null) { + photoUrl = content.trim(); + } + } - /** - * On waypoint start. - */ - private void onWaypointStart() { - // Reset all Placemark variables - name = null; - description = null; - category = null; - photoUrl = null; - latitude = null; - longitude = null; - altitude = null; - time = null; - waypointType = null; - } + // Reset element content + content = null; + } - /** - * On waypoint end. - */ - private void onWaypointEnd() throws SAXException { - // Add a waypoint if the waypointType matches - WaypointType type = null; - if (WAYPOINT_STYLE.equals(waypointType)) { - type = WaypointType.WAYPOINT; - } else if (STATISTICS_STYLE.equals(waypointType)) { - type = WaypointType.STATISTICS; + /** + * On waypoint start. + */ + private void onWaypointStart() { + // Reset all Placemark variables + name = null; + description = null; + category = null; + photoUrl = null; + latitude = null; + longitude = null; + altitude = null; + time = null; + waypointType = null; } - if (type == null) { - return; - } - if (photoUrl != null) { - Uri uri = Uri.parse(photoUrl); - photoUrl = getPhotoUrl(uri.getLastPathSegment()); - } - addWaypoint(type); - } - /** - * On waypoint location end. - */ - private void onWaypointLocationEnd() { - if (content != null) { - String[] parts = content.trim().split(","); - if (parts.length != 2 && parts.length != 3) { - return; - } - longitude = parts[0]; - latitude = parts[1]; - altitude = parts.length == 3 ? parts[2] : null; + /** + * On waypoint end. + */ + private void onWaypointEnd() throws SAXException { + // Add a waypoint if the waypointType matches + WaypointType type = null; + if (WAYPOINT_STYLE.equals(waypointType)) { + type = WaypointType.WAYPOINT; + } else if (STATISTICS_STYLE.equals(waypointType)) { + type = WaypointType.STATISTICS; + } + if (type == null) { + return; + } + if (photoUrl != null) { + Uri uri = Uri.parse(photoUrl); + photoUrl = getPhotoUrl(uri.getLastPathSegment()); + } + addWaypoint(type); } - } - @Override - protected void onTrackSegmentStart() { - super.onTrackSegmentStart(); - locationList = new ArrayList<>(); - heartRateList = new ArrayList<>(); - cadenceList = new ArrayList<>(); - powerList = new ArrayList<>(); - } - - /** - * On track segment end. - */ - private void onTrackSegmentEnd() { - // Close a track segment by inserting the segment locations - boolean hasHeartRate = heartRateList.size() == locationList.size(); - boolean hasCadence = cadenceList.size() == locationList.size(); - boolean hasPower = powerList.size() == locationList.size(); - - for (int i = 0; i < locationList.size(); i++) { - Location location = locationList.get(i); - - if (!hasPower && !hasCadence && !hasHeartRate) { - insertTrackPoint(location); - } else { - float heartrate = hasHeartRate ? heartRateList.get(i) : SensorDataSet.DATA_UNAVAILABLE; - float cadence = hasHeartRate ? cadenceList.get(i) : SensorDataSet.DATA_UNAVAILABLE; - float power = hasHeartRate ? powerList.get(i) : SensorDataSet.DATA_UNAVAILABLE; - - SensorDataSetLocation sensorDataSetLocation = new SensorDataSetLocation(location, new SensorDataSet(heartrate, cadence, power, SensorDataSet.DATA_UNAVAILABLE, location.getTime())); - insertTrackPoint(sensorDataSetLocation); - } + /** + * On waypoint location end. + */ + private void onWaypointLocationEnd() { + if (content != null) { + String[] parts = content.trim().split(","); + if (parts.length != 2 && parts.length != 3) { + return; + } + longitude = parts[0]; + latitude = parts[1]; + altitude = parts.length == 3 ? parts[2] : null; + } } - } - /** - * On track point end. gx:coord end tag. - */ - private void onTrackPointEnd() throws SAXException { - // Add location to locationList - if (content == null) { - return; + @Override + protected void onTrackSegmentStart() { + super.onTrackSegmentStart(); + locationList = new ArrayList<>(); + heartRateList = new ArrayList<>(); + cadenceList = new ArrayList<>(); + powerList = new ArrayList<>(); } - String[] parts = content.trim().split(" "); - if (parts.length != 2 && parts.length != 3) { - return; - } - longitude = parts[0]; - latitude = parts[1]; - altitude = parts.length == 3 ? parts[2] : null; - Location location = getTrackPoint(); - if (location == null) { - return; - } - locationList.add(location); - time = null; - } + /** + * On track segment end. + */ + private void onTrackSegmentEnd() { + // Close a track segment by inserting the segment locations + boolean hasHeartRate = heartRateList.size() == locationList.size(); + boolean hasCadence = cadenceList.size() == locationList.size(); + boolean hasPower = powerList.size() == locationList.size(); - /** - * On sensor data start. gx:SimpleArrayData start tag. - */ - private void onSensorDataStart(Attributes attributes) { - sensorName = attributes.getValue(ATTRIBUTE_NAME); - } + for (int i = 0; i < locationList.size(); i++) { + Location location = locationList.get(i); - /** - * On sensor value end. gx:value end tag. - */ - private void onSensorValueEnd() throws SAXException { - if (content == null) { - return; + if (!hasPower && !hasCadence && !hasHeartRate) { + insertTrackPoint(location); + } else { + float heartrate = hasHeartRate ? heartRateList.get(i) : SensorDataSet.DATA_UNAVAILABLE; + float cadence = hasHeartRate ? cadenceList.get(i) : SensorDataSet.DATA_UNAVAILABLE; + float power = hasHeartRate ? powerList.get(i) : SensorDataSet.DATA_UNAVAILABLE; + + SensorDataSetLocation sensorDataSetLocation = new SensorDataSetLocation(location, new SensorDataSet(heartrate, cadence, power, SensorDataSet.DATA_UNAVAILABLE, location.getTime())); + insertTrackPoint(sensorDataSetLocation); + } + } } - content = content.trim(); - if (content.equals("")) { - return; + + /** + * On track point end. gx:coord end tag. + */ + private void onTrackPointEnd() throws SAXException { + // Add location to locationList + if (content == null) { + return; + } + String[] parts = content.trim().split(" "); + if (parts.length != 2 && parts.length != 3) { + return; + } + longitude = parts[0]; + latitude = parts[1]; + altitude = parts.length == 3 ? parts[2] : null; + + Location location = getTrackPoint(); + if (location == null) { + return; + } + locationList.add(location); + time = null; } - int value; - try { - value = Integer.parseInt(content); - } catch (NumberFormatException e) { - throw new SAXException(createErrorMessage("Unable to parse gx:value:" + content), e); + + /** + * On sensor data start. gx:SimpleArrayData start tag. + */ + private void onSensorDataStart(Attributes attributes) { + sensorName = attributes.getValue(ATTRIBUTE_NAME); } - if (POWER.equals(sensorName)) { - powerList.add(value); - } else if (HEART_RATE.equals(sensorName)) { - heartRateList.add(value); - } else if (CADENCE.equals(sensorName)) { - cadenceList.add(value); + + /** + * On sensor value end. gx:value end tag. + */ + private void onSensorValueEnd() throws SAXException { + if (content == null) { + return; + } + content = content.trim(); + if (content.equals("")) { + return; + } + int value; + try { + value = Integer.parseInt(content); + } catch (NumberFormatException e) { + throw new SAXException(createErrorMessage("Unable to parse gx:value:" + content), e); + } + if (POWER.equals(sensorName)) { + powerList.add(value); + } else if (HEART_RATE.equals(sensorName)) { + heartRateList.add(value); + } else if (CADENCE.equals(sensorName)) { + cadenceList.add(value); + } } - } } diff --git a/src/main/java/com/google/android/apps/mytracks/io/file/importer/KmzTrackImporter.java b/src/main/java/com/google/android/apps/mytracks/io/file/importer/KmzTrackImporter.java index 65a8def2c..a13962e4e 100644 --- a/src/main/java/com/google/android/apps/mytracks/io/file/importer/KmzTrackImporter.java +++ b/src/main/java/com/google/android/apps/mytracks/io/file/importer/KmzTrackImporter.java @@ -16,14 +16,14 @@ package com.google.android.apps.mytracks.io.file.importer; -import com.google.android.apps.mytracks.content.ContentProviderUtils; -import com.google.android.apps.mytracks.io.file.exporter.KmzTrackExporter; -import com.google.android.apps.mytracks.util.FileUtils; - import android.content.Context; import android.net.Uri; import android.util.Log; +import com.google.android.apps.mytracks.content.ContentProviderUtils; +import com.google.android.apps.mytracks.io.file.exporter.KmzTrackExporter; +import com.google.android.apps.mytracks.util.FileUtils; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -35,164 +35,164 @@ import java.util.zip.ZipInputStream; /** * Imports a KMZ file. - * + * * @author Jimmy Shih */ public class KmzTrackImporter implements TrackImporter { - private static final String TAG = KmzTrackImporter.class.getSimpleName(); - private static final int BUFFER_SIZE = 4096; + private static final String TAG = KmzTrackImporter.class.getSimpleName(); + private static final int BUFFER_SIZE = 4096; - private final Context context; - private final long importTrackId; + private final Context context; + private final long importTrackId; - /** - * Constructor. - * - * @param context the context - * @param importTrackId track id to import to. This should not be -1L so that - * images in the kmz file can be imported. - */ - public KmzTrackImporter(Context context, long importTrackId) { - this.context = context; - this.importTrackId = importTrackId; - } + /** + * Constructor. + * + * @param context the context + * @param importTrackId track id to import to. This should not be -1L so that + * images in the kmz file can be imported. + */ + public KmzTrackImporter(Context context, long importTrackId) { + this.context = context; + this.importTrackId = importTrackId; + } - @Override - public long importFile(InputStream inputStream) { - ZipInputStream zipInputStream = null; - long trackId = importTrackId; - try { - ZipEntry zipEntry; + @Override + public long importFile(InputStream inputStream) { + ZipInputStream zipInputStream = null; + long trackId = importTrackId; + try { + ZipEntry zipEntry; - zipInputStream = new ZipInputStream(inputStream); - while ((zipEntry = zipInputStream.getNextEntry()) != null) { - if (Thread.interrupted()) { - Log.d(TAG, "Thread interrupted"); - cleanImport(trackId); - return -1L; - } - String fileName = zipEntry.getName(); - if (fileName.equals(KmzTrackExporter.KMZ_KML_FILE)) { - trackId = parseKml(zipInputStream); - if (trackId == -1L) { - Log.d(TAG, "Unable to parse kml in kmz"); + zipInputStream = new ZipInputStream(inputStream); + while ((zipEntry = zipInputStream.getNextEntry()) != null) { + if (Thread.interrupted()) { + Log.d(TAG, "Thread interrupted"); + cleanImport(trackId); + return -1L; + } + String fileName = zipEntry.getName(); + if (fileName.equals(KmzTrackExporter.KMZ_KML_FILE)) { + trackId = parseKml(zipInputStream); + if (trackId == -1L) { + Log.d(TAG, "Unable to parse kml in kmz"); + cleanImport(trackId); + return -1L; + } + } else { + String prefix = KmzTrackExporter.KMZ_IMAGES_DIR + File.separatorChar; + if (fileName.startsWith(prefix)) { + readImageFile(zipInputStream, fileName.substring(prefix.length())); + } + } + zipInputStream.closeEntry(); + } + return trackId; + } catch (IOException e) { + Log.e(TAG, "Unable to import file", e); cleanImport(trackId); return -1L; - } - } else { - String prefix = KmzTrackExporter.KMZ_IMAGES_DIR + File.separatorChar; - if (fileName.startsWith(prefix)) { - readImageFile(zipInputStream, fileName.substring(prefix.length())); - } + } finally { + if (zipInputStream != null) { + try { + zipInputStream.close(); + } catch (IOException e) { + Log.e(TAG, "Unable to close zip input stream", e); + } + } } - zipInputStream.closeEntry(); - } - return trackId; - } catch (IOException e) { - Log.e(TAG, "Unable to import file", e); - cleanImport(trackId); - return -1L; - } finally { - if (zipInputStream != null) { + } + + /** + * Cleans up import. + * + * @param trackId the trackId + */ + private void cleanImport(long trackId) { + if (trackId != -1L) { + ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context); + contentProviderUtils.deleteTrack(context, trackId); + } + + if (importTrackId != -1L) { + File dir = FileUtils.getPhotoDir(importTrackId); + if (FileUtils.isDirectory(dir)) { + for (File file : dir.listFiles()) { + file.delete(); + } + dir.delete(); + FileUtils.updateMediaScanner(context, Uri.fromFile(dir)); + } + } + } + + /** + * Parses kml + * + * @param zipInputStream the zip input stream + * @return the imported track id or -1L + */ + private long parseKml(ZipInputStream zipInputStream) throws IOException { + ByteArrayInputStream byteArrayInputStream = null; try { - zipInputStream.close(); - } catch (IOException e) { - Log.e(TAG, "Unable to close zip input stream", e); + KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, importTrackId); + byteArrayInputStream = new ByteArrayInputStream(getKml(zipInputStream)); + return kmlFileTrackImporter.importFile(byteArrayInputStream); + } finally { + if (byteArrayInputStream != null) { + byteArrayInputStream.close(); + } } - } - } - } - - /** - * Cleans up import. - * - * @param trackId the trackId - */ - private void cleanImport(long trackId) { - if (trackId != -1L) { - ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context); - contentProviderUtils.deleteTrack(context, trackId); } - if (importTrackId != -1L) { - File dir = FileUtils.getPhotoDir(importTrackId); - if (FileUtils.isDirectory(dir)) { - for (File file : dir.listFiles()) { - file.delete(); + /** + * Gets the kml as byte array. + * + * @param zipInputStream the zip input stream + */ + private byte[] getKml(ZipInputStream zipInputStream) throws IOException { + try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { + byte[] buffer = new byte[BUFFER_SIZE]; + int count; + while ((count = zipInputStream.read(buffer)) != -1) { + byteArrayOutputStream.write(buffer, 0, count); + } + return byteArrayOutputStream.toByteArray(); } - dir.delete(); - FileUtils.updateMediaScanner(context, Uri.fromFile(dir)); - } } - } - /** - * Parses kml - * - * @param zipInputStream the zip input stream - * @return the imported track id or -1L - */ - private long parseKml(ZipInputStream zipInputStream) throws IOException { - ByteArrayInputStream byteArrayInputStream = null; - try { - KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, importTrackId); - byteArrayInputStream = new ByteArrayInputStream(getKml(zipInputStream)); - return kmlFileTrackImporter.importFile(byteArrayInputStream); - } finally { - if (byteArrayInputStream != null) { - byteArrayInputStream.close(); - } + /** + * Reads an image file. + * + * @param zipInputStream the zip input stream + * @param fileName the file name + */ + private void readImageFile(ZipInputStream zipInputStream, String fileName) throws IOException { + FileOutputStream fileOutputStream = null; + try { + if (importTrackId == -1L) { + return; + } + if (fileName.equals("")) { + return; + } + File dir = FileUtils.getPhotoDir(importTrackId); + FileUtils.ensureDirectoryExists(dir); + + File file = new File(dir, fileName); + fileOutputStream = new FileOutputStream(file); + byte[] buffer = new byte[BUFFER_SIZE]; + int count; + while ((count = zipInputStream.read(buffer)) != -1) { + fileOutputStream.write(buffer, 0, count); + } + + FileUtils.updateMediaScanner(context, Uri.fromFile(file)); + } finally { + if (fileOutputStream != null) { + fileOutputStream.close(); + } + } } - } - - /** - * Gets the kml as byte array. - * - * @param zipInputStream the zip input stream - */ - private byte[] getKml(ZipInputStream zipInputStream) throws IOException { - try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { - byte[] buffer = new byte[BUFFER_SIZE]; - int count; - while ((count = zipInputStream.read(buffer)) != -1) { - byteArrayOutputStream.write(buffer, 0, count); - } - return byteArrayOutputStream.toByteArray(); - } - } - - /** - * Reads an image file. - * - * @param zipInputStream the zip input stream - * @param fileName the file name - */ - private void readImageFile(ZipInputStream zipInputStream, String fileName) throws IOException { - FileOutputStream fileOutputStream = null; - try { - if (importTrackId == -1L) { - return; - } - if (fileName.equals("")) { - return; - } - File dir = FileUtils.getPhotoDir(importTrackId); - FileUtils.ensureDirectoryExists(dir); - - File file = new File(dir, fileName); - fileOutputStream = new FileOutputStream(file); - byte[] buffer = new byte[BUFFER_SIZE]; - int count; - while ((count = zipInputStream.read(buffer)) != -1) { - fileOutputStream.write(buffer, 0, count); - } - - FileUtils.updateMediaScanner(context, Uri.fromFile(file)); - } finally { - if (fileOutputStream != null) { - fileOutputStream.close(); - } - } - } } diff --git a/src/main/java/com/google/android/apps/mytracks/io/file/importer/TrackImporter.java b/src/main/java/com/google/android/apps/mytracks/io/file/importer/TrackImporter.java index e37978a69..a6dfd6efc 100644 --- a/src/main/java/com/google/android/apps/mytracks/io/file/importer/TrackImporter.java +++ b/src/main/java/com/google/android/apps/mytracks/io/file/importer/TrackImporter.java @@ -19,16 +19,16 @@ import java.io.InputStream; /** * Interface for a track importer. - * + * * @author Jimmy Shih */ public interface TrackImporter { - /** - * Import a file. - * - * @param inputStream the file's input stream - * @return the imported track id or -1L. - */ - long importFile(InputStream inputStream); + /** + * Import a file. + * + * @param inputStream the file's input stream + * @return the imported track id or -1L. + */ + long importFile(InputStream inputStream); } diff --git a/src/main/java/com/google/android/apps/mytracks/services/AbsoluteLocationListenerPolicy.java b/src/main/java/com/google/android/apps/mytracks/services/AbsoluteLocationListenerPolicy.java index f9240baca..002dd29d3 100644 --- a/src/main/java/com/google/android/apps/mytracks/services/AbsoluteLocationListenerPolicy.java +++ b/src/main/java/com/google/android/apps/mytracks/services/AbsoluteLocationListenerPolicy.java @@ -1,12 +1,12 @@ /* * Copyright 2009 Google Inc. - * + * * 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 @@ -19,34 +19,34 @@ package com.google.android.apps.mytracks.services; /** * This is a simple location listener policy that will always dictate the same * polling interval. - * + * * @author Sandor Dornbush */ public class AbsoluteLocationListenerPolicy implements LocationListenerPolicy { - private final long interval; + private final long interval; - /** - * Constructor. - * - * @param interval the interval to request for gps signal - */ - public AbsoluteLocationListenerPolicy(long interval) { - this.interval = interval; - } + /** + * Constructor. + * + * @param interval the interval to request for gps signal + */ + public AbsoluteLocationListenerPolicy(long interval) { + this.interval = interval; + } - @Override - public long getDesiredPollingInterval() { - return interval; - } + @Override + public long getDesiredPollingInterval() { + return interval; + } - @Override - public int getMinDistance() { - return 0; - } + @Override + public int getMinDistance() { + return 0; + } - @Override - public void updateIdleTime(long idleTime) { - // Ignore - } + @Override + public void updateIdleTime(long idleTime) { + // Ignore + } } diff --git a/src/main/java/com/google/android/apps/mytracks/services/AdaptiveLocationListenerPolicy.java b/src/main/java/com/google/android/apps/mytracks/services/AdaptiveLocationListenerPolicy.java index 64ba60ddc..417b14ba1 100644 --- a/src/main/java/com/google/android/apps/mytracks/services/AdaptiveLocationListenerPolicy.java +++ b/src/main/java/com/google/android/apps/mytracks/services/AdaptiveLocationListenerPolicy.java @@ -1,12 +1,12 @@ /* * Copyright 2009 Google Inc. - * + * * 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 @@ -21,53 +21,53 @@ package com.google.android.apps.mytracks.services; * has been stationary. This policy will dictate a policy based on a min, max * and idle time. The policy will dictate an interval bounded by min and max, * and is half of the idle time. - * + * * @author Sandor Dornbush */ public class AdaptiveLocationListenerPolicy implements LocationListenerPolicy { - private final long minInterval; - private final long maxInterval; - private final int minDistance; + private final long minInterval; + private final long maxInterval; + private final int minDistance; - // The time the user has been idle at the current location, in milliseconds. - private long idleTime; + // The time the user has been idle at the current location, in milliseconds. + private long idleTime; - /** - * Creates a policy that will be bounded by the given minInterval and - * maxInterval. - * - * @param minInterval the smallest interval this policy will dictate, in - * milliseconds - * @param maxInterval the largest interval this policy will dictate, in - * milliseconds - * @param minDistance the minimum distance in meters - */ - public AdaptiveLocationListenerPolicy(long minInterval, long maxInterval, int minDistance) { - this.minInterval = minInterval; - this.maxInterval = maxInterval; - this.minDistance = minDistance; - } + /** + * Creates a policy that will be bounded by the given minInterval and + * maxInterval. + * + * @param minInterval the smallest interval this policy will dictate, in + * milliseconds + * @param maxInterval the largest interval this policy will dictate, in + * milliseconds + * @param minDistance the minimum distance in meters + */ + public AdaptiveLocationListenerPolicy(long minInterval, long maxInterval, int minDistance) { + this.minInterval = minInterval; + this.maxInterval = maxInterval; + this.minDistance = minDistance; + } - /* - * Returns an interval half of the idle time, but bounded by minInteval and - * maxInterval. - */ - @Override - public long getDesiredPollingInterval() { - long desiredInterval = idleTime / 2; - // Round to second to avoid setting the interval too often - desiredInterval = (desiredInterval / 1000) * 1000; - return Math.max(Math.min(maxInterval, desiredInterval), minInterval); - } + /* + * Returns an interval half of the idle time, but bounded by minInteval and + * maxInterval. + */ + @Override + public long getDesiredPollingInterval() { + long desiredInterval = idleTime / 2; + // Round to second to avoid setting the interval too often + desiredInterval = (desiredInterval / 1000) * 1000; + return Math.max(Math.min(maxInterval, desiredInterval), minInterval); + } - @Override - public int getMinDistance() { - return minDistance; - } + @Override + public int getMinDistance() { + return minDistance; + } - @Override - public void updateIdleTime(long newIdleTime) { - idleTime = newIdleTime; - } + @Override + public void updateIdleTime(long newIdleTime) { + idleTime = newIdleTime; + } } diff --git a/src/main/java/com/google/android/apps/mytracks/services/ControlRecordingService.java b/src/main/java/com/google/android/apps/mytracks/services/ControlRecordingService.java index 0dd476b55..b76ae944c 100644 --- a/src/main/java/com/google/android/apps/mytracks/services/ControlRecordingService.java +++ b/src/main/java/com/google/android/apps/mytracks/services/ControlRecordingService.java @@ -39,95 +39,95 @@ import com.google.android.maps.mytracks.R; */ public class ControlRecordingService extends IntentService implements ServiceConnection { - private static final String TAG = ControlRecordingService.class.getSimpleName(); - - private ITrackRecordingService trackRecordingService; - private boolean connected = false; + private static final String TAG = ControlRecordingService.class.getSimpleName(); - public ControlRecordingService() { - super(ControlRecordingService.class.getSimpleName()); - } + private ITrackRecordingService trackRecordingService; + private boolean connected = false; - @Override - public void onCreate() { - super.onCreate(); - - Intent newIntent = new Intent(this, TrackRecordingService.class); - startService(newIntent); - bindService(newIntent, this, 0); - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - trackRecordingService = (ITrackRecordingService)service; - notifyConnected(); - } - - @Override - public void onServiceDisconnected(ComponentName name) { - connected = false; - } - - /** - * Notifies all threads that connection to {@link TrackRecordingService} is - * available. - */ - private synchronized void notifyConnected() { - connected = true; - notifyAll(); - } - - /** - * Waits until the connection to {@link TrackRecordingService} is available. - */ - private synchronized void waitConnected() { - while (!connected) { - try { - wait(); - } catch (InterruptedException e) { - - // can safely ignore - } + public ControlRecordingService() { + super(ControlRecordingService.class.getSimpleName()); } - } - @Override - protected void onHandleIntent(Intent intent) { - waitConnected(); - onHandleIntent(intent, trackRecordingService); - } + @Override + public void onCreate() { + super.onCreate(); - - /** - * Handles the intent to start or stop a recording. - * - * @param intent to be handled - * @param service the trackRecordingService - */ - @VisibleForTesting - void onHandleIntent(Intent intent, ITrackRecordingService service) { - String action = intent.getAction(); - if (action != null) { - if (action.equals(getString(R.string.track_action_start))) { - service.startNewTrack(); - } else if (action.equals(getString(R.string.track_action_end))) { - service.endCurrentTrack(); - } else if (action.equals(getString(R.string.track_action_pause))) { - service.pauseCurrentTrack(); - } else if (action.equals(getString(R.string.track_action_resume))) { - service.resumeCurrentTrack(); - } + Intent newIntent = new Intent(this, TrackRecordingService.class); + startService(newIntent); + bindService(newIntent, this, 0); } - unbindService(this); - connected = false; - } - @Override - public void onDestroy() { - super.onDestroy(); - if (connected) { - unbindService(this); - connected = false; + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + trackRecordingService = (ITrackRecordingService) service; + notifyConnected(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + connected = false; + } + + /** + * Notifies all threads that connection to {@link TrackRecordingService} is + * available. + */ + private synchronized void notifyConnected() { + connected = true; + notifyAll(); + } + + /** + * Waits until the connection to {@link TrackRecordingService} is available. + */ + private synchronized void waitConnected() { + while (!connected) { + try { + wait(); + } catch (InterruptedException e) { + + // can safely ignore + } + } + } + + @Override + protected void onHandleIntent(Intent intent) { + waitConnected(); + onHandleIntent(intent, trackRecordingService); + } + + + /** + * Handles the intent to start or stop a recording. + * + * @param intent to be handled + * @param service the trackRecordingService + */ + @VisibleForTesting + void onHandleIntent(Intent intent, ITrackRecordingService service) { + String action = intent.getAction(); + if (action != null) { + if (action.equals(getString(R.string.track_action_start))) { + service.startNewTrack(); + } else if (action.equals(getString(R.string.track_action_end))) { + service.endCurrentTrack(); + } else if (action.equals(getString(R.string.track_action_pause))) { + service.pauseCurrentTrack(); + } else if (action.equals(getString(R.string.track_action_resume))) { + service.resumeCurrentTrack(); + } + } + unbindService(this); + connected = false; + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (connected) { + unbindService(this); + connected = false; + } } - } } diff --git a/src/main/java/com/google/android/apps/mytracks/services/ITrackRecordingService.java b/src/main/java/com/google/android/apps/mytracks/services/ITrackRecordingService.java index 179e88088..ec4e8d930 100644 --- a/src/main/java/com/google/android/apps/mytracks/services/ITrackRecordingService.java +++ b/src/main/java/com/google/android/apps/mytracks/services/ITrackRecordingService.java @@ -27,87 +27,87 @@ import com.google.android.apps.mytracks.content.sensor.SensorState; */ public interface ITrackRecordingService { - /** - * Starts gps. - */ - void startGps(); + /** + * Starts gps. + */ + void startGps(); - /** - * Stops gps. - */ - void stopGps(); + /** + * Stops gps. + */ + void stopGps(); - /** - * Starts recording a new track. - * - * @return the track ID of the new track. - */ - long startNewTrack(); + /** + * Starts recording a new track. + * + * @return the track ID of the new track. + */ + long startNewTrack(); - /** - * Pauses the current recording track. - */ - void pauseCurrentTrack(); + /** + * Pauses the current recording track. + */ + void pauseCurrentTrack(); - /** - * Resumes the current recording track. - */ - void resumeCurrentTrack(); + /** + * Resumes the current recording track. + */ + void resumeCurrentTrack(); - /** - * Ends the current recording track. - */ - void endCurrentTrack(); + /** + * Ends the current recording track. + */ + void endCurrentTrack(); - /** - * Returns true if currently recording a track. - */ - boolean isRecording(); + /** + * Returns true if currently recording a track. + */ + boolean isRecording(); - /** - * Returns true if the current recording track is paused. Returns true if not recording. - */ - boolean isPaused(); + /** + * Returns true if the current recording track is paused. Returns true if not recording. + */ + boolean isPaused(); - /** - * Gets the current recording track ID. Returns -1 if not recording. - */ - long getRecordingTrackId(); + /** + * Gets the current recording track ID. Returns -1 if not recording. + */ + long getRecordingTrackId(); - /** - * Gets the total time for the current recording track. Returns 0 if not recording. - */ - long getTotalTime(); + /** + * Gets the total time for the current recording track. Returns 0 if not recording. + */ + long getTotalTime(); - /** - * Inserts a waypoint in the current recording track. - * - * @param request the details of the waypoint to be inserted - * @return the ID of the inserted waypoint - */ - long insertWaypoint(WaypointCreationRequest request); + /** + * Inserts a waypoint in the current recording track. + * + * @param request the details of the waypoint to be inserted + * @return the ID of the inserted waypoint + */ + long insertWaypoint(WaypointCreationRequest request); - /** - * Inserts a track point in the current recording track. - * - * When recording a track, GPS locations are automatically inserted. This is used for - * inserting special track points or for testing. - * - * @param location the track point to be inserted - */ - void insertTrackPoint(Location location); + /** + * Inserts a track point in the current recording track. + *

+ * When recording a track, GPS locations are automatically inserted. This is used for + * inserting special track points or for testing. + * + * @param location the track point to be inserted + */ + void insertTrackPoint(Location location); - /** - * Gets the current sensor data. Returns null if there is no data. + /** + * Gets the current sensor data. Returns null if there is no data. + * + * @return SensorDataSet object. + */ + SensorDataSet getSensorData(); - * @return SensorDataSet object. - */ - SensorDataSet getSensorData(); - - /** - * Gets the current sensor manager state. - * - * return a Sensor.SensorState enum value. - */ - SensorState getSensorState(); + /** + * Gets the current sensor manager state. + *

+ * return a Sensor.SensorState enum value. + */ + SensorState getSensorState(); } diff --git a/src/main/java/com/google/android/apps/mytracks/services/LocationListenerPolicy.java b/src/main/java/com/google/android/apps/mytracks/services/LocationListenerPolicy.java index 4271e4ba8..880e5abba 100644 --- a/src/main/java/com/google/android/apps/mytracks/services/LocationListenerPolicy.java +++ b/src/main/java/com/google/android/apps/mytracks/services/LocationListenerPolicy.java @@ -1,12 +1,12 @@ /* * Copyright 2009 Google Inc. - * + * * 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 @@ -20,28 +20,28 @@ package com.google.android.apps.mytracks.services; * This is an interface for classes that will manage the location listener * policy. Different policy options are: {@link AbsoluteLocationListenerPolicy} * and {@link AdaptiveLocationListenerPolicy}. - * + * * @author Sandor Dornbush */ public interface LocationListenerPolicy { - /** - * Returns the polling interval this policy would like at this moment. - * - * @return the polling interval - */ - long getDesiredPollingInterval(); + /** + * Returns the polling interval this policy would like at this moment. + * + * @return the polling interval + */ + long getDesiredPollingInterval(); - /** - * Returns the minimum distance between updates. - */ - int getMinDistance(); + /** + * Returns the minimum distance between updates. + */ + int getMinDistance(); - /** - * Notifies the amount of time the user has been idle at his current location. - * - * @param idleTime the time that the user has been idle at his current - * location - */ - void updateIdleTime(long idleTime); + /** + * Notifies the amount of time the user has been idle at his current location. + * + * @param idleTime the time that the user has been idle at his current + * location + */ + void updateIdleTime(long idleTime); } diff --git a/src/main/java/com/google/android/apps/mytracks/services/TrackRecordingService.java b/src/main/java/com/google/android/apps/mytracks/services/TrackRecordingService.java index 2a328edea..4d92e3bb1 100644 --- a/src/main/java/com/google/android/apps/mytracks/services/TrackRecordingService.java +++ b/src/main/java/com/google/android/apps/mytracks/services/TrackRecordingService.java @@ -44,11 +44,11 @@ import androidx.core.app.TaskStackBuilder; import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.TrackDetailActivity; import com.google.android.apps.mytracks.TrackListActivity; -import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl; -import com.google.android.apps.mytracks.content.SensorDataSetLocation; -import com.google.android.apps.mytracks.content.CustomContentProvider; import com.google.android.apps.mytracks.content.ContentProviderUtils; import com.google.android.apps.mytracks.content.ContentProviderUtils.LocationIterator; +import com.google.android.apps.mytracks.content.CustomContentProvider; +import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl; +import com.google.android.apps.mytracks.content.SensorDataSetLocation; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.content.Waypoint.WaypointType; @@ -95,15 +95,11 @@ public class TrackRecordingService extends Service { * Anything faster than that (in meters per second) will be considered moving. */ public static final double MAX_NO_MOVEMENT_SPEED = 0.224; - - private static final String TAG = TrackRecordingService.class.getSimpleName(); - - // 1 minute in milliseconds - private static final long ONE_MINUTE = (long) (UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS); - @VisibleForTesting static final int MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS = 3; - + private static final String TAG = TrackRecordingService.class.getSimpleName(); + // 1 minute in milliseconds + private static final long ONE_MINUTE = (long) (UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS); // The following variables are set in onCreate: private ExecutorService executorService; private Context context; @@ -124,15 +120,6 @@ public class TrackRecordingService extends Service { // The following variables are set when recording: private TripStatisticsUpdater trackTripStatisticsUpdater; - private TripStatisticsUpdater markerTripStatisticsUpdater; - private WakeLock wakeLock; - private SensorManager sensorManager; - private Location lastLocation; - private boolean currentSegmentHasLocation; - private boolean isIdle; // true if idle - - private ServiceBinder binder = new ServiceBinder(this); - /* * Note that sharedPreferenceChangeListener cannot be an anonymous inner * class. Anonymous inner class will get garbage collected. @@ -193,7 +180,13 @@ public class TrackRecordingService extends Service { } } }; - + private TripStatisticsUpdater markerTripStatisticsUpdater; + private WakeLock wakeLock; + private SensorManager sensorManager; + private Location lastLocation; + private boolean currentSegmentHasLocation; + private boolean isIdle; // true if idle + private ServiceBinder binder = new ServiceBinder(this); private LocationListener locationListener = new LocationListener() { @Override diff --git a/src/main/java/com/google/android/apps/mytracks/services/TrackRecordingServiceConnection.java b/src/main/java/com/google/android/apps/mytracks/services/TrackRecordingServiceConnection.java index f87f03619..a1a6896ae 100644 --- a/src/main/java/com/google/android/apps/mytracks/services/TrackRecordingServiceConnection.java +++ b/src/main/java/com/google/android/apps/mytracks/services/TrackRecordingServiceConnection.java @@ -16,9 +16,6 @@ package com.google.android.apps.mytracks.services; -import com.google.android.apps.mytracks.util.TrackRecordingServiceConnectionUtils; -import com.google.android.maps.mytracks.BuildConfig; - import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -28,136 +25,136 @@ import android.os.IBinder.DeathRecipient; import android.os.RemoteException; import android.util.Log; +import com.google.android.apps.mytracks.util.TrackRecordingServiceConnectionUtils; +import com.google.android.maps.mytracks.BuildConfig; + /** * Wrapper for the track recording service. This handles service * start/bind/unbind/stop. The service must be started before it can be bound. * Returns the service if it is started and bound. - * + * * @author Rodrigo Damazio */ public class TrackRecordingServiceConnection { - private static final String TAG = TrackRecordingServiceConnection.class.getSimpleName(); + private static final String TAG = TrackRecordingServiceConnection.class.getSimpleName(); + private final Context context; + private final Runnable callback; + private ITrackRecordingService trackRecordingService; + private final DeathRecipient deathRecipient = new DeathRecipient() { + @Override + public void binderDied() { + Log.d(TAG, "Service died."); + setTrackRecordingService(null); + } + }; + private final ServiceConnection serviceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + Log.i(TAG, "Connected to the service."); + try { + service.linkToDeath(deathRecipient, 0); + } catch (RemoteException e) { + Log.e(TAG, "Failed to bind a death recipient.", e); + } + setTrackRecordingService((ITrackRecordingService) service); + } - private final DeathRecipient deathRecipient = new DeathRecipient() { - @Override - public void binderDied() { - Log.d(TAG, "Service died."); - setTrackRecordingService(null); - } - }; + @Override + public void onServiceDisconnected(ComponentName className) { + Log.i(TAG, "Disconnected from the service."); + setTrackRecordingService(null); + } + }; - private final ServiceConnection serviceConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - Log.i(TAG, "Connected to the service."); - try { - service.linkToDeath(deathRecipient, 0); - } catch (RemoteException e) { - Log.e(TAG, "Failed to bind a death recipient.", e); - } - setTrackRecordingService((ITrackRecordingService)service); + /** + * Constructor. + * + * @param context the context + * @param callback the callback to invoke when the service binding changes + */ + public TrackRecordingServiceConnection(Context context, Runnable callback) { + this.context = context; + this.callback = callback; } - @Override - public void onServiceDisconnected(ComponentName className) { - Log.i(TAG, "Disconnected from the service."); - setTrackRecordingService(null); - } - }; - - private final Context context; - private final Runnable callback; - private ITrackRecordingService trackRecordingService; - - /** - * Constructor. - * - * @param context the context - * @param callback the callback to invoke when the service binding changes - */ - public TrackRecordingServiceConnection(Context context, Runnable callback) { - this.context = context; - this.callback = callback; - } - - /** - * Starts and binds the service. - */ - public void startAndBind() { - bindService(true); - } - - /** - * Binds the service if it is started. - */ - public void bindIfStarted() { - bindService(false); - } - - /** - * Unbinds and stops the service. - */ - public void unbindAndStop() { - unbind(); - context.stopService(new Intent(context, TrackRecordingService.class)); - } - - /** - * Unbinds the service (but leave it running). - */ - public void unbind() { - try { - context.unbindService(serviceConnection); - } catch (IllegalArgumentException e) { - // Means not bound to the service. OK to ignore. - } - setTrackRecordingService(null); - } - - /** - * Gets the track recording service if bound. Returns null otherwise - */ - public ITrackRecordingService getServiceIfBound() { - return trackRecordingService; - } - - /** - * Binds the service if it is started. - * - * @param startIfNeeded start the service if needed - */ - private void bindService(boolean startIfNeeded) { - if (trackRecordingService != null) { - // Service is already started and bound. - return; + /** + * Starts and binds the service. + */ + public void startAndBind() { + bindService(true); } - if (!startIfNeeded - && !TrackRecordingServiceConnectionUtils.isRecordingServiceRunning(context)) { - Log.d(TAG, "Service is not started. Not binding it."); - return; + /** + * Binds the service if it is started. + */ + public void bindIfStarted() { + bindService(false); } - if (startIfNeeded) { - Log.i(TAG, "Starting the service."); - context.startService(new Intent(context, TrackRecordingService.class)); + /** + * Unbinds and stops the service. + */ + public void unbindAndStop() { + unbind(); + context.stopService(new Intent(context, TrackRecordingService.class)); } - Log.i(TAG, "Binding the service."); - int flags = BuildConfig.DEBUG ? Context.BIND_DEBUG_UNBIND : 0; - context.bindService(new Intent(context, TrackRecordingService.class), serviceConnection, flags); - } - - /** - * Sets the trackRecordingService. - * - * @param value the value - */ - private void setTrackRecordingService(ITrackRecordingService value) { - trackRecordingService = value; - if (callback != null) { - callback.run(); + /** + * Unbinds the service (but leave it running). + */ + public void unbind() { + try { + context.unbindService(serviceConnection); + } catch (IllegalArgumentException e) { + // Means not bound to the service. OK to ignore. + } + setTrackRecordingService(null); + } + + /** + * Gets the track recording service if bound. Returns null otherwise + */ + public ITrackRecordingService getServiceIfBound() { + return trackRecordingService; + } + + /** + * Binds the service if it is started. + * + * @param startIfNeeded start the service if needed + */ + private void bindService(boolean startIfNeeded) { + if (trackRecordingService != null) { + // Service is already started and bound. + return; + } + + if (!startIfNeeded + && !TrackRecordingServiceConnectionUtils.isRecordingServiceRunning(context)) { + Log.d(TAG, "Service is not started. Not binding it."); + return; + } + + if (startIfNeeded) { + Log.i(TAG, "Starting the service."); + context.startService(new Intent(context, TrackRecordingService.class)); + } + + Log.i(TAG, "Binding the service."); + int flags = BuildConfig.DEBUG ? Context.BIND_DEBUG_UNBIND : 0; + context.bindService(new Intent(context, TrackRecordingService.class), serviceConnection, flags); + } + + /** + * Sets the trackRecordingService. + * + * @param value the value + */ + private void setTrackRecordingService(ITrackRecordingService value) { + trackRecordingService = value; + if (callback != null) { + callback.run(); + } } - } } diff --git a/src/main/java/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java b/src/main/java/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java index f4a1bf31a..d8bc48a8f 100644 --- a/src/main/java/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java +++ b/src/main/java/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java @@ -41,15 +41,13 @@ import java.util.UUID; */ public class BluetoothConnectionManager { - private static final UUID HEART_RATE_SERVICE_UUID = new UUID(0x180D00001000L, 0x800000805f9b34fbL); - private static final UUID HEART_RATE_MEASUREMENT_CHAR_UUID = new UUID(0x2A3700001000L, 0x800000805f9b34fbL); - private static final UUID CLIENT_CHARACTERISTIC_CONFIG_UUID = new UUID(0x290200001000L, 0x800000805f9b34fbL); - // Message types sent to handler public static final int MESSAGE_DEVICE_NAME = 1; public static final int MESSAGE_READ = 2; public static final String KEY_DEVICE_NAME = "device_name"; - + private static final UUID HEART_RATE_SERVICE_UUID = new UUID(0x180D00001000L, 0x800000805f9b34fbL); + private static final UUID HEART_RATE_MEASUREMENT_CHAR_UUID = new UUID(0x2A3700001000L, 0x800000805f9b34fbL); + private static final UUID CLIENT_CHARACTERISTIC_CONFIG_UUID = new UUID(0x290200001000L, 0x800000805f9b34fbL); private static final String TAG = BluetoothConnectionManager.class.getSimpleName(); private final Context context; diff --git a/src/main/java/com/google/android/apps/mytracks/services/sensors/BluetoothLEUtils.java b/src/main/java/com/google/android/apps/mytracks/services/sensors/BluetoothLEUtils.java index 7c2f112c3..fa372897b 100644 --- a/src/main/java/com/google/android/apps/mytracks/services/sensors/BluetoothLEUtils.java +++ b/src/main/java/com/google/android/apps/mytracks/services/sensors/BluetoothLEUtils.java @@ -4,7 +4,8 @@ import android.bluetooth.BluetoothGattCharacteristic; public final class BluetoothLEUtils { - private BluetoothLEUtils() {} + private BluetoothLEUtils() { + } public static int parseHeartRate(BluetoothGattCharacteristic characteristic) { //DOCUMENTATION https://www.bluetooth.com/specifications/gatt/characteristics/ diff --git a/src/main/java/com/google/android/apps/mytracks/services/sensors/BluetoothSensorManager.java b/src/main/java/com/google/android/apps/mytracks/services/sensors/BluetoothSensorManager.java index c6840daf1..b05f3fe89 100644 --- a/src/main/java/com/google/android/apps/mytracks/services/sensors/BluetoothSensorManager.java +++ b/src/main/java/com/google/android/apps/mytracks/services/sensors/BluetoothSensorManager.java @@ -23,7 +23,6 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; -import android.widget.Toast; import com.google.android.apps.mytracks.content.sensor.SensorDataSet; import com.google.android.apps.mytracks.content.sensor.SensorState; @@ -35,128 +34,126 @@ import java.util.ArrayList; /** * Bluetooth sensor manager. - * + *

* TODO: Handle a BluetoothGatt.STATE_DISCONNECTED * * @author Sandor Dornbush */ public class BluetoothSensorManager extends SensorManager { - private static final BluetoothAdapter bluetoothAdapter = getDefaultBluetoothAdapter(); - private static final String TAG = BluetoothConnectionManager.class.getSimpleName(); - - private static BluetoothAdapter getDefaultBluetoothAdapter() { - // If from the main application thread, return directly - if (Thread.currentThread().equals(Looper.getMainLooper().getThread())) { - return BluetoothAdapter.getDefaultAdapter(); - } - - // Get the default adapter from the main application thread - final ArrayList adapters = new ArrayList<>(1); - final Object mutex = new Object(); - - Handler handler = new Handler(Looper.getMainLooper()); - handler.post(new Runnable() { + private static final String TAG = BluetoothConnectionManager.class.getSimpleName(); + private static final BluetoothAdapter bluetoothAdapter = getDefaultBluetoothAdapter(); + private final Context context; + private final BluetoothConnectionManager bluetoothConnectionManager; + private SensorDataSet sensorDataSet = null; + // Handler that gets information back from the bluetoothConnectionManager + private final Handler messageHandler = new Handler(Looper.getMainLooper()) { @Override - public void run() { - adapters.add(BluetoothAdapter.getDefaultAdapter()); - synchronized (mutex) { - mutex.notify(); + public void handleMessage(Message message) { + switch (message.what) { + case BluetoothConnectionManager.MESSAGE_DEVICE_NAME: + String deviceName = message.getData().getString(BluetoothConnectionManager.KEY_DEVICE_NAME); + break; + case BluetoothConnectionManager.MESSAGE_READ: + if (!(message.obj instanceof SensorDataSet)) { + Log.e(TAG, "Received message did not contain a SensorDataSet."); + sensorDataSet = null; + } else { + sensorDataSet = (SensorDataSet) message.obj; + } + break; + default: + break; + } } - } - }); - - while (adapters.isEmpty()) { - synchronized (mutex) { + }; + + /** + * @param context the context + */ + public BluetoothSensorManager(Context context) { + this.context = context; + bluetoothConnectionManager = new BluetoothConnectionManager(context, messageHandler); + } + + private static BluetoothAdapter getDefaultBluetoothAdapter() { + // If from the main application thread, return directly + if (Thread.currentThread().equals(Looper.getMainLooper().getThread())) { + return BluetoothAdapter.getDefaultAdapter(); + } + + // Get the default adapter from the main application thread + final ArrayList adapters = new ArrayList<>(1); + final Object mutex = new Object(); + + Handler handler = new Handler(Looper.getMainLooper()); + handler.post(new Runnable() { + @Override + public void run() { + adapters.add(BluetoothAdapter.getDefaultAdapter()); + synchronized (mutex) { + mutex.notify(); + } + } + }); + + while (adapters.isEmpty()) { + synchronized (mutex) { + try { + mutex.wait(UnitConversions.ONE_SECOND); + } catch (InterruptedException e) { + Log.e(TAG, "Interrupted while waiting for default bluetooth adapter", e); + } + } + } + + if (adapters.get(0) == null) { + Log.w(TAG, "No bluetooth adapter found."); + return null; + } + return adapters.get(0); + } + + @Override + public boolean isEnabled() { + return bluetoothAdapter != null && bluetoothAdapter.isEnabled(); + } + + @Override + protected void setUpChannel() { + if (!isEnabled()) { + Log.w(TAG, "Bluetooth not enabled."); + return; + } + String address = PreferencesUtils.getString(context, R.string.bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT); + if (PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(address)) { + Log.w(TAG, "No bluetooth address."); + return; + } + Log.w(TAG, "Connecting to bluetooth address: " + address); + + BluetoothDevice device; try { - mutex.wait(UnitConversions.ONE_SECOND); - } catch (InterruptedException e) { - Log.e(TAG, "Interrupted while waiting for default bluetooth adapter", e); + device = bluetoothAdapter.getRemoteDevice(address); + } catch (IllegalArgumentException e) { + Log.d(TAG, "Unable to get remote device for: " + address, e); + return; } - } + bluetoothConnectionManager.connect(device); } - - if (adapters.get(0) == null) { - Log.w(TAG, "No bluetooth adapter found."); - return null; + + @Override + protected void tearDownChannel() { + bluetoothConnectionManager.reset(); } - return adapters.get(0); - } - private final Context context; - private final BluetoothConnectionManager bluetoothConnectionManager; - private SensorDataSet sensorDataSet = null; - - // Handler that gets information back from the bluetoothConnectionManager - private final Handler messageHandler = new Handler(Looper.getMainLooper()) { - @Override - public void handleMessage(Message message) { - switch (message.what) { - case BluetoothConnectionManager.MESSAGE_DEVICE_NAME: - String deviceName = message.getData().getString(BluetoothConnectionManager.KEY_DEVICE_NAME); - break; - case BluetoothConnectionManager.MESSAGE_READ: - if (!(message.obj instanceof SensorDataSet)) { - Log.e(TAG ,"Received message did not contain a SensorDataSet."); - sensorDataSet = null; - } else { - sensorDataSet = (SensorDataSet) message.obj; - } - break; - default: - break; - } + @Override + public SensorState getSensorState() { + return bluetoothConnectionManager.getSensorState(); } - }; - /** - * @param context the context - */ - public BluetoothSensorManager(Context context) { - this.context = context; - bluetoothConnectionManager = new BluetoothConnectionManager(context, messageHandler); - } - - @Override - public boolean isEnabled() { - return bluetoothAdapter != null && bluetoothAdapter.isEnabled(); - } - - @Override - protected void setUpChannel() { - if (!isEnabled()) { - Log.w(TAG, "Bluetooth not enabled."); - return; + @Override + public SensorDataSet getSensorDataSet() { + return sensorDataSet; } - String address = PreferencesUtils.getString(context, R.string.bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT); - if (PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(address)) { - Log.w(TAG, "No bluetooth address."); - return; - } - Log.w(TAG, "Connecting to bluetooth address: " + address); - - BluetoothDevice device; - try { - device = bluetoothAdapter.getRemoteDevice(address); - } catch (IllegalArgumentException e) { - Log.d(TAG, "Unable to get remote device for: " + address, e); - return; - } - bluetoothConnectionManager.connect(device); - } - - @Override - protected void tearDownChannel() { - bluetoothConnectionManager.reset(); - } - - @Override - public SensorState getSensorState() { - return bluetoothConnectionManager.getSensorState(); - } - - @Override - public SensorDataSet getSensorDataSet() { - return sensorDataSet; - } } diff --git a/src/main/java/com/google/android/apps/mytracks/services/sensors/SensorManager.java b/src/main/java/com/google/android/apps/mytracks/services/sensors/SensorManager.java index 9ab6a415c..06fa3f207 100644 --- a/src/main/java/com/google/android/apps/mytracks/services/sensors/SensorManager.java +++ b/src/main/java/com/google/android/apps/mytracks/services/sensors/SensorManager.java @@ -26,113 +26,113 @@ import java.util.TimerTask; /** * Manage the connection to a sensor. - * + * * @author Sandor Dornbush */ public abstract class SensorManager { - public static final long MAX_SENSOR_DATE_SET_AGE = 5000; - public static final long MAX_SENSOR_STATE_AGE = 20000; + public static final long MAX_SENSOR_DATE_SET_AGE = 5000; + public static final long MAX_SENSOR_STATE_AGE = 20000; - private static final String TAG = SensorManager.class.getSimpleName(); - private static final int RETRY_PERIOD = 20000; + private static final String TAG = SensorManager.class.getSimpleName(); + private static final int RETRY_PERIOD = 20000; - private SensorState sensorState = SensorState.NONE; - private long sensorStateTimestamp = System.currentTimeMillis(); + private SensorState sensorState = SensorState.NONE; + private long sensorStateTimestamp = System.currentTimeMillis(); - private TimerTask timerTask; - private Timer timer; + private TimerTask timerTask; + private Timer timer; - /** - * Returns true if the sensor is enabled. - */ - public abstract boolean isEnabled(); + /** + * Returns true if the sensor is enabled. + */ + public abstract boolean isEnabled(); - /** - * Sets up the sensor channel. - */ - protected abstract void setUpChannel(); + /** + * Sets up the sensor channel. + */ + protected abstract void setUpChannel(); - /** - * Tears down the sensor channel. - */ - protected abstract void tearDownChannel(); + /** + * Tears down the sensor channel. + */ + protected abstract void tearDownChannel(); - /** - * Gets the sensor data set. - */ - public abstract SensorDataSet getSensorDataSet(); + /** + * Gets the sensor data set. + */ + public abstract SensorDataSet getSensorDataSet(); - /** - * Starts the sensor. - */ - public void startSensor() { - setUpChannel(); - timerTask = new TimerTask() { - @Override - public void run() { - switch (getSensorState()) { - case CONNECTING: - if (System.currentTimeMillis() - sensorStateTimestamp > MAX_SENSOR_STATE_AGE) { - Log.i(TAG, "Retry setUpChannel"); - setUpChannel(); - } - break; - case NONE: - case DISCONNECTED: - setUpChannel(); - break; - default: - // CONNECTED or SENDING - break; - } - } - }; - timer = new Timer(SensorManager.class.getSimpleName()); - timer.schedule(timerTask, RETRY_PERIOD, RETRY_PERIOD); - } - - /** - * Stops the sensor. - */ - public void stopSensor() { - if (timerTask != null) { - timerTask.cancel(); - timerTask = null; + /** + * Starts the sensor. + */ + public void startSensor() { + setUpChannel(); + timerTask = new TimerTask() { + @Override + public void run() { + switch (getSensorState()) { + case CONNECTING: + if (System.currentTimeMillis() - sensorStateTimestamp > MAX_SENSOR_STATE_AGE) { + Log.i(TAG, "Retry setUpChannel"); + setUpChannel(); + } + break; + case NONE: + case DISCONNECTED: + setUpChannel(); + break; + default: + // CONNECTED or SENDING + break; + } + } + }; + timer = new Timer(SensorManager.class.getSimpleName()); + timer.schedule(timerTask, RETRY_PERIOD, RETRY_PERIOD); } - if (timer != null) { - timer.cancel(); - timer.purge(); - timer = null; + + /** + * Stops the sensor. + */ + public void stopSensor() { + if (timerTask != null) { + timerTask.cancel(); + timerTask = null; + } + if (timer != null) { + timer.cancel(); + timer.purge(); + timer = null; + } + tearDownChannel(); } - tearDownChannel(); - } - /** - * Sets the sensor state. - * - * @param sensorState the sensor state - */ - public void setSensorState(SensorState sensorState) { - sensorStateTimestamp = System.currentTimeMillis(); - this.sensorState = sensorState; - } - - /** - * Gets the sensor state. - */ - public SensorState getSensorState() { - return sensorState; - } - - /** - * Returns true if the sensor data set is valid. - */ - public boolean isSensorDataSetValid() { - SensorDataSet sensorDataSet = getSensorDataSet(); - if (sensorDataSet == null) { - return false; + /** + * Gets the sensor state. + */ + public SensorState getSensorState() { + return sensorState; + } + + /** + * Sets the sensor state. + * + * @param sensorState the sensor state + */ + public void setSensorState(SensorState sensorState) { + sensorStateTimestamp = System.currentTimeMillis(); + this.sensorState = sensorState; + } + + /** + * Returns true if the sensor data set is valid. + */ + public boolean isSensorDataSetValid() { + SensorDataSet sensorDataSet = getSensorDataSet(); + if (sensorDataSet == null) { + return false; + } + return sensorDataSet.isRecent(MAX_SENSOR_DATE_SET_AGE); } - return sensorDataSet.isRecent(MAX_SENSOR_DATE_SET_AGE); - } } diff --git a/src/main/java/com/google/android/apps/mytracks/services/sensors/SensorManagerFactory.java b/src/main/java/com/google/android/apps/mytracks/services/sensors/SensorManagerFactory.java index b10712fd4..63a4271e7 100644 --- a/src/main/java/com/google/android/apps/mytracks/services/sensors/SensorManagerFactory.java +++ b/src/main/java/com/google/android/apps/mytracks/services/sensors/SensorManagerFactory.java @@ -25,67 +25,68 @@ import android.content.Context; */ public class SensorManagerFactory { - private static SensorManager systemSensorManager = null; - private static SensorManager sensorManagerTemporary = null; + private static SensorManager systemSensorManager = null; + private static SensorManager sensorManagerTemporary = null; - private SensorManagerFactory() {} - - /** - * Gets the system sensor manager. - * - * @param context the context - */ - public static SensorManager getSystemSensorManager(Context context) { - releaseSensorManagerTemporary(); - releaseSystemSensorManager(); - systemSensorManager = getSensorManager(context); - if (systemSensorManager != null) { - systemSensorManager.startSensor(); + private SensorManagerFactory() { } - return systemSensorManager; - } - /** - * Releases the system sensor manager. - */ - public static void releaseSystemSensorManager() { - if (systemSensorManager != null) { - systemSensorManager.stopSensor(); + /** + * Gets the system sensor manager. + * + * @param context the context + */ + public static SensorManager getSystemSensorManager(Context context) { + releaseSensorManagerTemporary(); + releaseSystemSensorManager(); + systemSensorManager = getSensorManager(context); + if (systemSensorManager != null) { + systemSensorManager.startSensor(); + } + return systemSensorManager; } - systemSensorManager = null; - } - /** - * Gets the temp sensor manager. - */ - public static SensorManager getSensorManagerTemporary(Context context) { - releaseSensorManagerTemporary(); - if (systemSensorManager != null) { - return null; + /** + * Releases the system sensor manager. + */ + public static void releaseSystemSensorManager() { + if (systemSensorManager != null) { + systemSensorManager.stopSensor(); + } + systemSensorManager = null; } - sensorManagerTemporary = getSensorManager(context); - if (sensorManagerTemporary != null) { - sensorManagerTemporary.startSensor(); - } - return sensorManagerTemporary; - } - /** - * Releases the temp sensor manager. - */ - public static void releaseSensorManagerTemporary() { - if (sensorManagerTemporary != null) { - sensorManagerTemporary.stopSensor(); + /** + * Gets the temp sensor manager. + */ + public static SensorManager getSensorManagerTemporary(Context context) { + releaseSensorManagerTemporary(); + if (systemSensorManager != null) { + return null; + } + sensorManagerTemporary = getSensorManager(context); + if (sensorManagerTemporary != null) { + sensorManagerTemporary.startSensor(); + } + return sensorManagerTemporary; } - sensorManagerTemporary = null; - } - /** - * Gets the sensor manager. - * - * @param context the context - */ - private static SensorManager getSensorManager(Context context) { - return new BluetoothSensorManager(context); - } + /** + * Releases the temp sensor manager. + */ + public static void releaseSensorManagerTemporary() { + if (sensorManagerTemporary != null) { + sensorManagerTemporary.stopSensor(); + } + sensorManagerTemporary = null; + } + + /** + * Gets the sensor manager. + * + * @param context the context + */ + private static SensorManager getSensorManager(Context context) { + return new BluetoothSensorManager(context); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/services/tasks/AnnouncementPeriodicTask.java b/src/main/java/com/google/android/apps/mytracks/services/tasks/AnnouncementPeriodicTask.java index cb8e801b2..8edc5cc44 100644 --- a/src/main/java/com/google/android/apps/mytracks/services/tasks/AnnouncementPeriodicTask.java +++ b/src/main/java/com/google/android/apps/mytracks/services/tasks/AnnouncementPeriodicTask.java @@ -38,294 +38,287 @@ import java.util.Locale; /** * This class will periodically announce the user's trip statistics. - * + * * @author Sandor Dornbush */ public class AnnouncementPeriodicTask implements PeriodicTask { - /** - * The rate at which announcements are spoken. - */ - @VisibleForTesting - static final float TTS_SPEECH_RATE = 0.9f; - - private static final String TAG = AnnouncementPeriodicTask.class.getSimpleName(); - - private final UtteranceProgressListener utteranceListener = new UtteranceProgressListener() { - @Override - public void onStart(String utteranceId) { - } - - @Override - public void onDone(String utteranceId) { - int result = audioManager.abandonAudioFocus(null); - if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) { - Log.w(TAG, "Failed to relinquish audio focus."); - } - } - - @Override - public void onError(String utteranceId) { - } - }; - - private final Context context; - private TextToSpeech tts; - - // Response from TTS after its initialization - private int initStatus = TextToSpeech.ERROR; - - // True if TTS engine is ready - private boolean ready = false; - - // True if speech is allowed - private boolean speechAllowed; - - private final AudioManager audioManager; - - /** - * Listener which updates {@link #speechAllowed} when the phone state changes. - */ - private final PhoneStateListener phoneStateListener = new PhoneStateListener() { - @Override - public void onCallStateChanged(int state, String incomingNumber) { - speechAllowed = state == TelephonyManager.CALL_STATE_IDLE; - if (!speechAllowed && tts != null && tts.isSpeaking()) { - // If we're already speaking, stop it. - tts.stop(); - } - } - }; - - public AnnouncementPeriodicTask(Context context) { - this.context = context; - audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - } - - @Override - public void start() { - if (tts == null) { - tts = newTextToSpeech(context, new OnInitListener() { - @Override - public void onInit(int status) { - initStatus = status; - } - }); - } - speechAllowed = true; - listenToPhoneState(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); - } - - @Override - public void run(TrackRecordingService trackRecordingService) { - if (trackRecordingService == null) { - Log.e(TAG, "TrackRecordingService is null."); - return; - } - announce(trackRecordingService.getTripStatistics()); - } - - /** - * Runs this task. - * - * @param tripStatistics the trip statistics - */ - @VisibleForTesting - void announce(TripStatistics tripStatistics) { - if (tripStatistics == null) { - Log.e(TAG, "TripStatistics is null."); - return; - } - - synchronized (this) { - if (!ready) { - ready = initStatus == TextToSpeech.SUCCESS; - if (ready) { - onTtsReady(); - } - } - if (!ready) { - Log.i(TAG, "TTS not ready."); - return; - } - } - - if (!speechAllowed) { - Log.i(TAG, "Speech is not allowed at this time."); - return; - } - speakAnnouncement(getAnnouncement(tripStatistics)); - } - - @Override - public void shutdown() { - listenToPhoneState(phoneStateListener, PhoneStateListener.LISTEN_NONE); - if (tts != null) { - tts.shutdown(); - tts = null; - } - } - - /** - * Called when TTS is ready. - */ - private void onTtsReady() { - Locale locale = Locale.getDefault(); - int languageAvailability = tts.isLanguageAvailable(locale); - if (languageAvailability == TextToSpeech.LANG_MISSING_DATA - || languageAvailability == TextToSpeech.LANG_NOT_SUPPORTED) { - Log.w(TAG, "Default locale not available, use English."); - locale = Locale.ENGLISH; - /* - * TODO: instead of using english, load the language if missing and show a - * toast if not supported. Not able to change the resource strings to - * English. - */ - } - tts.setLanguage(locale); - - // Slow down the speed just a bit as it is hard to hear when exercising. - tts.setSpeechRate(TTS_SPEECH_RATE); - - tts.setOnUtteranceProgressListener(utteranceListener); - } - - /** - * Speaks the announcement. - * - * @param announcement the announcement - */ - private void speakAnnouncement(String announcement) { - int result = audioManager.requestAudioFocus( - null, TextToSpeech.Engine.DEFAULT_STREAM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); - if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) { - Log.w(TAG, "Failed to request audio focus."); - } - - /* - * We don't care about the utterance id. It is supplied here to force - * onUtteranceCompleted to be called. + /** + * The rate at which announcements are spoken. */ - tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null, "not used"); - } + @VisibleForTesting + static final float TTS_SPEECH_RATE = 0.9f; - /** - * Create a new {@link TextToSpeech}. - * - * @param aContext a context - * @param onInitListener an on init listener - */ - @VisibleForTesting - protected TextToSpeech newTextToSpeech(Context aContext, OnInitListener onInitListener) { - return new TextToSpeech(aContext, onInitListener); - } + private static final String TAG = AnnouncementPeriodicTask.class.getSimpleName(); + private final Context context; + private final AudioManager audioManager; + private final UtteranceProgressListener utteranceListener = new UtteranceProgressListener() { + @Override + public void onStart(String utteranceId) { + } - /** - * Gets the announcement. - * - * @param tripStatistics the trip statistics - */ - @VisibleForTesting - protected String getAnnouncement(TripStatistics tripStatistics) { - boolean metricUnits = PreferencesUtils.isMetricUnits(context); - boolean reportSpeed = PreferencesUtils.isReportSpeed(context); - double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM; - double speed = tripStatistics.getAverageMovingSpeed() * UnitConversions.MS_TO_KMH; + @Override + public void onDone(String utteranceId) { + int result = audioManager.abandonAudioFocus(null); + if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) { + Log.w(TAG, "Failed to relinquish audio focus."); + } + } - if (distance == 0) { - return context.getString(R.string.voice_total_distance_zero); + @Override + public void onError(String utteranceId) { + } + }; + private TextToSpeech tts; + // Response from TTS after its initialization + private int initStatus = TextToSpeech.ERROR; + // True if TTS engine is ready + private boolean ready = false; + // True if speech is allowed + private boolean speechAllowed; + /** + * Listener which updates {@link #speechAllowed} when the phone state changes. + */ + private final PhoneStateListener phoneStateListener = new PhoneStateListener() { + @Override + public void onCallStateChanged(int state, String incomingNumber) { + speechAllowed = state == TelephonyManager.CALL_STATE_IDLE; + if (!speechAllowed && tts != null && tts.isSpeaking()) { + // If we're already speaking, stop it. + tts.stop(); + } + } + }; + + public AnnouncementPeriodicTask(Context context) { + this.context = context; + audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); } - if (!metricUnits) { - distance *= UnitConversions.KM_TO_MI; - speed *= UnitConversions.KM_TO_MI; + @Override + public void start() { + if (tts == null) { + tts = newTextToSpeech(context, new OnInitListener() { + @Override + public void onInit(int status) { + initStatus = status; + } + }); + } + speechAllowed = true; + listenToPhoneState(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); } - String rate; - if (reportSpeed) { - int speedId = metricUnits ? R.plurals.voiceSpeedKilometersPerHour - : R.plurals.voiceSpeedMilesPerHour; - rate = context.getResources().getQuantityString(speedId, getQuantityCount(speed), speed); - } else { - speed = speed == 0 ? 0.0 : 1 / speed; - int paceId = metricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile; - long time = Math.round( - speed * UnitConversions.HR_TO_MIN * UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS); - rate = context.getString(paceId, getAnnounceTime(time)); + @Override + public void run(TrackRecordingService trackRecordingService) { + if (trackRecordingService == null) { + Log.e(TAG, "TrackRecordingService is null."); + return; + } + announce(trackRecordingService.getTripStatistics()); } - int totalDistanceId = metricUnits ? R.plurals.voiceTotalDistanceKilometers - : R.plurals.voiceTotalDistanceMiles; - String totalDistance = context.getResources() - .getQuantityString(totalDistanceId, getQuantityCount(distance), distance); + /** + * Runs this task. + * + * @param tripStatistics the trip statistics + */ + @VisibleForTesting + void announce(TripStatistics tripStatistics) { + if (tripStatistics == null) { + Log.e(TAG, "TripStatistics is null."); + return; + } - return context.getString(R.string.voice_template, totalDistance, - getAnnounceTime(tripStatistics.getMovingTime()), rate); - } + synchronized (this) { + if (!ready) { + ready = initStatus == TextToSpeech.SUCCESS; + if (ready) { + onTtsReady(); + } + } + if (!ready) { + Log.i(TAG, "TTS not ready."); + return; + } + } - /** - * Listens to phone state. - * - * @param listener the listener - * @param events the interested events - */ - @VisibleForTesting - protected void listenToPhoneState(PhoneStateListener listener, int events) { - TelephonyManager telephony = (TelephonyManager) context.getSystemService( - Context.TELEPHONY_SERVICE); - if (telephony != null) { - telephony.listen(listener, events); + if (!speechAllowed) { + Log.i(TAG, "Speech is not allowed at this time."); + return; + } + speakAnnouncement(getAnnouncement(tripStatistics)); } - } - /** - * Gets the announce time. - * - * @param time the time - */ - @VisibleForTesting - String getAnnounceTime(long time) { - int[] parts = StringUtils.getTimeParts(time); - String seconds = context.getResources() - .getQuantityString(R.plurals.voiceSeconds, parts[0], parts[0]); - String minutes = context.getResources() - .getQuantityString(R.plurals.voiceMinutes, parts[1], parts[1]); - String hours = context.getResources() - .getQuantityString(R.plurals.voiceHours, parts[2], parts[2]); - StringBuilder sb = new StringBuilder(); - if (parts[2] != 0) { - sb.append(hours); - sb.append(" "); + @Override + public void shutdown() { + listenToPhoneState(phoneStateListener, PhoneStateListener.LISTEN_NONE); + if (tts != null) { + tts.shutdown(); + tts = null; + } } - sb.append(minutes); - sb.append(" "); - sb.append(seconds); - return sb.toString(); - } - /** - * Gets the plural count to be used by getQuantityString. getQuantityString - * only supports integer quantities, not a double quantity like "2.2". - *

- * As a temporary workaround, we convert a double quantity to an integer - * quantity. If the double quantity is exactly 0, 1, or 2, then we can return - * these integer quantities. Otherwise, we cast the double quantity to an - * integer quantity. However, we need to make sure that if the casted value is - * 0, 1, or 2, we don't return those, instead, return the next biggest integer - * 3. - * - * @param d the double value - */ - private int getQuantityCount(double d) { - if (d == 0) { - return 0; - } else if (d == 1) { - return 1; - } else if (d == 2) { - return 2; - } else { - int count = (int) d; - return count < 3 ? 3 : count; + /** + * Called when TTS is ready. + */ + private void onTtsReady() { + Locale locale = Locale.getDefault(); + int languageAvailability = tts.isLanguageAvailable(locale); + if (languageAvailability == TextToSpeech.LANG_MISSING_DATA + || languageAvailability == TextToSpeech.LANG_NOT_SUPPORTED) { + Log.w(TAG, "Default locale not available, use English."); + locale = Locale.ENGLISH; + /* + * TODO: instead of using english, load the language if missing and show a + * toast if not supported. Not able to change the resource strings to + * English. + */ + } + tts.setLanguage(locale); + + // Slow down the speed just a bit as it is hard to hear when exercising. + tts.setSpeechRate(TTS_SPEECH_RATE); + + tts.setOnUtteranceProgressListener(utteranceListener); + } + + /** + * Speaks the announcement. + * + * @param announcement the announcement + */ + private void speakAnnouncement(String announcement) { + int result = audioManager.requestAudioFocus( + null, TextToSpeech.Engine.DEFAULT_STREAM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); + if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) { + Log.w(TAG, "Failed to request audio focus."); + } + + /* + * We don't care about the utterance id. It is supplied here to force + * onUtteranceCompleted to be called. + */ + tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null, "not used"); + } + + /** + * Create a new {@link TextToSpeech}. + * + * @param aContext a context + * @param onInitListener an on init listener + */ + @VisibleForTesting + protected TextToSpeech newTextToSpeech(Context aContext, OnInitListener onInitListener) { + return new TextToSpeech(aContext, onInitListener); + } + + /** + * Gets the announcement. + * + * @param tripStatistics the trip statistics + */ + @VisibleForTesting + protected String getAnnouncement(TripStatistics tripStatistics) { + boolean metricUnits = PreferencesUtils.isMetricUnits(context); + boolean reportSpeed = PreferencesUtils.isReportSpeed(context); + double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM; + double speed = tripStatistics.getAverageMovingSpeed() * UnitConversions.MS_TO_KMH; + + if (distance == 0) { + return context.getString(R.string.voice_total_distance_zero); + } + + if (!metricUnits) { + distance *= UnitConversions.KM_TO_MI; + speed *= UnitConversions.KM_TO_MI; + } + + String rate; + if (reportSpeed) { + int speedId = metricUnits ? R.plurals.voiceSpeedKilometersPerHour + : R.plurals.voiceSpeedMilesPerHour; + rate = context.getResources().getQuantityString(speedId, getQuantityCount(speed), speed); + } else { + speed = speed == 0 ? 0.0 : 1 / speed; + int paceId = metricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile; + long time = Math.round( + speed * UnitConversions.HR_TO_MIN * UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS); + rate = context.getString(paceId, getAnnounceTime(time)); + } + + int totalDistanceId = metricUnits ? R.plurals.voiceTotalDistanceKilometers + : R.plurals.voiceTotalDistanceMiles; + String totalDistance = context.getResources() + .getQuantityString(totalDistanceId, getQuantityCount(distance), distance); + + return context.getString(R.string.voice_template, totalDistance, + getAnnounceTime(tripStatistics.getMovingTime()), rate); + } + + /** + * Listens to phone state. + * + * @param listener the listener + * @param events the interested events + */ + @VisibleForTesting + protected void listenToPhoneState(PhoneStateListener listener, int events) { + TelephonyManager telephony = (TelephonyManager) context.getSystemService( + Context.TELEPHONY_SERVICE); + if (telephony != null) { + telephony.listen(listener, events); + } + } + + /** + * Gets the announce time. + * + * @param time the time + */ + @VisibleForTesting + String getAnnounceTime(long time) { + int[] parts = StringUtils.getTimeParts(time); + String seconds = context.getResources() + .getQuantityString(R.plurals.voiceSeconds, parts[0], parts[0]); + String minutes = context.getResources() + .getQuantityString(R.plurals.voiceMinutes, parts[1], parts[1]); + String hours = context.getResources() + .getQuantityString(R.plurals.voiceHours, parts[2], parts[2]); + StringBuilder sb = new StringBuilder(); + if (parts[2] != 0) { + sb.append(hours); + sb.append(" "); + } + sb.append(minutes); + sb.append(" "); + sb.append(seconds); + return sb.toString(); + } + + /** + * Gets the plural count to be used by getQuantityString. getQuantityString + * only supports integer quantities, not a double quantity like "2.2". + *

+ * As a temporary workaround, we convert a double quantity to an integer + * quantity. If the double quantity is exactly 0, 1, or 2, then we can return + * these integer quantities. Otherwise, we cast the double quantity to an + * integer quantity. However, we need to make sure that if the casted value is + * 0, 1, or 2, we don't return those, instead, return the next biggest integer + * 3. + * + * @param d the double value + */ + private int getQuantityCount(double d) { + if (d == 0) { + return 0; + } else if (d == 1) { + return 1; + } else if (d == 2) { + return 2; + } else { + int count = (int) d; + return count < 3 ? 3 : count; + } } - } } diff --git a/src/main/java/com/google/android/apps/mytracks/services/tasks/AnnouncementPeriodicTaskFactory.java b/src/main/java/com/google/android/apps/mytracks/services/tasks/AnnouncementPeriodicTaskFactory.java index df9c9f1a9..43ab837d1 100644 --- a/src/main/java/com/google/android/apps/mytracks/services/tasks/AnnouncementPeriodicTaskFactory.java +++ b/src/main/java/com/google/android/apps/mytracks/services/tasks/AnnouncementPeriodicTaskFactory.java @@ -1,12 +1,12 @@ /* * Copyright 2010 Google Inc. - * + * * 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 @@ -19,13 +19,13 @@ import android.content.Context; /** * A {@link PeriodicTaskFactory} for text-to-speech announcement periodic task. - * + * * @author Rodrigo Damazio */ public class AnnouncementPeriodicTaskFactory implements PeriodicTaskFactory { - @Override - public PeriodicTask create(Context context) { - return new AnnouncementPeriodicTask(context); - } + @Override + public PeriodicTask create(Context context) { + return new AnnouncementPeriodicTask(context); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/services/tasks/PeriodicTask.java b/src/main/java/com/google/android/apps/mytracks/services/tasks/PeriodicTask.java index 958134d3f..d71195dcb 100644 --- a/src/main/java/com/google/android/apps/mytracks/services/tasks/PeriodicTask.java +++ b/src/main/java/com/google/android/apps/mytracks/services/tasks/PeriodicTask.java @@ -1,12 +1,12 @@ /* * Copyright 2009 Google Inc. - * + * * 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 @@ -20,24 +20,24 @@ import com.google.android.apps.mytracks.services.TrackRecordingService; /** * This is interface for a task that will be executed on some schedule. - * + * * @author Sandor Dornbush */ public interface PeriodicTask { - /** - * Sets up this task for subsequent calls to the run method. - */ - void start(); + /** + * Sets up this task for subsequent calls to the run method. + */ + void start(); - /** - * This method will be called periodically. - * - * @param trackRecordingService the track recording service - */ - void run(TrackRecordingService trackRecordingService); + /** + * This method will be called periodically. + * + * @param trackRecordingService the track recording service + */ + void run(TrackRecordingService trackRecordingService); - /** - * Shuts down this task and clean up resources. - */ - void shutdown(); + /** + * Shuts down this task and clean up resources. + */ + void shutdown(); } diff --git a/src/main/java/com/google/android/apps/mytracks/services/tasks/PeriodicTaskExecutor.java b/src/main/java/com/google/android/apps/mytracks/services/tasks/PeriodicTaskExecutor.java index 3d2ace603..56af15b91 100644 --- a/src/main/java/com/google/android/apps/mytracks/services/tasks/PeriodicTaskExecutor.java +++ b/src/main/java/com/google/android/apps/mytracks/services/tasks/PeriodicTaskExecutor.java @@ -15,188 +15,188 @@ */ package com.google.android.apps.mytracks.services.tasks; +import android.util.Log; + import com.google.android.apps.mytracks.services.TrackRecordingService; import com.google.android.apps.mytracks.stats.TripStatistics; import com.google.android.apps.mytracks.util.PreferencesUtils; import com.google.android.apps.mytracks.util.UnitConversions; -import android.util.Log; - /** * Execute a periodic task on a time or distance schedule. - * + * * @author Sandor Dornbush */ public class PeriodicTaskExecutor { - private static final String TAG = PeriodicTaskExecutor.class.getSimpleName(); - private static final long MINUTE_TO_MILLISECONDS = 60000L; + private static final String TAG = PeriodicTaskExecutor.class.getSimpleName(); + private static final long MINUTE_TO_MILLISECONDS = 60000L; - private final TrackRecordingService trackRecordingService; - private final PeriodicTaskFactory periodicTaskFactory; + private final TrackRecordingService trackRecordingService; + private final PeriodicTaskFactory periodicTaskFactory; - /** - * The task frequency. A positive value is a time frequency (minutes). A - * negative value is a distance frequency (km or mi). A zero value is to turn - * off periodic task. - */ - private int taskFrequency = PreferencesUtils.FREQUENCY_OFF; + /** + * The task frequency. A positive value is a time frequency (minutes). A + * negative value is a distance frequency (km or mi). A zero value is to turn + * off periodic task. + */ + private int taskFrequency = PreferencesUtils.FREQUENCY_OFF; - private PeriodicTask periodicTask; + private PeriodicTask periodicTask; - // Time periodic task executor - private TimerTaskExecutor timerTaskExecutor = null; + // Time periodic task executor + private TimerTaskExecutor timerTaskExecutor = null; - private boolean metricUnits; + private boolean metricUnits; - // The next distance for the distance periodic task - private double nextTaskDistance = Double.MAX_VALUE; + // The next distance for the distance periodic task + private double nextTaskDistance = Double.MAX_VALUE; - public PeriodicTaskExecutor( - TrackRecordingService trackRecordingService, PeriodicTaskFactory periodicTaskFactory) { - this.trackRecordingService = trackRecordingService; - this.periodicTaskFactory = periodicTaskFactory; - } - - /** - * Restores the executor. - */ - public void restore() { - if (!trackRecordingService.isRecording() || trackRecordingService.isPaused()) { - Log.d(TAG, "Not recording or paused."); - return; + public PeriodicTaskExecutor( + TrackRecordingService trackRecordingService, PeriodicTaskFactory periodicTaskFactory) { + this.trackRecordingService = trackRecordingService; + this.periodicTaskFactory = periodicTaskFactory; } - if (!isTimeFrequency()) { - if (timerTaskExecutor != null) { - timerTaskExecutor.shutdown(); - timerTaskExecutor = null; - } - } - if (taskFrequency == PreferencesUtils.FREQUENCY_OFF) { - Log.d(TAG, "Task frequency is off."); - return; + /** + * Restores the executor. + */ + public void restore() { + if (!trackRecordingService.isRecording() || trackRecordingService.isPaused()) { + Log.d(TAG, "Not recording or paused."); + return; + } + + if (!isTimeFrequency()) { + if (timerTaskExecutor != null) { + timerTaskExecutor.shutdown(); + timerTaskExecutor = null; + } + } + if (taskFrequency == PreferencesUtils.FREQUENCY_OFF) { + Log.d(TAG, "Task frequency is off."); + return; + } + + periodicTask = periodicTaskFactory.create(trackRecordingService); + + // Returning null is ok + if (periodicTask == null) { + Log.d(TAG, "Periodic task is null."); + return; + } + periodicTask.start(); + + if (isTimeFrequency()) { + if (timerTaskExecutor == null) { + timerTaskExecutor = new TimerTaskExecutor(periodicTask, trackRecordingService); + } + timerTaskExecutor.scheduleTask(taskFrequency * MINUTE_TO_MILLISECONDS); + } else { + // For distance periodic task + calculateNextTaskDistance(); + } } - periodicTask = periodicTaskFactory.create(trackRecordingService); - - // Returning null is ok - if (periodicTask == null) { - Log.d(TAG, "Periodic task is null."); - return; - } - periodicTask.start(); - - if (isTimeFrequency()) { - if (timerTaskExecutor == null) { - timerTaskExecutor = new TimerTaskExecutor(periodicTask, trackRecordingService); - } - timerTaskExecutor.scheduleTask(taskFrequency * MINUTE_TO_MILLISECONDS); - } else { - // For distance periodic task - calculateNextTaskDistance(); - } - } - - /** - * Shuts down the executor. - */ - public void shutdown() { - if (periodicTask != null) { - periodicTask.shutdown(); - periodicTask = null; - } - if (timerTaskExecutor != null) { - timerTaskExecutor.shutdown(); - timerTaskExecutor = null; - } - } - - /** - * Updates the executor. - */ - public void update() { - if (!isDistanceFrequency() || periodicTask == null) { - return; - } - TripStatistics tripStatistics = trackRecordingService.getTripStatistics(); - if (tripStatistics == null) { - return; - } - double distance = tripStatistics.getTotalDistance() - * UnitConversions.M_TO_KM; - if (!metricUnits) { - distance *= UnitConversions.KM_TO_MI; + /** + * Shuts down the executor. + */ + public void shutdown() { + if (periodicTask != null) { + periodicTask.shutdown(); + periodicTask = null; + } + if (timerTaskExecutor != null) { + timerTaskExecutor.shutdown(); + timerTaskExecutor = null; + } } - if (distance > nextTaskDistance) { - periodicTask.run(trackRecordingService); - calculateNextTaskDistance(); - } - } + /** + * Updates the executor. + */ + public void update() { + if (!isDistanceFrequency() || periodicTask == null) { + return; + } + TripStatistics tripStatistics = trackRecordingService.getTripStatistics(); + if (tripStatistics == null) { + return; + } + double distance = tripStatistics.getTotalDistance() + * UnitConversions.M_TO_KM; + if (!metricUnits) { + distance *= UnitConversions.KM_TO_MI; + } - /** - * Sets task frequency. - * - * @param taskFrequency the task frequency - */ - public void setTaskFrequency(int taskFrequency) { - this.taskFrequency = taskFrequency; - restore(); - } - - /** - * Sets metricUnits. - * - * @param metricUnits true to use metric units - */ - public void setMetricUnits(boolean metricUnits) { - this.metricUnits = metricUnits; - calculateNextTaskDistance(); - } - - /** - * Calculates the next distance for the distance periodic task. - */ - private void calculateNextTaskDistance() { - if (!trackRecordingService.isRecording() || trackRecordingService.isPaused() - || periodicTask == null) { - return; + if (distance > nextTaskDistance) { + periodicTask.run(trackRecordingService); + calculateNextTaskDistance(); + } } - TripStatistics tripStatistics = trackRecordingService.getTripStatistics(); - if (tripStatistics == null) { - return; - } - - if (!isDistanceFrequency()) { - nextTaskDistance = Double.MAX_VALUE; - Log.d(TAG, "SplitManager: Distance splits disabled."); - return; + /** + * Sets task frequency. + * + * @param taskFrequency the task frequency + */ + public void setTaskFrequency(int taskFrequency) { + this.taskFrequency = taskFrequency; + restore(); } - double distance = tripStatistics.getTotalDistance() - * UnitConversions.M_TO_KM; - if (!metricUnits) { - distance *= UnitConversions.KM_TO_MI; + /** + * Sets metricUnits. + * + * @param metricUnits true to use metric units + */ + public void setMetricUnits(boolean metricUnits) { + this.metricUnits = metricUnits; + calculateNextTaskDistance(); } - // The index will be negative since the frequency is negative. - int index = (int) (distance / taskFrequency); - index -= 1; - nextTaskDistance = taskFrequency * index; - } - /** - * True if time frequency. - */ - private boolean isTimeFrequency() { - return taskFrequency > 0; - } + /** + * Calculates the next distance for the distance periodic task. + */ + private void calculateNextTaskDistance() { + if (!trackRecordingService.isRecording() || trackRecordingService.isPaused() + || periodicTask == null) { + return; + } - /** - * True if distance frequency. - */ - private boolean isDistanceFrequency() { - return taskFrequency < 0; - } + TripStatistics tripStatistics = trackRecordingService.getTripStatistics(); + if (tripStatistics == null) { + return; + } + + if (!isDistanceFrequency()) { + nextTaskDistance = Double.MAX_VALUE; + Log.d(TAG, "SplitManager: Distance splits disabled."); + return; + } + + double distance = tripStatistics.getTotalDistance() + * UnitConversions.M_TO_KM; + if (!metricUnits) { + distance *= UnitConversions.KM_TO_MI; + } + // The index will be negative since the frequency is negative. + int index = (int) (distance / taskFrequency); + index -= 1; + nextTaskDistance = taskFrequency * index; + } + + /** + * True if time frequency. + */ + private boolean isTimeFrequency() { + return taskFrequency > 0; + } + + /** + * True if distance frequency. + */ + private boolean isDistanceFrequency() { + return taskFrequency < 0; + } } diff --git a/src/main/java/com/google/android/apps/mytracks/services/tasks/PeriodicTaskFactory.java b/src/main/java/com/google/android/apps/mytracks/services/tasks/PeriodicTaskFactory.java index 714ae75df..2d2f60710 100644 --- a/src/main/java/com/google/android/apps/mytracks/services/tasks/PeriodicTaskFactory.java +++ b/src/main/java/com/google/android/apps/mytracks/services/tasks/PeriodicTaskFactory.java @@ -1,12 +1,12 @@ /* * Copyright 2010 Google Inc. - * + * * 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 @@ -20,15 +20,15 @@ import android.content.Context; /** * An interface for classes that can create {@link PeriodicTask}. - * + * * @author Sandor Dornbush */ public interface PeriodicTaskFactory { - /** - * Creates a {@link PeriodicTask}. - * - * @return the task, or null if the task is not supported - */ - PeriodicTask create(Context context); + /** + * Creates a {@link PeriodicTask}. + * + * @return the task, or null if the task is not supported + */ + PeriodicTask create(Context context); } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/services/tasks/SplitPeriodicTask.java b/src/main/java/com/google/android/apps/mytracks/services/tasks/SplitPeriodicTask.java index 7d291df77..d97d6bd12 100644 --- a/src/main/java/com/google/android/apps/mytracks/services/tasks/SplitPeriodicTask.java +++ b/src/main/java/com/google/android/apps/mytracks/services/tasks/SplitPeriodicTask.java @@ -1,12 +1,12 @@ /* * Copyright 2010 Google Inc. - * + * * 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 @@ -21,19 +21,21 @@ import com.google.android.apps.mytracks.services.TrackRecordingService; /** * A simple task to insert statistics markers periodically. - * + * * @author Sandor Dornbush */ public class SplitPeriodicTask implements PeriodicTask { - @Override - public void start() {} + @Override + public void start() { + } - @Override - public void run(TrackRecordingService trackRecordingService) { - trackRecordingService.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS); - } + @Override + public void run(TrackRecordingService trackRecordingService) { + trackRecordingService.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS); + } - @Override - public void shutdown() {} + @Override + public void shutdown() { + } } diff --git a/src/main/java/com/google/android/apps/mytracks/services/tasks/SplitPeriodicTaskFactory.java b/src/main/java/com/google/android/apps/mytracks/services/tasks/SplitPeriodicTaskFactory.java index 93c0f6cc5..06bc99b0d 100644 --- a/src/main/java/com/google/android/apps/mytracks/services/tasks/SplitPeriodicTaskFactory.java +++ b/src/main/java/com/google/android/apps/mytracks/services/tasks/SplitPeriodicTaskFactory.java @@ -1,12 +1,12 @@ /* * Copyright 2012 Google Inc. - * + * * 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 @@ -20,13 +20,13 @@ import android.content.Context; /** * A {@link PeriodicTaskFactory} for {@link SplitPeriodicTask}. - * + * * @author Jimmy Shih */ public class SplitPeriodicTaskFactory implements PeriodicTaskFactory { - @Override - public PeriodicTask create(Context context) { - return new SplitPeriodicTask(); - } + @Override + public PeriodicTask create(Context context) { + return new SplitPeriodicTask(); + } } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/services/tasks/TimerTaskExecutor.java b/src/main/java/com/google/android/apps/mytracks/services/tasks/TimerTaskExecutor.java index 66ea8cd7c..4749a3a38 100644 --- a/src/main/java/com/google/android/apps/mytracks/services/tasks/TimerTaskExecutor.java +++ b/src/main/java/com/google/android/apps/mytracks/services/tasks/TimerTaskExecutor.java @@ -1,12 +1,12 @@ /* * Copyright 2009 Google Inc. - * + * * 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 @@ -25,67 +25,67 @@ import java.util.TimerTask; /** * This class will periodically perform a task. - * + * * @author Sandor Dornbush */ public class TimerTaskExecutor { - private final PeriodicTask periodicTask; - private final TrackRecordingService trackRecordingService; + private final PeriodicTask periodicTask; + private final TrackRecordingService trackRecordingService; - private TimerTask timerTask; - private Timer timer; + private TimerTask timerTask; + private Timer timer; - public TimerTaskExecutor(PeriodicTask periodicTask, TrackRecordingService trackRecordingService) { - this.periodicTask = periodicTask; - this.trackRecordingService = trackRecordingService; - } - - /** - * Schedules the periodic task at an interval. - * - * @param interval the interval in milliseconds - */ - public void scheduleTask(long interval) { - if (interval <= 0) { - return; + public TimerTaskExecutor(PeriodicTask periodicTask, TrackRecordingService trackRecordingService) { + this.periodicTask = periodicTask; + this.trackRecordingService = trackRecordingService; } - if (!trackRecordingService.isRecording() || trackRecordingService.isPaused()) { - return; + /** + * Schedules the periodic task at an interval. + * + * @param interval the interval in milliseconds + */ + public void scheduleTask(long interval) { + if (interval <= 0) { + return; + } + + if (!trackRecordingService.isRecording() || trackRecordingService.isPaused()) { + return; + } + + TripStatistics tripStatistics = trackRecordingService.getTripStatistics(); + if (tripStatistics == null) { + return; + } + + shutdown(); + periodicTask.start(); + timerTask = new TimerTask() { + @Override + public void run() { + periodicTask.run(trackRecordingService); + } + }; + timer = new Timer(TimerTaskExecutor.class.getSimpleName()); + long next = System.currentTimeMillis() + interval - (tripStatistics.getTotalTime() % interval); + timer.scheduleAtFixedRate(timerTask, new Date(next), interval); } - TripStatistics tripStatistics = trackRecordingService.getTripStatistics(); - if (tripStatistics == null) { - return; + /** + * Shuts down. + */ + public void shutdown() { + if (timerTask != null) { + timerTask.cancel(); + timerTask = null; + } + if (timer != null) { + timer.cancel(); + timer.purge(); + timer = null; + } + periodicTask.shutdown(); } - - shutdown(); - periodicTask.start(); - timerTask = new TimerTask() { - @Override - public void run() { - periodicTask.run(trackRecordingService); - } - }; - timer = new Timer(TimerTaskExecutor.class.getSimpleName()); - long next = System.currentTimeMillis() + interval - (tripStatistics.getTotalTime() % interval); - timer.scheduleAtFixedRate(timerTask, new Date(next), interval); - } - - /** - * Shuts down. - */ - public void shutdown() { - if (timerTask != null) { - timerTask.cancel(); - timerTask = null; - } - if (timer != null) { - timer.cancel(); - timer.purge(); - timer = null; - } - periodicTask.shutdown(); - } } diff --git a/src/main/java/com/google/android/apps/mytracks/settings/AbstractSettingsActivity.java b/src/main/java/com/google/android/apps/mytracks/settings/AbstractSettingsActivity.java index fa8e75ffd..a74d291c0 100644 --- a/src/main/java/com/google/android/apps/mytracks/settings/AbstractSettingsActivity.java +++ b/src/main/java/com/google/android/apps/mytracks/settings/AbstractSettingsActivity.java @@ -32,96 +32,96 @@ import com.google.android.maps.mytracks.R; /** * An abstract activity for all the settings activities. - * + * * @author Jimmy Shih */ public class AbstractSettingsActivity extends PreferenceActivity { - @Override - protected void onCreate(Bundle bundle) { - super.onCreate(bundle); - setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM); + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM); - ActionBar actionBar = this.getActionBar(); - if (actionBar != null) { - actionBar.setHomeButtonEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(true); - } - - PreferenceManager preferenceManager = getPreferenceManager(); - preferenceManager.setSharedPreferencesName(Constants.SETTINGS_NAME); - preferenceManager.setSharedPreferencesMode(Context.MODE_PRIVATE); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() != android.R.id.home) { - return super.onOptionsItemSelected(item); - } - finish(); - return true; - } - - /** - * Configures a list preference. - * - * @param listPreference the list preference - * @param summary the summary array - * @param options the options array - * @param values the values array - * @param value the value - * @param listener optional listener - */ - protected void configureListPreference(ListPreference listPreference, final String[] summary, - final String[] options, final String[] values, String value, - final OnPreferenceChangeListener listener) { - listPreference.setEntryValues(values); - listPreference.setEntries(options); - listPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference pref, Object newValue) { - updatePreferenceSummary(pref, summary, values, (String) newValue); - if (listener != null) { - listener.onPreferenceChange(pref, newValue); + ActionBar actionBar = this.getActionBar(); + if (actionBar != null) { + actionBar.setHomeButtonEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(true); } + + PreferenceManager preferenceManager = getPreferenceManager(); + preferenceManager.setSharedPreferencesName(Constants.SETTINGS_NAME); + preferenceManager.setSharedPreferencesMode(Context.MODE_PRIVATE); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() != android.R.id.home) { + return super.onOptionsItemSelected(item); + } + finish(); return true; - } - }); - updatePreferenceSummary(listPreference, summary, values, value); - if (listener != null) { - listener.onPreferenceChange(listPreference, value); } - } - /** - * Update the preference summary. - * - * @param preference the preference - * @param summary the summary array - * @param values the values array - * @param value the value - */ - private void updatePreferenceSummary(Preference preference, String[] summary, String[] values, String value) { - int index = getIndex(values, value); - if (index == -1) { - preference.setSummary(R.string.value_unknown); - } else { - preference.setSummary(summary[index]); + /** + * Configures a list preference. + * + * @param listPreference the list preference + * @param summary the summary array + * @param options the options array + * @param values the values array + * @param value the value + * @param listener optional listener + */ + protected void configureListPreference(ListPreference listPreference, final String[] summary, + final String[] options, final String[] values, String value, + final OnPreferenceChangeListener listener) { + listPreference.setEntryValues(values); + listPreference.setEntries(options); + listPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference pref, Object newValue) { + updatePreferenceSummary(pref, summary, values, (String) newValue); + if (listener != null) { + listener.onPreferenceChange(pref, newValue); + } + return true; + } + }); + updatePreferenceSummary(listPreference, summary, values, value); + if (listener != null) { + listener.onPreferenceChange(listPreference, value); + } } - } - /** - * Get the array index for a value. - * - * @param values the array - * @param value the value - */ - private int getIndex(String[] values, String value) { - for (int i = 0; i < values.length; i++) { - if (value.equals(values[i])) { - return i; - } + /** + * Update the preference summary. + * + * @param preference the preference + * @param summary the summary array + * @param values the values array + * @param value the value + */ + private void updatePreferenceSummary(Preference preference, String[] summary, String[] values, String value) { + int index = getIndex(values, value); + if (index == -1) { + preference.setSummary(R.string.value_unknown); + } else { + preference.setSummary(summary[index]); + } + } + + /** + * Get the array index for a value. + * + * @param values the array + * @param value the value + */ + private int getIndex(String[] values, String value) { + for (int i = 0; i < values.length; i++) { + if (value.equals(values[i])) { + return i; + } + } + return -1; } - return -1; - } } diff --git a/src/main/java/com/google/android/apps/mytracks/settings/ActivityTypePreference.java b/src/main/java/com/google/android/apps/mytracks/settings/ActivityTypePreference.java index d9ae38427..10f5d035f 100644 --- a/src/main/java/com/google/android/apps/mytracks/settings/ActivityTypePreference.java +++ b/src/main/java/com/google/android/apps/mytracks/settings/ActivityTypePreference.java @@ -16,11 +16,6 @@ package com.google.android.apps.mytracks.settings; -import com.google.android.apps.mytracks.util.DialogUtils; -import com.google.android.apps.mytracks.util.PreferencesUtils; -import com.google.android.apps.mytracks.util.TrackIconUtils; -import com.google.android.maps.mytracks.R; - import android.content.Context; import android.os.Bundle; import android.os.Parcelable; @@ -34,123 +29,128 @@ import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.Spinner; +import com.google.android.apps.mytracks.util.DialogUtils; +import com.google.android.apps.mytracks.util.PreferencesUtils; +import com.google.android.apps.mytracks.util.TrackIconUtils; +import com.google.android.maps.mytracks.R; + /** * For entering the default activity type. - * + * * @author Jimmy Shih */ public class ActivityTypePreference extends DialogPreference { - private RecordingSettingsActivity recordingSettingsActivity; - private AutoCompleteTextView textView; - private Spinner spinner; + private RecordingSettingsActivity recordingSettingsActivity; + private AutoCompleteTextView textView; + private Spinner spinner; - public ActivityTypePreference(Context context, AttributeSet attrs) { - super(context, attrs); - setDialogLayoutResource(R.layout.activity_type_preference); - setPositiveButtonText(android.R.string.ok); - setNegativeButtonText(android.R.string.cancel); - setDialogIcon(null); - setPersistent(false); - } - - @Override - protected View onCreateDialogView() { - View view = super.onCreateDialogView(); - textView = view.findViewById(R.id.activity_type_preference_text_view); - spinner = view.findViewById(R.id.activity_type_preference_spinner); - - String category = PreferencesUtils.getString( - getContext(), R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT); - textView.setText(category); - - ArrayAdapter adapter = ArrayAdapter.createFromResource( - getContext(), R.array.activity_types, android.R.layout.simple_dropdown_item_1line); - textView.setAdapter(adapter); - textView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View v, int position, long id) { - String iconValue = TrackIconUtils.getIconValue( - getContext(), (String) textView.getAdapter().getItem(position)); - TrackIconUtils.setIconSpinner(spinner, iconValue); - } - }); - textView.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (!hasFocus) { - String iconValue = TrackIconUtils.getIconValue( - getContext(), textView.getText().toString()); - TrackIconUtils.setIconSpinner(spinner, iconValue); - } - } - }); - - String iconValue = TrackIconUtils.getIconValue(getContext(), category); - spinner.setAdapter(TrackIconUtils.getIconSpinnerAdapter(getContext(), iconValue)); - spinner.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_UP) { - recordingSettingsActivity.showChooseActivityTypeDialog(); - } - return true; - } - }); - spinner.setOnKeyListener(new View.OnKeyListener() { - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { - recordingSettingsActivity.showChooseActivityTypeDialog(); - } - return true; - } - }); - return view; - } - - @Override - protected void showDialog(Bundle state) { - super.showDialog(state); - DialogUtils.setDialogTitleDivider(getContext(), getDialog()); - } - - @Override - protected void onDialogClosed(boolean positiveResult) { - if (positiveResult) { - String value = textView.getText().toString(); - if (callChangeListener(value)) { - PreferencesUtils.setString(getContext(), R.string.default_activity_key, value); - } + public ActivityTypePreference(Context context, AttributeSet attrs) { + super(context, attrs); + setDialogLayoutResource(R.layout.activity_type_preference); + setPositiveButtonText(android.R.string.ok); + setNegativeButtonText(android.R.string.cancel); + setDialogIcon(null); + setPersistent(false); } - } - @Override - protected void onRestoreInstanceState(Parcelable state) { - super.onRestoreInstanceState(state); - String iconValue = TrackIconUtils.getIconValue(getContext(), textView.getText().toString()); - TrackIconUtils.setIconSpinner(spinner, iconValue); - textView.clearFocus(); - } + @Override + protected View onCreateDialogView() { + View view = super.onCreateDialogView(); + textView = view.findViewById(R.id.activity_type_preference_text_view); + spinner = view.findViewById(R.id.activity_type_preference_spinner); - /** - * Sets the recording settings activity. - * - * @param activity the activity - */ - public void setRecordingSettingsActivity(RecordingSettingsActivity activity) { - recordingSettingsActivity = activity; - } + String category = PreferencesUtils.getString( + getContext(), R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT); + textView.setText(category); - /** - * Updates the value of the dialog. - * - * @param iconValue the icon value - */ - public void updateValue(String iconValue) { - TrackIconUtils.setIconSpinner(spinner, iconValue); - textView.setText( - recordingSettingsActivity.getString(TrackIconUtils.getIconActivityType(iconValue))); - textView.clearFocus(); - } + ArrayAdapter adapter = ArrayAdapter.createFromResource( + getContext(), R.array.activity_types, android.R.layout.simple_dropdown_item_1line); + textView.setAdapter(adapter); + textView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View v, int position, long id) { + String iconValue = TrackIconUtils.getIconValue( + getContext(), (String) textView.getAdapter().getItem(position)); + TrackIconUtils.setIconSpinner(spinner, iconValue); + } + }); + textView.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus) { + String iconValue = TrackIconUtils.getIconValue( + getContext(), textView.getText().toString()); + TrackIconUtils.setIconSpinner(spinner, iconValue); + } + } + }); + + String iconValue = TrackIconUtils.getIconValue(getContext(), category); + spinner.setAdapter(TrackIconUtils.getIconSpinnerAdapter(getContext(), iconValue)); + spinner.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP) { + recordingSettingsActivity.showChooseActivityTypeDialog(); + } + return true; + } + }); + spinner.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { + recordingSettingsActivity.showChooseActivityTypeDialog(); + } + return true; + } + }); + return view; + } + + @Override + protected void showDialog(Bundle state) { + super.showDialog(state); + DialogUtils.setDialogTitleDivider(getContext(), getDialog()); + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + if (positiveResult) { + String value = textView.getText().toString(); + if (callChangeListener(value)) { + PreferencesUtils.setString(getContext(), R.string.default_activity_key, value); + } + } + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + super.onRestoreInstanceState(state); + String iconValue = TrackIconUtils.getIconValue(getContext(), textView.getText().toString()); + TrackIconUtils.setIconSpinner(spinner, iconValue); + textView.clearFocus(); + } + + /** + * Sets the recording settings activity. + * + * @param activity the activity + */ + public void setRecordingSettingsActivity(RecordingSettingsActivity activity) { + recordingSettingsActivity = activity; + } + + /** + * Updates the value of the dialog. + * + * @param iconValue the icon value + */ + public void updateValue(String iconValue) { + TrackIconUtils.setIconSpinner(spinner, iconValue); + textView.setText( + recordingSettingsActivity.getString(TrackIconUtils.getIconActivityType(iconValue))); + textView.clearFocus(); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/settings/AdvancedSettingsActivity.java b/src/main/java/com/google/android/apps/mytracks/settings/AdvancedSettingsActivity.java index 0c61db445..071d1846d 100644 --- a/src/main/java/com/google/android/apps/mytracks/settings/AdvancedSettingsActivity.java +++ b/src/main/java/com/google/android/apps/mytracks/settings/AdvancedSettingsActivity.java @@ -16,11 +16,6 @@ package com.google.android.apps.mytracks.settings; -import com.google.android.apps.mytracks.Constants; -import com.google.android.apps.mytracks.util.DialogUtils; -import com.google.android.apps.mytracks.util.PreferencesUtils; -import com.google.android.maps.mytracks.R; - import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; @@ -28,160 +23,163 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.os.Bundle; -import android.preference.CheckBoxPreference; import android.preference.ListPreference; import android.preference.Preference; -import android.preference.Preference.OnPreferenceChangeListener; import android.preference.Preference.OnPreferenceClickListener; import android.util.Log; import android.widget.Toast; +import com.google.android.apps.mytracks.Constants; +import com.google.android.apps.mytracks.util.DialogUtils; +import com.google.android.apps.mytracks.util.PreferencesUtils; +import com.google.android.maps.mytracks.R; + /** * An activity for advanced settings. - * + * * @author Jimmy Shih */ public class AdvancedSettingsActivity extends AbstractSettingsActivity { - private static final String TAG = AdvancedSettingsActivity.class.getSimpleName(); + private static final String TAG = AdvancedSettingsActivity.class.getSimpleName(); - private static final int DIALOG_CONFIRM_RESET_ID = 1; + private static final int DIALOG_CONFIRM_RESET_ID = 1; - private Preference resetPreference; + private Preference resetPreference; - private SharedPreferences sharedPreferences; - private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; + private SharedPreferences sharedPreferences; + private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; - /* - * Note that sharedPreferenceChangeListenr cannot be an anonymous inner class. - * Anonymous inner class will get garbage collected. - */ - private final OnSharedPreferenceChangeListener - sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { - @Override + /* + * Note that sharedPreferenceChangeListenr cannot be an anonymous inner class. + * Anonymous inner class will get garbage collected. + */ + private final OnSharedPreferenceChangeListener + sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { + @Override public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { - if (key != null && key.equals(PreferencesUtils.getKey( - AdvancedSettingsActivity.this, R.string.recording_track_id_key))) { - recordingTrackId = PreferencesUtils.getLong( - AdvancedSettingsActivity.this, R.string.recording_track_id_key); - runOnUiThread(new Runnable() { - @Override - public void run() { - updateUi(); - } - }); - } + if (key != null && key.equals(PreferencesUtils.getKey( + AdvancedSettingsActivity.this, R.string.recording_track_id_key))) { + recordingTrackId = PreferencesUtils.getLong( + AdvancedSettingsActivity.this, R.string.recording_track_id_key); + runOnUiThread(new Runnable() { + @Override + public void run() { + updateUi(); + } + }); + } } - }; + }; - @Override - protected void onCreate(Bundle bundle) { - super.onCreate(bundle); - addPreferencesFromResource(R.xml.settings_advanced); + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + addPreferencesFromResource(R.xml.settings_advanced); - ListPreference preference = (ListPreference) findPreference(getString(R.string.photo_size_key)); - int value = PreferencesUtils.getInt(this, R.string.photo_size_key, PreferencesUtils.PHOTO_SIZE_DEFAULT); - String[] values = getResources().getStringArray(R.array.photo_size_values); - String[] options = new String[values.length]; - String[] summary = new String[values.length]; - setPhotoSizeSummaryAndOptions(summary, options, values); - configureListPreference(preference, summary, options, values, String.valueOf(value), null); + ListPreference preference = (ListPreference) findPreference(getString(R.string.photo_size_key)); + int value = PreferencesUtils.getInt(this, R.string.photo_size_key, PreferencesUtils.PHOTO_SIZE_DEFAULT); + String[] values = getResources().getStringArray(R.array.photo_size_values); + String[] options = new String[values.length]; + String[] summary = new String[values.length]; + setPhotoSizeSummaryAndOptions(summary, options, values); + configureListPreference(preference, summary, options, values, String.valueOf(value), null); - sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); + sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); - resetPreference = findPreference(getString(R.string.settings_reset_key)); - resetPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference arg0) { - showDialog(DIALOG_CONFIRM_RESET_ID); - return true; - } - }); - } - - @Override - protected void onResume() { - super.onResume(); - sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); - recordingTrackId = PreferencesUtils.getLong(this, R.string.recording_track_id_key); - updateUi(); - } - - @Override - protected void onPause() { - super.onPause(); - sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); - } - - @Override - protected Dialog onCreateDialog(int id) { - switch (id) { - case DIALOG_CONFIRM_RESET_ID: - return DialogUtils.createConfirmationDialog(this, R.string.settings_reset_confirm_title, - getString(R.string.settings_reset_confirm_message), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface d, int button) { - onResetPreferencesConfirmed(); - } - }); - default: - return null; - } - } - - private void setPhotoSizeSummaryAndOptions(String[] summary, String[] options, String[] values) { - for (int i = 0; i < values.length; i++) { - int value = Integer.parseInt(values[i]); - if (value == -1) { - options[i] = getString(R.string.settings_advanced_photo_size_original); - summary[i] = getString(R.string.settings_advanced_photo_size_original); - } else if (value < 1024) { - options[i] = getString(R.string.value_integer_kilobyte, value); - summary[i] = getString(R.string.settings_advanced_photo_size_summary, options[i]); - } else { - int megabyte = value / 1024; - options[i] = getString(R.string.value_integer_megabyte, megabyte); - summary[i] = getString(R.string.settings_advanced_photo_size_summary, options[i]); - } - } - } - - /** - * Updates the UI based on the recording state. - */ - private void updateUi() { - boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; - resetPreference.setEnabled(!isRecording); - resetPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : ""); - } - - /** - * Callback when the user confirms resetting all settings. - */ - private void onResetPreferencesConfirmed() { - // Change preferences in a separate thread - new Thread() { - @Override - public void run() { - Log.i(TAG, "Resetting all settings"); - - // Actually wipe preferences and save synchronously - sharedPreferences.edit().clear().apply(); - - // Give UI feedback in the UI thread - runOnUiThread(new Runnable() { + resetPreference = findPreference(getString(R.string.settings_reset_key)); + resetPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override - public void run() { - Toast.makeText( - AdvancedSettingsActivity.this, R.string.settings_reset_done, Toast.LENGTH_SHORT) - .show(); - // Restart the settings activity so all changes are loaded - Intent intent = getIntent().addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - } + public boolean onPreferenceClick(Preference arg0) { + showDialog(DIALOG_CONFIRM_RESET_ID); + return true; + } }); - } - }.start(); - } + } + + @Override + protected void onResume() { + super.onResume(); + sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + recordingTrackId = PreferencesUtils.getLong(this, R.string.recording_track_id_key); + updateUi(); + } + + @Override + protected void onPause() { + super.onPause(); + sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + } + + @Override + protected Dialog onCreateDialog(int id) { + switch (id) { + case DIALOG_CONFIRM_RESET_ID: + return DialogUtils.createConfirmationDialog(this, R.string.settings_reset_confirm_title, + getString(R.string.settings_reset_confirm_message), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface d, int button) { + onResetPreferencesConfirmed(); + } + }); + default: + return null; + } + } + + private void setPhotoSizeSummaryAndOptions(String[] summary, String[] options, String[] values) { + for (int i = 0; i < values.length; i++) { + int value = Integer.parseInt(values[i]); + if (value == -1) { + options[i] = getString(R.string.settings_advanced_photo_size_original); + summary[i] = getString(R.string.settings_advanced_photo_size_original); + } else if (value < 1024) { + options[i] = getString(R.string.value_integer_kilobyte, value); + summary[i] = getString(R.string.settings_advanced_photo_size_summary, options[i]); + } else { + int megabyte = value / 1024; + options[i] = getString(R.string.value_integer_megabyte, megabyte); + summary[i] = getString(R.string.settings_advanced_photo_size_summary, options[i]); + } + } + } + + /** + * Updates the UI based on the recording state. + */ + private void updateUi() { + boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; + resetPreference.setEnabled(!isRecording); + resetPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : ""); + } + + /** + * Callback when the user confirms resetting all settings. + */ + private void onResetPreferencesConfirmed() { + // Change preferences in a separate thread + new Thread() { + @Override + public void run() { + Log.i(TAG, "Resetting all settings"); + + // Actually wipe preferences and save synchronously + sharedPreferences.edit().clear().apply(); + + // Give UI feedback in the UI thread + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText( + AdvancedSettingsActivity.this, R.string.settings_reset_done, Toast.LENGTH_SHORT) + .show(); + // Restart the settings activity so all changes are loaded + Intent intent = getIntent().addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + }); + } + }.start(); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/settings/ChartSettingsActivity.java b/src/main/java/com/google/android/apps/mytracks/settings/ChartSettingsActivity.java index 88f0f0fb0..b44c94358 100644 --- a/src/main/java/com/google/android/apps/mytracks/settings/ChartSettingsActivity.java +++ b/src/main/java/com/google/android/apps/mytracks/settings/ChartSettingsActivity.java @@ -16,50 +16,50 @@ package com.google.android.apps.mytracks.settings; -import com.google.android.apps.mytracks.util.PreferencesUtils; -import com.google.android.maps.mytracks.R; - import android.os.Bundle; import android.preference.CheckBoxPreference; import android.preference.ListPreference; +import com.google.android.apps.mytracks.util.PreferencesUtils; +import com.google.android.maps.mytracks.R; + /** * An activity for accessing chart settings. - * + * * @author Jimmy Shih */ public class ChartSettingsActivity extends AbstractSettingsActivity { - @Override - protected void onCreate(Bundle bundle) { - super.onCreate(bundle); - addPreferencesFromResource(R.xml.settings_chart); - configXAxisListPreference(); - } + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + addPreferencesFromResource(R.xml.settings_chart); + configXAxisListPreference(); + } - @Override - protected void onResume() { - super.onResume(); - updateUi(); - } + @Override + protected void onResume() { + super.onResume(); + updateUi(); + } - private void updateUi() { - CheckBoxPreference speedCheckBoxPreference = (CheckBoxPreference) findPreference( - getString(R.string.chart_show_speed_key)); - speedCheckBoxPreference.setTitle( - PreferencesUtils.isReportSpeed(this) ? R.string.stats_speed : R.string.stats_pace); - } + private void updateUi() { + CheckBoxPreference speedCheckBoxPreference = (CheckBoxPreference) findPreference( + getString(R.string.chart_show_speed_key)); + speedCheckBoxPreference.setTitle( + PreferencesUtils.isReportSpeed(this) ? R.string.stats_speed : R.string.stats_pace); + } - /** - * Configures the x axis list preference. - */ - private void configXAxisListPreference() { - ListPreference listPreference = (ListPreference) findPreference( - getString(R.string.chart_x_axis_key)); - String value = PreferencesUtils.getString( - this, R.string.chart_x_axis_key, PreferencesUtils.CHART_X_AXIS_DEFAULT); - String[] values = getResources().getStringArray(R.array.chart_x_axis_values); - String[] options = getResources().getStringArray(R.array.chart_x_axis_options); - configureListPreference(listPreference, options, options, values, value, null); - } + /** + * Configures the x axis list preference. + */ + private void configXAxisListPreference() { + ListPreference listPreference = (ListPreference) findPreference( + getString(R.string.chart_x_axis_key)); + String value = PreferencesUtils.getString( + this, R.string.chart_x_axis_key, PreferencesUtils.CHART_X_AXIS_DEFAULT); + String[] values = getResources().getStringArray(R.array.chart_x_axis_values); + String[] options = getResources().getStringArray(R.array.chart_x_axis_options); + configureListPreference(listPreference, options, options, values, value, null); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/settings/RecordingSettingsActivity.java b/src/main/java/com/google/android/apps/mytracks/settings/RecordingSettingsActivity.java index cca71054d..6f6055c38 100644 --- a/src/main/java/com/google/android/apps/mytracks/settings/RecordingSettingsActivity.java +++ b/src/main/java/com/google/android/apps/mytracks/settings/RecordingSettingsActivity.java @@ -30,329 +30,329 @@ import com.google.android.maps.mytracks.R; /** * An activity for accessing recording settings. - * + * * @author Jimmy Shih */ public class RecordingSettingsActivity extends AbstractSettingsActivity - implements ChooseActivityTypeCaller { + implements ChooseActivityTypeCaller { - private static final int DIALOG_CHOOSE_ACTIVITY = 0; + private static final int DIALOG_CHOOSE_ACTIVITY = 0; - private ActivityTypePreference activityTypePreference; + private ActivityTypePreference activityTypePreference; - @Override - protected void onCreate(Bundle bundle) { - super.onCreate(bundle); - addPreferencesFromResource(R.xml.settings_recording); + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + addPreferencesFromResource(R.xml.settings_recording); - boolean metricUnits = PreferencesUtils.isMetricUnits(this); + boolean metricUnits = PreferencesUtils.isMetricUnits(this); - configFrequencyPreference(R.string.voice_frequency_key, - PreferencesUtils.VOICE_FREQUENCY_DEFAULT, R.array.frequency_values, metricUnits); - configFrequencyPreference(R.string.split_frequency_key, - PreferencesUtils.SPLIT_FREQUENCY_DEFAULT, R.array.frequency_values, metricUnits); - configTrackName(); - configDefaultActivity(); - configListPreference(R.string.min_recording_interval_key, - PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT, R.array.min_recording_interval_values, - metricUnits); - configListPreference(R.string.recording_distance_interval_key, - PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT, - R.array.recording_distance_interval_values, metricUnits); - configListPreference(R.string.max_recording_distance_key, - PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT, R.array.max_recording_distance_values, - metricUnits); - configListPreference(R.string.recording_gps_accuracy_key, - PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT, R.array.recording_gps_accuracy_values, - metricUnits); - configListPreference(R.string.auto_resume_track_timeout_key, - PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT, - R.array.auto_resume_track_timeout_values, metricUnits); - } - - @Override - protected Dialog onCreateDialog(int id, Bundle bundle) { - if (id != DIALOG_CHOOSE_ACTIVITY) { - return null; + configFrequencyPreference(R.string.voice_frequency_key, + PreferencesUtils.VOICE_FREQUENCY_DEFAULT, R.array.frequency_values, metricUnits); + configFrequencyPreference(R.string.split_frequency_key, + PreferencesUtils.SPLIT_FREQUENCY_DEFAULT, R.array.frequency_values, metricUnits); + configTrackName(); + configDefaultActivity(); + configListPreference(R.string.min_recording_interval_key, + PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT, R.array.min_recording_interval_values, + metricUnits); + configListPreference(R.string.recording_distance_interval_key, + PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT, + R.array.recording_distance_interval_values, metricUnits); + configListPreference(R.string.max_recording_distance_key, + PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT, R.array.max_recording_distance_values, + metricUnits); + configListPreference(R.string.recording_gps_accuracy_key, + PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT, R.array.recording_gps_accuracy_values, + metricUnits); + configListPreference(R.string.auto_resume_track_timeout_key, + PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT, + R.array.auto_resume_track_timeout_values, metricUnits); } - - String category = PreferencesUtils.getString( - this, R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT); - return ChooseActivityTypeDialogFragment.getDialog(this, category, this); - } - private void configFrequencyPreference(int key, int defaultValue, int valueArray, boolean metricUnits) { - ListPreference preference = (ListPreference) findPreference(getString(key)); - int value = PreferencesUtils.getInt(this, key, defaultValue); - String[] values = getResources().getStringArray(valueArray); - String[] options = StringUtils.getFrequencyOptions(this, metricUnits); - configureListPreference(preference, options, options, values, String.valueOf(value), null); - } + @Override + protected Dialog onCreateDialog(int id, Bundle bundle) { + if (id != DIALOG_CHOOSE_ACTIVITY) { + return null; + } - private void configTrackName() { - ListPreference preference = (ListPreference) findPreference(getString(R.string.track_name_key)); - String value = PreferencesUtils.getString( - this, R.string.track_name_key, PreferencesUtils.TRACK_NAME_DEFAULT); - String[] values = getResources().getStringArray(R.array.track_name_values); - String[] options = getResources().getStringArray(R.array.track_name_options); - configureListPreference(preference, options, options, values, value, null); - } + String category = PreferencesUtils.getString( + this, R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT); + return ChooseActivityTypeDialogFragment.getDialog(this, category, this); + } - private void configDefaultActivity() { - activityTypePreference = (ActivityTypePreference) findPreference( - getString(R.string.default_activity_key)); - String defaultActivity = PreferencesUtils.getString( - this, R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT); - activityTypePreference.setOnPreferenceChangeListener( - new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference pref, Object newValue) { - String stringValue = (String) newValue; - pref.setSummary(stringValue != null - && !stringValue.equals(PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT) ? stringValue + private void configFrequencyPreference(int key, int defaultValue, int valueArray, boolean metricUnits) { + ListPreference preference = (ListPreference) findPreference(getString(key)); + int value = PreferencesUtils.getInt(this, key, defaultValue); + String[] values = getResources().getStringArray(valueArray); + String[] options = StringUtils.getFrequencyOptions(this, metricUnits); + configureListPreference(preference, options, options, values, String.valueOf(value), null); + } + + private void configTrackName() { + ListPreference preference = (ListPreference) findPreference(getString(R.string.track_name_key)); + String value = PreferencesUtils.getString( + this, R.string.track_name_key, PreferencesUtils.TRACK_NAME_DEFAULT); + String[] values = getResources().getStringArray(R.array.track_name_values); + String[] options = getResources().getStringArray(R.array.track_name_options); + configureListPreference(preference, options, options, values, value, null); + } + + private void configDefaultActivity() { + activityTypePreference = (ActivityTypePreference) findPreference( + getString(R.string.default_activity_key)); + String defaultActivity = PreferencesUtils.getString( + this, R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT); + activityTypePreference.setOnPreferenceChangeListener( + new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference pref, Object newValue) { + String stringValue = (String) newValue; + pref.setSummary(stringValue != null + && !stringValue.equals(PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT) ? stringValue + : getString(R.string.value_unknown)); + return true; + } + }); + activityTypePreference.setSummary(defaultActivity != null + && !defaultActivity.equals(PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT) ? defaultActivity : getString(R.string.value_unknown)); - return true; - } - }); - activityTypePreference.setSummary(defaultActivity != null - && !defaultActivity.equals(PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT) ? defaultActivity - : getString(R.string.value_unknown)); - activityTypePreference.setRecordingSettingsActivity(this); - } - - private void configListPreference( - int key, int defaultValue, int valueArray, boolean metricUnits) { - ListPreference preference = (ListPreference) findPreference(getString(key)); - int value = PreferencesUtils.getInt(this, key, defaultValue); - String[] values = getResources().getStringArray(valueArray); - String[] options = new String[values.length]; - String[] summary = new String[values.length]; - switch (key) { - case R.string.min_recording_interval_key: - setMinRecordingIntervalSummaryAndOptions(summary, options, values); - break; - case R.string.recording_distance_interval_key: - setRecordingDistanceIntervalSummaryAndOptions(summary, options, values, metricUnits); - break; - case R.string.max_recording_distance_key: - setMaxRecordingDistanceSummaryAndOptions(summary, options, values, metricUnits); - break; - case R.string.recording_gps_accuracy_key: - setRecordingGpsAccuracySummaryAndOptions(summary, options, values, metricUnits); - break; - case R.string.auto_resume_track_timeout_key: - setAutoResumeTrackTimeoutSummaryAndOptions(summary, options, values); - break; - default: - return; + activityTypePreference.setRecordingSettingsActivity(this); } - configureListPreference(preference, summary, options, values, String.valueOf(value), null); - } - /** - * Sets the min recording interval summary and options. - * - * @param summary the summary - * @param options the options - * @param values the values - */ - private void setMinRecordingIntervalSummaryAndOptions( - String[] summary, String[] options, String[] values) { - for (int i = 0; i < values.length; i++) { - int value = Integer.parseInt(values[i]); - switch (value) { - case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_BATTERY_LIFE: - options[i] = getString(R.string.value_adapt_battery_life); - summary[i] = options[i]; - break; - case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_ACCURACY: - options[i] = getString(R.string.value_adapt_accuracy); - summary[i] = options[i]; - break; - case PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT: - options[i] = getString(R.string.value_smallest_recommended); - summary[i] = getString(R.string.value_smallest); - break; - default: - options[i] = value < 60 ? getString(R.string.value_integer_second, value) - : getString(R.string.value_integer_minute, value / 60); - summary[i] = getString( - R.string.settings_recording_location_frequency_summary, options[i]); - } - } - } - - /** - * Sets the recording distance interval summary and options. - * - * @param summary the summary - * @param options the options - * @param values the values - * @param metricUnits true for metric units - */ - private void setRecordingDistanceIntervalSummaryAndOptions( - String[] summary, String[] options, String[] values, boolean metricUnits) { - for (int i = 0; i < values.length; i++) { - int value = Integer.parseInt(values[i]); - String displayValue; - if (metricUnits) { - displayValue = getString(R.string.value_integer_meter, value); - if (value == PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT) { - options[i] = getString(R.string.value_integer_meter_recommended, value); - } else { - options[i] = displayValue; - } - } else { - int feet = (int) (value * UnitConversions.M_TO_FT); - displayValue = getString(R.string.value_integer_feet, feet); - if (value == PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT) { - options[i] = getString(R.string.value_integer_feet_recommended, feet); - } else { - options[i] = displayValue; - } - } - summary[i] = getString(R.string.settings_recording_location_frequency_summary, displayValue); - } - } - - /** - * Sets the max recording distance summary and options. - * - * @param summary the summary - * @param options the options - * @param values the values - * @param metricUnits true for metric units - */ - private void setMaxRecordingDistanceSummaryAndOptions( - String[] summary, String[] options, String[] values, boolean metricUnits) { - for (int i = 0; i < values.length; i++) { - int value = Integer.parseInt(values[i]); - String displayValue; - if (metricUnits) { - displayValue = getString(R.string.value_integer_meter, value); - if (value == PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT) { - options[i] = getString(R.string.value_integer_meter_recommended, value); - } else { - options[i] = displayValue; - } - } else { - int feet = (int) (value * UnitConversions.M_TO_FT); - if (feet < 2000) { - displayValue = getString(R.string.value_integer_feet, feet); - if (value == PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT) { - options[i] = getString(R.string.value_integer_feet_recommended, feet); - } else { - options[i] = displayValue; - } - } else { - double mile = feet * UnitConversions.FT_TO_MI; - displayValue = getString(R.string.value_float_mile, mile); - options[i] = displayValue; - } - } - summary[i] = getString( - R.string.settings_recording_max_recording_distance_summary, displayValue); - } - } - - /** - * Sets the recording gps accuracy summary and options. - * - * @param summary the summary - * @param options the options - * @param values the values - * @param metricUnits true for metric units - */ - private void setRecordingGpsAccuracySummaryAndOptions( - String[] summary, String[] options, String[] values, boolean metricUnits) { - for (int i = 0; i < values.length; i++) { - int value = Integer.parseInt(values[i]); - String displayValue; - if (metricUnits) { - displayValue = getString(R.string.value_integer_meter, value); - switch (value) { - case PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT: - options[i] = getString(R.string.value_integer_meter_recommended, value); - break; - case PreferencesUtils.RECORDING_GPS_ACCURACY_EXCELLENT: - options[i] = getString(R.string.value_integer_meter_excellent_gps, value); - break; - case PreferencesUtils.RECORDING_GPS_ACCURACY_POOR: - options[i] = getString(R.string.value_integer_meter_poor_gps, value); - break; - default: - options[i] = displayValue; - } - } else { - int feet = (int) (value * UnitConversions.M_TO_FT); - if (feet < 2000) { - displayValue = getString(R.string.value_integer_feet, feet); - switch (value) { - case PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT: - options[i] = getString(R.string.value_integer_feet_recommended, feet); - break; - case PreferencesUtils.RECORDING_GPS_ACCURACY_EXCELLENT: - options[i] = getString(R.string.value_integer_feet_excellent_gps, feet); - break; + private void configListPreference( + int key, int defaultValue, int valueArray, boolean metricUnits) { + ListPreference preference = (ListPreference) findPreference(getString(key)); + int value = PreferencesUtils.getInt(this, key, defaultValue); + String[] values = getResources().getStringArray(valueArray); + String[] options = new String[values.length]; + String[] summary = new String[values.length]; + switch (key) { + case R.string.min_recording_interval_key: + setMinRecordingIntervalSummaryAndOptions(summary, options, values); + break; + case R.string.recording_distance_interval_key: + setRecordingDistanceIntervalSummaryAndOptions(summary, options, values, metricUnits); + break; + case R.string.max_recording_distance_key: + setMaxRecordingDistanceSummaryAndOptions(summary, options, values, metricUnits); + break; + case R.string.recording_gps_accuracy_key: + setRecordingGpsAccuracySummaryAndOptions(summary, options, values, metricUnits); + break; + case R.string.auto_resume_track_timeout_key: + setAutoResumeTrackTimeoutSummaryAndOptions(summary, options, values); + break; default: - options[i] = displayValue; - } - } else { - double mile = feet * UnitConversions.FT_TO_MI; - displayValue = getString(R.string.value_float_mile, mile); - if (value == PreferencesUtils.RECORDING_GPS_ACCURACY_POOR) { - options[i] = getString(R.string.value_float_mile_poor_gps, mile); - } else { - options[i] = displayValue; - } + return; } - } - summary[i] = getString( - R.string.settings_recording_min_required_accuracy_summary, displayValue); + configureListPreference(preference, summary, options, values, String.valueOf(value), null); } - } - /** - * Sets the auto resume track timeout summary and options. - * - * @param summary the summary - * @param options the options - * @param values the values - */ - private void setAutoResumeTrackTimeoutSummaryAndOptions( - String[] summary, String[] options, String[] values) { - for (int i = 0; i < values.length; i++) { - int value = Integer.parseInt(values[i]); - switch (value) { - case PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_NEVER: - options[i] = getString(R.string.value_never); - summary[i] = getString( - R.string.settings_recording_auto_resume_track_timeout_never_summary); - break; - case PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_ALWAYS: - options[i] = getString(R.string.value_always); - summary[i] = getString( - R.string.settings_recording_auto_resume_track_timeout_always_summary); - break; - default: - options[i] = getString(R.string.value_integer_minute, value); - summary[i] = getString( - R.string.settings_recording_auto_resume_track_timeout_summary, options[i]); - } + /** + * Sets the min recording interval summary and options. + * + * @param summary the summary + * @param options the options + * @param values the values + */ + private void setMinRecordingIntervalSummaryAndOptions( + String[] summary, String[] options, String[] values) { + for (int i = 0; i < values.length; i++) { + int value = Integer.parseInt(values[i]); + switch (value) { + case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_BATTERY_LIFE: + options[i] = getString(R.string.value_adapt_battery_life); + summary[i] = options[i]; + break; + case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_ACCURACY: + options[i] = getString(R.string.value_adapt_accuracy); + summary[i] = options[i]; + break; + case PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT: + options[i] = getString(R.string.value_smallest_recommended); + summary[i] = getString(R.string.value_smallest); + break; + default: + options[i] = value < 60 ? getString(R.string.value_integer_second, value) + : getString(R.string.value_integer_minute, value / 60); + summary[i] = getString( + R.string.settings_recording_location_frequency_summary, options[i]); + } + } } - } - public void showChooseActivityTypeDialog() { - try { - removeDialog(DIALOG_CHOOSE_ACTIVITY); - } catch (Exception e) { - // Can safely ignore. + /** + * Sets the recording distance interval summary and options. + * + * @param summary the summary + * @param options the options + * @param values the values + * @param metricUnits true for metric units + */ + private void setRecordingDistanceIntervalSummaryAndOptions( + String[] summary, String[] options, String[] values, boolean metricUnits) { + for (int i = 0; i < values.length; i++) { + int value = Integer.parseInt(values[i]); + String displayValue; + if (metricUnits) { + displayValue = getString(R.string.value_integer_meter, value); + if (value == PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT) { + options[i] = getString(R.string.value_integer_meter_recommended, value); + } else { + options[i] = displayValue; + } + } else { + int feet = (int) (value * UnitConversions.M_TO_FT); + displayValue = getString(R.string.value_integer_feet, feet); + if (value == PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT) { + options[i] = getString(R.string.value_integer_feet_recommended, feet); + } else { + options[i] = displayValue; + } + } + summary[i] = getString(R.string.settings_recording_location_frequency_summary, displayValue); + } } - showDialog(DIALOG_CHOOSE_ACTIVITY); - } - @Override - public void onChooseActivityTypeDone(String iconValue) { - activityTypePreference.updateValue(iconValue); - } + /** + * Sets the max recording distance summary and options. + * + * @param summary the summary + * @param options the options + * @param values the values + * @param metricUnits true for metric units + */ + private void setMaxRecordingDistanceSummaryAndOptions( + String[] summary, String[] options, String[] values, boolean metricUnits) { + for (int i = 0; i < values.length; i++) { + int value = Integer.parseInt(values[i]); + String displayValue; + if (metricUnits) { + displayValue = getString(R.string.value_integer_meter, value); + if (value == PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT) { + options[i] = getString(R.string.value_integer_meter_recommended, value); + } else { + options[i] = displayValue; + } + } else { + int feet = (int) (value * UnitConversions.M_TO_FT); + if (feet < 2000) { + displayValue = getString(R.string.value_integer_feet, feet); + if (value == PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT) { + options[i] = getString(R.string.value_integer_feet_recommended, feet); + } else { + options[i] = displayValue; + } + } else { + double mile = feet * UnitConversions.FT_TO_MI; + displayValue = getString(R.string.value_float_mile, mile); + options[i] = displayValue; + } + } + summary[i] = getString( + R.string.settings_recording_max_recording_distance_summary, displayValue); + } + } + + /** + * Sets the recording gps accuracy summary and options. + * + * @param summary the summary + * @param options the options + * @param values the values + * @param metricUnits true for metric units + */ + private void setRecordingGpsAccuracySummaryAndOptions( + String[] summary, String[] options, String[] values, boolean metricUnits) { + for (int i = 0; i < values.length; i++) { + int value = Integer.parseInt(values[i]); + String displayValue; + if (metricUnits) { + displayValue = getString(R.string.value_integer_meter, value); + switch (value) { + case PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT: + options[i] = getString(R.string.value_integer_meter_recommended, value); + break; + case PreferencesUtils.RECORDING_GPS_ACCURACY_EXCELLENT: + options[i] = getString(R.string.value_integer_meter_excellent_gps, value); + break; + case PreferencesUtils.RECORDING_GPS_ACCURACY_POOR: + options[i] = getString(R.string.value_integer_meter_poor_gps, value); + break; + default: + options[i] = displayValue; + } + } else { + int feet = (int) (value * UnitConversions.M_TO_FT); + if (feet < 2000) { + displayValue = getString(R.string.value_integer_feet, feet); + switch (value) { + case PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT: + options[i] = getString(R.string.value_integer_feet_recommended, feet); + break; + case PreferencesUtils.RECORDING_GPS_ACCURACY_EXCELLENT: + options[i] = getString(R.string.value_integer_feet_excellent_gps, feet); + break; + default: + options[i] = displayValue; + } + } else { + double mile = feet * UnitConversions.FT_TO_MI; + displayValue = getString(R.string.value_float_mile, mile); + if (value == PreferencesUtils.RECORDING_GPS_ACCURACY_POOR) { + options[i] = getString(R.string.value_float_mile_poor_gps, mile); + } else { + options[i] = displayValue; + } + } + } + summary[i] = getString( + R.string.settings_recording_min_required_accuracy_summary, displayValue); + } + } + + /** + * Sets the auto resume track timeout summary and options. + * + * @param summary the summary + * @param options the options + * @param values the values + */ + private void setAutoResumeTrackTimeoutSummaryAndOptions( + String[] summary, String[] options, String[] values) { + for (int i = 0; i < values.length; i++) { + int value = Integer.parseInt(values[i]); + switch (value) { + case PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_NEVER: + options[i] = getString(R.string.value_never); + summary[i] = getString( + R.string.settings_recording_auto_resume_track_timeout_never_summary); + break; + case PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_ALWAYS: + options[i] = getString(R.string.value_always); + summary[i] = getString( + R.string.settings_recording_auto_resume_track_timeout_always_summary); + break; + default: + options[i] = getString(R.string.value_integer_minute, value); + summary[i] = getString( + R.string.settings_recording_auto_resume_track_timeout_summary, options[i]); + } + } + } + + public void showChooseActivityTypeDialog() { + try { + removeDialog(DIALOG_CHOOSE_ACTIVITY); + } catch (Exception e) { + // Can safely ignore. + } + showDialog(DIALOG_CHOOSE_ACTIVITY); + } + + @Override + public void onChooseActivityTypeDone(String iconValue) { + activityTypePreference.updateValue(iconValue); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/settings/SensorSettingsActivity.java b/src/main/java/com/google/android/apps/mytracks/settings/SensorSettingsActivity.java index c5c37aec1..d072f918a 100644 --- a/src/main/java/com/google/android/apps/mytracks/settings/SensorSettingsActivity.java +++ b/src/main/java/com/google/android/apps/mytracks/settings/SensorSettingsActivity.java @@ -33,58 +33,58 @@ import java.util.List; /** * An activity for accessing sensor settings. - * + * * @author Jimmy Shih */ public class SensorSettingsActivity extends AbstractSettingsActivity { - @Override - protected void onCreate(Bundle bundle) { - super.onCreate(bundle); - addPreferencesFromResource(R.xml.settings_sensors); + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + addPreferencesFromResource(R.xml.settings_sensors); - findPreference(getString(R.string.settings_sensor_bluetooth_pairing_key)) - .setOnPreferenceClickListener(new OnPreferenceClickListener() { - public boolean onPreferenceClick(Preference preference) { - Intent settingsIntent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS); - startActivity(settingsIntent); - return true; - } - }); - } - - @Override - protected void onResume() { - super.onResume(); - - // Update each time in case the list of bluetooth sensors has changed - configBluetoothSensor(); - } - - /** - * Configures the bluetooth sensor. - */ - private void configBluetoothSensor() { - ListPreference preference = (ListPreference) findPreference(getString(R.string.bluetooth_sensor_key)); - String value = PreferencesUtils.getString(this, R.string.bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT); - List devicesNameList = new ArrayList<>(); - List devicesAddressList = new ArrayList<>(); - BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - if (bluetoothAdapter != null) { - BluetoothDeviceUtils.populateDeviceLists(bluetoothAdapter, devicesNameList, devicesAddressList); + findPreference(getString(R.string.settings_sensor_bluetooth_pairing_key)) + .setOnPreferenceClickListener(new OnPreferenceClickListener() { + public boolean onPreferenceClick(Preference preference) { + Intent settingsIntent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS); + startActivity(settingsIntent); + return true; + } + }); } - // Was the previously configured device unpaired? Then forget it. - if (!devicesAddressList.contains(value)) { - value = PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT; - PreferencesUtils.setString(this, R.string.bluetooth_sensor_key, value); + @Override + protected void onResume() { + super.onResume(); + + // Update each time in case the list of bluetooth sensors has changed + configBluetoothSensor(); } - devicesNameList.add(0, getString(R.string.value_none)); - devicesAddressList.add(0, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT); + /** + * Configures the bluetooth sensor. + */ + private void configBluetoothSensor() { + ListPreference preference = (ListPreference) findPreference(getString(R.string.bluetooth_sensor_key)); + String value = PreferencesUtils.getString(this, R.string.bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT); + List devicesNameList = new ArrayList<>(); + List devicesAddressList = new ArrayList<>(); + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if (bluetoothAdapter != null) { + BluetoothDeviceUtils.populateDeviceLists(bluetoothAdapter, devicesNameList, devicesAddressList); + } - String[] options = devicesNameList.toArray(new String[0]); - String[] values = devicesAddressList.toArray(new String[0]); - configureListPreference(preference, options, options, values, value, null); - } + // Was the previously configured device unpaired? Then forget it. + if (!devicesAddressList.contains(value)) { + value = PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT; + PreferencesUtils.setString(this, R.string.bluetooth_sensor_key, value); + } + + devicesNameList.add(0, getString(R.string.value_none)); + devicesAddressList.add(0, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT); + + String[] options = devicesNameList.toArray(new String[0]); + String[] values = devicesAddressList.toArray(new String[0]); + configureListPreference(preference, options, options, values, value, null); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/settings/SettingsActivity.java b/src/main/java/com/google/android/apps/mytracks/settings/SettingsActivity.java index c0351158f..7ae433d70 100644 --- a/src/main/java/com/google/android/apps/mytracks/settings/SettingsActivity.java +++ b/src/main/java/com/google/android/apps/mytracks/settings/SettingsActivity.java @@ -26,39 +26,39 @@ import com.google.android.maps.mytracks.R; /** * An activity for accessing settings. - * + * * @author Jimmy Shih */ public class SettingsActivity extends AbstractSettingsActivity { - @Override - protected void onCreate(Bundle bundle) { - super.onCreate(bundle); + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); - addPreferencesFromResource(R.xml.settings); + addPreferencesFromResource(R.xml.settings); - configPreference(R.string.settings_chart_key, ChartSettingsActivity.class); - configPreference(R.string.settings_stats_key, StatsSettingsActivity.class); - configPreference(R.string.settings_recording_key, RecordingSettingsActivity.class); - configPreference(R.string.settings_sensor_key, SensorSettingsActivity.class); - configPreference(R.string.settings_advanced_key, AdvancedSettingsActivity.class); - } + configPreference(R.string.settings_chart_key, ChartSettingsActivity.class); + configPreference(R.string.settings_stats_key, StatsSettingsActivity.class); + configPreference(R.string.settings_recording_key, RecordingSettingsActivity.class); + configPreference(R.string.settings_sensor_key, SensorSettingsActivity.class); + configPreference(R.string.settings_advanced_key, AdvancedSettingsActivity.class); + } - /** - * Configures a preference by starting a new activity when it is clicked. - * - * @param key the preference key - * @param cl the class to start the new activity - */ - private void configPreference(int key, final Class cl) { - Preference preference = findPreference(getString(key)); - preference.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference pref) { - Intent intent = IntentUtils.newIntent(SettingsActivity.this, cl); - startActivity(intent); - return true; - } - }); - } + /** + * Configures a preference by starting a new activity when it is clicked. + * + * @param key the preference key + * @param cl the class to start the new activity + */ + private void configPreference(int key, final Class cl) { + Preference preference = findPreference(getString(key)); + preference.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference pref) { + Intent intent = IntentUtils.newIntent(SettingsActivity.this, cl); + startActivity(intent); + return true; + } + }); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/settings/StatsSettingsActivity.java b/src/main/java/com/google/android/apps/mytracks/settings/StatsSettingsActivity.java index 4b18fb84c..f6ef3adb8 100644 --- a/src/main/java/com/google/android/apps/mytracks/settings/StatsSettingsActivity.java +++ b/src/main/java/com/google/android/apps/mytracks/settings/StatsSettingsActivity.java @@ -16,68 +16,68 @@ package com.google.android.apps.mytracks.settings; -import com.google.android.apps.mytracks.util.PreferencesUtils; -import com.google.android.maps.mytracks.R; - import android.os.Bundle; import android.preference.ListPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; +import com.google.android.apps.mytracks.util.PreferencesUtils; +import com.google.android.maps.mytracks.R; + /** * An activity for accessing stats settings. - * + * * @author Jimmy Shih */ public class StatsSettingsActivity extends AbstractSettingsActivity { - @Override - protected void onCreate(Bundle bundle) { - super.onCreate(bundle); - addPreferencesFromResource(R.xml.settings_statistics); + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + addPreferencesFromResource(R.xml.settings_statistics); - /* - * Note configureUnitsListPreference will trigger - * configureRateListPreference + /* + * Note configureUnitsListPreference will trigger + * configureRateListPreference + */ + configUnitsListPreference(); + } + + /** + * Configures the preferred units list preference. */ - configUnitsListPreference(); - } + private void configUnitsListPreference() { + ListPreference listPreference = (ListPreference) findPreference( + getString(R.string.stats_units_key)); + OnPreferenceChangeListener listener = new OnPreferenceChangeListener() { - /** - * Configures the preferred units list preference. - */ - private void configUnitsListPreference() { - ListPreference listPreference = (ListPreference) findPreference( - getString(R.string.stats_units_key)); - OnPreferenceChangeListener listener = new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference pref, Object newValue) { + boolean metricUnits = PreferencesUtils.STATS_UNITS_DEFAULT.equals(newValue); + configRateListPreference(metricUnits); + return true; + } + }; + String value = PreferencesUtils.getString( + this, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT); + String[] values = getResources().getStringArray(R.array.stats_units_values); + String[] options = getResources().getStringArray(R.array.stats_units_options); + configureListPreference(listPreference, options, options, values, value, listener); + } - @Override - public boolean onPreferenceChange(Preference pref, Object newValue) { - boolean metricUnits = PreferencesUtils.STATS_UNITS_DEFAULT.equals(newValue); - configRateListPreference(metricUnits); - return true; - } - }; - String value = PreferencesUtils.getString( - this, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT); - String[] values = getResources().getStringArray(R.array.stats_units_values); - String[] options = getResources().getStringArray(R.array.stats_units_options); - configureListPreference(listPreference, options, options, values, value, listener); - } - - /** - * Configures the preferred rate list preference. - * - * @param metricUnits true if metric units - */ - private void configRateListPreference(boolean metricUnits) { - ListPreference listPreference = (ListPreference) findPreference( - getString(R.string.stats_rate_key)); - String value = PreferencesUtils.getString( - this, R.string.stats_rate_key, PreferencesUtils.STATS_RATE_DEFAULT); - String[] values = getResources().getStringArray(R.array.stats_rate_values); - String[] options = getResources().getStringArray( - metricUnits ? R.array.stats_rate_metric_options : R.array.stats_rate_imperial_options); - configureListPreference(listPreference, options, options, values, value, null); - } + /** + * Configures the preferred rate list preference. + * + * @param metricUnits true if metric units + */ + private void configRateListPreference(boolean metricUnits) { + ListPreference listPreference = (ListPreference) findPreference( + getString(R.string.stats_rate_key)); + String value = PreferencesUtils.getString( + this, R.string.stats_rate_key, PreferencesUtils.STATS_RATE_DEFAULT); + String[] values = getResources().getStringArray(R.array.stats_rate_values); + String[] options = getResources().getStringArray( + metricUnits ? R.array.stats_rate_metric_options : R.array.stats_rate_imperial_options); + configureListPreference(listPreference, options, options, values, value, null); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/stats/DoubleBuffer.java b/src/main/java/com/google/android/apps/mytracks/stats/DoubleBuffer.java index 2e55ac094..5668720c2 100644 --- a/src/main/java/com/google/android/apps/mytracks/stats/DoubleBuffer.java +++ b/src/main/java/com/google/android/apps/mytracks/stats/DoubleBuffer.java @@ -1,12 +1,12 @@ /* * Copyright 2009 Google Inc. - * + * * 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 @@ -22,115 +22,113 @@ import androidx.annotation.NonNull; * This class maintains a buffer of doubles. This buffer is a convenient class * for storing a series of doubles and calculating information about them. This * is a FIFO buffer. - * + * * @author Sandor Dornbush */ public class DoubleBuffer { - // The location that the next write will occur at. - private int index; + // The sliding buffer of doubles. + private final double[] buffer; + // The location that the next write will occur at. + private int index; + // True if the buffer is full + private boolean isFull; - // The sliding buffer of doubles. - private final double[] buffer; - - // True if the buffer is full - private boolean isFull; - - /** - * Creates a buffer with a certain size. - * - * @param size the size - */ - public DoubleBuffer(int size) { - if (size < 1) { - throw new IllegalArgumentException("The buffer size must be greater than 1."); - } - buffer = new double[size]; - reset(); - } - - /** - * Resets the buffer. - */ - public void reset() { - index = 0; - isFull = false; - } - - /** - * Returns true if the buffer is full. - */ - public boolean isFull() { - return isFull; - } - - /** - * Gets the average of the buffer. - */ - public double getAverage() { - int numberOfEntries = isFull ? buffer.length : index; - if (numberOfEntries == 0) { - return 0; - } - double sum = 0; - for (int i = 0; i < numberOfEntries; i++) { - sum += buffer[i]; - } - return sum / numberOfEntries; - } - - /** - * Gets the average and the variance of the buffer. - * - * @return an array of two elements - the first is the average, the second is - * the variance - */ - public double[] getAverageAndVariance() { - int numberOfEntries = isFull ? buffer.length : index; - if (numberOfEntries == 0) { - return new double[] { 0, 0 }; + /** + * Creates a buffer with a certain size. + * + * @param size the size + */ + public DoubleBuffer(int size) { + if (size < 1) { + throw new IllegalArgumentException("The buffer size must be greater than 1."); + } + buffer = new double[size]; + reset(); } - double sum = 0; - double sumSquares = 0; - for (int i = 0; i < numberOfEntries; i++) { - sum += buffer[i]; - sumSquares += Math.pow(buffer[i], 2); + /** + * Resets the buffer. + */ + public void reset() { + index = 0; + isFull = false; } - double average = sum / numberOfEntries; - double variance = sumSquares / numberOfEntries - Math.pow(average, 2); - return new double[] { average, variance }; - } + /** + * Returns true if the buffer is full. + */ + public boolean isFull() { + return isFull; + } - /** - * Adds a double to the buffer. If the buffer is full the oldest element is - * overwritten. - * - * @param value the double to add - */ - public void setNext(double value) { - if (index == buffer.length) { - index = 0; + /** + * Gets the average of the buffer. + */ + public double getAverage() { + int numberOfEntries = isFull ? buffer.length : index; + if (numberOfEntries == 0) { + return 0; + } + double sum = 0; + for (int i = 0; i < numberOfEntries; i++) { + sum += buffer[i]; + } + return sum / numberOfEntries; } - buffer[index] = value; - index++; - if (index == buffer.length) { - isFull = true; - } - } - @NonNull - @Override - public String toString() { - StringBuilder builder = new StringBuilder("Full: "); - builder.append(isFull); - builder.append("\n"); - for (int i = 0; i < buffer.length; i++) { - builder.append((i == index) ? "<<" : "["); - builder.append(buffer[i]); - builder.append((i == index) ? ">> " : "] "); + /** + * Gets the average and the variance of the buffer. + * + * @return an array of two elements - the first is the average, the second is + * the variance + */ + public double[] getAverageAndVariance() { + int numberOfEntries = isFull ? buffer.length : index; + if (numberOfEntries == 0) { + return new double[]{0, 0}; + } + + double sum = 0; + double sumSquares = 0; + for (int i = 0; i < numberOfEntries; i++) { + sum += buffer[i]; + sumSquares += Math.pow(buffer[i], 2); + } + + double average = sum / numberOfEntries; + double variance = sumSquares / numberOfEntries - Math.pow(average, 2); + return new double[]{average, variance}; + } + + /** + * Adds a double to the buffer. If the buffer is full the oldest element is + * overwritten. + * + * @param value the double to add + */ + public void setNext(double value) { + if (index == buffer.length) { + index = 0; + } + buffer[index] = value; + index++; + if (index == buffer.length) { + isFull = true; + } + } + + @NonNull + @Override + public String toString() { + StringBuilder builder = new StringBuilder("Full: "); + builder.append(isFull); + builder.append("\n"); + for (int i = 0; i < buffer.length; i++) { + builder.append((i == index) ? "<<" : "["); + builder.append(buffer[i]); + builder.append((i == index) ? ">> " : "] "); + } + return builder.toString(); } - return builder.toString(); - } } diff --git a/src/main/java/com/google/android/apps/mytracks/stats/ExtremityMonitor.java b/src/main/java/com/google/android/apps/mytracks/stats/ExtremityMonitor.java index 012cdb299..53b45df76 100644 --- a/src/main/java/com/google/android/apps/mytracks/stats/ExtremityMonitor.java +++ b/src/main/java/com/google/android/apps/mytracks/stats/ExtremityMonitor.java @@ -1,12 +1,12 @@ /* * Copyright 2009 Google Inc. - * + * * 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 @@ -20,101 +20,101 @@ import androidx.annotation.NonNull; /** * A helper class that tracks a minimum and a maximum of a variable. - * + * * @author Sandor Dornbush */ public class ExtremityMonitor { - // The smallest value seen so far. - private double min; + // The smallest value seen so far. + private double min; - // The largest value seen so far. - private double max; + // The largest value seen so far. + private double max; - public ExtremityMonitor() { - reset(); - } - - /** - * Resets this object to it's initial state where the min and max are unknown. - */ - public void reset() { - min = Double.POSITIVE_INFINITY; - max = Double.NEGATIVE_INFINITY; - } - - /** - * Gets the minimum value seen. - */ - public double getMin() { - return min; - } - - /** - * Gets the maximum value seen. - */ - public double getMax() { - return max; - } - - /** - * Updates the min and the max with a new value. - * - * @param value the new value - * @return true if an extremity was found - */ - public boolean update(double value) { - boolean changed = false; - if (value < min) { - min = value; - changed = true; + public ExtremityMonitor() { + reset(); } - if (value > max) { - max = value; - changed = true; + + /** + * Resets this object to it's initial state where the min and max are unknown. + */ + public void reset() { + min = Double.POSITIVE_INFINITY; + max = Double.NEGATIVE_INFINITY; } - return changed; - } - /** - * Sets the minimum and maximum values. - * - * @param min the minimum value - * @param max the maximum value - */ - public void set(double min, double max) { - this.min = min; - this.max = max; - } + /** + * Gets the minimum value seen. + */ + public double getMin() { + return min; + } - /** - * Sets the minimum value. - * - * @param min the minimum value - */ - public void setMin(double min) { - this.min = min; - } + /** + * Sets the minimum value. + * + * @param min the minimum value + */ + public void setMin(double min) { + this.min = min; + } - /** - * Sets the maximum value. - * - * @param max the maximum value - */ - public void setMax(double max) { - this.max = max; - } + /** + * Gets the maximum value seen. + */ + public double getMax() { + return max; + } - /** - * Returns true if has data. - */ - public boolean hasData() { - return min != Double.POSITIVE_INFINITY && max != Double.NEGATIVE_INFINITY; - } + /** + * Sets the maximum value. + * + * @param max the maximum value + */ + public void setMax(double max) { + this.max = max; + } - @NonNull - @Override - public String toString() { - return "Min: " + min + " Max: " + max; - } + /** + * Updates the min and the max with a new value. + * + * @param value the new value + * @return true if an extremity was found + */ + public boolean update(double value) { + boolean changed = false; + if (value < min) { + min = value; + changed = true; + } + if (value > max) { + max = value; + changed = true; + } + return changed; + } + + /** + * Sets the minimum and maximum values. + * + * @param min the minimum value + * @param max the maximum value + */ + public void set(double min, double max) { + this.min = min; + this.max = max; + } + + /** + * Returns true if has data. + */ + public boolean hasData() { + return min != Double.POSITIVE_INFINITY && max != Double.NEGATIVE_INFINITY; + } + + @NonNull + @Override + public String toString() { + return "Min: " + min + " Max: " + max; + } } diff --git a/src/main/java/com/google/android/apps/mytracks/stats/TripStatistics.java b/src/main/java/com/google/android/apps/mytracks/stats/TripStatistics.java index 5354cfe14..cd58ed4bf 100644 --- a/src/main/java/com/google/android/apps/mytracks/stats/TripStatistics.java +++ b/src/main/java/com/google/android/apps/mytracks/stats/TripStatistics.java @@ -26,541 +26,531 @@ import androidx.annotation.NonNull; * TripStatisticsBuilder. *

* TODO: hashCode and equals - * + * * @author Rodrigo Damazio */ public class TripStatistics implements Parcelable { - // The trip start time. This is the system time, might not match the GPs time. - private long startTime = -1L; + /** + * Creator of {@link TripStatistics} from parcels. + */ + public static final Creator CREATOR = new Creator(); + // The min and max latitude seen in this trip. + private final ExtremityMonitor latitudeExtremities = new ExtremityMonitor(); + // The min and max longitude seen in this trip. + private final ExtremityMonitor longitudeExtremities = new ExtremityMonitor(); + // The min and max elevation (meters) seen on this trip. + private final ExtremityMonitor elevationExtremities = new ExtremityMonitor(); + // The min and max grade seen on this trip. + private final ExtremityMonitor gradeExtremities = new ExtremityMonitor(); + // The trip start time. This is the system time, might not match the GPs time. + private long startTime = -1L; + // The trip stop time. This is the system time, might not match the GPS time. + private long stopTime = -1L; + // The total trip distance (meters). + private double totalDistance; + // The total time (ms). Updated when new points are received, may be stale. + private long totalTime; + // The total moving time (ms). Based on when we believe the user is traveling. + private long movingTime; + // The maximum speed (meters/second) that we believe is valid. + private double maxSpeed; + // The total elevation gained (meters). + private double totalElevationGain; - // The trip stop time. This is the system time, might not match the GPS time. - private long stopTime = -1L; - - // The total trip distance (meters). - private double totalDistance; - - // The total time (ms). Updated when new points are received, may be stale. - private long totalTime; - - // The total moving time (ms). Based on when we believe the user is traveling. - private long movingTime; - - // The min and max latitude seen in this trip. - private final ExtremityMonitor latitudeExtremities = new ExtremityMonitor(); - - // The min and max longitude seen in this trip. - private final ExtremityMonitor longitudeExtremities = new ExtremityMonitor(); - - // The maximum speed (meters/second) that we believe is valid. - private double maxSpeed; - - // The min and max elevation (meters) seen on this trip. - private final ExtremityMonitor elevationExtremities = new ExtremityMonitor(); - - // The total elevation gained (meters). - private double totalElevationGain; - - // The min and max grade seen on this trip. - private final ExtremityMonitor gradeExtremities = new ExtremityMonitor(); - - /** - * Default constructor. - */ - public TripStatistics() {} - - /** - * Copy constructor. - * - * @param other another statistics data object to copy from - */ - public TripStatistics(TripStatistics other) { - startTime = other.startTime; - stopTime = other.stopTime; - totalDistance = other.totalDistance; - totalTime = other.totalTime; - movingTime = other.movingTime; - latitudeExtremities.set(other.latitudeExtremities.getMin(), other.latitudeExtremities.getMax()); - longitudeExtremities.set(other.longitudeExtremities.getMin(), other.longitudeExtremities.getMax()); - maxSpeed = other.maxSpeed; - elevationExtremities.set(other.elevationExtremities.getMin(), other.elevationExtremities.getMax()); - totalElevationGain = other.totalElevationGain; - gradeExtremities.set(other.gradeExtremities.getMin(), other.gradeExtremities.getMax()); - } - - /** - * Combines these statistics with those from another object. This assumes that - * the time periods covered by each do not intersect. - * - * @param other another statistics data object - */ - public void merge(TripStatistics other) { - startTime = Math.min(startTime, other.startTime); - stopTime = Math.max(stopTime, other.stopTime); - totalDistance += other.totalDistance; - totalTime += other.totalTime; - movingTime += other.movingTime; - if (other.latitudeExtremities.hasData()) { - latitudeExtremities.update(other.latitudeExtremities.getMin()); - latitudeExtremities.update(other.latitudeExtremities.getMax()); + /** + * Default constructor. + */ + public TripStatistics() { } - if (other.longitudeExtremities.hasData()) { - longitudeExtremities.update(other.longitudeExtremities.getMin()); - longitudeExtremities.update(other.longitudeExtremities.getMax()); + + /** + * Copy constructor. + * + * @param other another statistics data object to copy from + */ + public TripStatistics(TripStatistics other) { + startTime = other.startTime; + stopTime = other.stopTime; + totalDistance = other.totalDistance; + totalTime = other.totalTime; + movingTime = other.movingTime; + latitudeExtremities.set(other.latitudeExtremities.getMin(), other.latitudeExtremities.getMax()); + longitudeExtremities.set(other.longitudeExtremities.getMin(), other.longitudeExtremities.getMax()); + maxSpeed = other.maxSpeed; + elevationExtremities.set(other.elevationExtremities.getMin(), other.elevationExtremities.getMax()); + totalElevationGain = other.totalElevationGain; + gradeExtremities.set(other.gradeExtremities.getMin(), other.gradeExtremities.getMax()); } - maxSpeed = Math.max(maxSpeed, other.maxSpeed); - if (other.elevationExtremities.hasData()) { - elevationExtremities.update(other.elevationExtremities.getMin()); - elevationExtremities.update(other.elevationExtremities.getMax()); + + /** + * Combines these statistics with those from another object. This assumes that + * the time periods covered by each do not intersect. + * + * @param other another statistics data object + */ + public void merge(TripStatistics other) { + startTime = Math.min(startTime, other.startTime); + stopTime = Math.max(stopTime, other.stopTime); + totalDistance += other.totalDistance; + totalTime += other.totalTime; + movingTime += other.movingTime; + if (other.latitudeExtremities.hasData()) { + latitudeExtremities.update(other.latitudeExtremities.getMin()); + latitudeExtremities.update(other.latitudeExtremities.getMax()); + } + if (other.longitudeExtremities.hasData()) { + longitudeExtremities.update(other.longitudeExtremities.getMin()); + longitudeExtremities.update(other.longitudeExtremities.getMax()); + } + maxSpeed = Math.max(maxSpeed, other.maxSpeed); + if (other.elevationExtremities.hasData()) { + elevationExtremities.update(other.elevationExtremities.getMin()); + elevationExtremities.update(other.elevationExtremities.getMax()); + } + totalElevationGain += other.totalElevationGain; + if (other.gradeExtremities.hasData()) { + gradeExtremities.update(other.gradeExtremities.getMin()); + gradeExtremities.update(other.gradeExtremities.getMax()); + } } - totalElevationGain += other.totalElevationGain; - if (other.gradeExtremities.hasData()) { - gradeExtremities.update(other.gradeExtremities.getMin()); - gradeExtremities.update(other.gradeExtremities.getMax()); + + /** + * Gets the trip start time. The number of milliseconds since epoch. + */ + public long getStartTime() { + return startTime; } - } - /** - * Gets the trip start time. The number of milliseconds since epoch. - */ - public long getStartTime() { - return startTime; - } - - /** - * Sets the trip start time. - * - * @param startTime the trip start time in milliseconds since the epoch - */ - public void setStartTime(long startTime) { - this.startTime = startTime; - } - - /** - * Gets the trip stop time. The number of milliseconds since epoch. - */ - public long getStopTime() { - return stopTime; - } - - /** - * Sets the trip stop time. - * - * @param stopTime the stop time in milliseconds since the epoch - */ - public void setStopTime(long stopTime) { - this.stopTime = stopTime; - } - - /** - * Gets the total distance the user traveled in meters. - */ - public double getTotalDistance() { - return totalDistance; - } - - /** - * Sets the total trip distance. - * - * @param totalDistance the trip distance in meters - */ - public void setTotalDistance(double totalDistance) { - this.totalDistance = totalDistance; - } - - /** - * Adds to the current total distance. - * - * @param distance the distance to add in meters - */ - public void addTotalDistance(double distance) { - totalDistance += distance; - } - - /** - * Gets the total time in milliseconds that this track has been active. This - * statistic is only updated when a new point is added to the statistics, so - * it may be off. If you need to calculate the proper total time, use - * {@link #getStartTime} with the current time. - */ - public long getTotalTime() { - return totalTime; - } - - /** - * Sets the trip total time. - * - * @param totalTime the trip total time in milliseconds - */ - public void setTotalTime(long totalTime) { - this.totalTime = totalTime; - } - - /** - * Gets the moving time in milliseconds. - */ - public long getMovingTime() { - return movingTime; - } - - /** - * Sets the trip total moving time. - * - * @param movingTime the trip total moving time in milliseconds - */ - public void setMovingTime(long movingTime) { - this.movingTime = movingTime; - } - - /** - * Adds to the trip total moving time. - * - * @param time the time in milliseconds - */ - public void addMovingTime(long time) { - movingTime += time; - } - - /** - * Gets the topmost position (highest latitude) of the track, in signed - * degrees. - */ - public double getTopDegrees() { - return latitudeExtremities.getMax(); - } - - /** - * Gets the topmost position (highest latitude) of the track, in signed - * millions of degrees. - */ - public int getTop() { - return (int) (latitudeExtremities.getMax() * 1E6); - } - - /** - * Gets the bottommost position (lowest latitude) of the track, in signed - * degrees. - */ - public double getBottomDegrees() { - return latitudeExtremities.getMin(); - } - - /** - * Gets the bottommost position (lowest latitude) of the track, in signed - * millions of degrees. - */ - public int getBottom() { - return (int) (latitudeExtremities.getMin() * 1E6); - } - - /** - * Gets the leftmost position (lowest longitude) of the track, in signed - * degrees. - */ - public double getLeftDegrees() { - return longitudeExtremities.getMin(); - } - - /** - * Gets the leftmost position (lowest longitude) of the track, in signed - * millions of degrees. - */ - public int getLeft() { - return (int) (longitudeExtremities.getMin() * 1E6); - } - - /** - * Gets the rightmost position (highest longitude) of the track, in signed - * degrees. - */ - public double getRightDegrees() { - return longitudeExtremities.getMax(); - } - - /** - * Gets the rightmost position (highest longitude) of the track, in signed - * millions of degrees. - */ - public int getRight() { - return (int) (longitudeExtremities.getMax() * 1E6); - } - - /** - * Gets the mean latitude position of the track, in signed degrees. - */ - public double getMeanLatitude() { - return (getBottomDegrees() + getTopDegrees()) / 2.0; - } - - /** - * Gets the mean longitude position of the track, in signed degrees. - */ - public double getMeanLongitude() { - return (getLeftDegrees() + getRightDegrees()) / 2.0; - } - - /** - * Sets the bounding box for this trip. The unit for all parameters is signed - * millions of degree (degrees * 1E6). - * - * @param leftE6 the leftmost longitude reached - * @param topE6 the topmost latitude reached - * @param rightE6 the rightmost longitude reached - * @param bottomE6 the bottommost latitude reached - */ - public void setBounds(int leftE6, int topE6, int rightE6, int bottomE6) { - latitudeExtremities.set(bottomE6 / 1E6, topE6 / 1E6); - longitudeExtremities.set(leftE6 / 1E6, rightE6 / 1E6); - } - - /** - * Updates a new latitude value. - * - * @param latitude the latitude value in signed decimal degrees - */ - public void updateLatitudeExtremities(double latitude) { - latitudeExtremities.update(latitude); - } - - /** - * Updates a new longitude value. - * - * @param longitude the longitude value in signed decimal degrees - */ - public void updateLongitudeExtremities(double longitude) { - longitudeExtremities.update(longitude); - } - - /** - * Gets the average speed in meters/second. This calculation only takes into - * account the displacement until the last point that was accounted for in - * statistics. - */ - public double getAverageSpeed() { - if (totalTime == 0L) { - return 0.0; + /** + * Sets the trip start time. + * + * @param startTime the trip start time in milliseconds since the epoch + */ + public void setStartTime(long startTime) { + this.startTime = startTime; } - return totalDistance / ((double) totalTime / 1000.0); - } - /** - * Gets the average moving speed in meters/second. - */ - public double getAverageMovingSpeed() { - if (movingTime == 0L) { - return 0.0; + /** + * Gets the trip stop time. The number of milliseconds since epoch. + */ + public long getStopTime() { + return stopTime; } - return totalDistance / ((double) movingTime / 1000.0); - } - /** - * Gets the maximum speed in meters/second. - */ - public double getMaxSpeed() { - return Math.max(maxSpeed, getAverageMovingSpeed()); - } + /** + * Sets the trip stop time. + * + * @param stopTime the stop time in milliseconds since the epoch + */ + public void setStopTime(long stopTime) { + this.stopTime = stopTime; + } - /** - * Sets the maximum speed. - * - * @param maxSpeed the maximum speed in meters/second - */ - public void setMaxSpeed(double maxSpeed) { - this.maxSpeed = maxSpeed; - } + /** + * Gets the total distance the user traveled in meters. + */ + public double getTotalDistance() { + return totalDistance; + } - /** - * Gets the minimum elevation. This is calculated from the smoothed elevation - * so this can actually be more than the current elevation. - */ - public double getMinElevation() { - return elevationExtremities.getMin(); - } + /** + * Sets the total trip distance. + * + * @param totalDistance the trip distance in meters + */ + public void setTotalDistance(double totalDistance) { + this.totalDistance = totalDistance; + } - /** - * Sets the minimum elevation. - * - * @param elevation the minimum elevation in meters - */ - public void setMinElevation(double elevation) { - elevationExtremities.setMin(elevation); - } + /** + * Adds to the current total distance. + * + * @param distance the distance to add in meters + */ + public void addTotalDistance(double distance) { + totalDistance += distance; + } - /** - * Gets the maximum elevation. This is calculated from the smoothed elevation - * so this can actually be less than the current elevation. - */ - public double getMaxElevation() { - return elevationExtremities.getMax(); - } + /** + * Gets the total time in milliseconds that this track has been active. This + * statistic is only updated when a new point is added to the statistics, so + * it may be off. If you need to calculate the proper total time, use + * {@link #getStartTime} with the current time. + */ + public long getTotalTime() { + return totalTime; + } - /** - * Sets the maximum elevation. - * - * @param elevation the maximum elevation in meters - */ - public void setMaxElevation(double elevation) { - elevationExtremities.setMax(elevation); - } + /** + * Sets the trip total time. + * + * @param totalTime the trip total time in milliseconds + */ + public void setTotalTime(long totalTime) { + this.totalTime = totalTime; + } - /** - * Updates a new elevation. - * - * @param elevation the elevation value in meters - */ - public void updateElevationExtremities(double elevation) { - elevationExtremities.update(elevation); - } + /** + * Gets the moving time in milliseconds. + */ + public long getMovingTime() { + return movingTime; + } - /** - * Gets the total elevation gain in meters. This is calculated as the sum of - * all positive differences in the smoothed elevation. - */ - public double getTotalElevationGain() { - return totalElevationGain; - } + /** + * Sets the trip total moving time. + * + * @param movingTime the trip total moving time in milliseconds + */ + public void setMovingTime(long movingTime) { + this.movingTime = movingTime; + } - /** - * Sets the total elevation gain. - * - * @param totalElevationGain the elevation gain in meters - */ - public void setTotalElevationGain(double totalElevationGain) { - this.totalElevationGain = totalElevationGain; - } + /** + * Adds to the trip total moving time. + * + * @param time the time in milliseconds + */ + public void addMovingTime(long time) { + movingTime += time; + } - /** - * Adds to the total elevation gain. - * - * @param gain the elevation gain in meters - */ - public void addTotalElevationGain(double gain) { - totalElevationGain += gain; - } + /** + * Gets the topmost position (highest latitude) of the track, in signed + * degrees. + */ + public double getTopDegrees() { + return latitudeExtremities.getMax(); + } - /** - * Gets the minimum grade for this trip. - */ - public double getMinGrade() { - return gradeExtremities.getMin(); - } + /** + * Gets the topmost position (highest latitude) of the track, in signed + * millions of degrees. + */ + public int getTop() { + return (int) (latitudeExtremities.getMax() * 1E6); + } - /** - * Sets the maximum grade. - * - * @param grade the grade as a fraction (1.0 would mean vertical upwards) - */ - public void setMaxGrade(double grade) { - gradeExtremities.setMax(grade); - } + /** + * Gets the bottommost position (lowest latitude) of the track, in signed + * degrees. + */ + public double getBottomDegrees() { + return latitudeExtremities.getMin(); + } - /** - * Gets the maximum grade for this trip. - */ - public double getMaxGrade() { - return gradeExtremities.getMax(); - } + /** + * Gets the bottommost position (lowest latitude) of the track, in signed + * millions of degrees. + */ + public int getBottom() { + return (int) (latitudeExtremities.getMin() * 1E6); + } - /** - * Sets the minimum grade. - * - * @param grade the grade as a fraction (-1.0 would mean vertical downwards) - */ - public void setMinGrade(double grade) { - gradeExtremities.setMin(grade); - } + /** + * Gets the leftmost position (lowest longitude) of the track, in signed + * degrees. + */ + public double getLeftDegrees() { + return longitudeExtremities.getMin(); + } - /** - * Updates a new grade value. - * - * @param grade the grade value as a fraction - */ - public void updateGradeExtremities(double grade) { - gradeExtremities.update(grade); - } + /** + * Gets the leftmost position (lowest longitude) of the track, in signed + * millions of degrees. + */ + public int getLeft() { + return (int) (longitudeExtremities.getMin() * 1E6); + } - @NonNull - @Override - public String toString() { - return "TripStatistics { Start Time: " + getStartTime() + "; Stop Time: " + getStopTime() - + "; Total Distance: " + getTotalDistance() + "; Total Time: " + getTotalTime() - + "; Moving Time: " + getMovingTime() + "; Min Latitude: " + getBottomDegrees() - + "; Max Latitude: " + getTopDegrees() + "; Min Longitude: " + getLeftDegrees() - + "; Max Longitude: " + getRightDegrees() + "; Max Speed: " + getMaxSpeed() - + "; Min Elevation: " + getMinElevation() + "; Max Elevation: " + getMaxElevation() - + "; Elevation Gain: " + getTotalElevationGain() + "; Min Grade: " + getMinGrade() - + "; Max Grade: " + getMaxGrade() + "}"; - } + /** + * Gets the rightmost position (highest longitude) of the track, in signed + * degrees. + */ + public double getRightDegrees() { + return longitudeExtremities.getMax(); + } - /** - * Creator of statistics data from parcels. - */ - public static class Creator implements Parcelable.Creator { + /** + * Gets the rightmost position (highest longitude) of the track, in signed + * millions of degrees. + */ + public int getRight() { + return (int) (longitudeExtremities.getMax() * 1E6); + } + /** + * Gets the mean latitude position of the track, in signed degrees. + */ + public double getMeanLatitude() { + return (getBottomDegrees() + getTopDegrees()) / 2.0; + } + + /** + * Gets the mean longitude position of the track, in signed degrees. + */ + public double getMeanLongitude() { + return (getLeftDegrees() + getRightDegrees()) / 2.0; + } + + /** + * Sets the bounding box for this trip. The unit for all parameters is signed + * millions of degree (degrees * 1E6). + * + * @param leftE6 the leftmost longitude reached + * @param topE6 the topmost latitude reached + * @param rightE6 the rightmost longitude reached + * @param bottomE6 the bottommost latitude reached + */ + public void setBounds(int leftE6, int topE6, int rightE6, int bottomE6) { + latitudeExtremities.set(bottomE6 / 1E6, topE6 / 1E6); + longitudeExtremities.set(leftE6 / 1E6, rightE6 / 1E6); + } + + /** + * Updates a new latitude value. + * + * @param latitude the latitude value in signed decimal degrees + */ + public void updateLatitudeExtremities(double latitude) { + latitudeExtremities.update(latitude); + } + + /** + * Updates a new longitude value. + * + * @param longitude the longitude value in signed decimal degrees + */ + public void updateLongitudeExtremities(double longitude) { + longitudeExtremities.update(longitude); + } + + /** + * Gets the average speed in meters/second. This calculation only takes into + * account the displacement until the last point that was accounted for in + * statistics. + */ + public double getAverageSpeed() { + if (totalTime == 0L) { + return 0.0; + } + return totalDistance / ((double) totalTime / 1000.0); + } + + /** + * Gets the average moving speed in meters/second. + */ + public double getAverageMovingSpeed() { + if (movingTime == 0L) { + return 0.0; + } + return totalDistance / ((double) movingTime / 1000.0); + } + + /** + * Gets the maximum speed in meters/second. + */ + public double getMaxSpeed() { + return Math.max(maxSpeed, getAverageMovingSpeed()); + } + + /** + * Sets the maximum speed. + * + * @param maxSpeed the maximum speed in meters/second + */ + public void setMaxSpeed(double maxSpeed) { + this.maxSpeed = maxSpeed; + } + + /** + * Gets the minimum elevation. This is calculated from the smoothed elevation + * so this can actually be more than the current elevation. + */ + public double getMinElevation() { + return elevationExtremities.getMin(); + } + + /** + * Sets the minimum elevation. + * + * @param elevation the minimum elevation in meters + */ + public void setMinElevation(double elevation) { + elevationExtremities.setMin(elevation); + } + + /** + * Gets the maximum elevation. This is calculated from the smoothed elevation + * so this can actually be less than the current elevation. + */ + public double getMaxElevation() { + return elevationExtremities.getMax(); + } + + /** + * Sets the maximum elevation. + * + * @param elevation the maximum elevation in meters + */ + public void setMaxElevation(double elevation) { + elevationExtremities.setMax(elevation); + } + + /** + * Updates a new elevation. + * + * @param elevation the elevation value in meters + */ + public void updateElevationExtremities(double elevation) { + elevationExtremities.update(elevation); + } + + /** + * Gets the total elevation gain in meters. This is calculated as the sum of + * all positive differences in the smoothed elevation. + */ + public double getTotalElevationGain() { + return totalElevationGain; + } + + /** + * Sets the total elevation gain. + * + * @param totalElevationGain the elevation gain in meters + */ + public void setTotalElevationGain(double totalElevationGain) { + this.totalElevationGain = totalElevationGain; + } + + /** + * Adds to the total elevation gain. + * + * @param gain the elevation gain in meters + */ + public void addTotalElevationGain(double gain) { + totalElevationGain += gain; + } + + /** + * Gets the minimum grade for this trip. + */ + public double getMinGrade() { + return gradeExtremities.getMin(); + } + + /** + * Sets the minimum grade. + * + * @param grade the grade as a fraction (-1.0 would mean vertical downwards) + */ + public void setMinGrade(double grade) { + gradeExtremities.setMin(grade); + } + + /** + * Gets the maximum grade for this trip. + */ + public double getMaxGrade() { + return gradeExtremities.getMax(); + } + + /** + * Sets the maximum grade. + * + * @param grade the grade as a fraction (1.0 would mean vertical upwards) + */ + public void setMaxGrade(double grade) { + gradeExtremities.setMax(grade); + } + + /** + * Updates a new grade value. + * + * @param grade the grade value as a fraction + */ + public void updateGradeExtremities(double grade) { + gradeExtremities.update(grade); + } + + @NonNull @Override - public TripStatistics createFromParcel(Parcel source) { - TripStatistics data = new TripStatistics(); - - data.startTime = source.readLong(); - data.stopTime = source.readLong(); - data.totalDistance = source.readDouble(); - data.totalTime = source.readLong(); - data.movingTime = source.readLong(); - - double minLat = source.readDouble(); - double maxLat = source.readDouble(); - data.latitudeExtremities.set(minLat, maxLat); - - double minLong = source.readDouble(); - double maxLong = source.readDouble(); - data.longitudeExtremities.set(minLong, maxLong); - - data.maxSpeed = source.readDouble(); - - double minElev = source.readDouble(); - double maxElev = source.readDouble(); - data.elevationExtremities.set(minElev, maxElev); - data.totalElevationGain = source.readDouble(); - - double minGrade = source.readDouble(); - double maxGrade = source.readDouble(); - data.gradeExtremities.set(minGrade, maxGrade); - - return data; + public String toString() { + return "TripStatistics { Start Time: " + getStartTime() + "; Stop Time: " + getStopTime() + + "; Total Distance: " + getTotalDistance() + "; Total Time: " + getTotalTime() + + "; Moving Time: " + getMovingTime() + "; Min Latitude: " + getBottomDegrees() + + "; Max Latitude: " + getTopDegrees() + "; Min Longitude: " + getLeftDegrees() + + "; Max Longitude: " + getRightDegrees() + "; Max Speed: " + getMaxSpeed() + + "; Min Elevation: " + getMinElevation() + "; Max Elevation: " + getMaxElevation() + + "; Elevation Gain: " + getTotalElevationGain() + "; Min Grade: " + getMinGrade() + + "; Max Grade: " + getMaxGrade() + "}"; } @Override - public TripStatistics[] newArray(int size) { - return new TripStatistics[size]; + public int describeContents() { + return 0; } - } - /** - * Creator of {@link TripStatistics} from parcels. - */ - public static final Creator CREATOR = new Creator(); + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(startTime); + dest.writeLong(stopTime); + dest.writeDouble(totalDistance); + dest.writeLong(totalTime); + dest.writeLong(movingTime); + dest.writeDouble(latitudeExtremities.getMin()); + dest.writeDouble(latitudeExtremities.getMax()); + dest.writeDouble(longitudeExtremities.getMin()); + dest.writeDouble(longitudeExtremities.getMax()); + dest.writeDouble(maxSpeed); + dest.writeDouble(elevationExtremities.getMin()); + dest.writeDouble(elevationExtremities.getMax()); + dest.writeDouble(totalElevationGain); + dest.writeDouble(gradeExtremities.getMin()); + dest.writeDouble(gradeExtremities.getMax()); + } - @Override - public int describeContents() { - return 0; - } + /** + * Creator of statistics data from parcels. + */ + public static class Creator implements Parcelable.Creator { - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeLong(startTime); - dest.writeLong(stopTime); - dest.writeDouble(totalDistance); - dest.writeLong(totalTime); - dest.writeLong(movingTime); - dest.writeDouble(latitudeExtremities.getMin()); - dest.writeDouble(latitudeExtremities.getMax()); - dest.writeDouble(longitudeExtremities.getMin()); - dest.writeDouble(longitudeExtremities.getMax()); - dest.writeDouble(maxSpeed); - dest.writeDouble(elevationExtremities.getMin()); - dest.writeDouble(elevationExtremities.getMax()); - dest.writeDouble(totalElevationGain); - dest.writeDouble(gradeExtremities.getMin()); - dest.writeDouble(gradeExtremities.getMax()); - } + @Override + public TripStatistics createFromParcel(Parcel source) { + TripStatistics data = new TripStatistics(); + + data.startTime = source.readLong(); + data.stopTime = source.readLong(); + data.totalDistance = source.readDouble(); + data.totalTime = source.readLong(); + data.movingTime = source.readLong(); + + double minLat = source.readDouble(); + double maxLat = source.readDouble(); + data.latitudeExtremities.set(minLat, maxLat); + + double minLong = source.readDouble(); + double maxLong = source.readDouble(); + data.longitudeExtremities.set(minLong, maxLong); + + data.maxSpeed = source.readDouble(); + + double minElev = source.readDouble(); + double maxElev = source.readDouble(); + data.elevationExtremities.set(minElev, maxElev); + data.totalElevationGain = source.readDouble(); + + double minGrade = source.readDouble(); + double maxGrade = source.readDouble(); + data.gradeExtremities.set(minGrade, maxGrade); + + return data; + } + + @Override + public TripStatistics[] newArray(int size) { + return new TripStatistics[size]; + } + } } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/stats/TripStatisticsUpdater.java b/src/main/java/com/google/android/apps/mytracks/stats/TripStatisticsUpdater.java index 7fa2475ff..accca7a0c 100644 --- a/src/main/java/com/google/android/apps/mytracks/stats/TripStatisticsUpdater.java +++ b/src/main/java/com/google/android/apps/mytracks/stats/TripStatisticsUpdater.java @@ -1,12 +1,12 @@ /* * Copyright 2009 Google Inc. - * + * * 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 @@ -30,299 +30,288 @@ import static com.google.android.apps.mytracks.services.TrackRecordingService.PA * Updater for {@link TripStatistics}. For updating track trip statistics as new * locations are added. Note that some of the locations represent pause/resume * separator. - * + * * @author Sandor Dornbush * @author Rodrigo Damazio */ public class TripStatisticsUpdater { - private static final String TAG = TripStatisticsUpdater.class.getSimpleName(); - - /** - * The number of elevation readings to smooth to get a somewhat accurate - * signal. - */ - @VisibleForTesting - static final int ELEVATION_SMOOTHING_FACTOR = 25; + /** + * The number of grade readings to smooth to get a somewhat accurate signal. + */ + public static final int GRADE_SMOOTHING_FACTOR = 5; + /** + * The number of elevation readings to smooth to get a somewhat accurate + * signal. + */ + @VisibleForTesting + static final int ELEVATION_SMOOTHING_FACTOR = 25; - /** - * The number of run readings to smooth for calculating grade. - */ - @VisibleForTesting - static final int RUN_SMOOTHING_FACTOR = 25; + /** + * The number of run readings to smooth for calculating grade. + */ + @VisibleForTesting + static final int RUN_SMOOTHING_FACTOR = 25; + /** + * The number of speed reading to smooth to get a somewhat accurate signal. + */ + @VisibleForTesting + static final int SPEED_SMOOTHING_FACTOR = 25; + private static final String TAG = TripStatisticsUpdater.class.getSimpleName(); + /** + * Ignore any acceleration faster than this. Will ignore any speeds that imply + * acceleration greater than 2g's 2g = 19.6 m/s^2 = 0.0002 m/ms^2 = 0.02 + * m/(m*ms) + */ + private static final double MAX_ACCELERATION = 0.02; - /** - * The number of grade readings to smooth to get a somewhat accurate signal. - */ - public static final int GRADE_SMOOTHING_FACTOR = 5; + // The track's trip statistics + private final TripStatistics tripStatistics; + // A buffer of the recent elevation readings (m) + private final DoubleBuffer elevationBuffer = new DoubleBuffer(ELEVATION_SMOOTHING_FACTOR); + // A buffer of the recent run readings (m) for calculating grade + private final DoubleBuffer runBuffer = new DoubleBuffer(RUN_SMOOTHING_FACTOR); + // A buffer of the recent grade calculations (%) + private final DoubleBuffer gradeBuffer = new DoubleBuffer(GRADE_SMOOTHING_FACTOR); + // A buffer of the recent speed readings (m/s) for calculating max speed + private final DoubleBuffer speedBuffer = new DoubleBuffer(SPEED_SMOOTHING_FACTOR); + // The current segment's trip statistics + private TripStatistics currentSegment; + // Current segment's last location. + private Location lastLocation; + // Current segment's last moving location + private Location lastMovingLocation; - /** - * The number of speed reading to smooth to get a somewhat accurate signal. - */ - @VisibleForTesting - static final int SPEED_SMOOTHING_FACTOR = 25; + /** + * Creates a new trip statistics updater. + * + * @param startTime the start time + */ + public TripStatisticsUpdater(long startTime) { + tripStatistics = init(startTime); + currentSegment = init(startTime); + } - /** - * Ignore any acceleration faster than this. Will ignore any speeds that imply - * acceleration greater than 2g's 2g = 19.6 m/s^2 = 0.0002 m/ms^2 = 0.02 - * m/(m*ms) - */ - private static final double MAX_ACCELERATION = 0.02; + public void updateTime(long time) { + currentSegment.setStopTime(time); + currentSegment.setTotalTime(time - currentSegment.getStartTime()); + } - // The track's trip statistics - private final TripStatistics tripStatistics; + /** + * Gets the track's trip statistics. + */ + public TripStatistics getTripStatistics() { + // Take a snapshot - we don't want anyone messing with our tripStatistics + TripStatistics stats = new TripStatistics(tripStatistics); + stats.merge(currentSegment); + return stats; + } - // The current segment's trip statistics - private TripStatistics currentSegment; - - // Current segment's last location. - private Location lastLocation; - - // Current segment's last moving location - private Location lastMovingLocation; - - // A buffer of the recent elevation readings (m) - private final DoubleBuffer elevationBuffer = new DoubleBuffer(ELEVATION_SMOOTHING_FACTOR); - - // A buffer of the recent run readings (m) for calculating grade - private final DoubleBuffer runBuffer = new DoubleBuffer(RUN_SMOOTHING_FACTOR); - - // A buffer of the recent grade calculations (%) - private final DoubleBuffer gradeBuffer = new DoubleBuffer(GRADE_SMOOTHING_FACTOR); - - // A buffer of the recent speed readings (m/s) for calculating max speed - private final DoubleBuffer speedBuffer = new DoubleBuffer(SPEED_SMOOTHING_FACTOR); - - /** - * Creates a new trip statistics updater. - * - * @param startTime the start time - */ - public TripStatisticsUpdater(long startTime) { - tripStatistics = init(startTime); - currentSegment = init(startTime); - } - - public void updateTime(long time) { - currentSegment.setStopTime(time); - currentSegment.setTotalTime(time - currentSegment.getStartTime()); - } - - /** - * Gets the track's trip statistics. - */ - public TripStatistics getTripStatistics() { - // Take a snapshot - we don't want anyone messing with our tripStatistics - TripStatistics stats = new TripStatistics(tripStatistics); - stats.merge(currentSegment); - return stats; - } - - /** - * Adds a location. TODO: This assume location has a valid time. - * - * @param location the location - * @param minRecordingDistance the min recording distance - */ - public void addLocation(Location location, int minRecordingDistance) { - // Always update time - updateTime(location.getTime()); - if (!LocationUtils.isValidLocation(location)) { - // Either pause or resume marker - if (location.getLatitude() == PAUSE_LATITUDE) { - if (lastLocation != null && lastMovingLocation != null - && lastLocation != lastMovingLocation) { - currentSegment.addTotalDistance(lastMovingLocation.distanceTo(lastLocation)); + /** + * Adds a location. TODO: This assume location has a valid time. + * + * @param location the location + * @param minRecordingDistance the min recording distance + */ + public void addLocation(Location location, int minRecordingDistance) { + // Always update time + updateTime(location.getTime()); + if (!LocationUtils.isValidLocation(location)) { + // Either pause or resume marker + if (location.getLatitude() == PAUSE_LATITUDE) { + if (lastLocation != null && lastMovingLocation != null + && lastLocation != lastMovingLocation) { + currentSegment.addTotalDistance(lastMovingLocation.distanceTo(lastLocation)); + } + tripStatistics.merge(currentSegment); + } + currentSegment = init(location.getTime()); + lastLocation = null; + lastMovingLocation = null; + elevationBuffer.reset(); + runBuffer.reset(); + gradeBuffer.reset(); + speedBuffer.reset(); + return; } - tripStatistics.merge(currentSegment); - } - currentSegment = init(location.getTime()); - lastLocation = null; - lastMovingLocation = null; - elevationBuffer.reset(); - runBuffer.reset(); - gradeBuffer.reset(); - speedBuffer.reset(); - return; - } - currentSegment.updateLatitudeExtremities(location.getLatitude()); - currentSegment.updateLongitudeExtremities(location.getLongitude()); + currentSegment.updateLatitudeExtremities(location.getLatitude()); + currentSegment.updateLongitudeExtremities(location.getLongitude()); - double elevationDifference = location.hasAltitude() ? updateElevation(location.getAltitude()) - : 0.0; + double elevationDifference = location.hasAltitude() ? updateElevation(location.getAltitude()) + : 0.0; - if (lastLocation == null || lastMovingLocation == null) { - lastLocation = location; - lastMovingLocation = location; - return; + if (lastLocation == null || lastMovingLocation == null) { + lastLocation = location; + lastMovingLocation = location; + return; + } + + double movingDistance = lastMovingLocation.distanceTo(location); + if (movingDistance < minRecordingDistance + && (!location.hasSpeed() || location.getSpeed() < MAX_NO_MOVEMENT_SPEED)) { + speedBuffer.reset(); + lastLocation = location; + return; + } + long movingTime = location.getTime() - lastLocation.getTime(); + if (movingTime < 0) { + lastLocation = location; + return; + } + + // Update total distance + currentSegment.addTotalDistance(movingDistance); + + // Update moving time + currentSegment.addMovingTime(movingTime); + + // Update grade + double run = lastLocation.distanceTo(location); + updateGrade(run, elevationDifference); + + // Update max speed + if (location.hasSpeed() && lastLocation.hasSpeed()) { + updateSpeed( + location.getTime(), location.getSpeed(), lastLocation.getTime(), lastLocation.getSpeed()); + } + + lastLocation = location; + lastMovingLocation = location; } - double movingDistance = lastMovingLocation.distanceTo(location); - if (movingDistance < minRecordingDistance - && (!location.hasSpeed() || location.getSpeed() < MAX_NO_MOVEMENT_SPEED)) { - speedBuffer.reset(); - lastLocation = location; - return; - } - long movingTime = location.getTime() - lastLocation.getTime(); - if (movingTime < 0) { - lastLocation = location; - return; - } - - // Update total distance - currentSegment.addTotalDistance(movingDistance); - - // Update moving time - currentSegment.addMovingTime(movingTime); - - // Update grade - double run = lastLocation.distanceTo(location); - updateGrade(run, elevationDifference); - - // Update max speed - if (location.hasSpeed() && lastLocation.hasSpeed()) { - updateSpeed( - location.getTime(), location.getSpeed(), lastLocation.getTime(), lastLocation.getSpeed()); - } - - lastLocation = location; - lastMovingLocation = location; - } - - /** - * Gets the smoothed elevation over several readings. The elevation readings - * is noisy so the smoothed elevation is better than the raw elevation for - * many tasks. - */ - public double getSmoothedElevation() { - return elevationBuffer.getAverage(); - } - - public double getSmoothedSpeed() { - return speedBuffer.getAverage(); - } - - /** - * Updates a speed reading. Assumes the user is moving. - * - * @param time the time - * @param speed the speed - * @param lastLocationTime the last location time - * @param lastLocationSpeed the last location speed - */ - @VisibleForTesting - void updateSpeed(long time, double speed, long lastLocationTime, double lastLocationSpeed) { - if (speed < MAX_NO_MOVEMENT_SPEED) { - speedBuffer.reset(); - } else if (isValidSpeed(time, speed, lastLocationTime, lastLocationSpeed)) { - speedBuffer.setNext(speed); - if (speedBuffer.getAverage() > currentSegment.getMaxSpeed()) { - currentSegment.setMaxSpeed(speedBuffer.getAverage()); - } - } else { - Log.d(TAG, "Invalid speed. speed: " + speed + " lastLocationSpeed: " + lastLocationSpeed); - } - } - - /** - * Updates an elevation reading. Returns the difference. - * - * @param elevation the elevation - */ - @VisibleForTesting - double updateElevation(double elevation) { - // Update elevation using the smoothed average - double oldAverage = elevationBuffer.getAverage(); - elevationBuffer.setNext(elevation); - double newAverage = elevationBuffer.getAverage(); - - currentSegment.updateElevationExtremities(newAverage); - double difference = newAverage - oldAverage; - if (difference > 0) { - currentSegment.addTotalElevationGain(difference); - } - return difference; - } - - /** - * Updates a grade reading. - * - * @param run the run - * @param rise the rise - */ - @VisibleForTesting - void updateGrade(double run, double rise) { - runBuffer.setNext(run); - - double smoothedRun = runBuffer.getAverage(); - - /* - * With the error in the altitude measurement it is dangerous to divide by - * anything less than 5. + /** + * Gets the smoothed elevation over several readings. The elevation readings + * is noisy so the smoothed elevation is better than the raw elevation for + * many tasks. */ - if (smoothedRun < 5.0) { - return; + public double getSmoothedElevation() { + return elevationBuffer.getAverage(); } - gradeBuffer.setNext(rise / smoothedRun); - currentSegment.updateGradeExtremities(gradeBuffer.getAverage()); - } - private TripStatistics init(long time) { - TripStatistics stats = new TripStatistics(); - stats.setStartTime(time); - stats.setStopTime(time); - return stats; - } + public double getSmoothedSpeed() { + return speedBuffer.getAverage(); + } - /** - * Returns true if the speed is valid. - * - * @param time the time - * @param speed the speed - * @param lastLocationTime the last location time - * @param lastLocationSpeed the last location speed - */ - private boolean isValidSpeed( - long time, double speed, long lastLocationTime, double lastLocationSpeed) { - - /* - * There are a lot of noisy speed readings. Do the cheapest checks first, - * most expensive last. + /** + * Updates a speed reading. Assumes the user is moving. + * + * @param time the time + * @param speed the speed + * @param lastLocationTime the last location time + * @param lastLocationSpeed the last location speed */ - if (speed == 0) { - return false; + @VisibleForTesting + void updateSpeed(long time, double speed, long lastLocationTime, double lastLocationSpeed) { + if (speed < MAX_NO_MOVEMENT_SPEED) { + speedBuffer.reset(); + } else if (isValidSpeed(time, speed, lastLocationTime, lastLocationSpeed)) { + speedBuffer.setNext(speed); + if (speedBuffer.getAverage() > currentSegment.getMaxSpeed()) { + currentSegment.setMaxSpeed(speedBuffer.getAverage()); + } + } else { + Log.d(TAG, "Invalid speed. speed: " + speed + " lastLocationSpeed: " + lastLocationSpeed); + } } - /* - * The following code will ignore unlikely readings. 128 m/s seems to be an - * internal android error code. + /** + * Updates an elevation reading. Returns the difference. + * + * @param elevation the elevation */ - if (Math.abs(speed - 128) < 1) { - return false; + @VisibleForTesting + double updateElevation(double elevation) { + // Update elevation using the smoothed average + double oldAverage = elevationBuffer.getAverage(); + elevationBuffer.setNext(elevation); + double newAverage = elevationBuffer.getAverage(); + + currentSegment.updateElevationExtremities(newAverage); + double difference = newAverage - oldAverage; + if (difference > 0) { + currentSegment.addTotalElevationGain(difference); + } + return difference; } - /* - * See if the speed seems physically likely. Ignore any speeds that imply - * acceleration greater than 2g. + /** + * Updates a grade reading. + * + * @param run the run + * @param rise the rise */ - long timeDifference = time - lastLocationTime; - double speedDifference = Math.abs(lastLocationSpeed - speed); - if (speedDifference > MAX_ACCELERATION * timeDifference) { - return false; + @VisibleForTesting + void updateGrade(double run, double rise) { + runBuffer.setNext(run); + + double smoothedRun = runBuffer.getAverage(); + + /* + * With the error in the altitude measurement it is dangerous to divide by + * anything less than 5. + */ + if (smoothedRun < 5.0) { + return; + } + gradeBuffer.setNext(rise / smoothedRun); + currentSegment.updateGradeExtremities(gradeBuffer.getAverage()); } - /* - * Only check if the speed buffer is full. Check that the speed is less than - * 10X the smoothed average and the speed difference doesn't imply 2g - * acceleration. - */ - if (speedBuffer.isFull()) { - double average = speedBuffer.getAverage(); - double diff = Math.abs(average - speed); - return (speed < average * 10) && (diff < MAX_ACCELERATION * timeDifference); - } else { - return true; + private TripStatistics init(long time) { + TripStatistics stats = new TripStatistics(); + stats.setStartTime(time); + stats.setStopTime(time); + return stats; + } + + /** + * Returns true if the speed is valid. + * + * @param time the time + * @param speed the speed + * @param lastLocationTime the last location time + * @param lastLocationSpeed the last location speed + */ + private boolean isValidSpeed( + long time, double speed, long lastLocationTime, double lastLocationSpeed) { + + /* + * There are a lot of noisy speed readings. Do the cheapest checks first, + * most expensive last. + */ + if (speed == 0) { + return false; + } + + /* + * The following code will ignore unlikely readings. 128 m/s seems to be an + * internal android error code. + */ + if (Math.abs(speed - 128) < 1) { + return false; + } + + /* + * See if the speed seems physically likely. Ignore any speeds that imply + * acceleration greater than 2g. + */ + long timeDifference = time - lastLocationTime; + double speedDifference = Math.abs(lastLocationSpeed - speed); + if (speedDifference > MAX_ACCELERATION * timeDifference) { + return false; + } + + /* + * Only check if the speed buffer is full. Check that the speed is less than + * 10X the smoothed average and the speed difference doesn't imply 2g + * acceleration. + */ + if (speedBuffer.isFull()) { + double average = speedBuffer.getAverage(); + double diff = Math.abs(average - speed); + return (speed < average * 10) && (diff < MAX_ACCELERATION * timeDifference); + } else { + return true; + } } - } } diff --git a/src/main/java/com/google/android/apps/mytracks/util/BluetoothDeviceUtils.java b/src/main/java/com/google/android/apps/mytracks/util/BluetoothDeviceUtils.java index 1feff7bbc..230c10b11 100644 --- a/src/main/java/com/google/android/apps/mytracks/util/BluetoothDeviceUtils.java +++ b/src/main/java/com/google/android/apps/mytracks/util/BluetoothDeviceUtils.java @@ -29,27 +29,28 @@ import java.util.Set; */ public class BluetoothDeviceUtils { - private BluetoothDeviceUtils() {} - - /** - * Populates the device names and the device addresses with all the suitable - * bluetooth devices. - * - * @param bluetoothAdapter the bluetooth adapter - * @param deviceNames list of device names - * @param deviceAddresses list of device addresses - */ - public static void populateDeviceLists(BluetoothAdapter bluetoothAdapter, List deviceNames, List deviceAddresses) { - // Ensure the bluetooth adapter is not in discovery mode. - bluetoothAdapter.cancelDiscovery(); - - Set pairedDevices = bluetoothAdapter.getBondedDevices(); - for (BluetoothDevice device : pairedDevices) { - BluetoothClass bluetoothClass = device.getBluetoothClass(); - if (bluetoothClass != null && (bluetoothClass.getDeviceClass() & BluetoothClass.Device.HEALTH_PULSE_RATE) != 0) { - deviceAddresses.add(device.getAddress()); - deviceNames.add(device.getName()); - } + private BluetoothDeviceUtils() { + } + + /** + * Populates the device names and the device addresses with all the suitable + * bluetooth devices. + * + * @param bluetoothAdapter the bluetooth adapter + * @param deviceNames list of device names + * @param deviceAddresses list of device addresses + */ + public static void populateDeviceLists(BluetoothAdapter bluetoothAdapter, List deviceNames, List deviceAddresses) { + // Ensure the bluetooth adapter is not in discovery mode. + bluetoothAdapter.cancelDiscovery(); + + Set pairedDevices = bluetoothAdapter.getBondedDevices(); + for (BluetoothDevice device : pairedDevices) { + BluetoothClass bluetoothClass = device.getBluetoothClass(); + if (bluetoothClass != null && (bluetoothClass.getDeviceClass() & BluetoothClass.Device.HEALTH_PULSE_RATE) != 0) { + deviceAddresses.add(device.getAddress()); + deviceNames.add(device.getName()); + } + } } - } } diff --git a/src/main/java/com/google/android/apps/mytracks/util/DialogUtils.java b/src/main/java/com/google/android/apps/mytracks/util/DialogUtils.java index 1271c9af4..ed209ca0f 100644 --- a/src/main/java/com/google/android/apps/mytracks/util/DialogUtils.java +++ b/src/main/java/com/google/android/apps/mytracks/util/DialogUtils.java @@ -1,12 +1,12 @@ /* * Copyright 2012 Google Inc. - * + * * 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 @@ -16,8 +16,6 @@ package com.google.android.apps.mytracks.util; -import com.google.android.maps.mytracks.R; - import android.app.AlertDialog; import android.app.Dialog; import android.app.ProgressDialog; @@ -28,6 +26,8 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.LinearLayout; +import com.google.android.maps.mytracks.R; + /** * Utilities for creating dialogs. * @@ -35,121 +35,122 @@ import android.widget.LinearLayout; */ public class DialogUtils { - private DialogUtils() {} - - /** - * Creates a confirmation dialog. - * - * @param context the context - * @param titleId the title - * @param message the message - * @param okListener the listener when OK is clicked - */ - public static Dialog createConfirmationDialog(final Context context, int titleId, String message, DialogInterface.OnClickListener okListener) { - final AlertDialog alertDialog = new AlertDialog.Builder(context) - .setCancelable(true) - .setIcon(android.R.drawable.ic_dialog_alert) - .setMessage(message) - .setNegativeButton(R.string.generic_no, null) - .setPositiveButton(R.string.generic_yes, okListener) - .setTitle(titleId).create(); - alertDialog.setOnShowListener(new DialogInterface.OnShowListener() { - - @Override - public void onShow(DialogInterface dialog) { - setDialogTitleDivider(context, alertDialog); - } - }); - return alertDialog; - } - - /** - * Creates a spinner progress dialog. - * - * @param context the context - * @param messageId the progress message id - * @param onCancelListener the cancel listener - */ - public static ProgressDialog createSpinnerProgressDialog(Context context, int messageId, DialogInterface.OnCancelListener onCancelListener) { - return createProgressDialog(true, context, messageId, onCancelListener); - } - - /** - * Creates a horizontal progress dialog. - * - * @param context the context - * @param messageId the progress message id - * @param onCancelListener the cancel listener - * @param formatArgs the format arguments for the messageId - */ - public static ProgressDialog createHorizontalProgressDialog(Context context, int messageId, DialogInterface.OnCancelListener onCancelListener, Object... formatArgs) { - return createProgressDialog(false, context, messageId, onCancelListener, formatArgs); - } - - /** - * Creates a progress dialog. - * - * @param spinner true to use the spinner style - * @param context the context - * @param messageId the progress message id - * @param onCancelListener the cancel listener - * @param formatArgs the format arguments for the message id - */ - private static ProgressDialog createProgressDialog(boolean spinner, final Context context, int messageId, DialogInterface.OnCancelListener onCancelListener, Object... formatArgs) { - final ProgressDialog progressDialog = new ProgressDialog(context); - progressDialog.setCancelable(true); - progressDialog.setCanceledOnTouchOutside(false); - progressDialog.setIcon(android.R.drawable.ic_dialog_info); - progressDialog.setIndeterminate(true); - progressDialog.setMessage(context.getString(messageId, formatArgs)); - progressDialog.setOnCancelListener(onCancelListener); - progressDialog.setProgressStyle(spinner ? ProgressDialog.STYLE_SPINNER : ProgressDialog.STYLE_HORIZONTAL); - progressDialog.setTitle(R.string.generic_progress_title); - progressDialog.setOnShowListener(new DialogInterface.OnShowListener() { - - @Override - public void onShow(DialogInterface dialog) { - setDialogTitleDivider(context, progressDialog); - } - }); - return progressDialog; - } - - /** - * Sets the dialog title divider. - * - * @param context the context - * @param dialog the dialog - */ - public static void setDialogTitleDivider(Context context, Dialog dialog) { - try { - ViewGroup decorView = (ViewGroup) dialog.getWindow().getDecorView(); - if (decorView == null) { - return; - } - FrameLayout windowContentView = (FrameLayout) decorView.getChildAt(0); - if (windowContentView == null) { - return; - } - FrameLayout contentView = (FrameLayout) windowContentView.getChildAt(0); - if (contentView == null) { - return; - } - LinearLayout parentPanel = (LinearLayout) contentView.getChildAt(0); - if (parentPanel == null) { - return; - } - LinearLayout topPanel = (LinearLayout) parentPanel.getChildAt(0); - if (topPanel == null) { - return; - } - View titleDivider = topPanel.getChildAt(2); - if (titleDivider == null) { - return; - } - titleDivider.setBackgroundColor(context.getResources().getColor(R.color.holo_orange_dark)); - } catch (Exception e) { - // Can safely ignore + private DialogUtils() { + } + + /** + * Creates a confirmation dialog. + * + * @param context the context + * @param titleId the title + * @param message the message + * @param okListener the listener when OK is clicked + */ + public static Dialog createConfirmationDialog(final Context context, int titleId, String message, DialogInterface.OnClickListener okListener) { + final AlertDialog alertDialog = new AlertDialog.Builder(context) + .setCancelable(true) + .setIcon(android.R.drawable.ic_dialog_alert) + .setMessage(message) + .setNegativeButton(R.string.generic_no, null) + .setPositiveButton(R.string.generic_yes, okListener) + .setTitle(titleId).create(); + alertDialog.setOnShowListener(new DialogInterface.OnShowListener() { + + @Override + public void onShow(DialogInterface dialog) { + setDialogTitleDivider(context, alertDialog); + } + }); + return alertDialog; + } + + /** + * Creates a spinner progress dialog. + * + * @param context the context + * @param messageId the progress message id + * @param onCancelListener the cancel listener + */ + public static ProgressDialog createSpinnerProgressDialog(Context context, int messageId, DialogInterface.OnCancelListener onCancelListener) { + return createProgressDialog(true, context, messageId, onCancelListener); + } + + /** + * Creates a horizontal progress dialog. + * + * @param context the context + * @param messageId the progress message id + * @param onCancelListener the cancel listener + * @param formatArgs the format arguments for the messageId + */ + public static ProgressDialog createHorizontalProgressDialog(Context context, int messageId, DialogInterface.OnCancelListener onCancelListener, Object... formatArgs) { + return createProgressDialog(false, context, messageId, onCancelListener, formatArgs); + } + + /** + * Creates a progress dialog. + * + * @param spinner true to use the spinner style + * @param context the context + * @param messageId the progress message id + * @param onCancelListener the cancel listener + * @param formatArgs the format arguments for the message id + */ + private static ProgressDialog createProgressDialog(boolean spinner, final Context context, int messageId, DialogInterface.OnCancelListener onCancelListener, Object... formatArgs) { + final ProgressDialog progressDialog = new ProgressDialog(context); + progressDialog.setCancelable(true); + progressDialog.setCanceledOnTouchOutside(false); + progressDialog.setIcon(android.R.drawable.ic_dialog_info); + progressDialog.setIndeterminate(true); + progressDialog.setMessage(context.getString(messageId, formatArgs)); + progressDialog.setOnCancelListener(onCancelListener); + progressDialog.setProgressStyle(spinner ? ProgressDialog.STYLE_SPINNER : ProgressDialog.STYLE_HORIZONTAL); + progressDialog.setTitle(R.string.generic_progress_title); + progressDialog.setOnShowListener(new DialogInterface.OnShowListener() { + + @Override + public void onShow(DialogInterface dialog) { + setDialogTitleDivider(context, progressDialog); + } + }); + return progressDialog; + } + + /** + * Sets the dialog title divider. + * + * @param context the context + * @param dialog the dialog + */ + public static void setDialogTitleDivider(Context context, Dialog dialog) { + try { + ViewGroup decorView = (ViewGroup) dialog.getWindow().getDecorView(); + if (decorView == null) { + return; + } + FrameLayout windowContentView = (FrameLayout) decorView.getChildAt(0); + if (windowContentView == null) { + return; + } + FrameLayout contentView = (FrameLayout) windowContentView.getChildAt(0); + if (contentView == null) { + return; + } + LinearLayout parentPanel = (LinearLayout) contentView.getChildAt(0); + if (parentPanel == null) { + return; + } + LinearLayout topPanel = (LinearLayout) parentPanel.getChildAt(0); + if (topPanel == null) { + return; + } + View titleDivider = topPanel.getChildAt(2); + if (titleDivider == null) { + return; + } + titleDivider.setBackgroundColor(context.getResources().getColor(R.color.holo_orange_dark)); + } catch (Exception e) { + // Can safely ignore + } } - } } diff --git a/src/main/java/com/google/android/apps/mytracks/util/FileUtils.java b/src/main/java/com/google/android/apps/mytracks/util/FileUtils.java index 1dc2c2479..4b31cf5a0 100644 --- a/src/main/java/com/google/android/apps/mytracks/util/FileUtils.java +++ b/src/main/java/com/google/android/apps/mytracks/util/FileUtils.java @@ -24,262 +24,258 @@ import java.io.File; /** * Utilities for dealing with files. - * + * * @author Rodrigo Damazio */ public class FileUtils { - private FileUtils() {} + /** + * Used to transfer picture from the camera. + */ + public static final String FILEPROVIDER = "com.google.android.apps.mytracks.fileprovider"; + /** + * The play tracks directory in the app's cache directory. + */ + public static final String PLAY_TRACKS_DIR = "temp"; + /** + * Our external storage directory. + */ + static final String SDCARD_TOP_DIR = "MyTracks"; + static final String TRACK_PREFIX = "MyTracks"; + /** + * The maximum FAT32 path length. See the FAT32 spec at + * http://msdn.microsoft.com/en-us/windows/hardware/gg463080 + */ + static final int MAX_FAT32_PATH_LENGTH = 260; + /** + * The pictures directory under the app's external storage directory. + */ + private static final String PICTURES_DIR = "pictures"; - /** - * Our external storage directory. - */ - static final String SDCARD_TOP_DIR = "MyTracks"; - - static final String TRACK_PREFIX = "MyTracks"; - - /** - * Used to transfer picture from the camera. - */ - public static final String FILEPROVIDER = "com.google.android.apps.mytracks.fileprovider"; - - /** - * The maximum FAT32 path length. See the FAT32 spec at - * http://msdn.microsoft.com/en-us/windows/hardware/gg463080 - */ - static final int MAX_FAT32_PATH_LENGTH = 260; - - /** - * The play tracks directory in the app's cache directory. - */ - public static final String PLAY_TRACKS_DIR = "temp"; - - /** - * The pictures directory under the app's external storage directory. - */ - private static final String PICTURES_DIR = "pictures"; - - /** - * Returns true if the external storage is available. - */ - public static boolean isExternalStorageAvailable() { - String state = Environment.getExternalStorageState(); - return Environment.MEDIA_MOUNTED.equals(state) - || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state); - } - - /** - * Returns true if the external storage is writable. - */ - public static boolean isExternalStorageWriteable() { - String state = Environment.getExternalStorageState(); - return Environment.MEDIA_MOUNTED.equals(state); - } - - /** - * Returns true if the directory exists. - * - * @param dir the directory - */ - public static boolean isDirectory(File dir) { - return dir.exists() && dir.isDirectory(); - } - - /** - * Ensures the directory exists by creating it and its parents if necessary. - * - * @return whether the directory exists (either already existed or was - * successfully created) - */ - public static boolean ensureDirectoryExists(File dir) { - if (isDirectory(dir)) { - return true; + private FileUtils() { } - return dir.mkdirs(); - } - public static File getPhotoDir() { - return new File(getPath(PICTURES_DIR)); - } - - public static File getPhotoDir(long trackId) { - return new File(getPath(PICTURES_DIR, TRACK_PREFIX + trackId)); - } - - /** - * Gets the display name for a path on the external storage. - * - * @param components the components - */ - public static String getPathDisplayName(String... components) { - StringBuilder dirNameBuilder = new StringBuilder(); - dirNameBuilder.append(File.separatorChar); - dirNameBuilder.append(SDCARD_TOP_DIR); - for (String component : components) { - dirNameBuilder.append(File.separatorChar); - dirNameBuilder.append(component); + /** + * Returns true if the external storage is available. + */ + public static boolean isExternalStorageAvailable() { + String state = Environment.getExternalStorageState(); + return Environment.MEDIA_MOUNTED.equals(state) + || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state); } - return dirNameBuilder.toString(); - } - /** - * Gets a path on the external storage. - * - * @param components the components - */ - public static String getPath(String... components) { - return Environment.getExternalStorageDirectory() + getPathDisplayName(components); - } - - /** - * Builds a filename with the given base name (prefix) and the given - * extension, possibly adding a suffix to ensure the file doesn't exist. - * - * @param directory the directory the file will live in - * @param fileBaseName the prefix for the file name - * @param extension the file's extension - * @return the complete file name, without the directory - */ - public static synchronized String buildUniqueFileName( - File directory, String fileBaseName, String extension) { - return buildUniqueFileName(directory, fileBaseName, extension, 0); - } - - /** - * Gets the name from a file name, without the extension. - * - * @param fileName the file name - */ - public static String getName(String fileName) { - int index = fileName.lastIndexOf('.'); - if (index == -1) { - return fileName; + /** + * Returns true if the external storage is writable. + */ + public static boolean isExternalStorageWriteable() { + String state = Environment.getExternalStorageState(); + return Environment.MEDIA_MOUNTED.equals(state); } - return fileName.substring(0, index); - } - /** - * Gets the extension from a file name. Returns null if there is no extension. - * - * @param fileName the file name - */ - public static String getExtension(String fileName) { - int index = fileName.lastIndexOf('.'); - if (index == -1) { - return null; + /** + * Returns true if the directory exists. + * + * @param dir the directory + */ + public static boolean isDirectory(File dir) { + return dir.exists() && dir.isDirectory(); } - return fileName.substring(index + 1); - } - - public static void updateMediaScanner(Context context, Uri uri) { - Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); - mediaScanIntent.setData(uri); - context.sendBroadcast(mediaScanIntent); - } - /** - * Builds a filename with the given base and the given extension, possibly - * adding a suffix to ensure the file doesn't exist. - * - * @param directory the directory the filename will be located in - * @param base the base for the filename - * @param extension the extension for the filename - * @param suffix the first numeric suffix to try to use, or 0 for none - * @return the complete filename, without the directory - */ - private static String buildUniqueFileName( - File directory, String base, String extension, int suffix) { - String suffixName = ""; - if (suffix > 0) { - suffixName += "(" + suffix + ")"; + /** + * Ensures the directory exists by creating it and its parents if necessary. + * + * @return whether the directory exists (either already existed or was + * successfully created) + */ + public static boolean ensureDirectoryExists(File dir) { + if (isDirectory(dir)) { + return true; + } + return dir.mkdirs(); } - suffixName += "." + extension; - String baseName = sanitizeFileName(base); - baseName = truncateFileName(directory, baseName, suffixName); - String fullName = baseName + suffixName; - - if (!new File(directory, fullName).exists()) { - return fullName; + public static File getPhotoDir() { + return new File(getPath(PICTURES_DIR)); } - return buildUniqueFileName(directory, base, extension, suffix + 1); - } - /** - * Sanitizes the name as a valid fat32 filename. For simplicity, fat32 - * filename characters may be any combination of letters, digits, or - * characters with code point values greater than 127. Replaces the invalid - * characters with "_" and collapses multiple "_" together. - * - * @param name name - */ - static String sanitizeFileName(String name) { - StringBuilder builder = new StringBuilder(name.length()); - for (int i = 0; i < name.length(); i++) { - int codePoint = name.codePointAt(i); - char character = name.charAt(i); - if (Character.isLetterOrDigit(character) || codePoint > 127 || isSpecialFat32(character)) { - builder.appendCodePoint(codePoint); - } else { - builder.append("_"); - } + public static File getPhotoDir(long trackId) { + return new File(getPath(PICTURES_DIR, TRACK_PREFIX + trackId)); } - String result = builder.toString(); - return result.replaceAll("_+", "_"); - } - /** - * Returns true if it is a special FAT32 character. - * - * @param character the character - */ - private static boolean isSpecialFat32(char character) { - switch (character) { - case '$': - case '%': - case '\'': - case '-': - case '_': - case '@': - case '~': - case '`': - case '!': - case '(': - case ')': - case '{': - case '}': - case '^': - case '#': - case '&': - case '+': - case ',': - case ';': - case '=': - case '[': - case ']': - case ' ': - return true; - default: - return false; + /** + * Gets the display name for a path on the external storage. + * + * @param components the components + */ + public static String getPathDisplayName(String... components) { + StringBuilder dirNameBuilder = new StringBuilder(); + dirNameBuilder.append(File.separatorChar); + dirNameBuilder.append(SDCARD_TOP_DIR); + for (String component : components) { + dirNameBuilder.append(File.separatorChar); + dirNameBuilder.append(component); + } + return dirNameBuilder.toString(); } - } - /** - * Truncates the name if necessary so the filename path length (directory + - * name + suffix) meets the Fat32 path limit. - * - * @param directory directory - * @param name name - * @param suffix suffix - */ - static String truncateFileName(File directory, String name, String suffix) { - // 1 at the end accounts for the FAT32 filename trailing NUL character - int requiredLength = directory.getPath().length() + suffix.length() + 1; - if (name.length() + requiredLength > MAX_FAT32_PATH_LENGTH) { - int limit = MAX_FAT32_PATH_LENGTH - requiredLength; - return name.substring(0, limit); - } else { - return name; + /** + * Gets a path on the external storage. + * + * @param components the components + */ + public static String getPath(String... components) { + return Environment.getExternalStorageDirectory() + getPathDisplayName(components); + } + + /** + * Builds a filename with the given base name (prefix) and the given + * extension, possibly adding a suffix to ensure the file doesn't exist. + * + * @param directory the directory the file will live in + * @param fileBaseName the prefix for the file name + * @param extension the file's extension + * @return the complete file name, without the directory + */ + public static synchronized String buildUniqueFileName( + File directory, String fileBaseName, String extension) { + return buildUniqueFileName(directory, fileBaseName, extension, 0); + } + + /** + * Gets the name from a file name, without the extension. + * + * @param fileName the file name + */ + public static String getName(String fileName) { + int index = fileName.lastIndexOf('.'); + if (index == -1) { + return fileName; + } + return fileName.substring(0, index); + } + + /** + * Gets the extension from a file name. Returns null if there is no extension. + * + * @param fileName the file name + */ + public static String getExtension(String fileName) { + int index = fileName.lastIndexOf('.'); + if (index == -1) { + return null; + } + return fileName.substring(index + 1); + } + + public static void updateMediaScanner(Context context, Uri uri) { + Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + mediaScanIntent.setData(uri); + context.sendBroadcast(mediaScanIntent); + } + + /** + * Builds a filename with the given base and the given extension, possibly + * adding a suffix to ensure the file doesn't exist. + * + * @param directory the directory the filename will be located in + * @param base the base for the filename + * @param extension the extension for the filename + * @param suffix the first numeric suffix to try to use, or 0 for none + * @return the complete filename, without the directory + */ + private static String buildUniqueFileName( + File directory, String base, String extension, int suffix) { + String suffixName = ""; + if (suffix > 0) { + suffixName += "(" + suffix + ")"; + } + suffixName += "." + extension; + + String baseName = sanitizeFileName(base); + baseName = truncateFileName(directory, baseName, suffixName); + String fullName = baseName + suffixName; + + if (!new File(directory, fullName).exists()) { + return fullName; + } + return buildUniqueFileName(directory, base, extension, suffix + 1); + } + + /** + * Sanitizes the name as a valid fat32 filename. For simplicity, fat32 + * filename characters may be any combination of letters, digits, or + * characters with code point values greater than 127. Replaces the invalid + * characters with "_" and collapses multiple "_" together. + * + * @param name name + */ + static String sanitizeFileName(String name) { + StringBuilder builder = new StringBuilder(name.length()); + for (int i = 0; i < name.length(); i++) { + int codePoint = name.codePointAt(i); + char character = name.charAt(i); + if (Character.isLetterOrDigit(character) || codePoint > 127 || isSpecialFat32(character)) { + builder.appendCodePoint(codePoint); + } else { + builder.append("_"); + } + } + String result = builder.toString(); + return result.replaceAll("_+", "_"); + } + + /** + * Returns true if it is a special FAT32 character. + * + * @param character the character + */ + private static boolean isSpecialFat32(char character) { + switch (character) { + case '$': + case '%': + case '\'': + case '-': + case '_': + case '@': + case '~': + case '`': + case '!': + case '(': + case ')': + case '{': + case '}': + case '^': + case '#': + case '&': + case '+': + case ',': + case ';': + case '=': + case '[': + case ']': + case ' ': + return true; + default: + return false; + } + } + + /** + * Truncates the name if necessary so the filename path length (directory + + * name + suffix) meets the Fat32 path limit. + * + * @param directory directory + * @param name name + * @param suffix suffix + */ + static String truncateFileName(File directory, String name, String suffix) { + // 1 at the end accounts for the FAT32 filename trailing NUL character + int requiredLength = directory.getPath().length() + suffix.length() + 1; + if (name.length() + requiredLength > MAX_FAT32_PATH_LENGTH) { + int limit = MAX_FAT32_PATH_LENGTH - requiredLength; + return name.substring(0, limit); + } else { + return name; + } } - } } diff --git a/src/main/java/com/google/android/apps/mytracks/util/IntentUtils.java b/src/main/java/com/google/android/apps/mytracks/util/IntentUtils.java index ad4559aae..ff1143be1 100644 --- a/src/main/java/com/google/android/apps/mytracks/util/IntentUtils.java +++ b/src/main/java/com/google/android/apps/mytracks/util/IntentUtils.java @@ -1,12 +1,12 @@ /* * Copyright 2012 Google Inc. - * + * * 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 @@ -20,8 +20,8 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; -import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl; import com.google.android.apps.mytracks.content.ContentProviderUtils; +import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.io.file.TrackFileFormat; @@ -31,58 +31,59 @@ import java.io.File; /** * Utilities for creating intents. - * + * * @author Jimmy Shih */ public class IntentUtils { - public static final String TEXT_PLAIN_TYPE = "text/plain"; + public static final String TEXT_PLAIN_TYPE = "text/plain"; - private IntentUtils() {} - - /** - * Creates an intent with {@link Intent#FLAG_ACTIVITY_CLEAR_TOP} and - * {@link Intent#FLAG_ACTIVITY_NEW_TASK}. - * - * @param context the context - * @param cls the class - */ - public static Intent newIntent(Context context, Class cls) { - return new Intent(context, cls).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); - } - - /** - * Creates an intent to share a track file with an app. - * - * @param context the context - * @param trackId the track id - * @param filePath the file path - * @param trackFileFormat the track file format - */ - public static Intent newShareFileIntent(Context context, long trackId, String filePath, TrackFileFormat trackFileFormat) { - Track track = ContentProviderUtils.Factory.get(context).getTrack(trackId); - String trackDescription = track == null ? "" : new DescriptionGeneratorImpl(context).generateTrackDescription(track, null, null, false); - - return new Intent(Intent.ACTION_SEND) - .putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(filePath))) - .putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.share_track_subject)) - .putExtra(Intent.EXTRA_TEXT, context.getString(R.string.share_track_share_file_body, trackDescription)) - .putExtra(context.getString(R.string.track_id_broadcast_extra), trackId) - .setType(trackFileFormat.getMimeType()); - } - - public static Intent newShowOnMapIntent(Waypoint waypoint) { - return newShowOnMapIntent(waypoint.getLocation().getLatitude(), waypoint.getLocation().getLongitude(), waypoint.getName()); - } - - public static Intent newShowOnMapIntent(double latitude, double longitude, String label) { - //SEE https://developer.android.com/guide/components/intents-common.html#Maps - Intent intent = new Intent(Intent.ACTION_VIEW); - String uri = "geo:0,0?q=" + latitude + "," + longitude; - if (label != null && label.length() > 0) { - uri += "(" + label + ")"; + private IntentUtils() { + } + + /** + * Creates an intent with {@link Intent#FLAG_ACTIVITY_CLEAR_TOP} and + * {@link Intent#FLAG_ACTIVITY_NEW_TASK}. + * + * @param context the context + * @param cls the class + */ + public static Intent newIntent(Context context, Class cls) { + return new Intent(context, cls).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); + } + + /** + * Creates an intent to share a track file with an app. + * + * @param context the context + * @param trackId the track id + * @param filePath the file path + * @param trackFileFormat the track file format + */ + public static Intent newShareFileIntent(Context context, long trackId, String filePath, TrackFileFormat trackFileFormat) { + Track track = ContentProviderUtils.Factory.get(context).getTrack(trackId); + String trackDescription = track == null ? "" : new DescriptionGeneratorImpl(context).generateTrackDescription(track, null, null, false); + + return new Intent(Intent.ACTION_SEND) + .putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(filePath))) + .putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.share_track_subject)) + .putExtra(Intent.EXTRA_TEXT, context.getString(R.string.share_track_share_file_body, trackDescription)) + .putExtra(context.getString(R.string.track_id_broadcast_extra), trackId) + .setType(trackFileFormat.getMimeType()); + } + + public static Intent newShowOnMapIntent(Waypoint waypoint) { + return newShowOnMapIntent(waypoint.getLocation().getLatitude(), waypoint.getLocation().getLongitude(), waypoint.getName()); + } + + public static Intent newShowOnMapIntent(double latitude, double longitude, String label) { + //SEE https://developer.android.com/guide/components/intents-common.html#Maps + Intent intent = new Intent(Intent.ACTION_VIEW); + String uri = "geo:0,0?q=" + latitude + "," + longitude; + if (label != null && label.length() > 0) { + uri += "(" + label + ")"; + } + intent.setData(Uri.parse(uri)); + return intent; } - intent.setData(Uri.parse(uri)); - return intent; - } } diff --git a/src/main/java/com/google/android/apps/mytracks/util/ListItemUtils.java b/src/main/java/com/google/android/apps/mytracks/util/ListItemUtils.java index a4833b4b5..81accca41 100644 --- a/src/main/java/com/google/android/apps/mytracks/util/ListItemUtils.java +++ b/src/main/java/com/google/android/apps/mytracks/util/ListItemUtils.java @@ -1,12 +1,12 @@ /* * Copyright 2012 Google Inc. - * + * * 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 @@ -32,219 +32,220 @@ import com.google.android.maps.mytracks.R; /** * Utilities to display a list item. - * + * * @author Jimmy Shih */ public class ListItemUtils { - private static final int LIST_PREFERRED_ITEM_HEIGHT_DEFAULT = 128; + private static final int LIST_PREFERRED_ITEM_HEIGHT_DEFAULT = 128; - private ListItemUtils() {} - - /** - * Sets a list item. - * - * @param activity the activity - * @param view the list item view - * @param isRecording true if recording - * @param isPaused true if paused - * @param iconId the icon id - * @param iconContentDescriptionId the icon content description id - * @param name the name value - * @param totalTime the total time value - * @param totalDistance the total distance value - * @param markerCount the marker count - * @param startTime the start time value - * @param useRelativeTime true to display relative time if appropriate - * @param category the category value - * @param description the description value - * @param photoUrl the photo url - */ - public static void setListItem(Activity activity, View view, boolean isRecording, - boolean isPaused, int iconId, int iconContentDescriptionId, String name, - String totalTime, String totalDistance, int markerCount, long startTime, - boolean useRelativeTime, String category, String description, String photoUrl) { - - // Set photo - ImageView photo = view.findViewById(R.id.list_item_photo); - ImageView textGradient = view.findViewById(R.id.list_item_text_gradient); - boolean hasPhoto = photoUrl != null && !photoUrl.equals(""); - - photo.setVisibility(hasPhoto ? View.VISIBLE : View.GONE); - textGradient.setVisibility(hasPhoto ? View.VISIBLE : View.GONE); - - if (hasPhoto) { - photo.getLayoutParams().height = getPhotoHeight(activity); - photo.setImageResource(android.R.color.transparent); - photo.setImageURI(Uri.parse(photoUrl)); - } - - // Set icon - if (isRecording) { - iconId = isPaused ? R.drawable.ic_track_paused : R.drawable.ic_track_recording; - iconContentDescriptionId = isPaused ? R.string.image_pause : R.string.image_record; + private ListItemUtils() { } - ImageView iconImageView = view.findViewById(R.id.list_item_icon); - iconImageView.setImageResource(iconId); - iconImageView.setContentDescription(activity.getString(iconContentDescriptionId)); - - // Set name - TextView nameTextView = view.findViewById(R.id.list_item_name); - setTextView(activity, nameTextView, name, hasPhoto); - - // Set totalTime/totalDistance - TextView timeDistanceTextView = view.findViewById(R.id.list_item_time_distance); - String timeDistanceText; - if (isRecording) { - timeDistanceTextView.setTextColor(activity.getResources().getColor(isPaused ? android.R.color.white : R.color.recording_text)); - timeDistanceText = activity.getString(isPaused ? R.string.generic_paused : R.string.generic_recording); - } else { - // Match list_item_time_distance in list_item.xml - timeDistanceTextView.setTextAppearance(activity, R.style.TextSmall); - timeDistanceText = getTimeDistance(totalTime, totalDistance); - if (markerCount > 0) { - timeDistanceText += " \u2027"; - } - } - setTextView(activity, timeDistanceTextView, timeDistanceText, hasPhoto); - - // Set markerCount - ImageView markerCountIcon = view.findViewById(R.id.list_item_marker_count_icon); - TextView markerCountTextView = view.findViewById(R.id.list_item_marker_count); - boolean hasMarker = markerCount > 0; - markerCountIcon.setVisibility(hasMarker ? View.VISIBLE : View.GONE); - String markerCountValue = hasMarker ? String.valueOf(markerCount) : null; - if (hasMarker) { - // Scale markerCountIcon - int lineHeight = markerCountTextView.getLineHeight(); - LayoutParams layoutParams = markerCountIcon.getLayoutParams(); - layoutParams.width = lineHeight; - layoutParams.height = lineHeight; - } - setTextView(activity, markerCountTextView, markerCountValue, hasPhoto); - - // Set date/time - String[] dateTime = getDateTime(isRecording, activity, startTime, useRelativeTime); - TextView dateTextView = view.findViewById(R.id.list_item_date); - setTextView(activity, dateTextView, dateTime[0], hasPhoto); - - TextView timeTextView = view.findViewById(R.id.list_item_time); - setTextView(activity, timeTextView, dateTime[1], hasPhoto); - - // Set category and description - TextView categoryDescriptionTextView = view.findViewById( - R.id.list_item_category_description); - String categoryDescription = isRecording ? null - : StringUtils.getCategoryDescription(category, description); - - /* - * Place categoryDescription in either ownerTimeDistanceTextView or - * categoryDescriptionTextView + /** + * Sets a list item. + * + * @param activity the activity + * @param view the list item view + * @param isRecording true if recording + * @param isPaused true if paused + * @param iconId the icon id + * @param iconContentDescriptionId the icon content description id + * @param name the name value + * @param totalTime the total time value + * @param totalDistance the total distance value + * @param markerCount the marker count + * @param startTime the start time value + * @param useRelativeTime true to display relative time if appropriate + * @param category the category value + * @param description the description value + * @param photoUrl the photo url */ - if (timeDistanceTextView.getVisibility() == View.GONE - && markerCountIcon.getVisibility() == View.GONE) { - setTextView(activity, categoryDescriptionTextView, null, hasPhoto); - // Match list_item_category_description in list_item.xml - timeDistanceTextView.setSingleLine(false); - timeDistanceTextView.setMaxLines(2); - setTextView(activity, timeDistanceTextView, categoryDescription, hasPhoto); - } else { - // Match list_item_time_distance in list_item.xml - timeDistanceTextView.setSingleLine(true); - setTextView(activity, categoryDescriptionTextView, categoryDescription, hasPhoto); + public static void setListItem(Activity activity, View view, boolean isRecording, + boolean isPaused, int iconId, int iconContentDescriptionId, String name, + String totalTime, String totalDistance, int markerCount, long startTime, + boolean useRelativeTime, String category, String description, String photoUrl) { + + // Set photo + ImageView photo = view.findViewById(R.id.list_item_photo); + ImageView textGradient = view.findViewById(R.id.list_item_text_gradient); + boolean hasPhoto = photoUrl != null && !photoUrl.equals(""); + + photo.setVisibility(hasPhoto ? View.VISIBLE : View.GONE); + textGradient.setVisibility(hasPhoto ? View.VISIBLE : View.GONE); + + if (hasPhoto) { + photo.getLayoutParams().height = getPhotoHeight(activity); + photo.setImageResource(android.R.color.transparent); + photo.setImageURI(Uri.parse(photoUrl)); + } + + // Set icon + if (isRecording) { + iconId = isPaused ? R.drawable.ic_track_paused : R.drawable.ic_track_recording; + iconContentDescriptionId = isPaused ? R.string.image_pause : R.string.image_record; + } + + ImageView iconImageView = view.findViewById(R.id.list_item_icon); + iconImageView.setImageResource(iconId); + iconImageView.setContentDescription(activity.getString(iconContentDescriptionId)); + + // Set name + TextView nameTextView = view.findViewById(R.id.list_item_name); + setTextView(activity, nameTextView, name, hasPhoto); + + // Set totalTime/totalDistance + TextView timeDistanceTextView = view.findViewById(R.id.list_item_time_distance); + String timeDistanceText; + if (isRecording) { + timeDistanceTextView.setTextColor(activity.getResources().getColor(isPaused ? android.R.color.white : R.color.recording_text)); + timeDistanceText = activity.getString(isPaused ? R.string.generic_paused : R.string.generic_recording); + } else { + // Match list_item_time_distance in list_item.xml + timeDistanceTextView.setTextAppearance(activity, R.style.TextSmall); + timeDistanceText = getTimeDistance(totalTime, totalDistance); + if (markerCount > 0) { + timeDistanceText += " \u2027"; + } + } + setTextView(activity, timeDistanceTextView, timeDistanceText, hasPhoto); + + // Set markerCount + ImageView markerCountIcon = view.findViewById(R.id.list_item_marker_count_icon); + TextView markerCountTextView = view.findViewById(R.id.list_item_marker_count); + boolean hasMarker = markerCount > 0; + markerCountIcon.setVisibility(hasMarker ? View.VISIBLE : View.GONE); + String markerCountValue = hasMarker ? String.valueOf(markerCount) : null; + if (hasMarker) { + // Scale markerCountIcon + int lineHeight = markerCountTextView.getLineHeight(); + LayoutParams layoutParams = markerCountIcon.getLayoutParams(); + layoutParams.width = lineHeight; + layoutParams.height = lineHeight; + } + setTextView(activity, markerCountTextView, markerCountValue, hasPhoto); + + // Set date/time + String[] dateTime = getDateTime(isRecording, activity, startTime, useRelativeTime); + TextView dateTextView = view.findViewById(R.id.list_item_date); + setTextView(activity, dateTextView, dateTime[0], hasPhoto); + + TextView timeTextView = view.findViewById(R.id.list_item_time); + setTextView(activity, timeTextView, dateTime[1], hasPhoto); + + // Set category and description + TextView categoryDescriptionTextView = view.findViewById( + R.id.list_item_category_description); + String categoryDescription = isRecording ? null + : StringUtils.getCategoryDescription(category, description); + + /* + * Place categoryDescription in either ownerTimeDistanceTextView or + * categoryDescriptionTextView + */ + if (timeDistanceTextView.getVisibility() == View.GONE + && markerCountIcon.getVisibility() == View.GONE) { + setTextView(activity, categoryDescriptionTextView, null, hasPhoto); + // Match list_item_category_description in list_item.xml + timeDistanceTextView.setSingleLine(false); + timeDistanceTextView.setMaxLines(2); + setTextView(activity, timeDistanceTextView, categoryDescription, hasPhoto); + } else { + // Match list_item_time_distance in list_item.xml + timeDistanceTextView.setSingleLine(true); + setTextView(activity, categoryDescriptionTextView, categoryDescription, hasPhoto); + } + + // Adjust iconImageView layout gravity + LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) iconImageView.getLayoutParams(); + params.gravity = timeDistanceTextView.getVisibility() == View.GONE + && markerCountIcon.getVisibility() == View.GONE ? Gravity.TOP : Gravity.CENTER_VERTICAL; } - // Adjust iconImageView layout gravity - LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) iconImageView.getLayoutParams(); - params.gravity = timeDistanceTextView.getVisibility() == View.GONE - && markerCountIcon.getVisibility() == View.GONE ? Gravity.TOP : Gravity.CENTER_VERTICAL; - } - - /** - * Gets a string for share owner, total time, and total distance. - * - * @param totalTime the total time. Can be null - * @param totalDistance the total distance. Can be null - */ - private static String getTimeDistance(String totalTime, String totalDistance) { - StringBuilder builder = new StringBuilder(); - if (totalTime != null && totalTime.length() != 0) { - if (builder.length() != 0) { - builder.append(" \u2027 "); - } - builder.append(totalTime); - } - if (totalDistance != null && totalDistance.length() != 0) { - if (builder.length() != 0) { - builder.append(" "); - } - builder.append("(").append(totalDistance).append(")"); - } - return builder.toString(); - } - - /** - * Gets the date and time as an array of two strings. - * - * @param isRecording true if recording - * @param context the context - * @param time the start time - */ - private static String[] getDateTime(boolean isRecording, Context context, long time, boolean useRelativeTime) { - if (isRecording || time == 0L) { - return new String[] { null, null }; - } - boolean isToday = DateUtils.isToday(time); - int timeFlags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL; - - if (isToday) { - if (useRelativeTime) { - return new String[] { DateUtils.getRelativeTimeSpanString( - time, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, - DateUtils.FORMAT_ABBREV_RELATIVE).toString(), null }; - } else { - return new String[] { DateUtils.formatDateTime(context, time, timeFlags), null }; - } + /** + * Gets a string for share owner, total time, and total distance. + * + * @param totalTime the total time. Can be null + * @param totalDistance the total distance. Can be null + */ + private static String getTimeDistance(String totalTime, String totalDistance) { + StringBuilder builder = new StringBuilder(); + if (totalTime != null && totalTime.length() != 0) { + if (builder.length() != 0) { + builder.append(" \u2027 "); + } + builder.append(totalTime); + } + if (totalDistance != null && totalDistance.length() != 0) { + if (builder.length() != 0) { + builder.append(" "); + } + builder.append("(").append(totalDistance).append(")"); + } + return builder.toString(); } - return new String[] { DateUtils.formatDateTime( - context, time, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL), - DateUtils.formatDateTime(context, time, timeFlags) }; - } + /** + * Gets the date and time as an array of two strings. + * + * @param isRecording true if recording + * @param context the context + * @param time the start time + */ + private static String[] getDateTime(boolean isRecording, Context context, long time, boolean useRelativeTime) { + if (isRecording || time == 0L) { + return new String[]{null, null}; + } + boolean isToday = DateUtils.isToday(time); + int timeFlags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL; - /** - * Sets a text view. - * - * @param context the context - * @param textView the text view - * @param value the value for the text view - * @param addShadow true to add shadow - */ - public static void setTextView(Context context, TextView textView, String value, boolean addShadow) { - if (value == null || value.length() == 0) { - textView.setVisibility(View.GONE); - } else { - textView.setVisibility(View.VISIBLE); - textView.setText(value); - if (addShadow) { - textView.setShadowLayer(5, 0, 2, context.getResources().getColor(android.R.color.black)); - } else { - textView.setShadowLayer(0, 0, 0, 0); - } + if (isToday) { + if (useRelativeTime) { + return new String[]{DateUtils.getRelativeTimeSpanString( + time, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, + DateUtils.FORMAT_ABBREV_RELATIVE).toString(), null}; + } else { + return new String[]{DateUtils.formatDateTime(context, time, timeFlags), null}; + } + } + + return new String[]{DateUtils.formatDateTime( + context, time, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL), + DateUtils.formatDateTime(context, time, timeFlags)}; } - } - /** - * Gets the photo height. - * - * @param context the context - */ - private static int getPhotoHeight(Context context) { - int[] attrs = new int[] { android.R.attr.listPreferredItemHeight }; - TypedArray typeArray = context.obtainStyledAttributes(attrs); - int height = typeArray.getDimensionPixelSize(0, LIST_PREFERRED_ITEM_HEIGHT_DEFAULT); - typeArray.recycle(); - return 2 * height; - } + /** + * Sets a text view. + * + * @param context the context + * @param textView the text view + * @param value the value for the text view + * @param addShadow true to add shadow + */ + public static void setTextView(Context context, TextView textView, String value, boolean addShadow) { + if (value == null || value.length() == 0) { + textView.setVisibility(View.GONE); + } else { + textView.setVisibility(View.VISIBLE); + textView.setText(value); + if (addShadow) { + textView.setShadowLayer(5, 0, 2, context.getResources().getColor(android.R.color.black)); + } else { + textView.setShadowLayer(0, 0, 0, 0); + } + } + } + + /** + * Gets the photo height. + * + * @param context the context + */ + private static int getPhotoHeight(Context context) { + int[] attrs = new int[]{android.R.attr.listPreferredItemHeight}; + TypedArray typeArray = context.obtainStyledAttributes(attrs); + int height = typeArray.getDimensionPixelSize(0, LIST_PREFERRED_ITEM_HEIGHT_DEFAULT); + typeArray.recycle(); + return 2 * height; + } } diff --git a/src/main/java/com/google/android/apps/mytracks/util/LocationUtils.java b/src/main/java/com/google/android/apps/mytracks/util/LocationUtils.java index 590aabb00..fdf2cd5f0 100644 --- a/src/main/java/com/google/android/apps/mytracks/util/LocationUtils.java +++ b/src/main/java/com/google/android/apps/mytracks/util/LocationUtils.java @@ -15,163 +15,164 @@ */ package com.google.android.apps.mytracks.util; -import com.google.android.apps.mytracks.content.Track; - import android.location.Location; import android.util.Log; +import com.google.android.apps.mytracks.content.Track; + import java.util.ArrayList; import java.util.Stack; /** * Utility class for decimating tracks at a given level of precision. - * + * * @author Leif Hendrik Wilden */ public class LocationUtils { - private static final String TAG = LocationUtils.class.getSimpleName(); - - // 1 minute in milliseconds - private static final long MAX_LOCATION_AGE_MS = (long) (UnitConversions.MIN_TO_S - * UnitConversions.S_TO_MS); + private static final String TAG = LocationUtils.class.getSimpleName(); - private LocationUtils() {} + // 1 minute in milliseconds + private static final long MAX_LOCATION_AGE_MS = (long) (UnitConversions.MIN_TO_S + * UnitConversions.S_TO_MS); - /** - * Computes the distance on the two sphere between the point c0 and the line - * segment c1 to c2. - * - * @param c0 the first coordinate - * @param c1 the beginning of the line segment - * @param c2 the end of the lone segment - * @return the distance in m (assuming spherical earth) - */ - private static double distance(final Location c0, final Location c1, final Location c2) { - if (c1.equals(c2)) { - return c2.distanceTo(c0); + private LocationUtils() { } - final double s0lat = c0.getLatitude() * UnitConversions.DEG_TO_RAD; - final double s0lng = c0.getLongitude() * UnitConversions.DEG_TO_RAD; - final double s1lat = c1.getLatitude() * UnitConversions.DEG_TO_RAD; - final double s1lng = c1.getLongitude() * UnitConversions.DEG_TO_RAD; - final double s2lat = c2.getLatitude() * UnitConversions.DEG_TO_RAD; - final double s2lng = c2.getLongitude() * UnitConversions.DEG_TO_RAD; - - double s2s1lat = s2lat - s1lat; - double s2s1lng = s2lng - s1lng; - final double u = ((s0lat - s1lat) * s2s1lat + (s0lng - s1lng) * s2s1lng) - / (s2s1lat * s2s1lat + s2s1lng * s2s1lng); - if (u <= 0) { - return c0.distanceTo(c1); - } - if (u >= 1) { - return c0.distanceTo(c2); - } - Location sa = new Location(""); - sa.setLatitude(c0.getLatitude() - c1.getLatitude()); - sa.setLongitude(c0.getLongitude() - c1.getLongitude()); - Location sb = new Location(""); - sb.setLatitude(u * (c2.getLatitude() - c1.getLatitude())); - sb.setLongitude(u * (c2.getLongitude() - c1.getLongitude())); - return sa.distanceTo(sb); - } - - /** - * Decimates the given locations for a given zoom level. This uses a - * Douglas-Peucker decimation algorithm. - * - * @param tolerance in meters - * @param locations input - * @param decimated output - */ - private static void decimate( - double tolerance, ArrayList locations, ArrayList decimated) { - final int n = locations.size(); - if (n < 1) { - return; - } - int idx; - int maxIdx = 0; - Stack stack = new Stack<>(); - double[] dists = new double[n]; - dists[0] = 1; - dists[n - 1] = 1; - double maxDist; - double dist; - int[] current; - - if (n > 2) { - int[] stackVal = new int[] { 0, (n - 1) }; - stack.push(stackVal); - while (stack.size() > 0) { - current = stack.pop(); - maxDist = 0; - for (idx = current[0] + 1; idx < current[1]; ++idx) { - dist = LocationUtils.distance( - locations.get(idx), locations.get(current[0]), locations.get(current[1])); - if (dist > maxDist) { - maxDist = dist; - maxIdx = idx; - } + /** + * Computes the distance on the two sphere between the point c0 and the line + * segment c1 to c2. + * + * @param c0 the first coordinate + * @param c1 the beginning of the line segment + * @param c2 the end of the lone segment + * @return the distance in m (assuming spherical earth) + */ + private static double distance(final Location c0, final Location c1, final Location c2) { + if (c1.equals(c2)) { + return c2.distanceTo(c0); } - if (maxDist > tolerance) { - dists[maxIdx] = maxDist; - int[] stackValCurMax = { current[0], maxIdx }; - stack.push(stackValCurMax); - int[] stackValMaxCur = { maxIdx, current[1] }; - stack.push(stackValMaxCur); + + final double s0lat = c0.getLatitude() * UnitConversions.DEG_TO_RAD; + final double s0lng = c0.getLongitude() * UnitConversions.DEG_TO_RAD; + final double s1lat = c1.getLatitude() * UnitConversions.DEG_TO_RAD; + final double s1lng = c1.getLongitude() * UnitConversions.DEG_TO_RAD; + final double s2lat = c2.getLatitude() * UnitConversions.DEG_TO_RAD; + final double s2lng = c2.getLongitude() * UnitConversions.DEG_TO_RAD; + + double s2s1lat = s2lat - s1lat; + double s2s1lng = s2lng - s1lng; + final double u = ((s0lat - s1lat) * s2s1lat + (s0lng - s1lng) * s2s1lng) + / (s2s1lat * s2s1lat + s2s1lng * s2s1lng); + if (u <= 0) { + return c0.distanceTo(c1); } - } + if (u >= 1) { + return c0.distanceTo(c2); + } + Location sa = new Location(""); + sa.setLatitude(c0.getLatitude() - c1.getLatitude()); + sa.setLongitude(c0.getLongitude() - c1.getLongitude()); + Location sb = new Location(""); + sb.setLatitude(u * (c2.getLatitude() - c1.getLatitude())); + sb.setLongitude(u * (c2.getLongitude() - c1.getLongitude())); + return sa.distanceTo(sb); } - int i = 0; - idx = 0; - decimated.clear(); - for (Location l : locations) { - if (dists[idx] != 0) { - decimated.add(l); - i++; - } - idx++; + /** + * Decimates the given locations for a given zoom level. This uses a + * Douglas-Peucker decimation algorithm. + * + * @param tolerance in meters + * @param locations input + * @param decimated output + */ + private static void decimate( + double tolerance, ArrayList locations, ArrayList decimated) { + final int n = locations.size(); + if (n < 1) { + return; + } + int idx; + int maxIdx = 0; + Stack stack = new Stack<>(); + double[] dists = new double[n]; + dists[0] = 1; + dists[n - 1] = 1; + double maxDist; + double dist; + int[] current; + + if (n > 2) { + int[] stackVal = new int[]{0, (n - 1)}; + stack.push(stackVal); + while (stack.size() > 0) { + current = stack.pop(); + maxDist = 0; + for (idx = current[0] + 1; idx < current[1]; ++idx) { + dist = LocationUtils.distance( + locations.get(idx), locations.get(current[0]), locations.get(current[1])); + if (dist > maxDist) { + maxDist = dist; + maxIdx = idx; + } + } + if (maxDist > tolerance) { + dists[maxIdx] = maxDist; + int[] stackValCurMax = {current[0], maxIdx}; + stack.push(stackValCurMax); + int[] stackValMaxCur = {maxIdx, current[1]}; + stack.push(stackValMaxCur); + } + } + } + + int i = 0; + idx = 0; + decimated.clear(); + for (Location l : locations) { + if (dists[idx] != 0) { + decimated.add(l); + i++; + } + idx++; + } + Log.d(TAG, "Decimating " + n + " points to " + i + " w/ tolerance = " + tolerance); } - Log.d(TAG, "Decimating " + n + " points to " + i + " w/ tolerance = " + tolerance); - } - /** - * Decimates the given track for the given precision. - * - * @param track a track - * @param precision desired precision in meters - */ - public static void decimate(Track track, double precision) { - ArrayList decimated = new ArrayList<>(); - decimate(precision, track.getLocations(), decimated); - track.setLocations(decimated); - } + /** + * Decimates the given track for the given precision. + * + * @param track a track + * @param precision desired precision in meters + */ + public static void decimate(Track track, double precision) { + ArrayList decimated = new ArrayList<>(); + decimate(precision, track.getLocations(), decimated); + track.setLocations(decimated); + } - /** - * Checks if a given location is a valid (i.e. physically possible) location - * on Earth. Note: The special separator locations (which have latitude = 100) - * will not qualify as valid. Neither will locations with lat=0 and lng=0 as - * these are most likely "bad" measurements which often cause trouble. - * - * @param location the location to test - * @return true if the location is a valid location. - */ - public static boolean isValidLocation(Location location) { - return location != null && Math.abs(location.getLatitude()) <= 90 - && Math.abs(location.getLongitude()) <= 180; - } + /** + * Checks if a given location is a valid (i.e. physically possible) location + * on Earth. Note: The special separator locations (which have latitude = 100) + * will not qualify as valid. Neither will locations with lat=0 and lng=0 as + * these are most likely "bad" measurements which often cause trouble. + * + * @param location the location to test + * @return true if the location is a valid location. + */ + public static boolean isValidLocation(Location location) { + return location != null && Math.abs(location.getLatitude()) <= 90 + && Math.abs(location.getLongitude()) <= 180; + } - /** - * Returns true if a location is old. - * - * @param location the location - */ - public static boolean isLocationOld(Location location) { - return !LocationUtils.isValidLocation(location) - || (System.currentTimeMillis() - location.getTime() > MAX_LOCATION_AGE_MS); - } + /** + * Returns true if a location is old. + * + * @param location the location + */ + public static boolean isLocationOld(Location location) { + return !LocationUtils.isValidLocation(location) + || (System.currentTimeMillis() - location.getTime() > MAX_LOCATION_AGE_MS); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/util/PreferencesUtils.java b/src/main/java/com/google/android/apps/mytracks/util/PreferencesUtils.java index 8a2a9ca2f..feab9691e 100644 --- a/src/main/java/com/google/android/apps/mytracks/util/PreferencesUtils.java +++ b/src/main/java/com/google/android/apps/mytracks/util/PreferencesUtils.java @@ -1,12 +1,12 @@ /* * Copyright 2012 Google Inc. - * + * * 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 @@ -19,227 +19,227 @@ package com.google.android.apps.mytracks.util; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; -import android.text.TextUtils; import com.google.android.apps.mytracks.Constants; import com.google.android.maps.mytracks.R; /** * Utilities to access preferences stored in {@link SharedPreferences}. - * + * * @author Jimmy Shih */ public class PreferencesUtils { - - /* - * Preferences values. The defaults need to match the defaults in the xml - * files. - */ - public static final int AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT = 0; - // Values for auto_resume_track_timeout_key - public static final int AUTO_RESUME_TRACK_TIMEOUT_ALWAYS = -1; - public static final int AUTO_RESUME_TRACK_TIMEOUT_DEFAULT = 10; - public static final int AUTO_RESUME_TRACK_TIMEOUT_NEVER = 0; + /* + * Preferences values. The defaults need to match the defaults in the xml + * files. + */ + public static final int AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT = 0; - public static final String BLUETOOTH_SENSOR_DEFAULT = ""; - - public static final boolean CHART_SHOW_CADENCE_DEFAULT = true; - public static final boolean CHART_SHOW_ELEVATION_DEFAULT = true; - public static final boolean CHART_SHOW_HEART_RATE_DEFAULT = true; - public static final boolean CHART_SHOW_POWER_DEFAULT = true; - public static final boolean CHART_SHOW_SPEED_DEFAULT = true; - public static final String CHART_X_AXIS_DEFAULT = "DISTANCE"; + // Values for auto_resume_track_timeout_key + public static final int AUTO_RESUME_TRACK_TIMEOUT_ALWAYS = -1; + public static final int AUTO_RESUME_TRACK_TIMEOUT_DEFAULT = 10; + public static final int AUTO_RESUME_TRACK_TIMEOUT_NEVER = 0; - public static final String DEFAULT_ACTIVITY_DEFAULT = ""; + public static final String BLUETOOTH_SENSOR_DEFAULT = ""; - // Value for split_frequency_key and voice_frequency_key - public static final int FREQUENCY_OFF = 0; + public static final boolean CHART_SHOW_CADENCE_DEFAULT = true; + public static final boolean CHART_SHOW_ELEVATION_DEFAULT = true; + public static final boolean CHART_SHOW_HEART_RATE_DEFAULT = true; + public static final boolean CHART_SHOW_POWER_DEFAULT = true; + public static final boolean CHART_SHOW_SPEED_DEFAULT = true; + public static final String CHART_X_AXIS_DEFAULT = "DISTANCE"; - public static final int MAP_TYPE_DEFAULT = 1; - public static final int MAX_RECORDING_DISTANCE_DEFAULT = 200; - - // Values for min_recording_interval_key - public static final int MIN_RECORDING_INTERVAL_ADAPT_ACCURACY = -1; - public static final int MIN_RECORDING_INTERVAL_ADAPT_BATTERY_LIFE = -2; - public static final int MIN_RECORDING_INTERVAL_DEFAULT = 0; + public static final String DEFAULT_ACTIVITY_DEFAULT = ""; - public static final int PHOTO_SIZE_DEFAULT = 1024; // 1024 kB - public static final int RECORDING_DISTANCE_INTERVAL_DEFAULT = 10; - - // Values for recording_gps_accuracy - public static final int RECORDING_GPS_ACCURACY_DEFAULT = 50; - public static final int RECORDING_GPS_ACCURACY_EXCELLENT = 10; - public static final int RECORDING_GPS_ACCURACY_POOR = 2000; - - public static final long RECORDING_TRACK_ID_DEFAULT = -1L; - public static final boolean RECORDING_TRACK_PAUSED_DEFAULT = true; + // Value for split_frequency_key and voice_frequency_key + public static final int FREQUENCY_OFF = 0; - public static final int SPLIT_FREQUENCY_DEFAULT = 0; - - // Stats - public static final String STATS_RATE_DEFAULT = "SPEED"; - public static final boolean STATS_SHOW_COORDINATE_DEFAULT = false; - public static final boolean STATS_SHOW_GRADE_ELEVATION_DEFAULT = false; - public static final String STATS_UNITS_DEFAULT = "METRIC"; + public static final int MAP_TYPE_DEFAULT = 1; + public static final int MAX_RECORDING_DISTANCE_DEFAULT = 200; - public static final String TRACK_NAME_DEFAULT = "DATE_ISO_8601_LOCATION"; - - // Track widget - public static final int TRACK_WIDGET_ITEM1_DEFAULT = 3; // moving time - public static final int TRACK_WIDGET_ITEM2_DEFAULT = 0; // distance - public static final int TRACK_WIDGET_ITEM3_DEFAULT = 1; // total time - public static final int TRACK_WIDGET_ITEM4_DEFAULT = 2; // average speed - public static final int VOICE_FREQUENCY_DEFAULT = 0; + // Values for min_recording_interval_key + public static final int MIN_RECORDING_INTERVAL_ADAPT_ACCURACY = -1; + public static final int MIN_RECORDING_INTERVAL_ADAPT_BATTERY_LIFE = -2; + public static final int MIN_RECORDING_INTERVAL_DEFAULT = 0; - private PreferencesUtils() {} + public static final int PHOTO_SIZE_DEFAULT = 1024; // 1024 kB + public static final int RECORDING_DISTANCE_INTERVAL_DEFAULT = 10; - /** - * Gets a preference key - * - * @param context the context - * @param keyId the key id - */ - public static String getKey(Context context, int keyId) { - return context.getString(keyId); - } + // Values for recording_gps_accuracy + public static final int RECORDING_GPS_ACCURACY_DEFAULT = 50; + public static final int RECORDING_GPS_ACCURACY_EXCELLENT = 10; + public static final int RECORDING_GPS_ACCURACY_POOR = 2000; - /** - * Gets a boolean preference value. - * - * @param context the context - * @param keyId the key id - * @param defaultValue the default value - */ - public static boolean getBoolean(Context context, int keyId, boolean defaultValue) { - SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); - return sharedPreferences.getBoolean(getKey(context, keyId), defaultValue); - } + public static final long RECORDING_TRACK_ID_DEFAULT = -1L; + public static final boolean RECORDING_TRACK_PAUSED_DEFAULT = true; - /** - * Sets a boolean preference value. - * - * @param context the context - * @param keyId the key id - * @param value the value - */ - public static void setBoolean(Context context, int keyId, boolean value) { - SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); - Editor editor = sharedPreferences.edit(); - editor.putBoolean(getKey(context, keyId), value); - editor.apply(); - } + public static final int SPLIT_FREQUENCY_DEFAULT = 0; - /** - * Gets an integer preference value. - * - * @param context the context - * @param keyId the key id - * @param defaultValue the default value - */ - public static int getInt(Context context, int keyId, int defaultValue) { - //NOTE: We assume that the data was stored as String due to use of ListPreference. - SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); - String stringValue = sharedPreferences.getString(getKey(context, keyId), null); + // Stats + public static final String STATS_RATE_DEFAULT = "SPEED"; + public static final boolean STATS_SHOW_COORDINATE_DEFAULT = false; + public static final boolean STATS_SHOW_GRADE_ELEVATION_DEFAULT = false; + public static final String STATS_UNITS_DEFAULT = "METRIC"; - int intValue = defaultValue; - try { - intValue = Integer.parseInt(stringValue); - } catch (NumberFormatException e){ + public static final String TRACK_NAME_DEFAULT = "DATE_ISO_8601_LOCATION"; + + // Track widget + public static final int TRACK_WIDGET_ITEM1_DEFAULT = 3; // moving time + public static final int TRACK_WIDGET_ITEM2_DEFAULT = 0; // distance + public static final int TRACK_WIDGET_ITEM3_DEFAULT = 1; // total time + public static final int TRACK_WIDGET_ITEM4_DEFAULT = 2; // average speed + public static final int VOICE_FREQUENCY_DEFAULT = 0; + + private PreferencesUtils() { } - return intValue; - } - /** - * Sets an integer preference value. - * - * @param context the context - * @param keyId the key id - * @param value the value - */ - public static void setInt(Context context, int keyId, int value) { - SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); - Editor editor = sharedPreferences.edit(); - editor.putInt(getKey(context, keyId), value); - editor.apply(); - } + /** + * Gets a preference key + * + * @param context the context + * @param keyId the key id + */ + public static String getKey(Context context, int keyId) { + return context.getString(keyId); + } - /** - * Gets a long preference value. - * - * @param context the context - * @param keyId the key id - */ - public static long getLong(Context context, int keyId) { - SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); - return sharedPreferences.getLong(getKey(context, keyId), -1L); - } + /** + * Gets a boolean preference value. + * + * @param context the context + * @param keyId the key id + * @param defaultValue the default value + */ + public static boolean getBoolean(Context context, int keyId, boolean defaultValue) { + SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); + return sharedPreferences.getBoolean(getKey(context, keyId), defaultValue); + } - /** - * Sets a long preference value. - * - * @param context the context - * @param keyId the key id - * @param value the value - */ - public static void setLong(Context context, int keyId, long value) { - SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); - Editor editor = sharedPreferences.edit(); - editor.putLong(getKey(context, keyId), value); - editor.apply(); - } + /** + * Sets a boolean preference value. + * + * @param context the context + * @param keyId the key id + * @param value the value + */ + public static void setBoolean(Context context, int keyId, boolean value) { + SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); + Editor editor = sharedPreferences.edit(); + editor.putBoolean(getKey(context, keyId), value); + editor.apply(); + } - /** - * Gets a string preference value. - * - * @param context the context - * @param keyId the key id - * @param defaultValue default value - */ - public static String getString(Context context, int keyId, String defaultValue) { - SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); - return sharedPreferences.getString(getKey(context, keyId), defaultValue); - } + /** + * Gets an integer preference value. + * + * @param context the context + * @param keyId the key id + * @param defaultValue the default value + */ + public static int getInt(Context context, int keyId, int defaultValue) { + //NOTE: We assume that the data was stored as String due to use of ListPreference. + SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); + String stringValue = sharedPreferences.getString(getKey(context, keyId), null); - /** - * Sets a string preference value. - * - * @param context the context - * @param keyId the key id - * @param value the value - */ - public static void setString(Context context, int keyId, String value) { - SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); - Editor editor = sharedPreferences.edit(); - editor.putString(getKey(context, keyId), value); - editor.apply(); - } - - /** - * Returns true if metric units. - * - * @param context the context - */ - public static boolean isMetricUnits(Context context) { - return STATS_UNITS_DEFAULT.equals(getString(context, R.string.stats_units_key, STATS_UNITS_DEFAULT)); - } + int intValue = defaultValue; + try { + intValue = Integer.parseInt(stringValue); + } catch (NumberFormatException e) { + } + return intValue; + } - /** - * Returns true if the preferred rate is speed, false if the preferred rate is - * pace. - * - * @param context the context - */ - public static boolean isReportSpeed(Context context) { - return STATS_RATE_DEFAULT.equals(getString(context, R.string.stats_rate_key, STATS_RATE_DEFAULT)); - } + /** + * Sets an integer preference value. + * + * @param context the context + * @param keyId the key id + * @param value the value + */ + public static void setInt(Context context, int keyId, int value) { + SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); + Editor editor = sharedPreferences.edit(); + editor.putInt(getKey(context, keyId), value); + editor.apply(); + } - /** - * Returns true if chart x axis is by distance, false if by time. - * - * @param context the context - */ - public static boolean isChartByDistance(Context context) { - return CHART_X_AXIS_DEFAULT.equals(getString(context, R.string.chart_x_axis_key, CHART_X_AXIS_DEFAULT)); - } + /** + * Gets a long preference value. + * + * @param context the context + * @param keyId the key id + */ + public static long getLong(Context context, int keyId) { + SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); + return sharedPreferences.getLong(getKey(context, keyId), -1L); + } + + /** + * Sets a long preference value. + * + * @param context the context + * @param keyId the key id + * @param value the value + */ + public static void setLong(Context context, int keyId, long value) { + SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); + Editor editor = sharedPreferences.edit(); + editor.putLong(getKey(context, keyId), value); + editor.apply(); + } + + /** + * Gets a string preference value. + * + * @param context the context + * @param keyId the key id + * @param defaultValue default value + */ + public static String getString(Context context, int keyId, String defaultValue) { + SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); + return sharedPreferences.getString(getKey(context, keyId), defaultValue); + } + + /** + * Sets a string preference value. + * + * @param context the context + * @param keyId the key id + * @param value the value + */ + public static void setString(Context context, int keyId, String value) { + SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); + Editor editor = sharedPreferences.edit(); + editor.putString(getKey(context, keyId), value); + editor.apply(); + } + + /** + * Returns true if metric units. + * + * @param context the context + */ + public static boolean isMetricUnits(Context context) { + return STATS_UNITS_DEFAULT.equals(getString(context, R.string.stats_units_key, STATS_UNITS_DEFAULT)); + } + + /** + * Returns true if the preferred rate is speed, false if the preferred rate is + * pace. + * + * @param context the context + */ + public static boolean isReportSpeed(Context context) { + return STATS_RATE_DEFAULT.equals(getString(context, R.string.stats_rate_key, STATS_RATE_DEFAULT)); + } + + /** + * Returns true if chart x axis is by distance, false if by time. + * + * @param context the context + */ + public static boolean isChartByDistance(Context context) { + return CHART_X_AXIS_DEFAULT.equals(getString(context, R.string.chart_x_axis_key, CHART_X_AXIS_DEFAULT)); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/util/StatsUtils.java b/src/main/java/com/google/android/apps/mytracks/util/StatsUtils.java index ef5d0de27..c22086997 100644 --- a/src/main/java/com/google/android/apps/mytracks/util/StatsUtils.java +++ b/src/main/java/com/google/android/apps/mytracks/util/StatsUtils.java @@ -37,329 +37,330 @@ import java.util.Locale; */ public class StatsUtils { - private static final String GRADE_PERCENTAGE = "%"; - private static final String GRADE_FORMAT = "%1$d"; + private static final String GRADE_PERCENTAGE = "%"; + private static final String GRADE_FORMAT = "%1$d"; - private StatsUtils() {} - - /** - * Sets the location values. - * - * @param context the context - * @param activity the activity for finding views. If null, the view cannot be - * null - * @param view the containing view for finding views. If null, the activity - * cannot be null - * @param location the location - * @param isRecording true if recording - */ - public static void setLocationValues(Context context, Activity activity, View view, Location location, boolean isRecording) { - boolean metricUnits = PreferencesUtils.isMetricUnits(context); - boolean reportSpeed = PreferencesUtils.isReportSpeed(context); - - // Set speed/pace - View speed = getView(activity, view, R.id.stats_speed); - speed.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE); - - if (isRecording) { - double value = isRecording && location != null && location.hasSpeed() ? location.getSpeed() : Double.NaN; - setSpeed(context, speed, R.string.stats_speed, R.string.stats_pace, value, metricUnits, reportSpeed); + private StatsUtils() { } - // Set elevation - boolean showGradeElevation = PreferencesUtils.getBoolean(context, R.string.stats_show_grade_elevation_key, PreferencesUtils.STATS_SHOW_GRADE_ELEVATION_DEFAULT) && isRecording; - View elevation = getView(activity, view, R.id.stats_elevation); - elevation.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE); + /** + * Sets the location values. + * + * @param context the context + * @param activity the activity for finding views. If null, the view cannot be + * null + * @param view the containing view for finding views. If null, the activity + * cannot be null + * @param location the location + * @param isRecording true if recording + */ + public static void setLocationValues(Context context, Activity activity, View view, Location location, boolean isRecording) { + boolean metricUnits = PreferencesUtils.isMetricUnits(context); + boolean reportSpeed = PreferencesUtils.isReportSpeed(context); - if (showGradeElevation) { - double altitude = location != null && location.hasAltitude() ? location.getAltitude() : Double.NaN; - setElevationValue(context, elevation, -1, altitude, metricUnits); + // Set speed/pace + View speed = getView(activity, view, R.id.stats_speed); + speed.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE); + + if (isRecording) { + double value = isRecording && location != null && location.hasSpeed() ? location.getSpeed() : Double.NaN; + setSpeed(context, speed, R.string.stats_speed, R.string.stats_pace, value, metricUnits, reportSpeed); + } + + // Set elevation + boolean showGradeElevation = PreferencesUtils.getBoolean(context, R.string.stats_show_grade_elevation_key, PreferencesUtils.STATS_SHOW_GRADE_ELEVATION_DEFAULT) && isRecording; + View elevation = getView(activity, view, R.id.stats_elevation); + elevation.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE); + + if (showGradeElevation) { + double altitude = location != null && location.hasAltitude() ? location.getAltitude() : Double.NaN; + setElevationValue(context, elevation, -1, altitude, metricUnits); + } + + // Set coordinate + boolean showCoordinate = isRecording && PreferencesUtils.getBoolean(context, R.string.stats_show_coordinate_key, PreferencesUtils.STATS_SHOW_COORDINATE_DEFAULT); + View coordinateSeparator = getView(activity, view, R.id.stats_coordinate_separator); + View coordinateContainer = getView(activity, view, R.id.stats_coordinate_container); + + if (coordinateSeparator != null) { + coordinateSeparator.setVisibility(showCoordinate ? View.VISIBLE : View.GONE); + } + coordinateContainer.setVisibility(showCoordinate ? View.VISIBLE : View.GONE); + + if (showCoordinate) { + double latitude = location != null ? location.getLatitude() : Double.NaN; + double longitude = location != null ? location.getLongitude() : Double.NaN; + setCoordinateValue(context, getView(activity, view, R.id.stats_latitude), R.string.stats_latitude, latitude); + setCoordinateValue(context, getView(activity, view, R.id.stats_longitude), R.string.stats_longitude, longitude); + } } - // Set coordinate - boolean showCoordinate = isRecording && PreferencesUtils.getBoolean(context, R.string.stats_show_coordinate_key, PreferencesUtils.STATS_SHOW_COORDINATE_DEFAULT); - View coordinateSeparator = getView(activity, view, R.id.stats_coordinate_separator); - View coordinateContainer = getView(activity, view, R.id.stats_coordinate_container); + public static void setSensorData(Context context, Activity activity, SensorDataSet sensorDataSet, boolean isRecording) { + { + // SensorDataSet: heart rate + int isVisible = View.VISIBLE; + if (!isRecording || PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(PreferencesUtils.getString(context, R.string.bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT))) { + isVisible = View.INVISIBLE; + } + getView(activity, null, R.id.stats_sensor_container).setVisibility(isVisible); - if (coordinateSeparator != null) { - coordinateSeparator.setVisibility(showCoordinate ? View.VISIBLE : View.GONE); - } - coordinateContainer.setVisibility(showCoordinate ? View.VISIBLE : View.GONE); - - if (showCoordinate) { - double latitude = location != null ? location.getLatitude() : Double.NaN; - double longitude = location != null ? location.getLongitude() : Double.NaN; - setCoordinateValue(context, getView(activity, view, R.id.stats_latitude), R.string.stats_latitude, latitude); - setCoordinateValue(context, getView(activity, view, R.id.stats_longitude), R.string.stats_longitude, longitude); - } - } - - public static void setSensorData(Context context, Activity activity, SensorDataSet sensorDataSet, boolean isRecording) { - { - // SensorDataSet: heart rate - int isVisible = View.VISIBLE; - if (!isRecording || PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(PreferencesUtils.getString(context, R.string.bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT))) { - isVisible = View.INVISIBLE; - } - getView(activity, null, R.id.stats_sensor_container).setVisibility(isVisible); - - View viewValue = getView(activity, null, R.id.stats_sensor_heart_rate_value); - View viewSensor = getView(activity, null, R.id.stats_sensor_heart_rate_sensor); - if (isRecording) { - setHeartRateData(context, viewValue, viewSensor, sensorDataSet); - } - } - } - - /** - * Sets the total time value. - * - * @param activity the activity - * @param totalTime the total time - */ - public static void setTotalTimeValue(Activity activity, long totalTime) { - setTimeValue(activity, activity.findViewById(R.id.stats_total_time), R.string.stats_total_time, totalTime); - } - - /** - * Sets the trip statistics values. - * - * @param context the context - * @param activity the activity for finding views. If null, then view cannot - * be null - * @param view the containing view for finding views. If null, the activity - * cannot be null - * @param tripStatistics the trip statistics - * @param trackIconValue the track icon value or null to hide the track icon - * spinner - */ - public static void setTripStatisticsValues(Context context, Activity activity, View view, TripStatistics tripStatistics, String trackIconValue) { - boolean metricUnits = PreferencesUtils.isMetricUnits(context); - boolean reportSpeed = PreferencesUtils.isReportSpeed(context); - - // Set total distance - double totalDistance = tripStatistics == null ? Double.NaN : tripStatistics.getTotalDistance(); - setDistanceValue(context, getView(activity, view, R.id.stats_distance), totalDistance, metricUnits); - - // Set activity type - Spinner spinner = (Spinner) getView(activity, view, R.id.stats_activity_type_icon); - spinner.setVisibility(trackIconValue != null ? View.VISIBLE : View.GONE); - if (trackIconValue != null) { - TrackIconUtils.setIconSpinner(spinner, trackIconValue); + View viewValue = getView(activity, null, R.id.stats_sensor_heart_rate_value); + View viewSensor = getView(activity, null, R.id.stats_sensor_heart_rate_sensor); + if (isRecording) { + setHeartRateData(context, viewValue, viewSensor, sensorDataSet); + } + } } - // Set total time - setTimeValue(context, getView(activity, view, R.id.stats_total_time), R.string.stats_total_time, tripStatistics != null ? tripStatistics.getTotalTime() : -1L); - - // Set moving time - setTimeValue(context, getView(activity, view, R.id.stats_moving_time), R.string.stats_moving_time, tripStatistics != null ? tripStatistics.getMovingTime() : -1L); - - // Set average speed/pace - double averageSpeed = tripStatistics != null ? tripStatistics.getAverageSpeed() : Double.NaN; - setSpeed(context, getView(activity, view, R.id.stats_average_speed), R.string.stats_average_speed, R.string.stats_average_pace, averageSpeed, metricUnits, reportSpeed); - - // Set max speed/pace - double maxSpeed = tripStatistics == null ? Double.NaN : tripStatistics.getMaxSpeed(); - setSpeed(context, getView(activity, view, R.id.stats_max_speed), R.string.stats_max_speed, R.string.stats_fastest_pace, maxSpeed, metricUnits, reportSpeed); - - // Set average moving speed/pace - double averageMovingSpeed = tripStatistics != null ? tripStatistics.getAverageMovingSpeed() : Double.NaN; - setSpeed(context, getView(activity, view, R.id.stats_average_moving_speed), R.string.stats_average_moving_speed, R.string.stats_average_moving_pace, averageMovingSpeed, metricUnits, reportSpeed); - - // Set grade/elevation - boolean showGradeElevation = PreferencesUtils.getBoolean(context,R.string.stats_show_grade_elevation_key, PreferencesUtils.STATS_SHOW_GRADE_ELEVATION_DEFAULT); - View gradeElevationSeparator = getView(activity, view, R.id.stats_grade_elevation_separator); - gradeElevationSeparator.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE); - - View gradeElevationContainer = getView(activity, view, R.id.stats_grade_elevation_container); - gradeElevationContainer.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE); - - if (showGradeElevation) { - // Set grade - double minGrade = tripStatistics == null ? Double.NaN : tripStatistics.getMinGrade(); - double maxGrade = tripStatistics == null ? Double.NaN : tripStatistics.getMaxGrade(); - setGradeValue(context, getView(activity, view, R.id.stats_grade_min), R.string.stats_min, minGrade); - setGradeValue(context, getView(activity, view, R.id.stats_grade_max), R.string.stats_max, maxGrade); - - // Set elevation - double elevationGain = tripStatistics == null ? Double.NaN : tripStatistics.getTotalElevationGain(); - double minElevation = tripStatistics == null ? Double.NaN : tripStatistics.getMinElevation(); - double maxElevation = tripStatistics == null ? Double.NaN : tripStatistics.getMaxElevation(); - setElevationValue(context, getView(activity, view, R.id.stats_elevation_gain), R.string.stats_gain, elevationGain, metricUnits); - setElevationValue(context, getView(activity, view, R.id.stats_elevation_min), R.string.stats_min, minElevation, metricUnits); - setElevationValue(context, getView(activity, view, R.id.stats_elevation_max), R.string.stats_max, maxElevation, metricUnits); - } - } - - /** - * Sets speed. - * - * @param context the context - * @param view the containing view - * @param speedLabelId the speed label id - * @param paceLabelId the pace label id - * @param speed the speed in meters per second - * @param metricUnits true if metric units - * @param reportSpeed true if report speed - */ - private static void setSpeed(Context context, View view, int speedLabelId, int paceLabelId, double speed, boolean metricUnits, boolean reportSpeed) { - String[] parts = StringUtils.getSpeedParts(context, speed, metricUnits, reportSpeed); - setItem(context, view, reportSpeed ? speedLabelId : paceLabelId, parts[0], parts[1]); - } - - /** - * Sets distance value. - * - * @param context the context - * @param view the containing view - * @param distance the distance in meters - * @param metricUnits true if metric units - */ - private static void setDistanceValue(Context context, View view, double distance, boolean metricUnits) { - String[] parts = StringUtils.getDistanceParts(context, distance, metricUnits); - setItem(context, view, R.string.stats_distance, parts[0], parts[1]); - } - - /** - * Sets a time value. - * - * @param context the context - * @param view the containing view - * @param labelId the label id - * @param time the time - */ - private static void setTimeValue(Context context, View view, int labelId, long time) { - String value = time == -1L ? null : StringUtils.formatElapsedTime(time); - setItem(context, view, labelId, value, null); - } - - /** - * Sets an elevation value. - * - * @param context the context - * @param view the containing view - * @param labelId the label id - * @param elevation the elevation in meters - * @param metricUnits true if metric units - */ - private static void setElevationValue(Context context, View view, int labelId, double elevation, boolean metricUnits) { - String value; - String unit; - if (Double.isNaN(elevation) || Double.isInfinite(elevation)) { - value = null; - unit = null; - } else { - if (metricUnits) { - value = StringUtils.formatDecimal(elevation); - unit = context.getString(R.string.unit_meter); - } else { - elevation *= UnitConversions.M_TO_FT; - value = StringUtils.formatDecimal(elevation); - unit = context.getString(R.string.unit_feet); - } - } - setItem(context, view, labelId, value, unit); - } - - /** - * Sets a grade value. - * - * @param context the context - * @param view the containing view - * @param labelId the label id - * @param grade the grade in fraction between 0 and 1 - */ - private static void setGradeValue(Context context, View view, int labelId, double grade) { - String value = Double.isNaN(grade) || Double.isInfinite(grade) ? null - : String.format(Locale.getDefault(), GRADE_FORMAT, Math.round(grade * 100)); - setItem(context, view, labelId, value, GRADE_PERCENTAGE); - } - - /** - * Sets a coordinate value. - * - * @param context the context - * @param view the containing view - * @param labelId the label id - * @param coordinate the coordinate in degrees - */ - private static void setCoordinateValue(Context context, View view, int labelId, double coordinate) { - String value = Double.isNaN(coordinate) || Double.isInfinite(coordinate) ? null - : StringUtils.formatCoordinate(coordinate); - setItem(context, view, labelId, value, null); - } - - /** - * Sets a heart rate value (sensor) - * - * @param context the context - * @param viewValue the containing view - * @param sensorDataSet the sensorDataSet - */ - private static void setHeartRateData(Context context, View viewValue, View viewSensor, SensorDataSet sensorDataSet) { - String heartRate = context.getString(R.string.value_unknown); - String sensor = context.getString(R.string.value_unknown); - if (sensorDataSet != null && sensorDataSet.hasHeartRate() && sensorDataSet.isRecent(SensorManager.MAX_SENSOR_DATE_SET_AGE)) { - heartRate = StringUtils.formatDecimal(sensorDataSet.getHeartRate(), 0); - sensor = sensorDataSet.getSensorName(); - } - setItem(context, viewValue, R.string.sensor_state_heart_rate, heartRate, context.getString(R.string.sensor_unit_beats_per_minute)); - setItem(context, viewSensor, R.string.settings_sensor_bluetooth_sensor, sensor, null); - } - - /** - * Sets an item. - * - * @param context the context - * @param view the containing view - * @param labelId the label id. -1 to hide the label - * @param value the value, can be null - * @param unit the unit. Null to hide the unit - */ - private static void setItem(Context context, View view, int labelId, CharSequence value, CharSequence unit) { - TextView labelTextView = view.findViewById(R.id.stats_label); - if (labelTextView != null) { - if (labelId == -1) { - labelTextView.setVisibility(View.GONE); - } else { - labelTextView.setVisibility(View.VISIBLE); - labelTextView.setText(labelId); - } + /** + * Sets the total time value. + * + * @param activity the activity + * @param totalTime the total time + */ + public static void setTotalTimeValue(Activity activity, long totalTime) { + setTimeValue(activity, activity.findViewById(R.id.stats_total_time), R.string.stats_total_time, totalTime); } - TextView valueTextView = view.findViewById(R.id.stats_value); - TextView unitTextView = view.findViewById(R.id.stats_unit); - if (valueTextView == null || unitTextView == null) { - return; + /** + * Sets the trip statistics values. + * + * @param context the context + * @param activity the activity for finding views. If null, then view cannot + * be null + * @param view the containing view for finding views. If null, the activity + * cannot be null + * @param tripStatistics the trip statistics + * @param trackIconValue the track icon value or null to hide the track icon + * spinner + */ + public static void setTripStatisticsValues(Context context, Activity activity, View view, TripStatistics tripStatistics, String trackIconValue) { + boolean metricUnits = PreferencesUtils.isMetricUnits(context); + boolean reportSpeed = PreferencesUtils.isReportSpeed(context); + + // Set total distance + double totalDistance = tripStatistics == null ? Double.NaN : tripStatistics.getTotalDistance(); + setDistanceValue(context, getView(activity, view, R.id.stats_distance), totalDistance, metricUnits); + + // Set activity type + Spinner spinner = (Spinner) getView(activity, view, R.id.stats_activity_type_icon); + spinner.setVisibility(trackIconValue != null ? View.VISIBLE : View.GONE); + if (trackIconValue != null) { + TrackIconUtils.setIconSpinner(spinner, trackIconValue); + } + + // Set total time + setTimeValue(context, getView(activity, view, R.id.stats_total_time), R.string.stats_total_time, tripStatistics != null ? tripStatistics.getTotalTime() : -1L); + + // Set moving time + setTimeValue(context, getView(activity, view, R.id.stats_moving_time), R.string.stats_moving_time, tripStatistics != null ? tripStatistics.getMovingTime() : -1L); + + // Set average speed/pace + double averageSpeed = tripStatistics != null ? tripStatistics.getAverageSpeed() : Double.NaN; + setSpeed(context, getView(activity, view, R.id.stats_average_speed), R.string.stats_average_speed, R.string.stats_average_pace, averageSpeed, metricUnits, reportSpeed); + + // Set max speed/pace + double maxSpeed = tripStatistics == null ? Double.NaN : tripStatistics.getMaxSpeed(); + setSpeed(context, getView(activity, view, R.id.stats_max_speed), R.string.stats_max_speed, R.string.stats_fastest_pace, maxSpeed, metricUnits, reportSpeed); + + // Set average moving speed/pace + double averageMovingSpeed = tripStatistics != null ? tripStatistics.getAverageMovingSpeed() : Double.NaN; + setSpeed(context, getView(activity, view, R.id.stats_average_moving_speed), R.string.stats_average_moving_speed, R.string.stats_average_moving_pace, averageMovingSpeed, metricUnits, reportSpeed); + + // Set grade/elevation + boolean showGradeElevation = PreferencesUtils.getBoolean(context, R.string.stats_show_grade_elevation_key, PreferencesUtils.STATS_SHOW_GRADE_ELEVATION_DEFAULT); + View gradeElevationSeparator = getView(activity, view, R.id.stats_grade_elevation_separator); + gradeElevationSeparator.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE); + + View gradeElevationContainer = getView(activity, view, R.id.stats_grade_elevation_container); + gradeElevationContainer.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE); + + if (showGradeElevation) { + // Set grade + double minGrade = tripStatistics == null ? Double.NaN : tripStatistics.getMinGrade(); + double maxGrade = tripStatistics == null ? Double.NaN : tripStatistics.getMaxGrade(); + setGradeValue(context, getView(activity, view, R.id.stats_grade_min), R.string.stats_min, minGrade); + setGradeValue(context, getView(activity, view, R.id.stats_grade_max), R.string.stats_max, maxGrade); + + // Set elevation + double elevationGain = tripStatistics == null ? Double.NaN : tripStatistics.getTotalElevationGain(); + double minElevation = tripStatistics == null ? Double.NaN : tripStatistics.getMinElevation(); + double maxElevation = tripStatistics == null ? Double.NaN : tripStatistics.getMaxElevation(); + setElevationValue(context, getView(activity, view, R.id.stats_elevation_gain), R.string.stats_gain, elevationGain, metricUnits); + setElevationValue(context, getView(activity, view, R.id.stats_elevation_min), R.string.stats_min, minElevation, metricUnits); + setElevationValue(context, getView(activity, view, R.id.stats_elevation_max), R.string.stats_max, maxElevation, metricUnits); + } } - if (value == null) { - value = context.getString(R.string.value_unknown); - unitTextView.setVisibility(View.GONE); - } else { - if (unit == null) { - unitTextView.setVisibility(View.GONE); - } else { - unitTextView.setVisibility(View.VISIBLE); - unitTextView.setText(unit); - } + /** + * Sets speed. + * + * @param context the context + * @param view the containing view + * @param speedLabelId the speed label id + * @param paceLabelId the pace label id + * @param speed the speed in meters per second + * @param metricUnits true if metric units + * @param reportSpeed true if report speed + */ + private static void setSpeed(Context context, View view, int speedLabelId, int paceLabelId, double speed, boolean metricUnits, boolean reportSpeed) { + String[] parts = StringUtils.getSpeedParts(context, speed, metricUnits, reportSpeed); + setItem(context, view, reportSpeed ? speedLabelId : paceLabelId, parts[0], parts[1]); } - valueTextView.setText(value); - } - /** - * Get a view. - * - * @param activity the activity - * @param view the containing view - * @param id the id - */ - //TODO What is the difference between the two cases? Is it (still) necessary? - private static View getView(Activity activity, View view, int id) { - if (activity != null) { - return activity.findViewById(id); - } else { - return view.findViewById(id); + /** + * Sets distance value. + * + * @param context the context + * @param view the containing view + * @param distance the distance in meters + * @param metricUnits true if metric units + */ + private static void setDistanceValue(Context context, View view, double distance, boolean metricUnits) { + String[] parts = StringUtils.getDistanceParts(context, distance, metricUnits); + setItem(context, view, R.string.stats_distance, parts[0], parts[1]); + } + + /** + * Sets a time value. + * + * @param context the context + * @param view the containing view + * @param labelId the label id + * @param time the time + */ + private static void setTimeValue(Context context, View view, int labelId, long time) { + String value = time == -1L ? null : StringUtils.formatElapsedTime(time); + setItem(context, view, labelId, value, null); + } + + /** + * Sets an elevation value. + * + * @param context the context + * @param view the containing view + * @param labelId the label id + * @param elevation the elevation in meters + * @param metricUnits true if metric units + */ + private static void setElevationValue(Context context, View view, int labelId, double elevation, boolean metricUnits) { + String value; + String unit; + if (Double.isNaN(elevation) || Double.isInfinite(elevation)) { + value = null; + unit = null; + } else { + if (metricUnits) { + value = StringUtils.formatDecimal(elevation); + unit = context.getString(R.string.unit_meter); + } else { + elevation *= UnitConversions.M_TO_FT; + value = StringUtils.formatDecimal(elevation); + unit = context.getString(R.string.unit_feet); + } + } + setItem(context, view, labelId, value, unit); + } + + /** + * Sets a grade value. + * + * @param context the context + * @param view the containing view + * @param labelId the label id + * @param grade the grade in fraction between 0 and 1 + */ + private static void setGradeValue(Context context, View view, int labelId, double grade) { + String value = Double.isNaN(grade) || Double.isInfinite(grade) ? null + : String.format(Locale.getDefault(), GRADE_FORMAT, Math.round(grade * 100)); + setItem(context, view, labelId, value, GRADE_PERCENTAGE); + } + + /** + * Sets a coordinate value. + * + * @param context the context + * @param view the containing view + * @param labelId the label id + * @param coordinate the coordinate in degrees + */ + private static void setCoordinateValue(Context context, View view, int labelId, double coordinate) { + String value = Double.isNaN(coordinate) || Double.isInfinite(coordinate) ? null + : StringUtils.formatCoordinate(coordinate); + setItem(context, view, labelId, value, null); + } + + /** + * Sets a heart rate value (sensor) + * + * @param context the context + * @param viewValue the containing view + * @param sensorDataSet the sensorDataSet + */ + private static void setHeartRateData(Context context, View viewValue, View viewSensor, SensorDataSet sensorDataSet) { + String heartRate = context.getString(R.string.value_unknown); + String sensor = context.getString(R.string.value_unknown); + if (sensorDataSet != null && sensorDataSet.hasHeartRate() && sensorDataSet.isRecent(SensorManager.MAX_SENSOR_DATE_SET_AGE)) { + heartRate = StringUtils.formatDecimal(sensorDataSet.getHeartRate(), 0); + sensor = sensorDataSet.getSensorName(); + } + setItem(context, viewValue, R.string.sensor_state_heart_rate, heartRate, context.getString(R.string.sensor_unit_beats_per_minute)); + setItem(context, viewSensor, R.string.settings_sensor_bluetooth_sensor, sensor, null); + } + + /** + * Sets an item. + * + * @param context the context + * @param view the containing view + * @param labelId the label id. -1 to hide the label + * @param value the value, can be null + * @param unit the unit. Null to hide the unit + */ + private static void setItem(Context context, View view, int labelId, CharSequence value, CharSequence unit) { + TextView labelTextView = view.findViewById(R.id.stats_label); + if (labelTextView != null) { + if (labelId == -1) { + labelTextView.setVisibility(View.GONE); + } else { + labelTextView.setVisibility(View.VISIBLE); + labelTextView.setText(labelId); + } + } + + TextView valueTextView = view.findViewById(R.id.stats_value); + TextView unitTextView = view.findViewById(R.id.stats_unit); + if (valueTextView == null || unitTextView == null) { + return; + } + + if (value == null) { + value = context.getString(R.string.value_unknown); + unitTextView.setVisibility(View.GONE); + } else { + if (unit == null) { + unitTextView.setVisibility(View.GONE); + } else { + unitTextView.setVisibility(View.VISIBLE); + unitTextView.setText(unit); + } + } + valueTextView.setText(value); + } + + /** + * Get a view. + * + * @param activity the activity + * @param view the containing view + * @param id the id + */ + //TODO What is the difference between the two cases? Is it (still) necessary? + private static View getView(Activity activity, View view, int id) { + if (activity != null) { + return activity.findViewById(id); + } else { + return view.findViewById(id); + } } - } } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/util/StringUtils.java b/src/main/java/com/google/android/apps/mytracks/util/StringUtils.java index 95c8244ea..f3d739515 100644 --- a/src/main/java/com/google/android/apps/mytracks/util/StringUtils.java +++ b/src/main/java/com/google/android/apps/mytracks/util/StringUtils.java @@ -34,386 +34,388 @@ import java.util.regex.Pattern; /** * Various string manipulation methods. - * + * * @author Sandor Dornbush * @author Rodrigo Damazio */ public class StringUtils { - private static final String COORDINATE_DEGREE = "\u00B0"; - private static final SimpleDateFormat ISO_8601_DATE_TIME_FORMAT = new SimpleDateFormat( - "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); - private static final SimpleDateFormat ISO_8601_BASE = new SimpleDateFormat( - "yyyy-MM-dd'T'HH:mm:ss", Locale.US); - private static final Pattern ISO_8601_EXTRAS = Pattern.compile( - "^(\\.\\d+)?(?:Z|([+-])(\\d{2}):(\\d{2}))?$"); - static { - ISO_8601_DATE_TIME_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); - ISO_8601_BASE.setTimeZone(TimeZone.getTimeZone("UTC")); - } + private static final String COORDINATE_DEGREE = "\u00B0"; + private static final SimpleDateFormat ISO_8601_DATE_TIME_FORMAT = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); + private static final SimpleDateFormat ISO_8601_BASE = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss", Locale.US); + private static final Pattern ISO_8601_EXTRAS = Pattern.compile( + "^(\\.\\d+)?(?:Z|([+-])(\\d{2}):(\\d{2}))?$"); - private StringUtils() {} + static { + ISO_8601_DATE_TIME_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); + ISO_8601_BASE.setTimeZone(TimeZone.getTimeZone("UTC")); + } - /** - * Formats the date and time based on user's phone date/time preferences. - * - * @param context the context - * @param time the time in milliseconds - */ - public static String formatDateTime(Context context, long time) { - return DateUtils.formatDateTime( - context, time, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE) + " " - + DateUtils.formatDateTime(context, time, DateUtils.FORMAT_SHOW_TIME); - } + private StringUtils() { + } - /** - * Formats the time using the ISO 8601 date time format with fractional - * seconds in UTC time zone. - * - * @param time the time in milliseconds - */ - public static String formatDateTimeIso8601(long time) { - return ISO_8601_DATE_TIME_FORMAT.format(time); - } - - /** - * Formats the elapsed timed in the form "MM:SS" or "H:MM:SS". - * - * @param time the time in milliseconds - */ - public static String formatElapsedTime(long time) { - /* - * TODO - * Temporary workaround for DateUtils.formatElapsedTime(time * MS_TO_S). In API - * level 17, it returns strings like "1:0:00" instead of "1:00:00", which - * breaks several unit tests. + /** + * Formats the date and time based on user's phone date/time preferences. + * + * @param context the context + * @param time the time in milliseconds */ - if (time < 0) { - return "-"; - } - long hours = 0; - long minutes = 0; - long elapsedSeconds = (long) (time * UnitConversions.MS_TO_S); - - if (elapsedSeconds >= 3600) { - hours = elapsedSeconds / 3600; - elapsedSeconds -= hours * 3600; - } - if (elapsedSeconds >= 60) { - minutes = elapsedSeconds / 60; - elapsedSeconds -= minutes * 60; - } - long seconds = elapsedSeconds; - - if (hours > 0) { - return String.format(Locale.US, "%d:%02d:%02d", hours, minutes, seconds); - } else { - return String.format(Locale.US, "%02d:%02d", minutes, seconds); - } - } - - /** - * Formats the elapsed time in the form "H:MM:SS". - * - * @param time the time in milliseconds - */ - public static String formatElapsedTimeWithHour(long time) { - String value = formatElapsedTime(time); - return TextUtils.split(value, ":").length == 2 ? "0:" + value : value; - } - - /** - * Formats the distance. - * - * @param context the context - * @param distance the distance in meters - * @param metricUnits true to use metric units. False to use imperial units - */ - public static String formatDistance(Context context, double distance, boolean metricUnits) { - if (Double.isNaN(distance) || Double.isInfinite(distance)) { - return context.getString(R.string.value_unknown); - } - if (metricUnits) { - if (distance > 500.0) { - distance *= UnitConversions.M_TO_KM; - return context.getString(R.string.value_float_kilometer, distance); - } else { - return context.getString(R.string.value_float_meter, distance); - } - } else { - if (distance * UnitConversions.M_TO_MI > 0.5) { - distance *= UnitConversions.M_TO_MI; - return context.getString(R.string.value_float_mile, distance); - } else { - distance *= UnitConversions.M_TO_FT; - return context.getString(R.string.value_float_feet, distance); - } - } - } - - public static String formatDecimal(double value) { - return StringUtils.formatDecimal(value, 2); - } - - public static String formatDecimal(double value, int decimalPlaces) { - String result = String.format(Locale.getDefault(), "%1$,." + decimalPlaces +"f", value); - return result.replaceAll("[0]*$", "").replaceAll("\\.$", ""); - } - - /** - * Formats a coordinate - * - * @param coordinate the coordinate - */ - public static String formatCoordinate(double coordinate) { - return Location.convert(coordinate, Location.FORMAT_DEGREES) + COORDINATE_DEGREE; - } - - /** - * Gets the distance in an array of two strings. The first string is the - * distance. The second string is the unit. The first string is null if the - * distance is invalid. - * - * @param context the context - * @param distance the distance - * @param metricUnits true to use metric unit - */ - public static String[] getDistanceParts(Context context, double distance, boolean metricUnits) { - String[] result = new String[2]; - if (Double.isNaN(distance) || Double.isInfinite(distance)) { - result[0] = null; - result[1] = context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet); - return result; + public static String formatDateTime(Context context, long time) { + return DateUtils.formatDateTime( + context, time, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE) + " " + + DateUtils.formatDateTime(context, time, DateUtils.FORMAT_SHOW_TIME); } - int unitId; - if (metricUnits) { - if (distance > 500.0) { - distance *= UnitConversions.M_TO_KM; - unitId = R.string.unit_kilometer; - } else { - unitId = R.string.unit_meter; - } - } else { - if (distance * UnitConversions.M_TO_MI > 0.5) { - distance *= UnitConversions.M_TO_MI; - unitId = R.string.unit_mile; - } else { - distance *= UnitConversions.M_TO_FT; - unitId = R.string.unit_feet; - } - } - result[0] = formatDecimal(distance); - result[1] = context.getString(unitId); - return result; - } - - /** - * Gets the speed in an array of two strings. The first string is the speed. - * The second string is the unit. The first string is null if speed is - * invalid. - * - * @param context the context - * @param speed the speed - * @param metricUnits true to use metric unit - * @param reportSpeed true to report speed - */ - public static String[] getSpeedParts( - Context context, double speed, boolean metricUnits, boolean reportSpeed) { - String[] result = new String[2]; - int unitId; - if (metricUnits) { - unitId = reportSpeed ? R.string.unit_kilometer_per_hour : R.string.unit_minute_per_kilometer; - } else { - unitId = reportSpeed ? R.string.unit_mile_per_hour : R.string.unit_minute_per_mile; - } - result[1] = context.getString(unitId); - if (Double.isNaN(speed) || Double.isInfinite(speed)) { - result[0] = null; - return result; - } - speed *= UnitConversions.MS_TO_KMH; - if (!metricUnits) { - speed *= UnitConversions.KM_TO_MI; - } - if (reportSpeed) { - result[0] = StringUtils.formatDecimal(speed); - } else { - // convert from hours to minutes - double pace = speed == 0 ? 0.0 : 60.0 / speed; - int minutes = (int) pace; - int seconds = (int) Math.round((pace - minutes) * 60.0); - result[0] = String.format(Locale.US, "%d:%02d", minutes, seconds); - } - return result; - } - - /** - * Gets a string for category. - * - * @param category the category - */ - public static String getCategory(String category) { - if (category == null || category.length() == 0) { - return null; - } - return "[" + category + "]"; - } - - /** - * Gets a string for category and description. - * - * @param category the category - * @param description the description - */ - public static String getCategoryDescription(String category, String description) { - if (category == null || category.length() == 0) { - return description; + /** + * Formats the time using the ISO 8601 date time format with fractional + * seconds in UTC time zone. + * + * @param time the time in milliseconds + */ + public static String formatDateTimeIso8601(long time) { + return ISO_8601_DATE_TIME_FORMAT.format(time); } - StringBuilder builder = new StringBuilder(); - builder.append("[").append(category).append("]"); - if (description != null && description.length() != 0) { - builder.append(" ").append(description); - } - return builder.toString(); - } - - /** - * Formats the given text as a XML CDATA element. This includes adding the - * starting and ending CDATA tags. Please notice that this may result in - * multiple consecutive CDATA tags. - * - * @param text the given text - */ - public static String formatCData(String text) { - return "", "]]]]>") + "]]>"; - } + /** + * Formats the elapsed timed in the form "MM:SS" or "H:MM:SS". + * + * @param time the time in milliseconds + */ + public static String formatElapsedTime(long time) { + /* + * TODO + * Temporary workaround for DateUtils.formatElapsedTime(time * MS_TO_S). In API + * level 17, it returns strings like "1:0:00" instead of "1:00:00", which + * breaks several unit tests. + */ + if (time < 0) { + return "-"; + } + long hours = 0; + long minutes = 0; + long elapsedSeconds = (long) (time * UnitConversions.MS_TO_S); - /** - * Gets the time, in milliseconds, from an XML date time string as defined at - * http://www.w3.org/TR/xmlschema-2/#dateTime - * - * @param xmlDateTime the XML date time string - */ - public static long getTime(String xmlDateTime) { - // Parse the date time base - ParsePosition position = new ParsePosition(0); - Date date = ISO_8601_BASE.parse(xmlDateTime, position); - if (date == null) { - throw new IllegalArgumentException("Invalid XML dateTime value: " + xmlDateTime - + " (at position " + position.getErrorIndex() + ")"); + if (elapsedSeconds >= 3600) { + hours = elapsedSeconds / 3600; + elapsedSeconds -= hours * 3600; + } + if (elapsedSeconds >= 60) { + minutes = elapsedSeconds / 60; + elapsedSeconds -= minutes * 60; + } + long seconds = elapsedSeconds; + + if (hours > 0) { + return String.format(Locale.US, "%d:%02d:%02d", hours, minutes, seconds); + } else { + return String.format(Locale.US, "%02d:%02d", minutes, seconds); + } } - // Parse the date time extras - Matcher matcher = ISO_8601_EXTRAS.matcher(xmlDateTime.substring(position.getIndex())); - if (!matcher.matches()) { - // This will match even an empty string as all groups are optional. Thus a - // non-match means invalid content. - throw new IllegalArgumentException("Invalid XML dateTime value: " + xmlDateTime); + /** + * Formats the elapsed time in the form "H:MM:SS". + * + * @param time the time in milliseconds + */ + public static String formatElapsedTimeWithHour(long time) { + String value = formatElapsedTime(time); + return TextUtils.split(value, ":").length == 2 ? "0:" + value : value; } - long time = date.getTime(); - - // Account for fractional seconds - String fractional = matcher.group(1); - if (fractional != null) { - // Regex ensures fractional part is in (0,1) - float fractionalSeconds = Float.parseFloat(fractional); - long fractionalMillis = Math.round(fractionalSeconds * UnitConversions.S_TO_MS); - time += fractionalMillis; + /** + * Formats the distance. + * + * @param context the context + * @param distance the distance in meters + * @param metricUnits true to use metric units. False to use imperial units + */ + public static String formatDistance(Context context, double distance, boolean metricUnits) { + if (Double.isNaN(distance) || Double.isInfinite(distance)) { + return context.getString(R.string.value_unknown); + } + if (metricUnits) { + if (distance > 500.0) { + distance *= UnitConversions.M_TO_KM; + return context.getString(R.string.value_float_kilometer, distance); + } else { + return context.getString(R.string.value_float_meter, distance); + } + } else { + if (distance * UnitConversions.M_TO_MI > 0.5) { + distance *= UnitConversions.M_TO_MI; + return context.getString(R.string.value_float_mile, distance); + } else { + distance *= UnitConversions.M_TO_FT; + return context.getString(R.string.value_float_feet, distance); + } + } } - // Account for timezones - String sign = matcher.group(2); - String offsetHoursStr = matcher.group(3); - String offsetMinsStr = matcher.group(4); - if (sign != null && offsetHoursStr != null && offsetMinsStr != null) { - // Regex ensures sign is + or - - boolean plusSign = sign.equals("+"); - int offsetHours = Integer.parseInt(offsetHoursStr); - int offsetMins = Integer.parseInt(offsetMinsStr); - - // Regex ensures values are >= 0 - if (offsetHours > 14 || offsetMins > 59) { - throw new IllegalArgumentException("Bad timezone: " + xmlDateTime); - } - - long totalOffsetMillis = (offsetMins + offsetHours * 60L) * 60000L; - - // Convert to UTC - if (plusSign) { - time -= totalOffsetMillis; - } else { - time += totalOffsetMillis; - } + public static String formatDecimal(double value) { + return StringUtils.formatDecimal(value, 2); } - return time; - } - /** - * Gets the time as an array of three integers. Index 0 contains the number of - * seconds, index 1 contains the number of minutes, and index 2 contains the - * number of hours. - * - * @param time the time in milliseconds - * @return an array of 3 elements. - */ - public static int[] getTimeParts(long time) { - if (time < 0) { - int[] parts = getTimeParts(time * -1); - parts[0] *= -1; - parts[1] *= -1; - parts[2] *= -1; - return parts; + public static String formatDecimal(double value, int decimalPlaces) { + String result = String.format(Locale.getDefault(), "%1$,." + decimalPlaces + "f", value); + return result.replaceAll("[0]*$", "").replaceAll("\\.$", ""); } - int[] parts = new int[3]; - long seconds = (long) (time * UnitConversions.MS_TO_S); - parts[0] = (int) (seconds % 60); - int minutes = (int) (seconds / 60); - parts[1] = minutes % 60; - parts[2] = minutes / 60; - return parts; - } - - /** - * Gets the html. - * - * @param context the context - * @param resId the string resource id - * @param formatArgs the string resource ids of the format arguments - */ - public static Spanned getHtml(Context context, int resId, Object... formatArgs) { - Object[] args = new Object[formatArgs.length]; - for (int i = 0; i < formatArgs.length; i++) { - String url = context.getString((Integer) formatArgs[i]); - args[i] = " " + url + " "; + /** + * Formats a coordinate + * + * @param coordinate the coordinate + */ + public static String formatCoordinate(double coordinate) { + return Location.convert(coordinate, Location.FORMAT_DEGREES) + COORDINATE_DEGREE; } - return Html.fromHtml(context.getString(resId, args)); - } - /** - * Gets the frequency display options. - * - * @param context the context - * @param metricUnits true to display in metric units - */ - public static String[] getFrequencyOptions(Context context, boolean metricUnits) { - String[] values = context.getResources().getStringArray(R.array.frequency_values); - String[] options = new String[values.length]; - for (int i = 0; i < values.length; i++) { - int value = Integer.parseInt(values[i]); - if (value == PreferencesUtils.FREQUENCY_OFF) { - options[i] = context.getString(R.string.value_off); - } else if (value < 0) { - options[i] = context.getString( - metricUnits ? R.string.value_integer_kilometer : R.string.value_integer_mile, - Math.abs(value)); - } else { - options[i] = context.getString(R.string.value_integer_minute, value); - } + /** + * Gets the distance in an array of two strings. The first string is the + * distance. The second string is the unit. The first string is null if the + * distance is invalid. + * + * @param context the context + * @param distance the distance + * @param metricUnits true to use metric unit + */ + public static String[] getDistanceParts(Context context, double distance, boolean metricUnits) { + String[] result = new String[2]; + if (Double.isNaN(distance) || Double.isInfinite(distance)) { + result[0] = null; + result[1] = context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet); + return result; + } + + int unitId; + if (metricUnits) { + if (distance > 500.0) { + distance *= UnitConversions.M_TO_KM; + unitId = R.string.unit_kilometer; + } else { + unitId = R.string.unit_meter; + } + } else { + if (distance * UnitConversions.M_TO_MI > 0.5) { + distance *= UnitConversions.M_TO_MI; + unitId = R.string.unit_mile; + } else { + distance *= UnitConversions.M_TO_FT; + unitId = R.string.unit_feet; + } + } + result[0] = formatDecimal(distance); + result[1] = context.getString(unitId); + return result; + } + + /** + * Gets the speed in an array of two strings. The first string is the speed. + * The second string is the unit. The first string is null if speed is + * invalid. + * + * @param context the context + * @param speed the speed + * @param metricUnits true to use metric unit + * @param reportSpeed true to report speed + */ + public static String[] getSpeedParts( + Context context, double speed, boolean metricUnits, boolean reportSpeed) { + String[] result = new String[2]; + int unitId; + if (metricUnits) { + unitId = reportSpeed ? R.string.unit_kilometer_per_hour : R.string.unit_minute_per_kilometer; + } else { + unitId = reportSpeed ? R.string.unit_mile_per_hour : R.string.unit_minute_per_mile; + } + result[1] = context.getString(unitId); + if (Double.isNaN(speed) || Double.isInfinite(speed)) { + result[0] = null; + return result; + } + speed *= UnitConversions.MS_TO_KMH; + if (!metricUnits) { + speed *= UnitConversions.KM_TO_MI; + } + if (reportSpeed) { + result[0] = StringUtils.formatDecimal(speed); + } else { + // convert from hours to minutes + double pace = speed == 0 ? 0.0 : 60.0 / speed; + int minutes = (int) pace; + int seconds = (int) Math.round((pace - minutes) * 60.0); + result[0] = String.format(Locale.US, "%d:%02d", minutes, seconds); + } + return result; + } + + /** + * Gets a string for category. + * + * @param category the category + */ + public static String getCategory(String category) { + if (category == null || category.length() == 0) { + return null; + } + return "[" + category + "]"; + } + + /** + * Gets a string for category and description. + * + * @param category the category + * @param description the description + */ + public static String getCategoryDescription(String category, String description) { + if (category == null || category.length() == 0) { + return description; + } + + StringBuilder builder = new StringBuilder(); + builder.append("[").append(category).append("]"); + if (description != null && description.length() != 0) { + builder.append(" ").append(description); + } + return builder.toString(); + } + + /** + * Formats the given text as a XML CDATA element. This includes adding the + * starting and ending CDATA tags. Please notice that this may result in + * multiple consecutive CDATA tags. + * + * @param text the given text + */ + public static String formatCData(String text) { + return "", "]]]]>") + "]]>"; + } + + /** + * Gets the time, in milliseconds, from an XML date time string as defined at + * http://www.w3.org/TR/xmlschema-2/#dateTime + * + * @param xmlDateTime the XML date time string + */ + public static long getTime(String xmlDateTime) { + // Parse the date time base + ParsePosition position = new ParsePosition(0); + Date date = ISO_8601_BASE.parse(xmlDateTime, position); + if (date == null) { + throw new IllegalArgumentException("Invalid XML dateTime value: " + xmlDateTime + + " (at position " + position.getErrorIndex() + ")"); + } + + // Parse the date time extras + Matcher matcher = ISO_8601_EXTRAS.matcher(xmlDateTime.substring(position.getIndex())); + if (!matcher.matches()) { + // This will match even an empty string as all groups are optional. Thus a + // non-match means invalid content. + throw new IllegalArgumentException("Invalid XML dateTime value: " + xmlDateTime); + } + + long time = date.getTime(); + + // Account for fractional seconds + String fractional = matcher.group(1); + if (fractional != null) { + // Regex ensures fractional part is in (0,1) + float fractionalSeconds = Float.parseFloat(fractional); + long fractionalMillis = Math.round(fractionalSeconds * UnitConversions.S_TO_MS); + time += fractionalMillis; + } + + // Account for timezones + String sign = matcher.group(2); + String offsetHoursStr = matcher.group(3); + String offsetMinsStr = matcher.group(4); + if (sign != null && offsetHoursStr != null && offsetMinsStr != null) { + // Regex ensures sign is + or - + boolean plusSign = sign.equals("+"); + int offsetHours = Integer.parseInt(offsetHoursStr); + int offsetMins = Integer.parseInt(offsetMinsStr); + + // Regex ensures values are >= 0 + if (offsetHours > 14 || offsetMins > 59) { + throw new IllegalArgumentException("Bad timezone: " + xmlDateTime); + } + + long totalOffsetMillis = (offsetMins + offsetHours * 60L) * 60000L; + + // Convert to UTC + if (plusSign) { + time -= totalOffsetMillis; + } else { + time += totalOffsetMillis; + } + } + return time; + } + + /** + * Gets the time as an array of three integers. Index 0 contains the number of + * seconds, index 1 contains the number of minutes, and index 2 contains the + * number of hours. + * + * @param time the time in milliseconds + * @return an array of 3 elements. + */ + public static int[] getTimeParts(long time) { + if (time < 0) { + int[] parts = getTimeParts(time * -1); + parts[0] *= -1; + parts[1] *= -1; + parts[2] *= -1; + return parts; + } + int[] parts = new int[3]; + + long seconds = (long) (time * UnitConversions.MS_TO_S); + parts[0] = (int) (seconds % 60); + int minutes = (int) (seconds / 60); + parts[1] = minutes % 60; + parts[2] = minutes / 60; + return parts; + } + + /** + * Gets the html. + * + * @param context the context + * @param resId the string resource id + * @param formatArgs the string resource ids of the format arguments + */ + public static Spanned getHtml(Context context, int resId, Object... formatArgs) { + Object[] args = new Object[formatArgs.length]; + for (int i = 0; i < formatArgs.length; i++) { + String url = context.getString((Integer) formatArgs[i]); + args[i] = " " + url + " "; + } + return Html.fromHtml(context.getString(resId, args)); + } + + /** + * Gets the frequency display options. + * + * @param context the context + * @param metricUnits true to display in metric units + */ + public static String[] getFrequencyOptions(Context context, boolean metricUnits) { + String[] values = context.getResources().getStringArray(R.array.frequency_values); + String[] options = new String[values.length]; + for (int i = 0; i < values.length; i++) { + int value = Integer.parseInt(values[i]); + if (value == PreferencesUtils.FREQUENCY_OFF) { + options[i] = context.getString(R.string.value_off); + } else if (value < 0) { + options[i] = context.getString( + metricUnits ? R.string.value_integer_kilometer : R.string.value_integer_mile, + Math.abs(value)); + } else { + options[i] = context.getString(R.string.value_integer_minute, value); + } + } + return options; } - return options; - } } diff --git a/src/main/java/com/google/android/apps/mytracks/util/SystemUtils.java b/src/main/java/com/google/android/apps/mytracks/util/SystemUtils.java index 761c6a232..dd72f9472 100644 --- a/src/main/java/com/google/android/apps/mytracks/util/SystemUtils.java +++ b/src/main/java/com/google/android/apps/mytracks/util/SystemUtils.java @@ -27,62 +27,63 @@ import android.util.Log; /** * Utility class for accessing basic Android functionality. - * + * * @author Rodrigo Damazio */ public class SystemUtils { - private static final String TAG = SystemUtils.class.getSimpleName(); + private static final String TAG = SystemUtils.class.getSimpleName(); - private SystemUtils() {} - - /** - * Get the app version from the manifest. - * - * @return the version, or an empty string in case of failure. - */ - public static String getAppVersion(Context context) { - try { - PackageInfo pi = context.getPackageManager() - .getPackageInfo("com.google.android.maps.mytracks", PackageManager.GET_META_DATA); - return pi.versionName; - } catch (NameNotFoundException e) { - Log.w(TAG, "Failed to get version info.", e); - return ""; + private SystemUtils() { } - } - /** - * Acquire a wake lock if not already acquired. - * - * @param context the context - * @param wakeLock wake lock or null - */ - @SuppressLint("WakelockTimeout") - public static WakeLock acquireWakeLock(Context context, WakeLock wakeLock) { - Log.i(TAG, "Acquiring wake lock."); - try { - PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - if (powerManager == null) { - Log.e(TAG, "Power manager null."); + /** + * Get the app version from the manifest. + * + * @return the version, or an empty string in case of failure. + */ + public static String getAppVersion(Context context) { + try { + PackageInfo pi = context.getPackageManager() + .getPackageInfo("com.google.android.maps.mytracks", PackageManager.GET_META_DATA); + return pi.versionName; + } catch (NameNotFoundException e) { + Log.w(TAG, "Failed to get version info.", e); + return ""; + } + } + + /** + * Acquire a wake lock if not already acquired. + * + * @param context the context + * @param wakeLock wake lock or null + */ + @SuppressLint("WakelockTimeout") + public static WakeLock acquireWakeLock(Context context, WakeLock wakeLock) { + Log.i(TAG, "Acquiring wake lock."); + try { + PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + if (powerManager == null) { + Log.e(TAG, "Power manager null."); + return wakeLock; + } + if (wakeLock == null) { + wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + if (wakeLock == null) { + Log.e(TAG, "Cannot create a new wake lock."); + return null; + } + } + if (!wakeLock.isHeld()) { + wakeLock.acquire(); + if (!wakeLock.isHeld()) { + Log.e(TAG, "Cannot acquire wake lock."); + } + } + } catch (RuntimeException e) { + Log.e(TAG, e.getMessage(), e); + } return wakeLock; - } - if (wakeLock == null) { - wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); - if (wakeLock == null) { - Log.e(TAG, "Cannot create a new wake lock."); - return null; - } - } - if (!wakeLock.isHeld()) { - wakeLock.acquire(); - if (!wakeLock.isHeld()) { - Log.e(TAG, "Cannot acquire wake lock."); - } - } - } catch (RuntimeException e) { - Log.e(TAG, e.getMessage(), e); } - return wakeLock; - } } \ No newline at end of file diff --git a/src/main/java/com/google/android/apps/mytracks/util/TrackIconUtils.java b/src/main/java/com/google/android/apps/mytracks/util/TrackIconUtils.java index f2b00ec40..6fe798973 100644 --- a/src/main/java/com/google/android/apps/mytracks/util/TrackIconUtils.java +++ b/src/main/java/com/google/android/apps/mytracks/util/TrackIconUtils.java @@ -41,201 +41,202 @@ import java.util.List; /** * Utilities for track icon. - * + * * @author Jimmy Shih */ public class TrackIconUtils { - private static final String AIRPLANE = "AIRPLANE"; - private static final String BIKE = "BIKE"; - private static final String BOAT = "BOAT"; - private static final String DRIVE = "DRIVE"; - private static final String RUN = "RUN"; - private static final String SKI = "SKI"; - private static final String SNOW_BOARDING = "SNOW_BOARDING"; - private static final String WALK = "WALK"; + private static final String AIRPLANE = "AIRPLANE"; + private static final String BIKE = "BIKE"; + private static final String BOAT = "BOAT"; + private static final String DRIVE = "DRIVE"; + private static final String RUN = "RUN"; + private static final String SKI = "SKI"; + private static final String SNOW_BOARDING = "SNOW_BOARDING"; + private static final String WALK = "WALK"; - private static final int[] AIRPLANE_LIST = new int[] { R.string.activity_type_airplane, - R.string.activity_type_commercial_airplane, R.string.activity_type_rc_airplane }; - private static final int[] BIKE_LIST = new int[] { R.string.activity_type_biking, - R.string.activity_type_cycling, R.string.activity_type_dirt_bike, - R.string.activity_type_motor_bike, R.string.activity_type_mountain_biking, - R.string.activity_type_road_biking, R.string.activity_type_track_cycling }; - private static final int[] BOAT_LIST = new int[] { R.string.activity_type_boat, - R.string.activity_type_ferry, R.string.activity_type_motor_boating, - R.string.activity_type_rc_boat }; - private static final int[] DRIVE_LIST = new int[] { R.string.activity_type_atv, - R.string.activity_type_driving, R.string.activity_type_driving_bus, - R.string.activity_type_driving_car }; - private static final int[] RUN_LIST = new int[] { R.string.activity_type_running, - R.string.activity_type_street_running, R.string.activity_type_track_running, - R.string.activity_type_trail_running }; - private static final int[] SKI_LIST = new int[] { - R.string.activity_type_cross_country_skiing, R.string.activity_type_skiing }; - private static final int[] SNOW_BOARDING_LIST = new int[] { - R.string.activity_type_snow_boarding }; - private static final int[] WALK_LIST = new int[] { R.string.activity_type_hiking, - R.string.activity_type_off_trail_hiking, R.string.activity_type_speed_walking, - R.string.activity_type_trail_hiking, R.string.activity_type_walking }; + private static final int[] AIRPLANE_LIST = new int[]{R.string.activity_type_airplane, + R.string.activity_type_commercial_airplane, R.string.activity_type_rc_airplane}; + private static final int[] BIKE_LIST = new int[]{R.string.activity_type_biking, + R.string.activity_type_cycling, R.string.activity_type_dirt_bike, + R.string.activity_type_motor_bike, R.string.activity_type_mountain_biking, + R.string.activity_type_road_biking, R.string.activity_type_track_cycling}; + private static final int[] BOAT_LIST = new int[]{R.string.activity_type_boat, + R.string.activity_type_ferry, R.string.activity_type_motor_boating, + R.string.activity_type_rc_boat}; + private static final int[] DRIVE_LIST = new int[]{R.string.activity_type_atv, + R.string.activity_type_driving, R.string.activity_type_driving_bus, + R.string.activity_type_driving_car}; + private static final int[] RUN_LIST = new int[]{R.string.activity_type_running, + R.string.activity_type_street_running, R.string.activity_type_track_running, + R.string.activity_type_trail_running}; + private static final int[] SKI_LIST = new int[]{ + R.string.activity_type_cross_country_skiing, R.string.activity_type_skiing}; + private static final int[] SNOW_BOARDING_LIST = new int[]{ + R.string.activity_type_snow_boarding}; + private static final int[] WALK_LIST = new int[]{R.string.activity_type_hiking, + R.string.activity_type_off_trail_hiking, R.string.activity_type_speed_walking, + R.string.activity_type_trail_hiking, R.string.activity_type_walking}; - private static final LinkedHashMap> - MAP = new LinkedHashMap<>(); + private static final LinkedHashMap> + MAP = new LinkedHashMap<>(); - static { - MAP.put(RUN, new Pair<>(R.string.activity_type_running, R.drawable.ic_track_run)); - MAP.put(WALK, new Pair<>(R.string.activity_type_walking, R.drawable.ic_track_walk)); - MAP.put(BIKE, new Pair<>(R.string.activity_type_biking, R.drawable.ic_track_bike)); - MAP.put(DRIVE, new Pair<>(R.string.activity_type_driving, R.drawable.ic_track_drive)); - MAP.put(SKI, new Pair<>(R.string.activity_type_skiing, R.drawable.ic_track_ski)); - MAP.put(SNOW_BOARDING, new Pair<>(R.string.activity_type_snow_boarding, R.drawable.ic_track_snow_boarding)); - MAP.put(AIRPLANE,new Pair<>(R.string.activity_type_airplane, R.drawable.ic_track_airplane)); - MAP.put(BOAT, new Pair<>(R.string.activity_type_boat, R.drawable.ic_track_boat)); - } + static { + MAP.put(RUN, new Pair<>(R.string.activity_type_running, R.drawable.ic_track_run)); + MAP.put(WALK, new Pair<>(R.string.activity_type_walking, R.drawable.ic_track_walk)); + MAP.put(BIKE, new Pair<>(R.string.activity_type_biking, R.drawable.ic_track_bike)); + MAP.put(DRIVE, new Pair<>(R.string.activity_type_driving, R.drawable.ic_track_drive)); + MAP.put(SKI, new Pair<>(R.string.activity_type_skiing, R.drawable.ic_track_ski)); + MAP.put(SNOW_BOARDING, new Pair<>(R.string.activity_type_snow_boarding, R.drawable.ic_track_snow_boarding)); + MAP.put(AIRPLANE, new Pair<>(R.string.activity_type_airplane, R.drawable.ic_track_airplane)); + MAP.put(BOAT, new Pair<>(R.string.activity_type_boat, R.drawable.ic_track_boat)); + } - private TrackIconUtils() {} + private TrackIconUtils() { + } - /** - * Gets the icon drawable. - * - * @param iconValue the icon value - */ - public static int getIconDrawable(String iconValue) { - if (iconValue == null || iconValue.equals("")) { - return R.drawable.ic_track_generic; + /** + * Gets the icon drawable. + * + * @param iconValue the icon value + */ + public static int getIconDrawable(String iconValue) { + if (iconValue == null || iconValue.equals("")) { + return R.drawable.ic_track_generic; + } + Pair pair = MAP.get(iconValue); + return pair == null ? R.drawable.ic_track_generic : pair.second; } - Pair pair = MAP.get(iconValue); - return pair == null ? R.drawable.ic_track_generic : pair.second; - } - /** - * Gets the icon activity type. - * - * @param iconValue the icon value - */ - public static int getIconActivityType(String iconValue) { - if (iconValue == null || iconValue.equals("")) { - return R.string.activity_type_walking; + /** + * Gets the icon activity type. + * + * @param iconValue the icon value + */ + public static int getIconActivityType(String iconValue) { + if (iconValue == null || iconValue.equals("")) { + return R.string.activity_type_walking; + } + Pair pair = MAP.get(iconValue); + return pair == null ? R.string.activity_type_walking : pair.first; } - Pair pair = MAP.get(iconValue); - return pair == null ? R.string.activity_type_walking : pair.first; - } - /** - * Gets all icon values. - */ - public static List getAllIconValues() { - return new ArrayList<>(MAP.keySet()); - } + /** + * Gets all icon values. + */ + public static List getAllIconValues() { + return new ArrayList<>(MAP.keySet()); + } - /** - * Gets the icon value. - * - * @param context the context - * @param activityType the activity type - */ - public static String getIconValue(Context context, String activityType) { - if (activityType == null || activityType.equals("")) { - return ""; + /** + * Gets the icon value. + * + * @param context the context + * @param activityType the activity type + */ + public static String getIconValue(Context context, String activityType) { + if (activityType == null || activityType.equals("")) { + return ""; + } + if (inList(context, activityType, AIRPLANE_LIST)) { + return AIRPLANE; + } + if (inList(context, activityType, BIKE_LIST)) { + return BIKE; + } + if (inList(context, activityType, BOAT_LIST)) { + return BOAT; + } + if (inList(context, activityType, DRIVE_LIST)) { + return DRIVE; + } + if (inList(context, activityType, RUN_LIST)) { + return RUN; + } + if (inList(context, activityType, SKI_LIST)) { + return SKI; + } + if (inList(context, activityType, SNOW_BOARDING_LIST)) { + return SNOW_BOARDING; + } + if (inList(context, activityType, WALK_LIST)) { + return WALK; + } + return ""; } - if (inList(context, activityType, AIRPLANE_LIST)) { - return AIRPLANE; - } - if (inList(context, activityType, BIKE_LIST)) { - return BIKE; - } - if (inList(context, activityType, BOAT_LIST)) { - return BOAT; - } - if (inList(context, activityType, DRIVE_LIST)) { - return DRIVE; - } - if (inList(context, activityType, RUN_LIST)) { - return RUN; - } - if (inList(context, activityType, SKI_LIST)) { - return SKI; - } - if (inList(context, activityType, SNOW_BOARDING_LIST)) { - return SNOW_BOARDING; - } - if (inList(context, activityType, WALK_LIST)) { - return WALK; - } - return ""; - } - public static void setIconSpinner(Spinner spinner, String iconValue) { - ArrayAdapter adapter = (ArrayAdapter) spinner.getAdapter(); - StringBuilder stringBuilder = adapter.getItem(0); - stringBuilder.delete(0, stringBuilder.length()); - stringBuilder.append(iconValue); - adapter.notifyDataSetChanged(); - } - - public static ArrayAdapter getIconSpinnerAdapter( - final Context context, String iconValue) { - return new ArrayAdapter(context, android.R.layout.simple_spinner_item, - new StringBuilder[] { new StringBuilder(iconValue) }) { - @NonNull - @Override - public View getView(int position, View convertView, @NonNull android.view.ViewGroup parent) { - ImageView imageView = convertView != null ? (ImageView) convertView : new ImageView(getContext()); - Bitmap source = BitmapFactory.decodeResource(context.getResources(), TrackIconUtils.getIconDrawable(getItem(position) + "")); - imageView.setImageBitmap(source); - imageView.setPadding(4, 4, -4, -4); - return imageView; - } - }; - } - - /** - * Returns true if the activity type is in the list. - * - * @param context the context - * @param activityType the activity type - * @param list the list - */ - private static boolean inList(Context context, String activityType, int[] list) { - for (int i : list) { - if (context.getString(i).equals(activityType)) { - return true; - } + public static void setIconSpinner(Spinner spinner, String iconValue) { + ArrayAdapter adapter = (ArrayAdapter) spinner.getAdapter(); + StringBuilder stringBuilder = adapter.getItem(0); + stringBuilder.delete(0, stringBuilder.length()); + stringBuilder.append(iconValue); + adapter.notifyDataSetChanged(); } - return false; - } - /** - * Sets the menu icon color. - * - * @param menu the menu - */ - public static void setMenuIconColor(Menu menu) { - int size = menu.size(); - for (int i = 0; i < size; i++) { - MenuItem menuitem = menu.getItem(i); - setMenuIconColor(menuitem); + public static ArrayAdapter getIconSpinnerAdapter( + final Context context, String iconValue) { + return new ArrayAdapter(context, android.R.layout.simple_spinner_item, + new StringBuilder[]{new StringBuilder(iconValue)}) { + @NonNull + @Override + public View getView(int position, View convertView, @NonNull android.view.ViewGroup parent) { + ImageView imageView = convertView != null ? (ImageView) convertView : new ImageView(getContext()); + Bitmap source = BitmapFactory.decodeResource(context.getResources(), TrackIconUtils.getIconDrawable(getItem(position) + "")); + imageView.setImageBitmap(source); + imageView.setPadding(4, 4, -4, -4); + return imageView; + } + }; } - } - /** - * Sets the menu icon color. - * - * @param menuitem the menu item - */ - public static void setMenuIconColor(MenuItem menuitem) { - revertMenuIconColor(menuitem); - } - - /** - * Reverts the menu icon color. - * - * @param menuitem the menu item - */ - private static void revertMenuIconColor(MenuItem menuitem) { - Drawable drawable = menuitem.getIcon(); - if (drawable != null) { - final PorterDuffColorFilter colorFilter = new PorterDuffColorFilter(Color.rgb(255, 255, 255), PorterDuff.Mode.MULTIPLY); - drawable.setColorFilter(colorFilter); + /** + * Returns true if the activity type is in the list. + * + * @param context the context + * @param activityType the activity type + * @param list the list + */ + private static boolean inList(Context context, String activityType, int[] list) { + for (int i : list) { + if (context.getString(i).equals(activityType)) { + return true; + } + } + return false; + } + + /** + * Sets the menu icon color. + * + * @param menu the menu + */ + public static void setMenuIconColor(Menu menu) { + int size = menu.size(); + for (int i = 0; i < size; i++) { + MenuItem menuitem = menu.getItem(i); + setMenuIconColor(menuitem); + } + } + + /** + * Sets the menu icon color. + * + * @param menuitem the menu item + */ + public static void setMenuIconColor(MenuItem menuitem) { + revertMenuIconColor(menuitem); + } + + /** + * Reverts the menu icon color. + * + * @param menuitem the menu item + */ + private static void revertMenuIconColor(MenuItem menuitem) { + Drawable drawable = menuitem.getIcon(); + if (drawable != null) { + final PorterDuffColorFilter colorFilter = new PorterDuffColorFilter(Color.rgb(255, 255, 255), PorterDuff.Mode.MULTIPLY); + drawable.setColorFilter(colorFilter); + } } - } } diff --git a/src/main/java/com/google/android/apps/mytracks/util/TrackNameUtils.java b/src/main/java/com/google/android/apps/mytracks/util/TrackNameUtils.java index 03ce3ba46..ecb69d28d 100644 --- a/src/main/java/com/google/android/apps/mytracks/util/TrackNameUtils.java +++ b/src/main/java/com/google/android/apps/mytracks/util/TrackNameUtils.java @@ -20,6 +20,7 @@ import android.content.Context; import android.location.Address; import android.location.Geocoder; import android.location.Location; + import androidx.annotation.VisibleForTesting; import com.google.android.maps.mytracks.R; @@ -36,82 +37,82 @@ import java.util.Locale; */ public class TrackNameUtils { - @VisibleForTesting - static final String ISO_8601_FORMAT = "yyyy-MM-dd HH:mm"; + @VisibleForTesting + static final String ISO_8601_FORMAT = "yyyy-MM-dd HH:mm"; - private TrackNameUtils() { - } - - /** - * Gets the track name. - * - * @param context the context - * @param trackId the track id - * @param startTime the track start time - * @param location the track location, can be null - */ - public static String getTrackName(Context context, long trackId, long startTime, Location location) { - String trackName = PreferencesUtils.getString(context, R.string.track_name_key, PreferencesUtils.TRACK_NAME_DEFAULT); - - if (trackName.equals(context.getString(R.string.settings_recording_track_name_location_value))) { - // Use the startTime if location is null - return location != null ? getReverseGeoCoding(context, location) : StringUtils.formatDateTime(context, startTime); - } else if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_local_value))) { - return StringUtils.formatDateTime(context, startTime); - } else if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_iso_8601_value))) { - return new SimpleDateFormat(ISO_8601_FORMAT, Locale.US).format(startTime); - } else if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_iso_8601_location_value))) { - StringBuilder builder = new StringBuilder(new SimpleDateFormat(ISO_8601_FORMAT, Locale.US).format(startTime)); - - if (location != null) { - String address = getReverseGeoCoding(context, location); - if (address != null && !address.equals("")) { - builder.append(" ").append(address); - } - } - return builder.toString(); - } else { - // R.string.settings_recording_track_name_number_value) - return context.getString(R.string.track_name_format, trackId); + private TrackNameUtils() { } - } - /** - * Gets the reverse geo coding string for a location. - * - * @param context the context - * @param location the location - */ - private static String getReverseGeoCoding(Context context, Location location) { - if (location == null || !Geocoder.isPresent()) { - return null; + /** + * Gets the track name. + * + * @param context the context + * @param trackId the track id + * @param startTime the track start time + * @param location the track location, can be null + */ + public static String getTrackName(Context context, long trackId, long startTime, Location location) { + String trackName = PreferencesUtils.getString(context, R.string.track_name_key, PreferencesUtils.TRACK_NAME_DEFAULT); + + if (trackName.equals(context.getString(R.string.settings_recording_track_name_location_value))) { + // Use the startTime if location is null + return location != null ? getReverseGeoCoding(context, location) : StringUtils.formatDateTime(context, startTime); + } else if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_local_value))) { + return StringUtils.formatDateTime(context, startTime); + } else if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_iso_8601_value))) { + return new SimpleDateFormat(ISO_8601_FORMAT, Locale.US).format(startTime); + } else if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_iso_8601_location_value))) { + StringBuilder builder = new StringBuilder(new SimpleDateFormat(ISO_8601_FORMAT, Locale.US).format(startTime)); + + if (location != null) { + String address = getReverseGeoCoding(context, location); + if (address != null && !address.equals("")) { + builder.append(" ").append(address); + } + } + return builder.toString(); + } else { + // R.string.settings_recording_track_name_number_value) + return context.getString(R.string.track_name_format, trackId); + } } - Geocoder geocoder = new Geocoder(context); - try { - List

addresses = geocoder.getFromLocation( - location.getLatitude(), location.getLongitude(), 1); - if (addresses.size() > 0) { - Address address = addresses.get(0); - int lines = address.getMaxAddressLineIndex(); - if (lines > 0) { - return address.getAddressLine(0); + + /** + * Gets the reverse geo coding string for a location. + * + * @param context the context + * @param location the location + */ + private static String getReverseGeoCoding(Context context, Location location) { + if (location == null || !Geocoder.isPresent()) { + return null; } - String featureName = address.getFeatureName(); - if (featureName != null) { - return featureName; + Geocoder geocoder = new Geocoder(context); + try { + List
addresses = geocoder.getFromLocation( + location.getLatitude(), location.getLongitude(), 1); + if (addresses.size() > 0) { + Address address = addresses.get(0); + int lines = address.getMaxAddressLineIndex(); + if (lines > 0) { + return address.getAddressLine(0); + } + String featureName = address.getFeatureName(); + if (featureName != null) { + return featureName; + } + String thoroughfare = address.getThoroughfare(); + if (thoroughfare != null) { + return thoroughfare; + } + String locality = address.getLocality(); + if (locality != null) { + return locality; + } + } + } catch (IOException e) { + // Can safely ignore } - String thoroughfare = address.getThoroughfare(); - if (thoroughfare != null) { - return thoroughfare; - } - String locality = address.getLocality(); - if (locality != null) { - return locality; - } - } - } catch (IOException e) { - // Can safely ignore + return null; } - return null; - } } diff --git a/src/main/java/com/google/android/apps/mytracks/util/TrackRecordingServiceConnectionUtils.java b/src/main/java/com/google/android/apps/mytracks/util/TrackRecordingServiceConnectionUtils.java index ec71aa2ab..92ccee7cb 100644 --- a/src/main/java/com/google/android/apps/mytracks/util/TrackRecordingServiceConnectionUtils.java +++ b/src/main/java/com/google/android/apps/mytracks/util/TrackRecordingServiceConnectionUtils.java @@ -35,146 +35,147 @@ import java.util.List; /** * Utilities for {@link TrackRecordingServiceConnection}. - * + * * @author Rodrigo Damazio */ public class TrackRecordingServiceConnectionUtils { - private static final String TAG = TrackRecordingServiceConnectionUtils.class.getSimpleName(); + private static final String TAG = TrackRecordingServiceConnectionUtils.class.getSimpleName(); - private TrackRecordingServiceConnectionUtils() {} - - /** - * Returns true if the recording service is running. - * - * @param context the current context - */ - public static boolean isRecordingServiceRunning(Context context) { - ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - List services = activityManager.getRunningServices(Integer.MAX_VALUE); - - for (RunningServiceInfo serviceInfo : services) { - ComponentName componentName = serviceInfo.service; - String serviceName = componentName.getClassName(); - if (TrackRecordingService.class.getName().equals(serviceName)) { - return true; - } + private TrackRecordingServiceConnectionUtils() { } - return false; - } - /** - * Resumes the recording track. - * - * @param trackRecordingServiceConnection the track recording service - */ - public static void resumeTrack(TrackRecordingServiceConnection trackRecordingServiceConnection) { - ITrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound(); - if (service != null) { - service.resumeCurrentTrack(); + /** + * Returns true if the recording service is running. + * + * @param context the current context + */ + public static boolean isRecordingServiceRunning(Context context) { + ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + List services = activityManager.getRunningServices(Integer.MAX_VALUE); + + for (RunningServiceInfo serviceInfo : services) { + ComponentName componentName = serviceInfo.service; + String serviceName = componentName.getClassName(); + if (TrackRecordingService.class.getName().equals(serviceName)) { + return true; + } + } + return false; } - } - /** - * Pauses the recording track. - * - * @param trackRecordingServiceConnection the track recording service - * connection - */ - public static void pauseTrack(TrackRecordingServiceConnection trackRecordingServiceConnection) { - ITrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound(); - if (service != null) { - service.pauseCurrentTrack(); + /** + * Resumes the recording track. + * + * @param trackRecordingServiceConnection the track recording service + */ + public static void resumeTrack(TrackRecordingServiceConnection trackRecordingServiceConnection) { + ITrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound(); + if (service != null) { + service.resumeCurrentTrack(); + } } - } - /** - * Stops the recording. - * - * @param context the context - * @param trackRecordingServiceConnection the track recording service - * connection - * @param showEditor true to show the editor - */ - public static void stopRecording(Context context, TrackRecordingServiceConnection trackRecordingServiceConnection, boolean showEditor) { - ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound(); - if (trackRecordingService != null) { - try { - if (showEditor) { - /* - * Need to remember the recordingTrackId before calling - * endCurrentTrack. endCurrentTrack sets the value to -1L. - */ - long recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key); - trackRecordingService.endCurrentTrack(); - if (recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) { - Intent intent = IntentUtils.newIntent(context, TrackEditActivity.class) - .putExtra(TrackEditActivity.EXTRA_TRACK_ID, recordingTrackId) - .putExtra(TrackEditActivity.EXTRA_NEW_TRACK, true); - context.startActivity(intent); - } + /** + * Pauses the recording track. + * + * @param trackRecordingServiceConnection the track recording service + * connection + */ + public static void pauseTrack(TrackRecordingServiceConnection trackRecordingServiceConnection) { + ITrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound(); + if (service != null) { + service.pauseCurrentTrack(); + } + } + + /** + * Stops the recording. + * + * @param context the context + * @param trackRecordingServiceConnection the track recording service + * connection + * @param showEditor true to show the editor + */ + public static void stopRecording(Context context, TrackRecordingServiceConnection trackRecordingServiceConnection, boolean showEditor) { + ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound(); + if (trackRecordingService != null) { + try { + if (showEditor) { + /* + * Need to remember the recordingTrackId before calling + * endCurrentTrack. endCurrentTrack sets the value to -1L. + */ + long recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key); + trackRecordingService.endCurrentTrack(); + if (recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) { + Intent intent = IntentUtils.newIntent(context, TrackEditActivity.class) + .putExtra(TrackEditActivity.EXTRA_TRACK_ID, recordingTrackId) + .putExtra(TrackEditActivity.EXTRA_NEW_TRACK, true); + context.startActivity(intent); + } + } else { + trackRecordingService.endCurrentTrack(); + } + } catch (Exception e) { + Log.e(TAG, "Unable to stop recording.", e); + } } else { - trackRecordingService.endCurrentTrack(); + resetRecordingState(context); } - } catch (Exception e) { - Log.e(TAG, "Unable to stop recording.", e); - } - } else { - resetRecordingState(context); + trackRecordingServiceConnection.unbindAndStop(); } - trackRecordingServiceConnection.unbindAndStop(); - } - /** - * Resumes the track recording service connection. - * - * @param context the context - * @param trackRecordingServiceConnection the track recording service - * connection - */ - public static void startConnection(Context context, TrackRecordingServiceConnection trackRecordingServiceConnection) { - trackRecordingServiceConnection.bindIfStarted(); - if (!isRecordingServiceRunning(context)) { - resetRecordingState(context); - } - } - - private static void resetRecordingState(Context context) { - long recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key); - if (recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) { - PreferencesUtils.setLong( - context, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT); - } - boolean recordingTrackPaused = PreferencesUtils.getBoolean(context, - R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); - if (!recordingTrackPaused) { - PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, - PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); - } - } - - /** - * Adds a marker. - */ - public static long addMarker(Context context, - TrackRecordingServiceConnection trackRecordingServiceConnection, - WaypointCreationRequest waypointCreationRequest) { - ITrackRecordingService trackRecordingService = trackRecordingServiceConnection - .getServiceIfBound(); - if (trackRecordingService == null) { - Log.d(TAG, "Unable to add marker, no track recording service"); - } else { - try { - long markerId = trackRecordingService.insertWaypoint(waypointCreationRequest); - if (markerId != -1L) { - Toast.makeText(context, R.string.marker_add_success, Toast.LENGTH_SHORT).show(); - return markerId; + /** + * Resumes the track recording service connection. + * + * @param context the context + * @param trackRecordingServiceConnection the track recording service + * connection + */ + public static void startConnection(Context context, TrackRecordingServiceConnection trackRecordingServiceConnection) { + trackRecordingServiceConnection.bindIfStarted(); + if (!isRecordingServiceRunning(context)) { + resetRecordingState(context); } - } catch (IllegalStateException e) { - Log.e(TAG, "Unable to add marker.", e); - } } - Toast.makeText(context, R.string.marker_add_error, Toast.LENGTH_LONG).show(); - return -1L; - } + + private static void resetRecordingState(Context context) { + long recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key); + if (recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) { + PreferencesUtils.setLong( + context, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT); + } + boolean recordingTrackPaused = PreferencesUtils.getBoolean(context, + R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); + if (!recordingTrackPaused) { + PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, + PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); + } + } + + /** + * Adds a marker. + */ + public static long addMarker(Context context, + TrackRecordingServiceConnection trackRecordingServiceConnection, + WaypointCreationRequest waypointCreationRequest) { + ITrackRecordingService trackRecordingService = trackRecordingServiceConnection + .getServiceIfBound(); + if (trackRecordingService == null) { + Log.d(TAG, "Unable to add marker, no track recording service"); + } else { + try { + long markerId = trackRecordingService.insertWaypoint(waypointCreationRequest); + if (markerId != -1L) { + Toast.makeText(context, R.string.marker_add_success, Toast.LENGTH_SHORT).show(); + return markerId; + } + } catch (IllegalStateException e) { + Log.e(TAG, "Unable to add marker.", e); + } + } + Toast.makeText(context, R.string.marker_add_error, Toast.LENGTH_LONG).show(); + return -1L; + } } diff --git a/src/main/java/com/google/android/apps/mytracks/util/TrackUtils.java b/src/main/java/com/google/android/apps/mytracks/util/TrackUtils.java index e5b49c35e..6416898c5 100644 --- a/src/main/java/com/google/android/apps/mytracks/util/TrackUtils.java +++ b/src/main/java/com/google/android/apps/mytracks/util/TrackUtils.java @@ -16,36 +16,37 @@ package com.google.android.apps.mytracks.util; +import android.content.Context; + import com.google.android.apps.mytracks.content.ContentProviderUtils; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.TracksColumns; -import android.content.Context; - /** * Utilities for updating track. - * + * * @author Jimmy Shih */ public class TrackUtils { - private TrackUtils() {} + public static String TRACK_SORT_ORDER = TracksColumns.STARTTIME + " DESC"; - public static String TRACK_SORT_ORDER = TracksColumns.STARTTIME + " DESC"; - - public static void updateTrack(Context context, Track track, String name, String category, String description, ContentProviderUtils contentProviderUtils) { - if (name != null) { - track.setName(name); - } - if (category != null) { - track.setCategory(category); - track.setIcon(TrackIconUtils.getIconValue(context, category)); + private TrackUtils() { } - if (description != null) { - track.setDescription(description); + public static void updateTrack(Context context, Track track, String name, String category, String description, ContentProviderUtils contentProviderUtils) { + if (name != null) { + track.setName(name); + } + if (category != null) { + track.setCategory(category); + track.setIcon(TrackIconUtils.getIconValue(context, category)); + } + + if (description != null) { + track.setDescription(description); + } + track.setModifiedTime(System.currentTimeMillis()); + contentProviderUtils.updateTrack(track); } - track.setModifiedTime(System.currentTimeMillis()); - contentProviderUtils.updateTrack(track); - } } diff --git a/src/main/java/com/google/android/apps/mytracks/util/UnitConversions.java b/src/main/java/com/google/android/apps/mytracks/util/UnitConversions.java index b563fb34c..87a268d13 100644 --- a/src/main/java/com/google/android/apps/mytracks/util/UnitConversions.java +++ b/src/main/java/com/google/android/apps/mytracks/util/UnitConversions.java @@ -1,12 +1,12 @@ /* * Copyright 2008 Google Inc. - * + * * 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 @@ -17,63 +17,49 @@ package com.google.android.apps.mytracks.util; /** * Unit conversion constants. - * + * * @author Sandor Dornbush */ public class UnitConversions { - private UnitConversions() {} + // multiplication factor to convert seconds to milliseconds + public static final double S_TO_MS = 1000.0; + // Time + // 1 second in milliseconds + public static final long ONE_SECOND = (long) UnitConversions.S_TO_MS; + // multiplication factor to convert milliseconds to seconds + public static final double MS_TO_S = 1 / S_TO_MS; + // multiplication factor to convert minutes to seconds + public static final double MIN_TO_S = 60.0; + // multiplication factor to convert seconds to minutes + public static final double S_TO_MIN = 1 / MIN_TO_S; + // multiplication factor to convert hours to minutes + public static final double HR_TO_MIN = 60.0; + // multiplication factor to convert minutes to hours + public static final double MIN_TO_HR = 1 / HR_TO_MIN; + // multiplication factor to convert kilometers to miles + public static final double KM_TO_MI = 0.621371192; - // Time - // 1 second in milliseconds - public static final long ONE_SECOND = (long) UnitConversions.S_TO_MS; + // Distance + // multiplication factor to convert miles to feet + public static final double MI_TO_FT = 5280.0; + // multiplication factor to convert feet to miles + public static final double FT_TO_MI = 1 / MI_TO_FT; + // multiplication factor to covert kilometers to meters + public static final double KM_TO_M = 1000.0; + // multiplication factor to convert meters to kilometers + public static final double M_TO_KM = 1 / KM_TO_M; + // multiplication factor to convert meters to miles + public static final double M_TO_MI = M_TO_KM * KM_TO_MI; + // multiplication factor to convert meters to feet + public static final double M_TO_FT = M_TO_MI * MI_TO_FT; + // multiplication factor to convert meters per second to kilometers per hour + public static final double MS_TO_KMH = M_TO_KM / (S_TO_MIN * MIN_TO_HR); - // multiplication factor to convert seconds to milliseconds - public static final double S_TO_MS = 1000.0; + // Others + // multiplication factor to convert degrees to radians + public static final double DEG_TO_RAD = Math.PI / 180.0; - // multiplication factor to convert milliseconds to seconds - public static final double MS_TO_S = 1 / S_TO_MS; - - // multiplication factor to convert minutes to seconds - public static final double MIN_TO_S = 60.0; - - // multiplication factor to convert seconds to minutes - public static final double S_TO_MIN = 1 / MIN_TO_S; - - // multiplication factor to convert hours to minutes - public static final double HR_TO_MIN = 60.0; - - // multiplication factor to convert minutes to hours - public static final double MIN_TO_HR = 1 / HR_TO_MIN; - - // Distance - - // multiplication factor to convert kilometers to miles - public static final double KM_TO_MI = 0.621371192; - - // multiplication factor to convert miles to feet - public static final double MI_TO_FT = 5280.0; - - // multiplication factor to convert feet to miles - public static final double FT_TO_MI = 1 / MI_TO_FT; - - // multiplication factor to covert kilometers to meters - public static final double KM_TO_M = 1000.0; - - // multiplication factor to convert meters to kilometers - public static final double M_TO_KM = 1 / KM_TO_M; - - // multiplication factor to convert meters to miles - public static final double M_TO_MI = M_TO_KM * KM_TO_MI; - - // multiplication factor to convert meters to feet - public static final double M_TO_FT = M_TO_MI * MI_TO_FT; - - // Others - - // multiplication factor to convert meters per second to kilometers per hour - public static final double MS_TO_KMH = M_TO_KM / (S_TO_MIN * MIN_TO_HR); - - // multiplication factor to convert degrees to radians - public static final double DEG_TO_RAD = Math.PI / 180.0; + private UnitConversions() { + } } diff --git a/src/main/java/com/google/android/apps/mytracks/widgets/TrackWidgetConfigActivity.java b/src/main/java/com/google/android/apps/mytracks/widgets/TrackWidgetConfigActivity.java index eba1045e6..acf55bdce 100644 --- a/src/main/java/com/google/android/apps/mytracks/widgets/TrackWidgetConfigActivity.java +++ b/src/main/java/com/google/android/apps/mytracks/widgets/TrackWidgetConfigActivity.java @@ -16,9 +16,6 @@ package com.google.android.apps.mytracks.widgets; -import com.google.android.apps.mytracks.util.PreferencesUtils; -import com.google.android.maps.mytracks.R; - import android.app.Activity; import android.appwidget.AppWidgetManager; import android.content.Intent; @@ -27,111 +24,114 @@ import android.view.View; import android.widget.ArrayAdapter; import android.widget.Spinner; +import com.google.android.apps.mytracks.util.PreferencesUtils; +import com.google.android.maps.mytracks.R; + import java.util.ArrayList; import java.util.List; import java.util.Locale; /** * Activity to configure the track widget. - * + * * @author Jimmy Shih */ public class TrackWidgetConfigActivity extends Activity { - private int appWidgetId; - private Spinner item1; - private Spinner item2; - private Spinner item3; - private Spinner item4; + private int appWidgetId; + private Spinner item1; + private Spinner item2; + private Spinner item3; + private Spinner item4; - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - /* - * Set the result to CANCELED. This will cause the widget host to cancel out - * of the widget placement if they press the back button. - */ - setResult(RESULT_CANCELED); + /* + * Set the result to CANCELED. This will cause the widget host to cancel out + * of the widget placement if they press the back button. + */ + setResult(RESULT_CANCELED); - setContentView(R.layout.track_widget_config); - item1 = findViewById(R.id.track_widget_config_item1); - item2 = findViewById(R.id.track_widget_config_item2); - item3 = findViewById(R.id.track_widget_config_item3); - item4 = findViewById(R.id.track_widget_config_item4); + setContentView(R.layout.track_widget_config); + item1 = findViewById(R.id.track_widget_config_item1); + item2 = findViewById(R.id.track_widget_config_item2); + item3 = findViewById(R.id.track_widget_config_item3); + item4 = findViewById(R.id.track_widget_config_item4); - boolean reportSpeed = PreferencesUtils.isReportSpeed(this); + boolean reportSpeed = PreferencesUtils.isReportSpeed(this); - List list = new ArrayList<>(); - addItem(list, R.string.stats_distance); - addItem(list, R.string.stats_total_time); - addItem(list, reportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace); - addItem(list, R.string.stats_moving_time); - addItem(list, - reportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace); + List list = new ArrayList<>(); + addItem(list, R.string.stats_distance); + addItem(list, R.string.stats_total_time); + addItem(list, reportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace); + addItem(list, R.string.stats_moving_time); + addItem(list, + reportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace); - configSpinner(item1, list, PreferencesUtils.getInt( - this, R.string.track_widget_item1, PreferencesUtils.TRACK_WIDGET_ITEM1_DEFAULT)); - configSpinner(item2, list, PreferencesUtils.getInt( - this, R.string.track_widget_item2, PreferencesUtils.TRACK_WIDGET_ITEM2_DEFAULT)); - configSpinner(item3, list, PreferencesUtils.getInt( - this, R.string.track_widget_item3, PreferencesUtils.TRACK_WIDGET_ITEM3_DEFAULT)); - configSpinner(item4, list, PreferencesUtils.getInt( - this, R.string.track_widget_item4, PreferencesUtils.TRACK_WIDGET_ITEM4_DEFAULT)); + configSpinner(item1, list, PreferencesUtils.getInt( + this, R.string.track_widget_item1, PreferencesUtils.TRACK_WIDGET_ITEM1_DEFAULT)); + configSpinner(item2, list, PreferencesUtils.getInt( + this, R.string.track_widget_item2, PreferencesUtils.TRACK_WIDGET_ITEM2_DEFAULT)); + configSpinner(item3, list, PreferencesUtils.getInt( + this, R.string.track_widget_item3, PreferencesUtils.TRACK_WIDGET_ITEM3_DEFAULT)); + configSpinner(item4, list, PreferencesUtils.getInt( + this, R.string.track_widget_item4, PreferencesUtils.TRACK_WIDGET_ITEM4_DEFAULT)); - findViewById(R.id.track_widget_config_add).setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { + findViewById(R.id.track_widget_config_add).setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { - // Push widget update to surface with newly set prefix - AppWidgetManager appWidgetManager = AppWidgetManager.getInstance( - TrackWidgetConfigActivity.this); - TrackWidgetProvider.updateAppWidget( - TrackWidgetConfigActivity.this, appWidgetManager, appWidgetId, -1L); - PreferencesUtils.setInt(TrackWidgetConfigActivity.this, R.string.track_widget_item1, - item1.getSelectedItemPosition()); - PreferencesUtils.setInt(TrackWidgetConfigActivity.this, R.string.track_widget_item2, - item2.getSelectedItemPosition()); - PreferencesUtils.setInt(TrackWidgetConfigActivity.this, R.string.track_widget_item3, - item3.getSelectedItemPosition()); - PreferencesUtils.setInt(TrackWidgetConfigActivity.this, R.string.track_widget_item4, - item4.getSelectedItemPosition()); + // Push widget update to surface with newly set prefix + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance( + TrackWidgetConfigActivity.this); + TrackWidgetProvider.updateAppWidget( + TrackWidgetConfigActivity.this, appWidgetManager, appWidgetId, -1L); + PreferencesUtils.setInt(TrackWidgetConfigActivity.this, R.string.track_widget_item1, + item1.getSelectedItemPosition()); + PreferencesUtils.setInt(TrackWidgetConfigActivity.this, R.string.track_widget_item2, + item2.getSelectedItemPosition()); + PreferencesUtils.setInt(TrackWidgetConfigActivity.this, R.string.track_widget_item3, + item3.getSelectedItemPosition()); + PreferencesUtils.setInt(TrackWidgetConfigActivity.this, R.string.track_widget_item4, + item4.getSelectedItemPosition()); - // Make sure we pass back the original appWidgetId - Intent resultValue = new Intent(); - resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); - setResult(RESULT_OK, resultValue); - finish(); - } - }); - findViewById(R.id.track_widget_config_cancel).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - finish(); - } - }); + // Make sure we pass back the original appWidgetId + Intent resultValue = new Intent(); + resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); + setResult(RESULT_OK, resultValue); + finish(); + } + }); + findViewById(R.id.track_widget_config_cancel).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); - // Find the app widget id from the intent. - Intent intent = getIntent(); - Bundle extras = intent.getExtras(); - appWidgetId = extras != null ? extras.getInt( - AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID) - : AppWidgetManager.INVALID_APPWIDGET_ID; + // Find the app widget id from the intent. + Intent intent = getIntent(); + Bundle extras = intent.getExtras(); + appWidgetId = extras != null ? extras.getInt( + AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID) + : AppWidgetManager.INVALID_APPWIDGET_ID; - // If they gave us an intent without the widget id, just bail. - if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { - finish(); + // If they gave us an intent without the widget id, just bail. + if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { + finish(); + } } - } - private void addItem(List list, int id) { - list.add(getString(id).toUpperCase(Locale.getDefault())); - } + private void addItem(List list, int id) { + list.add(getString(id).toUpperCase(Locale.getDefault())); + } - private void configSpinner(Spinner spinner, List list, int position) { - ArrayAdapter adapter = new ArrayAdapter<>( - this, android.R.layout.simple_spinner_item, list); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spinner.setAdapter(adapter); - spinner.setSelection(position); - } + private void configSpinner(Spinner spinner, List list, int position) { + ArrayAdapter adapter = new ArrayAdapter<>( + this, android.R.layout.simple_spinner_item, list); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(adapter); + spinner.setSelection(position); + } } diff --git a/src/main/java/com/google/android/apps/mytracks/widgets/TrackWidgetProvider.java b/src/main/java/com/google/android/apps/mytracks/widgets/TrackWidgetProvider.java index 9c6cfd5de..df3470160 100644 --- a/src/main/java/com/google/android/apps/mytracks/widgets/TrackWidgetProvider.java +++ b/src/main/java/com/google/android/apps/mytracks/widgets/TrackWidgetProvider.java @@ -16,6 +16,21 @@ package com.google.android.apps.mytracks.widgets; +import android.annotation.TargetApi; +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.SystemClock; +import android.view.View; +import android.widget.RemoteViews; + +import androidx.core.app.TaskStackBuilder; + import com.google.android.apps.mytracks.TrackDetailActivity; import com.google.android.apps.mytracks.TrackListActivity; import com.google.android.apps.mytracks.content.ContentProviderUtils; @@ -27,484 +42,470 @@ import com.google.android.apps.mytracks.util.PreferencesUtils; import com.google.android.apps.mytracks.util.StringUtils; import com.google.android.maps.mytracks.R; -import android.annotation.TargetApi; -import android.app.PendingIntent; -import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProvider; -import android.appwidget.AppWidgetProviderInfo; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.SystemClock; -import androidx.core.app.TaskStackBuilder; -import android.view.View; -import android.widget.RemoteViews; - /** * A track widget to start/stop/pause/resume recording, launch the main activity, and * display track statistics (total distance, total time, average speed, and * moving time) for the recording track, the selected track or the last track. - * + * * @author Sandor Dornbush * @author Paul R. Saxman */ public class TrackWidgetProvider extends AppWidgetProvider { - public static final int HOME_SCREEN_DEFAULT_SIZE = 2; + public static final int HOME_SCREEN_DEFAULT_SIZE = 2; - private static final String APP_WIDGET_SIZE_KEY = "app_widget_size_key"; + private static final String APP_WIDGET_SIZE_KEY = "app_widget_size_key"; - private static final int TWO_CELLS = 110; - private static final int THREE_CELLS = 180; - private static final int FOUR_CELLS = 250; + private static final int TWO_CELLS = 110; + private static final int THREE_CELLS = 180; + private static final int FOUR_CELLS = 250; - private static final int[] ITEM1_IDS = { R.id.track_widget_item1_label, - R.id.track_widget_item1_value, R.id.track_widget_item1_unit, - R.id.track_widget_item1_chronometer }; - private static final int[] ITEM2_IDS = { R.id.track_widget_item2_label, - R.id.track_widget_item2_value, R.id.track_widget_item2_unit, - R.id.track_widget_item2_chronometer }; - private static final int[] ITEM3_IDS = { R.id.track_widget_item3_label, - R.id.track_widget_item3_value, R.id.track_widget_item3_unit, - R.id.track_widget_item3_chronometer }; - private static final int[] ITEM4_IDS = { R.id.track_widget_item4_label, - R.id.track_widget_item4_value, R.id.track_widget_item4_unit, - R.id.track_widget_item4_chronometer }; + private static final int[] ITEM1_IDS = {R.id.track_widget_item1_label, + R.id.track_widget_item1_value, R.id.track_widget_item1_unit, + R.id.track_widget_item1_chronometer}; + private static final int[] ITEM2_IDS = {R.id.track_widget_item2_label, + R.id.track_widget_item2_value, R.id.track_widget_item2_unit, + R.id.track_widget_item2_chronometer}; + private static final int[] ITEM3_IDS = {R.id.track_widget_item3_label, + R.id.track_widget_item3_value, R.id.track_widget_item3_unit, + R.id.track_widget_item3_chronometer}; + private static final int[] ITEM4_IDS = {R.id.track_widget_item4_label, + R.id.track_widget_item4_value, R.id.track_widget_item4_unit, + R.id.track_widget_item4_chronometer}; - @Override - public void onReceive(Context context, Intent intent) { - super.onReceive(context, intent); - String action = intent.getAction(); - if (context.getString(R.string.track_paused_broadcast_action).equals(action) - || context.getString(R.string.track_resumed_broadcast_action).equals(action) - || context.getString(R.string.track_started_broadcast_action).equals(action) - || context.getString(R.string.track_stopped_broadcast_action).equals(action) - || context.getString(R.string.track_update_broadcast_action).equals(action)) { - long trackId = intent.getLongExtra(context.getString(R.string.track_id_broadcast_extra), -1L); - updateAllAppWidgets(context, trackId); + /** + * Updates an app widget. + * + * @param context the context + * @param appWidgetManager the app widget manager + * @param appWidgetId the app widget id + * @param trackId the track id. -1L to not specify one + */ + public static void updateAppWidget( + Context context, AppWidgetManager appWidgetManager, int appWidgetId, long trackId) { + int size = getAppWidgetSize(appWidgetManager, appWidgetId); + RemoteViews remoteViews = getRemoteViews(context, trackId, size); + appWidgetManager.updateAppWidget(appWidgetId, remoteViews); } - } - @Override - public void onEnabled(Context context) { - super.onEnabled(context); - // Need to update all app widgets after phone reboot - updateAllAppWidgets(context, -1L); - } - - @Override - public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { - super.onUpdate(context, appWidgetManager, appWidgetIds); - // Need to update all app widgets after software update - updateAllAppWidgets(context, -1L); - } - - @TargetApi(16) - @Override - public void onAppWidgetOptionsChanged( - Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) { - super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions); - if (newOptions != null) { - int newSize; - if (newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, -1) - == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) { - newSize = 1; - } else { - int height = newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT); - if (height == 0) { - newSize = 2; - } else if (height >= FOUR_CELLS) { - newSize = 4; - } else if (height >= THREE_CELLS) { - newSize = 3; - } else if (height >= TWO_CELLS) { - newSize = 2; - } else { - newSize = 1; + /** + * Updates all app widgets. + * + * @param context the context + * @param trackId track id + */ + private static void updateAllAppWidgets(Context context, long trackId) { + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + int[] appWidgetIds = appWidgetManager.getAppWidgetIds( + new ComponentName(context, TrackWidgetProvider.class)); + for (int appWidgetId : appWidgetIds) { + updateAppWidget(context, appWidgetManager, appWidgetId, trackId); } - } - int size = getAppWidgetSize(appWidgetManager, appWidgetId); - - if (size != newSize) { - Bundle bundle = new Bundle(); - bundle.putInt(APP_WIDGET_SIZE_KEY, size); - appWidgetManager.updateAppWidgetOptions(appWidgetId, bundle); - - updateAppWidget(context, appWidgetManager, appWidgetId, -1L); - } } - } - /** - * Updates an app widget. - * - * @param context the context - * @param appWidgetManager the app widget manager - * @param appWidgetId the app widget id - * @param trackId the track id. -1L to not specify one - */ - public static void updateAppWidget( - Context context, AppWidgetManager appWidgetManager, int appWidgetId, long trackId) { - int size = getAppWidgetSize(appWidgetManager, appWidgetId); - RemoteViews remoteViews = getRemoteViews(context, trackId, size); - appWidgetManager.updateAppWidget(appWidgetId, remoteViews); - } - - /** - * Updates all app widgets. - * - * @param context the context - * @param trackId track id - */ - private static void updateAllAppWidgets(Context context, long trackId) { - AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); - int[] appWidgetIds = appWidgetManager.getAppWidgetIds( - new ComponentName(context, TrackWidgetProvider.class)); - for (int appWidgetId : appWidgetIds) { - updateAppWidget(context, appWidgetManager, appWidgetId, trackId); + /** + * Gets the remote views. + * + * @param context the context + * @param trackId the track id + * @param heightSize the layout height size + */ + private static RemoteViews getRemoteViews(Context context, long trackId, int heightSize) { + int layout; + switch (heightSize) { + case 4: + layout = R.layout.track_widget_4x4; + break; + case 3: + layout = R.layout.track_widget_4x3; + break; + case 2: + layout = R.layout.track_widget_4x2; + break; + case 1: + layout = R.layout.track_widget_4x1; + break; + default: + layout = R.layout.track_widget_4x2; + break; + } + RemoteViews remoteViews = new RemoteViews(context.getPackageName(), layout); + + // Get the preferences + long recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key); + boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; + boolean isPaused = PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, + PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); + boolean metricUnits = PreferencesUtils.isMetricUnits(context); + boolean reportSpeed = PreferencesUtils.isReportSpeed(context); + int item1 = PreferencesUtils.getInt( + context, R.string.track_widget_item1, PreferencesUtils.TRACK_WIDGET_ITEM1_DEFAULT); + int item2 = PreferencesUtils.getInt( + context, R.string.track_widget_item2, PreferencesUtils.TRACK_WIDGET_ITEM2_DEFAULT); + + // Get track and trip statistics + ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context); + if (trackId == -1L) { + trackId = recordingTrackId; + } + Track track = trackId != -1L ? contentProviderUtils.getTrack(trackId) + : contentProviderUtils.getLastTrack(); + TripStatistics tripStatistics = track == null ? null : track.getTripStatistics(); + + updateStatisticsContainer(context, remoteViews, track); + setItem(context, remoteViews, ITEM1_IDS, item1, tripStatistics, isRecording, isPaused, + metricUnits, reportSpeed); + setItem(context, remoteViews, ITEM2_IDS, item2, tripStatistics, isRecording, isPaused, + metricUnits, reportSpeed); + + updateRecordButton(context, remoteViews, isRecording, isPaused); + updateStopButton(context, remoteViews, isRecording); + if (heightSize > 1) { + int item3 = PreferencesUtils.getInt( + context, R.string.track_widget_item3, PreferencesUtils.TRACK_WIDGET_ITEM3_DEFAULT); + int item4 = PreferencesUtils.getInt( + context, R.string.track_widget_item4, PreferencesUtils.TRACK_WIDGET_ITEM4_DEFAULT); + setItem(context, remoteViews, ITEM3_IDS, item3, tripStatistics, isRecording, isPaused, + metricUnits, reportSpeed); + setItem(context, remoteViews, ITEM4_IDS, item4, tripStatistics, isRecording, isPaused, + metricUnits, reportSpeed); + updateRecordStatus(context, remoteViews, isRecording, isPaused); + } + return remoteViews; } - } - /** - * Gets the remote views. - * - * @param context the context - * @param trackId the track id - * @param heightSize the layout height size - */ - private static RemoteViews getRemoteViews(Context context, long trackId, int heightSize) { - int layout; - switch (heightSize) { - case 4: - layout = R.layout.track_widget_4x4; - break; - case 3: - layout = R.layout.track_widget_4x3; - break; - case 2: - layout = R.layout.track_widget_4x2; - break; - case 1: - layout = R.layout.track_widget_4x1; - break; - default: - layout = R.layout.track_widget_4x2; - break; + /** + * Gets the app widget size. + * + * @param appWidgetManager the app widget manager + * @param appWidgetId the app widget id + */ + private static int getAppWidgetSize(AppWidgetManager appWidgetManager, int appWidgetId) { + Bundle bundle = appWidgetManager.getAppWidgetOptions(appWidgetId); + return bundle.getInt(APP_WIDGET_SIZE_KEY, HOME_SCREEN_DEFAULT_SIZE); } - RemoteViews remoteViews = new RemoteViews(context.getPackageName(), layout); - // Get the preferences - long recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key); - boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; - boolean isPaused = PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, - PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); - boolean metricUnits = PreferencesUtils.isMetricUnits(context); - boolean reportSpeed = PreferencesUtils.isReportSpeed(context); - int item1 = PreferencesUtils.getInt( - context, R.string.track_widget_item1, PreferencesUtils.TRACK_WIDGET_ITEM1_DEFAULT); - int item2 = PreferencesUtils.getInt( - context, R.string.track_widget_item2, PreferencesUtils.TRACK_WIDGET_ITEM2_DEFAULT); + /** + * Sets a widget item. + * + * @param context the context + * @param remoteViews the remote view + * @param ids the item's ids + * @param value the item value + * @param tripStatistics the trip statistics + * @param metricUnits true to use metric units + * @param reportSpeed try to report speed + */ + private static void setItem(Context context, RemoteViews remoteViews, int[] ids, int value, + TripStatistics tripStatistics, boolean isRecording, boolean isPaused, boolean metricUnits, + boolean reportSpeed) { + switch (value) { + case 0: + updateDistance(context, remoteViews, ids, tripStatistics, metricUnits); + break; + case 1: + updateTotalTime(context, remoteViews, ids, tripStatistics, isRecording, isPaused); + break; + case 2: + updateAverageSpeed(context, remoteViews, ids, tripStatistics, metricUnits, reportSpeed); + break; + case 3: + updateMovingTime(context, remoteViews, ids, tripStatistics); + break; + case 4: + updateAverageMovingSpeed( + context, remoteViews, ids, tripStatistics, metricUnits, reportSpeed); + break; + default: + updateDistance(context, remoteViews, ids, tripStatistics, metricUnits); + break; - // Get track and trip statistics - ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context); - if (trackId == -1L) { - trackId = recordingTrackId; + } + if (value != 1) { + remoteViews.setViewVisibility(ids[1], View.VISIBLE); + remoteViews.setViewVisibility(ids[2], View.VISIBLE); + remoteViews.setViewVisibility(ids[3], View.GONE); + remoteViews.setChronometer(ids[3], SystemClock.elapsedRealtime(), null, false); + } } - Track track = trackId != -1L ? contentProviderUtils.getTrack(trackId) - : contentProviderUtils.getLastTrack(); - TripStatistics tripStatistics = track == null ? null : track.getTripStatistics(); - updateStatisticsContainer(context, remoteViews, track); - setItem(context, remoteViews, ITEM1_IDS, item1, tripStatistics, isRecording, isPaused, - metricUnits, reportSpeed); - setItem(context, remoteViews, ITEM2_IDS, item2, tripStatistics, isRecording, isPaused, - metricUnits, reportSpeed); - - updateRecordButton(context, remoteViews, isRecording, isPaused); - updateStopButton(context, remoteViews, isRecording); - if (heightSize > 1) { - int item3 = PreferencesUtils.getInt( - context, R.string.track_widget_item3, PreferencesUtils.TRACK_WIDGET_ITEM3_DEFAULT); - int item4 = PreferencesUtils.getInt( - context, R.string.track_widget_item4, PreferencesUtils.TRACK_WIDGET_ITEM4_DEFAULT); - setItem(context, remoteViews, ITEM3_IDS, item3, tripStatistics, isRecording, isPaused, - metricUnits, reportSpeed); - setItem(context, remoteViews, ITEM4_IDS, item4, tripStatistics, isRecording, isPaused, - metricUnits, reportSpeed); - updateRecordStatus(context, remoteViews, isRecording, isPaused); + /** + * Updates the statistics container. + * + * @param context the context + * @param remoteViews the remote views + * @param track the track + */ + private static void updateStatisticsContainer( + Context context, RemoteViews remoteViews, Track track) { + PendingIntent pendingIntent; + if (track != null) { + Intent intent = IntentUtils.newIntent(context, TrackDetailActivity.class) + .putExtra(TrackDetailActivity.EXTRA_TRACK_ID, track.getId()); + pendingIntent = TaskStackBuilder.create(context) + .addParentStack(TrackDetailActivity.class).addNextIntent(intent).getPendingIntent(0, 0); + } else { + Intent intent = IntentUtils.newIntent(context, TrackListActivity.class); + pendingIntent = TaskStackBuilder.create(context).addNextIntent(intent).getPendingIntent(0, 0); + } + remoteViews.setOnClickPendingIntent(R.id.track_widget_stats_container, pendingIntent); } - return remoteViews; - } - - /** - * Gets the app widget size. - * - * @param appWidgetManager the app widget manager - * @param appWidgetId the app widget id - */ - private static int getAppWidgetSize(AppWidgetManager appWidgetManager, int appWidgetId) { - Bundle bundle = appWidgetManager.getAppWidgetOptions(appWidgetId); - return bundle.getInt(APP_WIDGET_SIZE_KEY, HOME_SCREEN_DEFAULT_SIZE); - } - - /** - * Sets a widget item. - * - * @param context the context - * @param remoteViews the remote view - * @param ids the item's ids - * @param value the item value - * @param tripStatistics the trip statistics - * @param metricUnits true to use metric units - * @param reportSpeed try to report speed - */ - private static void setItem(Context context, RemoteViews remoteViews, int[] ids, int value, - TripStatistics tripStatistics, boolean isRecording, boolean isPaused, boolean metricUnits, - boolean reportSpeed) { - switch (value) { - case 0: - updateDistance(context, remoteViews, ids, tripStatistics, metricUnits); - break; - case 1: - updateTotalTime(context, remoteViews, ids, tripStatistics, isRecording, isPaused); - break; - case 2: - updateAverageSpeed(context, remoteViews, ids, tripStatistics, metricUnits, reportSpeed); - break; - case 3: - updateMovingTime(context, remoteViews, ids, tripStatistics); - break; - case 4: - updateAverageMovingSpeed( - context, remoteViews, ids, tripStatistics, metricUnits, reportSpeed); - break; - default: - updateDistance(context, remoteViews, ids, tripStatistics, metricUnits); - break; + /** + * Updates distance. + * + * @param context the context + * @param remoteViews the remote views + * @param ids the item's ids + * @param tripStatistics the trip statistics + * @param metricUnits true to use metric units + */ + private static void updateDistance(Context context, RemoteViews remoteViews, int[] ids, + TripStatistics tripStatistics, boolean metricUnits) { + double totalDistance = tripStatistics == null ? Double.NaN : tripStatistics.getTotalDistance(); + String[] totalDistanceParts = StringUtils.getDistanceParts(context, totalDistance, metricUnits); + if (totalDistanceParts[0] == null) { + totalDistanceParts[0] = context.getString(R.string.value_unknown); + } + remoteViews.setTextViewText(ids[0], context.getString(R.string.stats_distance)); + remoteViews.setTextViewText(ids[1], totalDistanceParts[0]); + remoteViews.setTextViewText(ids[2], totalDistanceParts[1]); } - if (value != 1) { - remoteViews.setViewVisibility(ids[1], View.VISIBLE); - remoteViews.setViewVisibility(ids[2], View.VISIBLE); - remoteViews.setViewVisibility(ids[3], View.GONE); - remoteViews.setChronometer(ids[3], SystemClock.elapsedRealtime(), null, false); + + /** + * Updates total time. + * + * @param context the context + * @param remoteViews the remote views + * @param ids the item's ids + * @param tripStatistics the trip statistics + */ + private static void updateTotalTime(Context context, RemoteViews remoteViews, int[] ids, + TripStatistics tripStatistics, boolean isRecording, boolean isPaused) { + if (isRecording && !isPaused && tripStatistics != null) { + long time = tripStatistics.getTotalTime() + System.currentTimeMillis() + - tripStatistics.getStopTime(); + remoteViews.setChronometer(ids[3], SystemClock.elapsedRealtime() - time, null, true); + remoteViews.setViewVisibility(ids[1], View.GONE); + remoteViews.setViewVisibility(ids[2], View.GONE); + remoteViews.setViewVisibility(ids[3], View.VISIBLE); + } else { + remoteViews.setChronometer(ids[3], SystemClock.elapsedRealtime(), null, false); + remoteViews.setViewVisibility(ids[1], View.VISIBLE); + remoteViews.setViewVisibility(ids[2], View.GONE); + remoteViews.setViewVisibility(ids[3], View.GONE); + + String totalTime = tripStatistics == null ? context.getString(R.string.value_unknown) + : StringUtils.formatElapsedTime(tripStatistics.getTotalTime()); + remoteViews.setTextViewText(ids[0], context.getString(R.string.stats_total_time)); + remoteViews.setTextViewText(ids[1], totalTime); + } } - } - /** - * Updates the statistics container. - * - * @param context the context - * @param remoteViews the remote views - * @param track the track - */ - private static void updateStatisticsContainer( - Context context, RemoteViews remoteViews, Track track) { - PendingIntent pendingIntent; - if (track != null) { - Intent intent = IntentUtils.newIntent(context, TrackDetailActivity.class) - .putExtra(TrackDetailActivity.EXTRA_TRACK_ID, track.getId()); - pendingIntent = TaskStackBuilder.create(context) - .addParentStack(TrackDetailActivity.class).addNextIntent(intent).getPendingIntent(0, 0); - } else { - Intent intent = IntentUtils.newIntent(context, TrackListActivity.class); - pendingIntent = TaskStackBuilder.create(context).addNextIntent(intent).getPendingIntent(0, 0); + /** + * Updates average speed. + * + * @param context the context + * @param remoteViews the remote views + * @param ids the item's ids + * @param tripStatistics the trip statistics + * @param metricUnits true to use metric units + * @param reportSpeed true to report speed + */ + private static void updateAverageSpeed(Context context, RemoteViews remoteViews, int[] ids, + TripStatistics tripStatistics, boolean metricUnits, boolean reportSpeed) { + String averageSpeedLabel = context.getString( + reportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace); + remoteViews.setTextViewText(ids[0], averageSpeedLabel); + + Double speed = tripStatistics == null ? Double.NaN : tripStatistics.getAverageSpeed(); + String[] speedParts = StringUtils.getSpeedParts(context, speed, metricUnits, reportSpeed); + + if (speedParts[0] == null) { + speedParts[0] = context.getString(R.string.value_unknown); + } + + remoteViews.setTextViewText(ids[1], speedParts[0]); + remoteViews.setTextViewText(ids[2], speedParts[1]); } - remoteViews.setOnClickPendingIntent(R.id.track_widget_stats_container, pendingIntent); - } - /** - * Updates distance. - * - * @param context the context - * @param remoteViews the remote views - * @param ids the item's ids - * @param tripStatistics the trip statistics - * @param metricUnits true to use metric units - */ - private static void updateDistance(Context context, RemoteViews remoteViews, int[] ids, - TripStatistics tripStatistics, boolean metricUnits) { - double totalDistance = tripStatistics == null ? Double.NaN : tripStatistics.getTotalDistance(); - String[] totalDistanceParts = StringUtils.getDistanceParts(context, totalDistance, metricUnits); - if (totalDistanceParts[0] == null) { - totalDistanceParts[0] = context.getString(R.string.value_unknown); - } - remoteViews.setTextViewText(ids[0], context.getString(R.string.stats_distance)); - remoteViews.setTextViewText(ids[1], totalDistanceParts[0]); - remoteViews.setTextViewText(ids[2], totalDistanceParts[1]); - } - - /** - * Updates total time. - * - * @param context the context - * @param remoteViews the remote views - * @param ids the item's ids - * @param tripStatistics the trip statistics - */ - private static void updateTotalTime(Context context, RemoteViews remoteViews, int[] ids, - TripStatistics tripStatistics, boolean isRecording, boolean isPaused) { - if (isRecording && !isPaused && tripStatistics != null) { - long time = tripStatistics.getTotalTime() + System.currentTimeMillis() - - tripStatistics.getStopTime(); - remoteViews.setChronometer(ids[3], SystemClock.elapsedRealtime() - time, null, true); - remoteViews.setViewVisibility(ids[1], View.GONE); - remoteViews.setViewVisibility(ids[2], View.GONE); - remoteViews.setViewVisibility(ids[3], View.VISIBLE); - } else { - remoteViews.setChronometer(ids[3], SystemClock.elapsedRealtime(), null, false); - remoteViews.setViewVisibility(ids[1], View.VISIBLE); - remoteViews.setViewVisibility(ids[2], View.GONE); - remoteViews.setViewVisibility(ids[3], View.GONE); - - String totalTime = tripStatistics == null ? context.getString(R.string.value_unknown) - : StringUtils.formatElapsedTime(tripStatistics.getTotalTime()); - remoteViews.setTextViewText(ids[0], context.getString(R.string.stats_total_time)); - remoteViews.setTextViewText(ids[1], totalTime); + /** + * Updates moving time. + * + * @param context the context + * @param remoteViews the remote views + * @param ids the item's ids + * @param tripStatistics the trip statistics + */ + private static void updateMovingTime( + Context context, RemoteViews remoteViews, int[] ids, TripStatistics tripStatistics) { + String movingTime = tripStatistics == null ? context.getString(R.string.value_unknown) + : StringUtils.formatElapsedTime(tripStatistics.getMovingTime()); + remoteViews.setTextViewText(ids[0], context.getString(R.string.stats_moving_time)); + remoteViews.setTextViewText(ids[1], movingTime); + remoteViews.setViewVisibility(ids[2], View.GONE); } - } - /** - * Updates average speed. - * - * @param context the context - * @param remoteViews the remote views - * @param ids the item's ids - * @param tripStatistics the trip statistics - * @param metricUnits true to use metric units - * @param reportSpeed true to report speed - */ - private static void updateAverageSpeed(Context context, RemoteViews remoteViews, int[] ids, - TripStatistics tripStatistics, boolean metricUnits, boolean reportSpeed) { - String averageSpeedLabel = context.getString( - reportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace); - remoteViews.setTextViewText(ids[0], averageSpeedLabel); + /** + * Updates average moving speed. + * + * @param context the context + * @param remoteViews the remote views + * @param ids the item's ids + * @param tripStatistics the trip statistics + * @param metricUnits true to use metric units + * @param reportSpeed true to report speed + */ + private static void updateAverageMovingSpeed(Context context, RemoteViews remoteViews, int[] ids, + TripStatistics tripStatistics, boolean metricUnits, boolean reportSpeed) { + String averageMovingSpeedLabel = context.getString( + reportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace); + remoteViews.setTextViewText(ids[0], averageMovingSpeedLabel); - Double speed = tripStatistics == null ? Double.NaN : tripStatistics.getAverageSpeed(); - String[] speedParts = StringUtils.getSpeedParts(context, speed, metricUnits, reportSpeed); - - if (speedParts[0] == null) { - speedParts[0] = context.getString(R.string.value_unknown); + Double speed = tripStatistics == null ? Double.NaN : tripStatistics.getAverageMovingSpeed(); + String[] speedParts = StringUtils.getSpeedParts(context, speed, metricUnits, reportSpeed); + + if (speedParts[0] == null) { + speedParts[0] = context.getString(R.string.value_unknown); + } + + remoteViews.setTextViewText(ids[1], speedParts[0]); + remoteViews.setTextViewText(ids[2], speedParts[1]); } - - remoteViews.setTextViewText(ids[1], speedParts[0]); - remoteViews.setTextViewText(ids[2], speedParts[1]); - } - /** - * Updates moving time. - * - * @param context the context - * @param remoteViews the remote views - * @param ids the item's ids - * @param tripStatistics the trip statistics - */ - private static void updateMovingTime( - Context context, RemoteViews remoteViews, int[] ids, TripStatistics tripStatistics) { - String movingTime = tripStatistics == null ? context.getString(R.string.value_unknown) - : StringUtils.formatElapsedTime(tripStatistics.getMovingTime()); - remoteViews.setTextViewText(ids[0], context.getString(R.string.stats_moving_time)); - remoteViews.setTextViewText(ids[1], movingTime); - remoteViews.setViewVisibility(ids[2], View.GONE); - } - - /** - * Updates average moving speed. - * - * @param context the context - * @param remoteViews the remote views - * @param ids the item's ids - * @param tripStatistics the trip statistics - * @param metricUnits true to use metric units - * @param reportSpeed true to report speed - */ - private static void updateAverageMovingSpeed(Context context, RemoteViews remoteViews, int[] ids, - TripStatistics tripStatistics, boolean metricUnits, boolean reportSpeed) { - String averageMovingSpeedLabel = context.getString( - reportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace); - remoteViews.setTextViewText(ids[0], averageMovingSpeedLabel); - - Double speed = tripStatistics == null ? Double.NaN : tripStatistics.getAverageMovingSpeed(); - String[] speedParts = StringUtils.getSpeedParts(context, speed, metricUnits, reportSpeed); - - if (speedParts[0] == null) { - speedParts[0] = context.getString(R.string.value_unknown); + /** + * Updates the record button. + * + * @param context the context + * @param remoteViews the remote views + * @param isRecording true if recording + * @param recordingTrackPaused true if recording track is paused + */ + private static void updateRecordButton( + Context context, RemoteViews remoteViews, boolean isRecording, boolean recordingTrackPaused) { + remoteViews.setImageViewResource(R.id.track_widget_record_button, + isRecording && !recordingTrackPaused ? R.drawable.button_pause : R.drawable.button_record); + int recordActionId; + if (isRecording) { + recordActionId = recordingTrackPaused ? R.string.track_action_resume + : R.string.track_action_pause; + } else { + recordActionId = R.string.track_action_start; + } + Intent intent = new Intent(context, ControlRecordingService.class).setAction( + context.getString(recordActionId)); + PendingIntent pendingIntent = PendingIntent.getService( + context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + remoteViews.setOnClickPendingIntent(R.id.track_widget_record_button, pendingIntent); } - - remoteViews.setTextViewText(ids[1], speedParts[0]); - remoteViews.setTextViewText(ids[2], speedParts[1]); - } - /** - * Updates the record button. - * - * @param context the context - * @param remoteViews the remote views - * @param isRecording true if recording - * @param recordingTrackPaused true if recording track is paused - */ - private static void updateRecordButton( - Context context, RemoteViews remoteViews, boolean isRecording, boolean recordingTrackPaused) { - remoteViews.setImageViewResource(R.id.track_widget_record_button, - isRecording && !recordingTrackPaused ? R.drawable.button_pause : R.drawable.button_record); - int recordActionId; - if (isRecording) { - recordActionId = recordingTrackPaused ? R.string.track_action_resume - : R.string.track_action_pause; - } else { - recordActionId = R.string.track_action_start; + /** + * Updates the stop button. + * + * @param context the context + * @param remoteViews the remote views + * @param isRecording true if recording + */ + private static void updateStopButton( + Context context, RemoteViews remoteViews, boolean isRecording) { + remoteViews.setImageViewResource(R.id.track_widget_stop_button, + isRecording ? R.drawable.button_stop : R.drawable.ic_button_stop_disabled); + remoteViews.setBoolean(R.id.track_widget_stop_button, "setEnabled", isRecording); + if (isRecording) { + Intent intent = new Intent(context, ControlRecordingService.class).setAction( + context.getString(R.string.track_action_end)); + PendingIntent pendingIntent = PendingIntent.getService( + context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + remoteViews.setOnClickPendingIntent(R.id.track_widget_stop_button, pendingIntent); + } } - Intent intent = new Intent(context, ControlRecordingService.class).setAction( - context.getString(recordActionId)); - PendingIntent pendingIntent = PendingIntent.getService( - context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); - remoteViews.setOnClickPendingIntent(R.id.track_widget_record_button, pendingIntent); - } - /** - * Updates the stop button. - * - * @param context the context - * @param remoteViews the remote views - * @param isRecording true if recording - */ - private static void updateStopButton( - Context context, RemoteViews remoteViews, boolean isRecording) { - remoteViews.setImageViewResource(R.id.track_widget_stop_button, - isRecording ? R.drawable.button_stop : R.drawable.ic_button_stop_disabled); - remoteViews.setBoolean(R.id.track_widget_stop_button, "setEnabled", isRecording); - if (isRecording) { - Intent intent = new Intent(context, ControlRecordingService.class).setAction( - context.getString(R.string.track_action_end)); - PendingIntent pendingIntent = PendingIntent.getService( - context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); - remoteViews.setOnClickPendingIntent(R.id.track_widget_stop_button, pendingIntent); + /** + * Updates recording status. + * + * @param context the context + * @param remoteViews the remote views + * @param isRecording true if recording + * @param recordingTrackPaused true if recording track is paused + */ + private static void updateRecordStatus( + Context context, RemoteViews remoteViews, boolean isRecording, boolean recordingTrackPaused) { + String status; + int colorId; + if (isRecording) { + status = context.getString( + recordingTrackPaused ? R.string.generic_paused : R.string.generic_recording); + colorId = recordingTrackPaused ? android.R.color.white : R.color.recording_text; + } else { + status = ""; + colorId = android.R.color.white; + } + remoteViews.setTextColor( + R.id.track_widget_record_status, context.getResources().getColor(colorId)); + remoteViews.setTextViewText(R.id.track_widget_record_status, status); } - } - /** - * Updates recording status. - * - * @param context the context - * @param remoteViews the remote views - * @param isRecording true if recording - * @param recordingTrackPaused true if recording track is paused - */ - private static void updateRecordStatus( - Context context, RemoteViews remoteViews, boolean isRecording, boolean recordingTrackPaused) { - String status; - int colorId; - if (isRecording) { - status = context.getString( - recordingTrackPaused ? R.string.generic_paused : R.string.generic_recording); - colorId = recordingTrackPaused ? android.R.color.white : R.color.recording_text; - } else { - status = ""; - colorId = android.R.color.white; + @Override + public void onReceive(Context context, Intent intent) { + super.onReceive(context, intent); + String action = intent.getAction(); + if (context.getString(R.string.track_paused_broadcast_action).equals(action) + || context.getString(R.string.track_resumed_broadcast_action).equals(action) + || context.getString(R.string.track_started_broadcast_action).equals(action) + || context.getString(R.string.track_stopped_broadcast_action).equals(action) + || context.getString(R.string.track_update_broadcast_action).equals(action)) { + long trackId = intent.getLongExtra(context.getString(R.string.track_id_broadcast_extra), -1L); + updateAllAppWidgets(context, trackId); + } + } + + @Override + public void onEnabled(Context context) { + super.onEnabled(context); + // Need to update all app widgets after phone reboot + updateAllAppWidgets(context, -1L); + } + + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + super.onUpdate(context, appWidgetManager, appWidgetIds); + // Need to update all app widgets after software update + updateAllAppWidgets(context, -1L); + } + + @TargetApi(16) + @Override + public void onAppWidgetOptionsChanged( + Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) { + super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions); + if (newOptions != null) { + int newSize; + if (newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, -1) + == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) { + newSize = 1; + } else { + int height = newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT); + if (height == 0) { + newSize = 2; + } else if (height >= FOUR_CELLS) { + newSize = 4; + } else if (height >= THREE_CELLS) { + newSize = 3; + } else if (height >= TWO_CELLS) { + newSize = 2; + } else { + newSize = 1; + } + } + int size = getAppWidgetSize(appWidgetManager, appWidgetId); + + if (size != newSize) { + Bundle bundle = new Bundle(); + bundle.putInt(APP_WIDGET_SIZE_KEY, size); + appWidgetManager.updateAppWidgetOptions(appWidgetId, bundle); + + updateAppWidget(context, appWidgetManager, appWidgetId, -1L); + } + } } - remoteViews.setTextColor( - R.id.track_widget_record_status, context.getResources().getColor(colorId)); - remoteViews.setTextViewText(R.id.track_widget_record_status, status); - } }