diff --git a/MyTracks/.settings/org.eclipse.jdt.core.prefs b/MyTracks/.settings/org.eclipse.jdt.core.prefs index dec15e80b..65082d868 100644 --- a/MyTracks/.settings/org.eclipse.jdt.core.prefs +++ b/MyTracks/.settings/org.eclipse.jdt.core.prefs @@ -1,4 +1,4 @@ -#Thu Aug 12 18:54:52 BRT 2010 +#Wed Sep 29 18:44:16 BRT 2010 eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 diff --git a/MyTracks/AndroidManifest.xml b/MyTracks/AndroidManifest.xml index 2b14aad27..79f8ff518 100755 --- a/MyTracks/AndroidManifest.xml +++ b/MyTracks/AndroidManifest.xml @@ -56,8 +56,11 @@ + + + android:label="@string/statistics" /> diff --git a/MyTracks/res/drawable/btn_arrow_left_default.png b/MyTracks/res/drawable/btn_arrow_left_default.png deleted file mode 100644 index a9709aaba..000000000 Binary files a/MyTracks/res/drawable/btn_arrow_left_default.png and /dev/null differ diff --git a/MyTracks/res/drawable/btn_arrow_left_press.png b/MyTracks/res/drawable/btn_arrow_left_press.png deleted file mode 100644 index 262c928c6..000000000 Binary files a/MyTracks/res/drawable/btn_arrow_left_press.png and /dev/null differ diff --git a/MyTracks/res/drawable/btn_arrow_right_default.png b/MyTracks/res/drawable/btn_arrow_right_default.png deleted file mode 100644 index a4f78dc0d..000000000 Binary files a/MyTracks/res/drawable/btn_arrow_right_default.png and /dev/null differ diff --git a/MyTracks/res/drawable/btn_arrow_right_press.png b/MyTracks/res/drawable/btn_arrow_right_press.png deleted file mode 100644 index 4b37b0df2..000000000 Binary files a/MyTracks/res/drawable/btn_arrow_right_press.png and /dev/null differ diff --git a/MyTracks/res/drawable/left_arrow_chart.png b/MyTracks/res/drawable/left_arrow_chart.png new file mode 100644 index 000000000..1dca36914 Binary files /dev/null and b/MyTracks/res/drawable/left_arrow_chart.png differ diff --git a/MyTracks/res/drawable/left_arrow_chart_press.png b/MyTracks/res/drawable/left_arrow_chart_press.png new file mode 100644 index 000000000..58810b417 Binary files /dev/null and b/MyTracks/res/drawable/left_arrow_chart_press.png differ diff --git a/MyTracks/res/drawable/btn_arrow_left.xml b/MyTracks/res/drawable/left_arrow_chart_stateful.xml similarity index 80% rename from MyTracks/res/drawable/btn_arrow_left.xml rename to MyTracks/res/drawable/left_arrow_chart_stateful.xml index 042c8bd06..d76183f4d 100644 --- a/MyTracks/res/drawable/btn_arrow_left.xml +++ b/MyTracks/res/drawable/left_arrow_chart_stateful.xml @@ -17,11 +17,11 @@ limitations under the License. + android:drawable="@drawable/left_arrow_chart" /> + android:drawable="@drawable/left_arrow_chart_press" /> - + diff --git a/MyTracks/res/drawable/left_arrow_stats.png b/MyTracks/res/drawable/left_arrow_stats.png new file mode 100644 index 000000000..841377b58 Binary files /dev/null and b/MyTracks/res/drawable/left_arrow_stats.png differ diff --git a/MyTracks/res/drawable/left_arrow_stats_press.png b/MyTracks/res/drawable/left_arrow_stats_press.png new file mode 100644 index 000000000..517293a7a Binary files /dev/null and b/MyTracks/res/drawable/left_arrow_stats_press.png differ diff --git a/MyTracks/res/drawable/btn_arrow_right.xml b/MyTracks/res/drawable/left_arrow_stats_stateful.xml similarity index 80% rename from MyTracks/res/drawable/btn_arrow_right.xml rename to MyTracks/res/drawable/left_arrow_stats_stateful.xml index 762436c9c..5b3169af4 100644 --- a/MyTracks/res/drawable/btn_arrow_right.xml +++ b/MyTracks/res/drawable/left_arrow_stats_stateful.xml @@ -17,11 +17,11 @@ limitations under the License. + android:drawable="@drawable/left_arrow_stats" /> + android:drawable="@drawable/left_arrow_stats_press" /> - + diff --git a/MyTracks/res/drawable/left_arrow_track.png b/MyTracks/res/drawable/left_arrow_track.png new file mode 100644 index 000000000..415e5dbf2 Binary files /dev/null and b/MyTracks/res/drawable/left_arrow_track.png differ diff --git a/MyTracks/res/drawable/left_arrow_track_press.png b/MyTracks/res/drawable/left_arrow_track_press.png new file mode 100644 index 000000000..acf9fb36d Binary files /dev/null and b/MyTracks/res/drawable/left_arrow_track_press.png differ diff --git a/MyTracks/res/drawable/left_arrow_track_stateful.xml b/MyTracks/res/drawable/left_arrow_track_stateful.xml new file mode 100644 index 000000000..3ae27009c --- /dev/null +++ b/MyTracks/res/drawable/left_arrow_track_stateful.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + diff --git a/MyTracks/res/drawable/right_arrow_chart.png b/MyTracks/res/drawable/right_arrow_chart.png new file mode 100644 index 000000000..43e3d57b8 Binary files /dev/null and b/MyTracks/res/drawable/right_arrow_chart.png differ diff --git a/MyTracks/res/drawable/right_arrow_chart_press.png b/MyTracks/res/drawable/right_arrow_chart_press.png new file mode 100644 index 000000000..c2c8ad37f Binary files /dev/null and b/MyTracks/res/drawable/right_arrow_chart_press.png differ diff --git a/MyTracks/res/drawable/right_arrow_chart_stateful.xml b/MyTracks/res/drawable/right_arrow_chart_stateful.xml new file mode 100644 index 000000000..eeb6b33f8 --- /dev/null +++ b/MyTracks/res/drawable/right_arrow_chart_stateful.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + diff --git a/MyTracks/res/drawable/right_arrow_stats.png b/MyTracks/res/drawable/right_arrow_stats.png new file mode 100644 index 000000000..57094ed4d Binary files /dev/null and b/MyTracks/res/drawable/right_arrow_stats.png differ diff --git a/MyTracks/res/drawable/right_arrow_stats_press.png b/MyTracks/res/drawable/right_arrow_stats_press.png new file mode 100644 index 000000000..e57fc81fc Binary files /dev/null and b/MyTracks/res/drawable/right_arrow_stats_press.png differ diff --git a/MyTracks/res/drawable/right_arrow_stats_stateful.xml b/MyTracks/res/drawable/right_arrow_stats_stateful.xml new file mode 100644 index 000000000..3ade4f429 --- /dev/null +++ b/MyTracks/res/drawable/right_arrow_stats_stateful.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + diff --git a/MyTracks/res/drawable/right_arrow_track.png b/MyTracks/res/drawable/right_arrow_track.png new file mode 100644 index 000000000..5accdf690 Binary files /dev/null and b/MyTracks/res/drawable/right_arrow_track.png differ diff --git a/MyTracks/res/drawable/right_arrow_track_press.png b/MyTracks/res/drawable/right_arrow_track_press.png new file mode 100644 index 000000000..feba85b1c Binary files /dev/null and b/MyTracks/res/drawable/right_arrow_track_press.png differ diff --git a/MyTracks/res/drawable/right_arrow_track_stateful.xml b/MyTracks/res/drawable/right_arrow_track_stateful.xml new file mode 100644 index 000000000..9e5c364fd --- /dev/null +++ b/MyTracks/res/drawable/right_arrow_track_stateful.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + diff --git a/MyTracks/res/menu/main.xml b/MyTracks/res/menu/main.xml index 23053e2d5..9acffc9b3 100644 --- a/MyTracks/res/menu/main.xml +++ b/MyTracks/res/menu/main.xml @@ -20,10 +20,15 @@ android:icon="@android:drawable/ic_menu_preferences" android:orderInCategory="1001" android:title="@string/settings"/> + + diff --git a/MyTracks/res/values-de/strings.xml b/MyTracks/res/values-de/strings.xml index 1a6c5b61f..af2c04ed5 100644 --- a/MyTracks/res/values-de/strings.xml +++ b/MyTracks/res/values-de/strings.xml @@ -155,7 +155,6 @@ limitations under the License. Dauer zwischen Ansagen: Kürzeste Zeit zwichen zwei Ansagen - Gesprochene Ansagen sind mit Ihrer Version von Android nicht verfügbar. Kleinste Entfernung zwischen Punkten: Kleinste Entfernung zwischen zwei Positionsaufzeichnungen diff --git a/MyTracks/res/values-es/strings.xml b/MyTracks/res/values-es/strings.xml index 4e905cd18..5e6a29f8b 100644 --- a/MyTracks/res/values-es/strings.xml +++ b/MyTracks/res/values-es/strings.xml @@ -158,7 +158,6 @@ limitations under the License. Tiempo entre anuncios Tiempo mínimo entre dos anuncios de voz - Conversión de anuncios de texto a voz no esta disponible en su versión de Android Distancia mínima entre puntos consecutivos Distancia mínima entre dos grabaciones de posición diff --git a/MyTracks/res/values-fr/strings.xml b/MyTracks/res/values-fr/strings.xml index 6de3c3823..a67f90749 100644 --- a/MyTracks/res/values-fr/strings.xml +++ b/MyTracks/res/values-fr/strings.xml @@ -158,7 +158,6 @@ limitations under the License. Fréquence des annonces Temps minimum entre eux annonces vocales - La synthèse vocale n\'est pas disponible pour cette version d\'Android Distance min. entre 2 points Plus petite distance entre deux enregistrements de votre position diff --git a/MyTracks/res/values-it/strings.xml b/MyTracks/res/values-it/strings.xml index 344770cf2..2f9024552 100644 --- a/MyTracks/res/values-it/strings.xml +++ b/MyTracks/res/values-it/strings.xml @@ -164,7 +164,6 @@ limitations under the License. Intervallo fra due annunci: Tempo minimo fra due annunci audio - Gli annunci audio non sono disponibili in questa varsione di Android Distanza minima fra due punti successivi: Distanza minima fra due successive registrazioni della posizione diff --git a/MyTracks/res/values-ja/strings.xml b/MyTracks/res/values-ja/strings.xml index 447a48540..b88304819 100644 --- a/MyTracks/res/values-ja/strings.xml +++ b/MyTracks/res/values-ja/strings.xml @@ -164,7 +164,7 @@ limitations under the License. 定期通知 音声通知間隔の最小時間 - お使いのAndroidの版では音声読み上げ機能がありません + ポイント間の最短距離 位置記録間の最短距離 diff --git a/MyTracks/res/values-pl/strings.xml b/MyTracks/res/values-pl/strings.xml index e15093745..b22ab3119 100644 --- a/MyTracks/res/values-pl/strings.xml +++ b/MyTracks/res/values-pl/strings.xml @@ -164,7 +164,7 @@ limitations under the License. Częstotliwość komunikatów Minimalny czas między dwoma komunikatami głosowymi - Brak wsparcia dla syntezy mowy w twojej wersji Androida + Min. odległość między punktami Najmniejsza odległość między dwoma zapisami twojej pozycji diff --git a/MyTracks/res/values-pt-rBR/strings.xml b/MyTracks/res/values-pt-rBR/strings.xml index 6efc9338f..f84c710ce 100644 --- a/MyTracks/res/values-pt-rBR/strings.xml +++ b/MyTracks/res/values-pt-rBR/strings.xml @@ -157,7 +157,6 @@ limitations under the License. Compartilhamento Tempo entre anúncios Tempo mínimo entre dois anúncios de voz - Anúncios de voz não estão disponíveis em sua versão do Android Dist. mín. entre pontos Menor distância entre duas gravações da sua posição Dist. máx. entre pontos diff --git a/MyTracks/res/values-sv/strings.xml b/MyTracks/res/values-sv/strings.xml index 541327a22..43cd3eba0 100644 --- a/MyTracks/res/values-sv/strings.xml +++ b/MyTracks/res/values-sv/strings.xml @@ -43,19 +43,19 @@ limitations under the License. Ett fel uppstod. Försök igen. Spåret finns inte. Den här filen är för stor (måste vara mindre än 500 KB). Avbryter. - Kan inte skapa markör. Ingen possition tillgänglig. + Kan inte skapa markör. Ingen position tillgänglig. - Väntar på bättre GPS-posstion. Se till att himlen är synlig. + Väntar på bättre GPS-position. Se till att himlen är synlig. Spelar nu in ett nytt spår. Stoppade inspelning av ditt spår. En ny Google My Map har skapats på maps.google.com. Ditt spår har skickats till maps.google.com. - Ditt spår har skockats till ett nytt kalkylblad på docs.google.com. + Ditt spår har skickats till ett nytt kalkylblad på docs.google.com. Ditt spår har skickats till docs.google.com. Det kan ibland ta några minuter innan nya spår visas. Klicka här för att gå till Säkerhet och Platsinställningar och kryssa i \'aktivera gps-satteliter\'. Tryck på tillbakaknappen för att återgå. - OBS: En ungerfärlig position som avgörs från mobilmaster och WIFI-nät visas. + OBS: En ungefärlig position som avgörs från mobilmaster och WIFI-nät visas. En position kan för närvarande inte fastställas. Aktivera mer platskällor (se Säkerhet och Platsinställningar) och se till att himlen är synlig. Försök igen senare. En positionsmarkör har lagts in. En statistikmarkör har lagts in. @@ -71,7 +71,7 @@ limitations under the License. Skickar spår till Google Maps... Skapar en ny My Map... Importerar ett GPS-spår. Detta kan ta en stund. - Skriver til till SD-kortet. Detta kan ta en stund. + Skriver till SD-kortet. Detta kan ta en stund. Uppdaterar spårstatistik... @@ -125,7 +125,7 @@ limitations under the License. Exportera alla spår Exporteringen slutfördes - Välj ett spår. TryckPress&håll för fler alternativ. + Välj ett spår. Tryck&håll för fler alternativ. Alla spår: Det här spåret Dela med vänner... @@ -143,13 +143,13 @@ limitations under the License. Visa på karta Infoga positionspunkt Infoga statistik - Välj en markör. TryckPress&håll för fler alternativ. + Välj en markör. Tryck&håll för fler alternativ. Spårdetaljer Spårnamn Spårbeskrivning - Aktivitetstyp (t ex vandring, cyckling, låpning, klättring...) + Aktivitetstyp (t ex vandring, cyckling, löpning, klättring...) Markördetaljer @@ -165,7 +165,7 @@ limitations under the License. Meddelandefrekvens Minimumtid mellan två röstmeddelanden - Text-till-talmeddelanden är inte tillgängligt i din version av Android + Min distans mellan två punkter Minsta distans mellan två inspelningar av din position @@ -199,11 +199,11 @@ limitations under the License. Tid mellan två inspelningar av telefonens signalstyrka Delningsfrekvens - Maximala tiden eller distansen varefter en ny delnginsmarkör kommer infogas + Maximala tiden eller distansen varefter en ny delningsmarkör kommer infogas Tempo - GPS-satteliter + GPS-satelliter Välje en karta från ditt konto: @@ -240,7 +240,7 @@ limitations under the License. Publik Olistat - Du har inge kartor än. + Du har inga kartor än. @@ -253,7 +253,7 @@ limitations under the License. Höjddiagram Höjdvinst - Höjt + Höjd Latitud Longitud diff --git a/MyTracks/res/values-zh-rCN/strings.xml b/MyTracks/res/values-zh-rCN/strings.xml index c4d44207a..00d38bcd3 100644 --- a/MyTracks/res/values-zh-rCN/strings.xml +++ b/MyTracks/res/values-zh-rCN/strings.xml @@ -167,7 +167,7 @@ limitations under the License. 提示频度 语音提示的最小时间间隔 - 你所使用的 Android 版本不支持文本到语音转换 + 最小记录间距 位置记录的最小间隔距离 diff --git a/MyTracks/res/values/keys.xml b/MyTracks/res/values/keys.xml index 6288aa969..74b14b50a 100644 --- a/MyTracks/res/values/keys.xml +++ b/MyTracks/res/values/keys.xml @@ -38,6 +38,7 @@ limitations under the License. shareUrlOnly signalSamplingFrequency splitFrequency + debugDrawBounds recordingTrack diff --git a/MyTracks/res/values/strings.xml b/MyTracks/res/values/strings.xml index b55a3ff5c..cab226775 100644 --- a/MyTracks/res/values/strings.xml +++ b/MyTracks/res/values/strings.xml @@ -32,6 +32,7 @@ limitations under the License. Please select the track to view Current track Last segment + Aggregated Statistics No location manager was found on this device! diff --git a/MyTracks/res/xml/preferences.xml b/MyTracks/res/xml/preferences.xml index 386240b6a..3233bd070 100644 --- a/MyTracks/res/xml/preferences.xml +++ b/MyTracks/res/xml/preferences.xml @@ -133,4 +133,13 @@ limitations under the License. android:persistent="false" /> + + + diff --git a/MyTracks/src/com/google/android/apps/mymaps/MyMapsConstants.java b/MyTracks/src/com/google/android/apps/mymaps/MyMapsConstants.java index 8fa70a9c2..d88a2ff5a 100644 --- a/MyTracks/src/com/google/android/apps/mymaps/MyMapsConstants.java +++ b/MyTracks/src/com/google/android/apps/mymaps/MyMapsConstants.java @@ -25,5 +25,8 @@ public class MyMapsConstants { "http://maps.google.com/maps/ms"; public static final String MAPSHOP_SERVICE = MapsClient.SERVICE; + /** + * Private constructor to prevent instantiation. + */ private MyMapsConstants() { } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/AggregatedStatsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/AggregatedStatsActivity.java new file mode 100644 index 000000000..c0f95242a --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/AggregatedStatsActivity.java @@ -0,0 +1,114 @@ +package com.google.android.apps.mytracks; + +import com.google.android.apps.mytracks.content.MyTracksProviderUtils; +import com.google.android.apps.mytracks.content.Track; +import com.google.android.apps.mytracks.stats.TripStatistics; +import com.google.android.maps.mytracks.R; + +import android.app.Activity; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.os.Bundle; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.Window; +import android.widget.ScrollView; +import android.widget.TextView; + +import java.util.List; + +/** + * Activity for viewing the combined statistics for all the recorded tracks. + * + * Other features to add - menu items to change setings. + * + * @author Fergus Nelson + */ +public class AggregatedStatsActivity extends Activity implements + OnSharedPreferenceChangeListener { + + private final StatsUtilities utils; + + private MyTracksProviderUtils tracksProvider; + + private boolean metricUnits = true; + + public AggregatedStatsActivity() { + this.utils = new StatsUtilities(this); + this.tracksProvider = MyTracksProviderUtils.Factory.get(this); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, + String key) { + Log.d(MyTracksConstants.TAG, "StatsActivity: onSharedPreferences changed " + + key); + if (key != null) { + if (key.equals(R.string.metric_units_key)) { + metricUnits = sharedPreferences.getBoolean( + getString(R.string.metric_units_key), true); + utils.setMetricUnits(metricUnits); + utils.updateUnits(); + loadAggregatedStats(); + } + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // We don't need a window title bar: + requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(R.layout.stats); + + ScrollView sv = ((ScrollView) findViewById(R.id.scrolly)); + sv.setScrollBarStyle(ScrollView.SCROLLBARS_OUTSIDE_INSET); + + SharedPreferences preferences = getSharedPreferences( + MyTracksSettings.SETTINGS_NAME, 0); + if (preferences != null) { + metricUnits = preferences.getBoolean(getString(R.string.metric_units_key), true); + preferences.registerOnSharedPreferenceChangeListener(this); + } + utils.setMetricUnits(metricUnits); + utils.updateUnits(); + utils.setSpeedLabel(R.id.speed_label, R.string.speed, R.string.pace_label); + utils.setSpeedLabels(); + + DisplayMetrics metrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(metrics); + if (metrics.heightPixels > 600) { + ((TextView) findViewById(R.id.speed_register)).setTextSize(80.0f); + } + loadAggregatedStats(); + } + + /** + * 1. Reads tracks from the db + * 2. Merges the trip stats from the tracks + * 3. Updates the view + */ + private void loadAggregatedStats() { + List tracks = retrieveTracks(); + TripStatistics rollingStats = null; + if (!tracks.isEmpty()) { + rollingStats = new TripStatistics(tracks.iterator().next() + .getStatistics()); + for (int i = 1; i < tracks.size(); i++) { + rollingStats.merge(tracks.get(i).getStatistics()); + } + } + updateView(rollingStats); + } + + private List retrieveTracks() { + return tracksProvider.getAllTracks(); + } + + private void updateView(TripStatistics aggStats) { + if (aggStats != null) { + utils.setAllStats(aggStats); + } + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java b/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java index f665d1b5e..ff43a2647 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java @@ -176,11 +176,15 @@ public class ChartActivity extends Activity implements final int idColumnIdx = cursor.getColumnIndexOrThrow(TrackPointsColumns._ID); ArrayList data = new ArrayList(); + // Need two locations so we can keep track of the last location. + Location location = new Location(""); do { lastSeenLocationId = cursor.getLong(idColumnIdx); - Location location = providerUtils.createLocation(cursor); - if (location != null && MyTracksUtils.isValidLocation(location)) { - data.add(getDataPoint(location, track)); + providerUtils.fillLocation(cursor, location); + if (MyTracksUtils.isValidLocation(location)) { + double[] point = new double[3]; + location = getDataPoint(location, track, point); + data.add(point); } } while (cursor.moveToPrevious()); cv.addDataPoints(data); @@ -461,11 +465,14 @@ public class ChartActivity extends Activity implements * data[1] = the elevation * data[2] = the speed * - * @param location a location - * @return the data point + * This must be called in order for each point. + * + * @param location the location to get data for (this method takes ownership of that location) + * @param track the track to get data from + * @param result the resulting point to fill out + * @return the previous location, now available for reuse */ - public double[] getDataPoint(Location location, Track track) { - double[] result = new double[3]; + private Location getDataPoint(Location location, Track track, double[] result) { switch (mode) { case BY_DISTANCE: result[0] = profileLength; @@ -512,8 +519,16 @@ public class ChartActivity extends Activity implements // Format as hours per unit result[2] = (60.0 / result[2]); } + + Location oldLastLocation = lastLocation; lastLocation = location; - return result; + + if (oldLastLocation == null) { + // No previous location, but return a blank one for reuse + return new Location(""); + } + + return oldLastLocation; } /** @@ -556,6 +571,8 @@ public class ChartActivity extends Activity implements try { final ArrayList theData = new ArrayList(); int points = 0; + // Need two locations so we can keep track of the last location. + Location location = new Location(""); while (lastLocationRead < track.getStopId()) { cursor = providerUtils.getLocationsCursor( selectedTrackId, lastLocationRead, bufferSize, false); @@ -567,11 +584,12 @@ public class ChartActivity extends Activity implements cursor.getColumnIndexOrThrow(TrackPointsColumns._ID); while (cursor.moveToNext()) { points++; - Location location = providerUtils.createLocation(cursor); + providerUtils.fillLocation(cursor, location); if (MyTracksUtils.isValidLocation(location)) { lastLocationRead = lastSeenLocationId = cursor.getLong(idColumnIdx); - double[] point = getDataPoint(location, track); + double[] point = new double[3]; + location = getDataPoint(location, track, point); if (points % chartSamplingFrequency == 0) { theData.add(point); } diff --git a/MyTracks/src/com/google/android/apps/mytracks/MenuManager.java b/MyTracks/src/com/google/android/apps/mytracks/MenuManager.java index e09011b86..784f835d1 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MenuManager.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MenuManager.java @@ -78,6 +78,11 @@ class MenuManager { activity.startActivity(startIntent); return true; } + case R.id.menu_aggregated_stats: { + Intent startIntent = new Intent(activity, AggregatedStatsActivity.class); + activity.startActivity(startIntent); + return true; + } case R.id.menu_help: { Intent startIntent = new Intent(activity, WelcomeActivity.class); activity.startActivity(startIntent); diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java index e8a0444ba..759260a28 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java @@ -75,7 +75,6 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; -import java.util.Random; import javax.xml.parsers.ParserConfigurationException; @@ -217,40 +216,25 @@ public class MyTracks extends TabActivity implements OnTouchListener, * Tabs/View navigation: */ - private static final int NUM_TABS = 3; - private int currentTab = 0; - private NavControls navControls; - private final int icons[] = - { R.drawable.arrow_grey, R.drawable.menu_by_time, - R.drawable.menu_elevation }; + /** Icons shown on the left for each tab. */ + private final int leftIcons[] = + { R.drawable.left_arrow_chart_stateful, + R.drawable.left_arrow_track_stateful, + R.drawable.left_arrow_stats_stateful }; + /** Icons shown on the right for each tab. */ + private final int rightIcons[] = + { R.drawable.right_arrow_stats_stateful, + R.drawable.right_arrow_chart_stateful, + R.drawable.right_arrow_track_stateful }; - private final Runnable nextActivity = new Runnable() { + private final Runnable changeTab = new Runnable() { public void run() { - currentTab = (currentTab + 1) % NUM_TABS; - navControls.setLeftIcon(icons[(currentTab + NUM_TABS - 1) % NUM_TABS]); - navControls.setRightIcon(icons[(currentTab + NUM_TABS + 1) % NUM_TABS]); - getTabHost().setCurrentTab(currentTab); - navControls.show(); + getTabHost().setCurrentTab(navControls.getCurrentIcons()); } }; - private final Runnable prevActivity = new Runnable() { - public void run() { - currentTab--; - if (currentTab < 0) { - currentTab = NUM_TABS - 1; - } - navControls.setLeftIcon(icons[(currentTab + NUM_TABS - 1) % NUM_TABS]); - navControls.setRightIcon(icons[(currentTab + NUM_TABS + 1) % NUM_TABS]); - getTabHost().setCurrentTab(currentTab); - navControls.show(); - } - }; - - private final Random random = new Random(); - public static MyTracks getInstance() { return instance; } @@ -311,9 +295,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); layout.setLayoutParams(params); - navControls = new NavControls(this, layout, prevActivity, nextActivity); - navControls.setLeftIcon(icons[NUM_TABS - 1]); - navControls.setRightIcon(icons[1]); + navControls = new NavControls(this, layout, leftIcons, rightIcons, changeTab); navControls.show(); tabHost.addView(layout); layout.setOnTouchListener(this); @@ -376,8 +358,6 @@ public class MyTracks extends TabActivity implements OnTouchListener, Log.d(MyTracksConstants.TAG, "MyTracks.onResume"); super.onResume(); tryBindTrackRecordingService(); - navControls.setLeftIcon(icons[(currentTab + NUM_TABS - 1) % NUM_TABS]); - navControls.setRightIcon(icons[(currentTab + NUM_TABS + 1) % NUM_TABS]); } @Override @@ -828,33 +808,33 @@ public class MyTracks extends TabActivity implements OnTouchListener, * This is for debugging and testing only. Useful if there is no GPS signal * available. */ - public void recordRandomLocation() { - if (trackRecordingService != null) { - Location loc = new Location("gps"); - double latitude = 37.5 + random.nextDouble() / 1000; - double longitude = -120.0 + random.nextDouble() / 1000; - loc.setLatitude(latitude); - loc.setLongitude(longitude); - loc.setAltitude(random.nextDouble() * 100); - loc.setTime(System.currentTimeMillis()); - loc.setSpeed(random.nextFloat()); - MyTracksMap map = - (MyTracksMap) getLocalActivityManager().getActivity("tab1"); - if (map != null) { - map.onLocationChanged(loc); - } - StatsActivity stats = - (StatsActivity) getLocalActivityManager().getActivity("tab2"); - if (stats != null) { - stats.onLocationChanged(loc); - } - try { - trackRecordingService.recordLocation(loc); - } catch (RemoteException e) { - Log.e(MyTracksConstants.TAG, "MyTracks", e); - } - } - } +// public void recordRandomLocation() { +// if (trackRecordingService != null) { +// Location loc = new Location("gps"); +// double latitude = 37.5 + random.nextDouble() / 1000; +// double longitude = -120.0 + random.nextDouble() / 1000; +// loc.setLatitude(latitude); +// loc.setLongitude(longitude); +// loc.setAltitude(random.nextDouble() * 100); +// loc.setTime(System.currentTimeMillis()); +// loc.setSpeed(random.nextFloat()); +// MyTracksMap map = +// (MyTracksMap) getLocalActivityManager().getActivity("tab1"); +// if (map != null) { +// map.onLocationChanged(loc); +// } +// StatsActivity stats = +// (StatsActivity) getLocalActivityManager().getActivity("tab2"); +// if (stats != null) { +// stats.onLocationChanged(loc); +// } +// try { +// trackRecordingService.recordLocation(loc); +// } catch (RemoteException e) { +// Log.e(MyTracksConstants.TAG, "MyTracks", e); +// } +// } +// } /** * Resets status information for sending to MyMaps/Docs. diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java index cf450bdb1..0aba9c5fd 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java @@ -70,8 +70,9 @@ import android.widget.Toast; * @author Leif Hendrik Wilden */ public class MyTracksMap extends MapActivity - implements LocationListener, SensorEventListener, View.OnTouchListener, - View.OnClickListener, SharedPreferences.OnSharedPreferenceChangeListener { + implements View.OnTouchListener, View.OnClickListener, + SharedPreferences.OnSharedPreferenceChangeListener { + private static final int TRACKPOINT_BUFFER_SIZE = 1024; // Saved instance state keys: // --------------------------- @@ -82,9 +83,9 @@ public class MyTracksMap extends MapActivity public static final String KEY_HAVE_GOOD_FIX = "haveGoodFix"; /** - * The currently selected track (or null if nothing selected). + * The ID of the currently selected track (or -1 if nothing selected). */ - private Track selectedTrack; + private long selectedTrackId = -1; /** * The id of the currently recording track. @@ -97,13 +98,19 @@ public class MyTracksMap extends MapActivity */ private boolean keepMyLocationVisible; + /** + * Id of the first location that was seen when reading tracks from the + * provider. + */ + private long firstSeenLocationId = -1; + /** * Id of the last location that was seen when reading tracks from the * provider. This is used to determine which locations are new compared to the * last time the mapOverlay was updated. */ private long lastSeenLocationId = -1; - + /** * Magnetic variation. */ @@ -130,19 +137,13 @@ public class MyTracksMap extends MapActivity * A thread with a looper. Post to updateTrackHandler to execute Runnables on * this thread. */ - private final HandlerThread updateTrackThread = - new HandlerThread("updateTrackThread"); + private HandlerThread updateTrackThread; /** Handler for updateTrackThread */ private Handler updateTrackHandler; private MyTracksProviderUtils providerUtils; - /** - * This value is used to decide how many points to display. - */ - private int samplingFrequency = 1; - /** * A runnable that updates the track from the provider (looking for points * added after "lastSeenLocationId". @@ -150,7 +151,7 @@ public class MyTracksMap extends MapActivity private final Runnable updateTrackRunnable = new Runnable() { @Override public void run() { - if (selectedTrack == null) { + if (!isATrackSelected()) { return; } readAllNewTrackPoints(); @@ -163,11 +164,12 @@ public class MyTracksMap extends MapActivity private Runnable restoreTrackRunnable = new Runnable() { @Override public void run() { - if (selectedTrack == null) { + if (!isATrackSelected()) { return; } mapOverlay.clearPoints(); - lastSeenLocationId = selectedTrack.getStartId(); + firstSeenLocationId = -1; + lastSeenLocationId = -1; readAllNewTrackPoints(); } }; @@ -178,7 +180,7 @@ public class MyTracksMap extends MapActivity private final Runnable restoreWaypointsRunnable = new Runnable() { @Override public void run() { - if (selectedTrack == null) { + if (!isATrackSelected()) { return; } @@ -188,7 +190,7 @@ public class MyTracksMap extends MapActivity // We will silently drop extra waypoints to make the app responsive. // TODO: Try to only load the waypoints in the view port. cursor = providerUtils.getWaypointsCursor( - selectedTrack.getId(), 0, + selectedTrackId, 0, MyTracksConstants.MAX_DISPLAYED_WAYPOINTS_POINTS); if (cursor != null) { if (cursor.moveToFirst()) { @@ -219,10 +221,9 @@ public class MyTracksMap extends MapActivity public void run() { uiHandler.post(new Runnable() { public void run() { - showTrack(selectedTrack); - mapOverlay.setSelectedTrack(selectedTrack); - mapOverlay.setShowEndMarker(selectedTrack == null || - selectedTrack.getId() != recordingTrackId); + showTrack(selectedTrackId); + mapOverlay.setTrackDrawingEnabled(isATrackSelected()); + mapOverlay.setShowEndMarker(!isRecordingSelected()); mapView.invalidate(); busyPane.setVisibility(View.GONE); updateOptionsButton(); @@ -316,6 +317,7 @@ public class MyTracksMap extends MapActivity locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); + updateTrackThread = new HandlerThread("updateTrackThread"); updateTrackThread.start(); updateTrackHandler = new Handler(updateTrackThread.getLooper()); @@ -331,8 +333,7 @@ public class MyTracksMap extends MapActivity // No track is being recorded. We should not be here. return; } - if (selectedTrack == null - || selectedTrack.getId() != recordingTrackId) { + if (!isRecordingSelected()) { // No track, or one other than the recording track is selected, // don't bother. return; @@ -348,7 +349,7 @@ public class MyTracksMap extends MapActivity public void onChange(boolean selfChange) { Log.d(MyTracksConstants.TAG, "MyTracksMap: ContentObserver.onChange waypoints"); - if (selectedTrack == null) { + if (!isATrackSelected()) { return; } updateTrackHandler.post(restoreWaypointsRunnable); @@ -371,20 +372,29 @@ public class MyTracksMap extends MapActivity setSelectedTrack(selectedTrackId); } updateOptionsButton(); + mapOverlay.setDrawBounds(preferences.getBoolean( + getString(R.string.debug_draw_bounds_key), false)); preferences.registerOnSharedPreferenceChangeListener(this); } } - protected void setupZoomControls() { - mapView.setBuiltInZoomControls(true); + /** + * Returns whether there's a track currently selected for display. + */ + private boolean isATrackSelected() { + return selectedTrackId >= 0; } - @Override - protected void onDestroy() { - // Final cleanup before activity is destroyed. - // May not be called at all in some situations. - Log.d(MyTracksConstants.TAG, "MyTracksMap.onDestroy"); - super.onDestroy(); + /** + * Returns whether we're currently recording the same track that's selected + * for display. + */ + private boolean isRecordingSelected() { + return isATrackSelected() && selectedTrackId == recordingTrackId; + } + + protected void setupZoomControls() { + mapView.setBuiltInZoomControls(true); } @Override @@ -449,10 +459,9 @@ public class MyTracksMap extends MapActivity // While this activity was paused the user may have deleted the selected // track. In that case the map overlay needs to be cleared: - Track track = mapOverlay.getSelectedTrack(); - if (track != null && !providerUtils.trackExists(track.getId())) { + if (isATrackSelected() && !providerUtils.trackExists(selectedTrackId)) { // The recording track must have been deleted meanwhile. - mapOverlay.setSelectedTrack(null); + mapOverlay.setTrackDrawingEnabled(false); mapView.invalidate(); } } @@ -514,10 +523,10 @@ public class MyTracksMap extends MapActivity + gpsProvider.getName()); } locationManager.requestLocationUpdates(gpsProvider.getName(), - 0 /*minTime*/, 0 /*minDist*/, this); + 0 /*minTime*/, 0 /*minDist*/, locationListener); try { locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, - 1000 * 60 * 5 /*minTime*/, 0 /*minDist*/, this); + 1000 * 60 * 5 /*minTime*/, 0 /*minDist*/, locationListener); } catch (RuntimeException e) { // If anything at all goes wrong with getting a cell location do not // abort. Cell location is not essential to this app. @@ -535,7 +544,7 @@ public class MyTracksMap extends MapActivity Log.d(MyTracksConstants.TAG, "MyTracksMap: Now registering sensor listeners."); sensorManager.registerListener( - this, compass, SensorManager.SENSOR_DELAY_UI); + sensorListener, compass, SensorManager.SENSOR_DELAY_UI); } /** @@ -545,12 +554,12 @@ public class MyTracksMap extends MapActivity if (locationManager != null) { Log.d(MyTracksConstants.TAG, "MyTracksMap: Now unregistering location listeners."); - locationManager.removeUpdates(this); + locationManager.removeUpdates(locationListener); } if (sensorManager != null) { Log.d(MyTracksConstants.TAG, "MyTracksMap: Now unregistering sensor listeners."); - sensorManager.unregisterListener(this); + sensorManager.unregisterListener(sensorListener); } } @@ -579,7 +588,7 @@ public class MyTracksMap extends MapActivity */ private void updateOptionsButton() { optionsBtn.setVisibility( - selectedTrack != null ? View.VISIBLE : View.INVISIBLE); + isATrackSelected() ? View.VISIBLE : View.INVISIBLE); } /** @@ -632,12 +641,18 @@ public class MyTracksMap extends MapActivity /** * Zooms and pans the map so that the given track is visible. * - * @param track a given track + * @param trackId a given track ID */ - public void showTrack(Track track) { - if (track == null || mapView == null || track.getNumberOfPoints() < 2) { + public void showTrack(long trackId) { + if (mapView == null) { return; } + + Track track = providerUtils.getTrack(trackId); + if (track == null || track.getNumberOfPoints() < 2) { + return; + } + TripStatistics stats = track.getStatistics(); int bottom = stats.getBottom(); int left = stats.getLeft(); @@ -680,24 +695,30 @@ public class MyTracksMap extends MapActivity * @param trackId a given track id */ public void setSelectedTrack(final long trackId) { - if (selectedTrack != null && selectedTrack.getId() == trackId) { + if (selectedTrackId == trackId) { // Selected track did not change, nothing to do. - mapOverlay.setSelectedTrack(selectedTrack); + mapOverlay.setTrackDrawingEnabled(isATrackSelected()); mapView.invalidate(); updateOptionsButton(); return; } + if (trackId < 0) { // Remove selection. - selectedTrack = null; - mapOverlay.setSelectedTrack(null); + selectedTrackId = -1; + mapOverlay.setTrackDrawingEnabled(false); mapOverlay.clearWaypoints(); updateOptionsButton(); mapView.invalidate(); return; } + busyPane.setVisibility(View.VISIBLE); - selectedTrack = providerUtils.getTrack(trackId); + selectedTrackId = trackId; + loadSelectedTrack(); + } + + private void loadSelectedTrack() { updateTrackHandler.post(restoreTrackRunnable); updateTrackHandler.post(restoreWaypointsRunnable); updateTrackHandler.post(setSelectedTrackRunnable); @@ -753,8 +774,7 @@ public class MyTracksMap extends MapActivity menu.setHeaderTitle(R.string.tracklist_this_track); menu.add(0, MyTracksConstants.MENU_EDIT, 0, R.string.tracklist_edit_track); - if (!MyTracks.getInstance().isRecording() || (selectedTrack != null - && selectedTrack.getId() != recordingTrackId)) { + if (!MyTracks.getInstance().isRecording() || !isRecordingSelected()) { menu.add(0, MyTracksConstants.MENU_SEND_TO_GOOGLE, 0, R.string.tracklist_send_to_google); SubMenu share = menu.addSubMenu(0, MyTracksConstants.MENU_SHARE, 0, @@ -791,7 +811,7 @@ public class MyTracksMap extends MapActivity @Override public boolean onMenuItemSelected(int featureId, MenuItem item) { if (!super.onMenuItemSelected(featureId, item)) { - if (selectedTrack != null) { + if (isATrackSelected()) { MyTracks.getInstance().onActivityResult( MyTracksConstants.getActionFromMenuId(item.getItemId()), RESULT_OK, new Intent()); @@ -852,8 +872,7 @@ public class MyTracksMap extends MapActivity public void onClick(View v) { if (v == messagePane) { launchMyLocationSettings(); - } - if (v == optionsBtn) { + } else if (v == optionsBtn) { optionsBtn.performLongClick(); } } @@ -895,139 +914,155 @@ public class MyTracksMap extends MapActivity sharedPreferences.getLong( getString(R.string.recording_track_key), -1); - if (selectedTrack != null) { - mapOverlay.setShowEndMarker( - selectedTrack.getId() != recordingTrackId); + if (isATrackSelected()) { + mapOverlay.setShowEndMarker(!isRecordingSelected()); mapView.postInvalidate(); } + } else if (key.equals(getString(R.string.debug_draw_bounds_key))) { + mapOverlay.setDrawBounds( + sharedPreferences.getBoolean( + getString(R.string.debug_draw_bounds_key), false)); } } }); } } - @Override - public void onProviderEnabled(String provider) { - if (provider.equals(MyTracksConstants.GPS_PROVIDER)) { - messageText.setText(R.string.wait_for_fix); - } - } - - @Override - public void onProviderDisabled(String provider) { - if (provider.equals(MyTracksConstants.GPS_PROVIDER)) { - messageText.setText(R.string.status_enable_gps); - messagePane.setVisibility(View.VISIBLE); - messagePane.setOnClickListener(this); - screen.requestLayout(); - } - } - - @Override - public void onLocationChanged(Location location) { - if (location.getProvider().equals(MyTracksConstants.GPS_PROVIDER)) { - // Recalculate the variation if there was a jump in location > 1km: - if (currentLocation == null - || location.distanceTo(currentLocation) > 1000) { - setVariation(location); + private final LocationListener locationListener = new LocationListener() { + @Override + public void onProviderEnabled(String provider) { + if (provider.equals(MyTracksConstants.GPS_PROVIDER)) { + messageText.setText(R.string.wait_for_fix); } - currentLocation = location; - boolean haveGoodFixNow = - currentLocation.getAccuracy() < minRequiredAccuracy; - if (haveGoodFixNow != haveGoodFix) { - haveGoodFix = haveGoodFixNow; - messagePane.setVisibility(haveGoodFix ? View.GONE : View.VISIBLE); + } + + @Override + public void onProviderDisabled(String provider) { + if (provider.equals(MyTracksConstants.GPS_PROVIDER)) { + messageText.setText(R.string.status_enable_gps); + messagePane.setVisibility(View.VISIBLE); + messagePane.setOnClickListener(MyTracksMap.this); screen.requestLayout(); } - showCurrentLocation(); - } else { - Log.d(MyTracksConstants.TAG, - "MyTracksMap: Network location update received."); } - } - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - if (provider.equals(MyTracksConstants.GPS_PROVIDER)) { - switch (status) { - case LocationProvider.OUT_OF_SERVICE: - case LocationProvider.TEMPORARILY_UNAVAILABLE: - haveGoodFix = false; - messagePane.setVisibility(View.VISIBLE); + @Override + public void onLocationChanged(Location location) { + if (location.getProvider().equals(MyTracksConstants.GPS_PROVIDER)) { + // Recalculate the variation if there was a jump in location > 1km: + if (currentLocation == null + || location.distanceTo(currentLocation) > 1000) { + setVariation(location); + } + currentLocation = location; + boolean haveGoodFixNow = + currentLocation.getAccuracy() < minRequiredAccuracy; + if (haveGoodFixNow != haveGoodFix) { + haveGoodFix = haveGoodFixNow; + messagePane.setVisibility(haveGoodFix ? View.GONE : View.VISIBLE); screen.requestLayout(); - break; + } + showCurrentLocation(); + } else { + Log.d(MyTracksConstants.TAG, + "MyTracksMap: Network location update received."); } } - } - @Override - public void onSensorChanged(SensorEvent se) { - synchronized (this) { - float magneticHeading = se.values[0]; - double heading = magneticHeading + variation; - if (mapOverlay.setHeading((float) heading)) { - mapView.invalidate(); + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + if (provider.equals(MyTracksConstants.GPS_PROVIDER)) { + switch (status) { + case LocationProvider.OUT_OF_SERVICE: + case LocationProvider.TEMPORARILY_UNAVAILABLE: + haveGoodFix = false; + messagePane.setVisibility(View.VISIBLE); + screen.requestLayout(); + break; + } } } - } + }; - @Override - public void onAccuracyChanged(Sensor s, int accuracy) { - // do nothing - } - - /** - * Set the sampling frequency from the total number of points in the track. - * - * @param track The track to read the total number of points from. - */ - private void setSamplingFrequency(Track track) { - long totalLocations = track.getStopId() - track.getStartId(); + private final SensorEventListener sensorListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent se) { + synchronized (this) { + float magneticHeading = se.values[0]; + double heading = magneticHeading + variation; + if (mapOverlay.setHeading((float) heading)) { + mapView.invalidate(); + } + } + } + + @Override + public void onAccuracyChanged(Sensor s, int accuracy) { + // do nothing + } + }; - // Limit the number of map points readings. - samplingFrequency = - (int) (1 + - totalLocations / MyTracksConstants.TARGET_DISPLAYED_TRACK_POINTS); - Log.i(MyTracksConstants.TAG, "Sampling locations: " + samplingFrequency); - } - private void readAllNewTrackPoints() { - Cursor cursor = null; - // Refetch the track to get the latest StopId - selectedTrack = providerUtils.getTrack(selectedTrack.getId()); - long totalLocations = selectedTrack.getStopId() - - selectedTrack.getStartId(); + int numPoints = mapOverlay.getNumLocations(); + if (numPoints >= MyTracksConstants.MAX_DISPLAYED_TRACK_POINTS) { + // We're about to exceed the maximum allowed number of points, so reload + // the whole track with fewer points (the sampling frequency will be + // lower). + loadSelectedTrack(); + return; + } + + long lastStoredLocationId = + providerUtils.getLastLocationId(selectedTrackId); + int samplingFrequency = -1; + Location location = new Location(""); + for (;;) { + Cursor cursor = null; + try { + cursor = providerUtils.getLocationsCursor(selectedTrackId, + lastSeenLocationId + 1, TRACKPOINT_BUFFER_SIZE, false); + if (cursor == null || !cursor.moveToFirst()) { + // No (more) data + break; + } - setSamplingFrequency(selectedTrack); - int bufferSize = 1024; - int points = 0; - while (lastSeenLocationId < selectedTrack.getStopId()) { - cursor = providerUtils.getLocationsCursor( - selectedTrack.getId(), lastSeenLocationId, bufferSize, false); - if (cursor != null && cursor.moveToFirst()) { final int idColumnIdx = cursor.getColumnIndexOrThrow( TrackPointsColumns._ID); - while (cursor.moveToNext()) { - points++; - Location location = providerUtils.createLocation(cursor); - lastSeenLocationId = cursor.getLong(idColumnIdx); - // Include a point if it fits one of the following criteria: - // - Has the mod for the sampling frequency. - // - Is the first point. - // - Is the last point and we are not recording this track. - if (!MyTracksUtils.isValidLocation(location) || - points % samplingFrequency == 0 || - points == 0 || - (recordingTrackId != selectedTrack.getId() && - points == (totalLocations - 1))) { - mapOverlay.addLocation(location); + do { + long locationId = cursor.getLong(idColumnIdx); + lastSeenLocationId = locationId; + if (firstSeenLocationId == -1) { + // This was our first point, keep its ID + firstSeenLocationId = locationId; } + if (samplingFrequency == -1) { + // Now we already have at least one point, calculate the sampling + // frequency + long numTotalPoints = lastStoredLocationId - firstSeenLocationId; + samplingFrequency = (int) (1 + + numTotalPoints / MyTracksConstants.TARGET_DISPLAYED_TRACK_POINTS); + } + + providerUtils.fillLocation(cursor, location); + + // Include a point if it fits one of the following criteria: + // - Has the mod for the sampling frequency (includes first point). + // - Is the last point and we are not recording this track. + // - The point is a segment split + if (numPoints % samplingFrequency == 0 || + (!isRecordingSelected() && locationId == lastStoredLocationId) || + !MyTracksUtils.isValidLocation(location)) { + // Only allocate a new location if it is going to be kept around. + mapOverlay.addLocation(new Location(location)); + } + + numPoints++; + } while (cursor.moveToNext()); + } finally { + if (cursor != null) { + cursor.close(); } - } else { - lastSeenLocationId += bufferSize; } - cursor.close(); - cursor = null; } mapView.postInvalidate(); } diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracksOverlay.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracksOverlay.java index 8441dabc7..47faf8fad 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracksOverlay.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracksOverlay.java @@ -15,10 +15,8 @@ */ package com.google.android.apps.mytracks; -import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.util.MyTracksUtils; - import com.google.android.maps.GeoPoint; import com.google.android.maps.MapView; import com.google.android.maps.Overlay; @@ -31,6 +29,7 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; import android.graphics.Rect; +import android.graphics.Region; import android.graphics.drawable.Drawable; import android.location.Location; @@ -57,10 +56,11 @@ public class MyTracksOverlay extends Overlay { private final ArrayList waypoints = new ArrayList(); private final ArrayList points = new ArrayList(1024); - private Track selectedTrack; + private boolean trackDrawingEnabled; private int lastHeading = 0; private Location myLocation; private boolean showEndMarker = true; + private boolean drawBounds; public MyTracksOverlay(Context context) { this.context = context; @@ -121,14 +121,6 @@ public class MyTracksOverlay extends Overlay { errorCirclePaint.setAntiAlias(true); } - public void setSelectedTrack(Track track) { - selectedTrack = track; - } - - public Track getSelectedTrack() { - return selectedTrack; - } - /** * Add a location to the map overlay. * @@ -137,19 +129,35 @@ public class MyTracksOverlay extends Overlay { * @param l the location to add */ public void addLocation(Location l) { - points.add(l); + if (l != null) { + synchronized (points) { + points.add(l); + } + } } public void addWaypoint(Waypoint wpt) { waypoints.add(wpt); } + public int getNumLocations() { + synchronized (points) { + return points.size(); + } + } + public void clearWaypoints() { waypoints.clear(); } public void clearPoints() { - points.clear(); + synchronized (points) { + points.clear(); + } + } + + public void setTrackDrawingEnabled(boolean trackDrawingEnabled) { + this.trackDrawingEnabled = trackDrawingEnabled; } public void setShowEndMarker(boolean showEndMarker) { @@ -158,15 +166,23 @@ public class MyTracksOverlay extends Overlay { @Override public void draw(Canvas canvas, MapView mapView, boolean shadow) { - if (shadow) { return; } - // Draw the selected track: - drawTrack(canvas, mapView, selectedTrack, true); + if (trackDrawingEnabled) { + // Draw the selected track: + drawTrack(canvas, mapView); - // Draw the waypoints: + // Draw the waypoints: + drawWaypoints(canvas, mapView); + } + + // Draw the current location + drawMyLocation(canvas, mapView); + } + + private void drawWaypoints(Canvas canvas, MapView mapView) { ArrayList currentWaypoints = waypoints; for (int i = 1; i < currentWaypoints.size(); i++) { Waypoint wpt = currentWaypoints.get(i); @@ -189,11 +205,14 @@ public class MyTracksOverlay extends Overlay { } canvas.restore(); } + } + private void drawMyLocation(Canvas canvas, MapView mapView) { // Draw the arrow icon: if (myLocation == null) { return; } + GeoPoint geoPoint = new GeoPoint( (int) (myLocation.getLatitude() * 1E6), (int) (myLocation.getLongitude() * 1E6)); @@ -210,79 +229,119 @@ public class MyTracksOverlay extends Overlay { canvas.drawCircle(pt.x, pt.y, radius, errorCirclePaint); } - public void drawTrack(Canvas canvas, MapView mapView, Track track, - boolean selected) { - - if (track == null) { - return; - } - if (points.size() < 2) { - return; - } + private void drawTrack(Canvas canvas, MapView mapView) { + Path path; + Point pt = new Point(); + Location lastValidLocation; + int locLon = 0, locLat = 0; + GeoPoint firstGeoPoint = null; + boolean lastLocValid; // Get the current viewing window: int w = mapView.getLongitudeSpan(); int h = mapView.getLatitudeSpan(); int cx = mapView.getMapCenter().getLongitudeE6(); int cy = mapView.getMapCenter().getLatitudeE6(); - Rect rect = new Rect(cx - w, cy - h, cx + w, cy + h); + Rect viewRect = new Rect(cx - w, cy - h, cx + w, cy + h); - Point pt = new Point(); - GeoPoint geoPoint; - Location loc; - Location lastValidLocation = null; - Path path; - boolean wasInside; - - // Do as much allocation and preparation outside the loop over track - // points: - wasInside = false; - int locLon = 0, locLat = 0, minLon, maxLon, minLat, maxLat; - int lastLocLon = (int) (points.get(0).getLongitude() * 1E6); - int lastLocLat = (int) (points.get(0).getLatitude() * 1E6); - - // Loop over track points: - path = new Path(); - for (int i = 1; i < points.size(); i++) { - - loc = points.get(i); - if (loc == null) { - continue; + // Global bounding box, including points not visible + int allMinLat, allMinLon, allMaxLat, allMaxLon; + + synchronized (points) { + int numPoints = points.size(); + if (numPoints < 2) { + return; } - locLon = (int) (loc.getLongitude() * 1E6); - locLat = (int) (loc.getLatitude() * 1E6); - // Draw a line segment if it's inside the viewing window: - if (locLat < 90E6 && lastLocLat < 90E6) { - lastValidLocation = loc; - minLon = Math.min(locLon, lastLocLon); - maxLon = Math.max(locLon, lastLocLon); - minLat = Math.min(locLat, lastLocLat); - maxLat = Math.max(locLat, lastLocLat); - if (rect.intersects(minLon, minLat, maxLon, maxLat)) { - if (!wasInside) { - geoPoint = new GeoPoint(lastLocLat, lastLocLon); - mapView.getProjection().toPixels(geoPoint, pt); - path.moveTo(pt.x, pt.y); + GeoPoint geoPoint; + Location loc; + int minLon, maxLon, minLat, maxLat; + lastValidLocation = points.get(0); + int lastLocLon = allMinLon = allMaxLon = (int) (lastValidLocation.getLongitude() * 1E6); + int lastLocLat = allMinLat = allMaxLat = (int) (lastValidLocation.getLatitude() * 1E6); + lastLocValid = MyTracksUtils.isValidLocation(lastValidLocation); + boolean lastLocVisible = false; + path = new Path(); + + // Loop over track points: + path.incReserve(numPoints); + for (int i = 1; i < numPoints; i++) { + loc = points.get(i); + + boolean locValid = MyTracksUtils.isValidLocation(loc); + boolean locVisible = false; + if (locValid) { + locLon = (int) (loc.getLongitude() * 1E6); + locLat = (int) (loc.getLatitude() * 1E6); + + if (firstGeoPoint == null) { + // Found the starting point + firstGeoPoint = new GeoPoint(locLat, locLon); + } + + // If both the current and previous locations were valid + if (lastLocValid) { + lastValidLocation = loc; + + // Get the bounding box of the segment about to be drawn + if (locLon > lastLocLon) { + minLon = lastLocLon; + maxLon = locLon; + } else { + minLon = locLon; + maxLon = lastLocLon; + } + if (locLat > lastLocLat) { + minLat = lastLocLat; + maxLat = locLat; + } else { + minLat = locLat; + maxLat = lastLocLat; + } + + if (drawBounds) { + allMaxLat = Math.max(allMaxLat, maxLat); + allMinLat = Math.min(allMinLat, minLat); + allMaxLon = Math.max(allMaxLon, maxLon); + allMinLon = Math.min(allMinLon, minLon); + } + + // See if that bounding box intersects the viewable bounding box + // Assume that if it does, the location is visible + locVisible = viewRect.intersects(minLon, minLat, maxLon, maxLat); + if (locVisible) { + // If the previous point wasn't drawn, start at its position + if (!lastLocVisible) { + geoPoint = new GeoPoint(lastLocLat, lastLocLon); + mapView.getProjection().toPixels(geoPoint, pt); + path.moveTo(pt.x, pt.y); + } + + // Draw a line to the new point + geoPoint = new GeoPoint(locLat, locLon); + mapView.getProjection().toPixels(geoPoint, pt); + path.lineTo(pt.x, pt.y); + } } - geoPoint = new GeoPoint(locLat, locLon); - mapView.getProjection().toPixels(geoPoint, pt); - path.lineTo(pt.x, pt.y); - wasInside = rect.contains(locLon, locLat); } - } else { - wasInside = false; + lastLocLon = locLon; + lastLocLat = locLat; + lastLocValid = locValid; + lastLocVisible = locVisible; } - lastLocLon = locLon; - lastLocLat = locLat; } + canvas.drawPath(path, selectedTrackPaint); + if (drawBounds) { + drawBoundingBox(canvas, mapView, + allMinLat, allMinLon, allMaxLat, allMaxLon); + } + // Draw the "End" marker: - Location currentLastValidLocation = lastValidLocation; - if (showEndMarker && currentLastValidLocation != null) { + if (showEndMarker && lastValidLocation != null) { canvas.save(); - geoPoint = MyTracksUtils.getGeoPoint(currentLastValidLocation); + GeoPoint geoPoint = MyTracksUtils.getGeoPoint(lastValidLocation); mapView.getProjection().toPixels(geoPoint, pt); canvas.translate(pt.x - (markerWidth / 2), pt.y - markerHeight); endMarker.draw(canvas); @@ -290,22 +349,46 @@ public class MyTracksOverlay extends Overlay { } // Draw the "Start" marker: - for (int i = 0; i < points.size(); i++) { - loc = points.get(i); - if (loc.getLatitude() < 90) { - locLon = (int) (loc.getLongitude() * 1E6); - locLat = (int) (loc.getLatitude() * 1E6); - geoPoint = new GeoPoint(locLat, locLon); - mapView.getProjection().toPixels(geoPoint, pt); - canvas.save(); - canvas.translate(pt.x - (markerWidth / 2), pt.y - markerHeight); - startMarker.draw(canvas); - canvas.restore(); - break; - } + if (firstGeoPoint != null) { + mapView.getProjection().toPixels(firstGeoPoint, pt); + canvas.save(); + canvas.translate(pt.x - (markerWidth / 2), pt.y - markerHeight); + startMarker.draw(canvas); + canvas.restore(); } } + private void drawBoundingBox(Canvas canvas, MapView mapView, int allMinLat, + int allMinLon, int allMaxLat, int allMaxLon) { + // Transform coordinates + GeoPoint maxPoint = new GeoPoint(allMaxLat, allMaxLon); + GeoPoint minPoint = new GeoPoint(allMinLat, allMinLon); + Point minPt = new Point(); + Point maxPt = new Point(); + mapView.getProjection().toPixels(minPoint, minPt); + mapView.getProjection().toPixels(maxPoint, maxPt); + Rect allBounds = new Rect(minPt.x, minPt.y, maxPt.x, maxPt.y); + + // Prepare a green paint + Paint boundingBoxPaint = new Paint(); + boundingBoxPaint.setColor(context.getResources().getColor(R.color.green)); + boundingBoxPaint.setStrokeWidth(2); + boundingBoxPaint.setStyle(Paint.Style.STROKE); + boundingBoxPaint.setAntiAlias(true); + + // Disable clipping + canvas.save(); + canvas.clipRect(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, + Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, + Region.Op.REPLACE); + + // Draw the bounding box + canvas.drawRect(allBounds, boundingBoxPaint); + + // Re-enable clipping + canvas.restore(); + } + @Override public boolean draw(Canvas canvas, MapView mapView, boolean shadow, long when) { @@ -374,4 +457,8 @@ public class MyTracksOverlay extends Overlay { } return super.onTap(p, mapView); } + + public void setDrawBounds(boolean drawBounds) { + this.drawBounds = drawBounds; + } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracksSettings.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracksSettings.java index 49161ef0e..20512ce42 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracksSettings.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracksSettings.java @@ -115,7 +115,7 @@ public class MyTracksSettings extends PreferenceActivity { announcementFrequency.setEnabled(false); announcementFrequency.setValue("-1"); announcementFrequency.setSummary( - R.string.settings_announcement_not_available_summary); + R.string.settings_not_available_summary); } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracksWaypointDetails.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracksWaypointDetails.java index 82604320b..d159fb236 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracksWaypointDetails.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracksWaypointDetails.java @@ -135,7 +135,6 @@ public class MyTracksWaypointDetails extends Activity iconId = R.drawable.ylw_pushpin; TripStatistics waypointStats = waypoint.getStatistics(); utils.setAllStats(waypointStats); - utils.setTime(R.id.total_time_register, waypointStats.getTotalTime()); utils.setAltitude( R.id.elevation_register, waypoint.getLocation().getAltitude()); break; diff --git a/MyTracks/src/com/google/android/apps/mytracks/NavControls.java b/MyTracks/src/com/google/android/apps/mytracks/NavControls.java index 3b402b851..0622efab7 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/NavControls.java +++ b/MyTracks/src/com/google/android/apps/mytracks/NavControls.java @@ -15,8 +15,6 @@ */ package com.google.android.apps.mytracks; -import com.google.android.maps.mytracks.R; - import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Handler; @@ -50,24 +48,22 @@ public class NavControls { /** * A touchable image view. + * When touched it changes the navigation control icons accordingly. */ - public class TouchLayout extends RelativeLayout { - private final ImageView arrow; + private class TouchLayout extends RelativeLayout implements Runnable { + private final boolean isLeft; private final ImageView icon; public TouchLayout(Context context, boolean isLeft) { super(context); - arrow = new ImageView(context); - arrow.setImageDrawable(context.getResources().getDrawable( - isLeft ? R.drawable.btn_arrow_left : R.drawable.btn_arrow_right)); - icon = new ImageView(context); + this.isLeft = isLeft; + this.icon = new ImageView(context); icon.setVisibility(View.GONE); - addView(arrow); addView(icon); - icon.setPadding((isLeft ? 15 : 10), 27, 15, 0); } - public void setIcon(Drawable drawable) { + public void setIcon(int iconId) { + Drawable drawable = getContext().getResources().getDrawable(iconId); icon.setImageDrawable(drawable); icon.setVisibility(View.VISIBLE); } @@ -77,18 +73,11 @@ public class NavControls { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: setPressed(true); - hide(); - if (this == prevImage) { - if (prevRunnable != null) { - handler.post(prevRunnable); - return true; - } - } else if (this == nextImage) { - if (nextRunnable != null) { - handler.post(nextRunnable); - return true; - } - } + shiftIcons(isLeft); + + // Call the user back + handler.post(this); + break; case MotionEvent.ACTION_UP: setPressed(false); @@ -96,55 +85,69 @@ public class NavControls { } return super.onTouchEvent(event); } + + @Override + public void run() { + touchRunnable.run(); + setPressed(false); + } } - private final Context context; - private final Runnable prevRunnable; - private final Runnable nextRunnable; private final Handler handler = new Handler(); private final Runnable dismissControls = new Runnable() { public void run() { hide(); } }; + private final TouchLayout prevImage; private final TouchLayout nextImage; + private final int[] leftIcons; + private final int[] rightIcons; + private final Runnable touchRunnable; private boolean isVisible = false; - private boolean hasNext = true; - private boolean hasPrev = true; + private int currentIcons; public NavControls(Context context, ViewGroup container, - Runnable prevRunnable, Runnable nextRunnable) { - this.context = context; - this.prevRunnable = prevRunnable; - this.nextRunnable = nextRunnable; + int[] leftIcons, int[] rightIcons, + Runnable touchRunnable) { + this.leftIcons = leftIcons; + this.rightIcons = rightIcons; + this.touchRunnable = touchRunnable; + + if (leftIcons.length != rightIcons.length || leftIcons.length < 1) { + throw new IllegalArgumentException("Invalid icons specified"); + } + if (touchRunnable == null) { + throw new NullPointerException("Runnable cannot be null"); + } + LayoutParams prevParams = new LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); - prevParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); - prevParams.addRule(RelativeLayout.CENTER_VERTICAL); - prevImage = new TouchLayout(context, true); - prevImage.setLayoutParams(prevParams); - prevImage.setVisibility(View.INVISIBLE); - container.addView(prevImage); LayoutParams nextParams = new LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + + prevParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); nextParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); + prevParams.addRule(RelativeLayout.CENTER_VERTICAL); nextParams.addRule(RelativeLayout.CENTER_VERTICAL); + nextImage = new TouchLayout(context, false); + prevImage = new TouchLayout(context, true); nextImage.setLayoutParams(nextParams); + prevImage.setLayoutParams(prevParams); nextImage.setVisibility(View.INVISIBLE); + prevImage.setVisibility(View.INVISIBLE); + + container.addView(prevImage); container.addView(nextImage); - } - public void setHasNext(boolean hasNext) { - this.hasNext = hasNext; - } - - public void setHasPrev(boolean hasPrev) { - this.hasPrev = hasPrev; + prevImage.setIcon(leftIcons[0]); + nextImage.setIcon(rightIcons[0]); + this.currentIcons = 0; } private void keepVisible() { @@ -156,20 +159,18 @@ public class NavControls { public void show() { if (!isVisible) { - if (prevRunnable != null && hasPrev) { - SHOW_PREV_ANIMATION.setDuration(500); - SHOW_PREV_ANIMATION.startNow(); - prevImage.setPressed(false); - prevImage.setAnimation(SHOW_PREV_ANIMATION); - prevImage.setVisibility(View.VISIBLE); - } - if (nextRunnable != null && hasNext) { - SHOW_NEXT_ANIMATION.setDuration(500); - SHOW_NEXT_ANIMATION.startNow(); - nextImage.setPressed(false); - nextImage.setAnimation(SHOW_NEXT_ANIMATION); - nextImage.setVisibility(View.VISIBLE); - } + SHOW_PREV_ANIMATION.setDuration(500); + SHOW_PREV_ANIMATION.startNow(); + prevImage.setPressed(false); + prevImage.setAnimation(SHOW_PREV_ANIMATION); + prevImage.setVisibility(View.VISIBLE); + + SHOW_NEXT_ANIMATION.setDuration(500); + SHOW_NEXT_ANIMATION.startNow(); + nextImage.setPressed(false); + nextImage.setAnimation(SHOW_NEXT_ANIMATION); + nextImage.setVisibility(View.VISIBLE); + isVisible = true; keepVisible(); } else { @@ -188,33 +189,25 @@ public class NavControls { public void hide() { isVisible = false; - if (prevRunnable != null) { - if (hasPrev) { - prevImage.setAnimation(HIDE_PREV_ANIMATION); - HIDE_PREV_ANIMATION.setDuration(500); - HIDE_PREV_ANIMATION.startNow(); - } else { - prevImage.clearAnimation(); - } - prevImage.setVisibility(View.INVISIBLE); - } - if (nextRunnable != null) { - if (hasNext) { - nextImage.setAnimation(HIDE_NEXT_ANIMATION); - HIDE_NEXT_ANIMATION.setDuration(500); - HIDE_NEXT_ANIMATION.startNow(); - } else { - nextImage.clearAnimation(); - } - nextImage.setVisibility(View.INVISIBLE); - } + prevImage.setAnimation(HIDE_PREV_ANIMATION); + HIDE_PREV_ANIMATION.setDuration(500); + HIDE_PREV_ANIMATION.startNow(); + prevImage.setVisibility(View.INVISIBLE); + + nextImage.setAnimation(HIDE_NEXT_ANIMATION); + HIDE_NEXT_ANIMATION.setDuration(500); + HIDE_NEXT_ANIMATION.startNow(); + nextImage.setVisibility(View.INVISIBLE); } - public void setLeftIcon(int resourceId) { - prevImage.setIcon(context.getResources().getDrawable(resourceId)); + public int getCurrentIcons() { + return currentIcons; } - public void setRightIcon(int resourceId) { - nextImage.setIcon(context.getResources().getDrawable(resourceId)); + private void shiftIcons(boolean isLeft) { + // Increment or decrement by one, with wrap around + currentIcons = (currentIcons + leftIcons.length + (isLeft ? -1 : 1)) % leftIcons.length; + prevImage.setIcon(leftIcons[currentIcons]); + nextImage.setIcon(rightIcons[currentIcons]); } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java index 1432888f5..9eed646a2 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java @@ -49,14 +49,12 @@ import android.widget.Toast; * @author Sandor Dornbush */ public class StatsActivity extends Activity - implements OnSharedPreferenceChangeListener, LocationListener { - + implements OnSharedPreferenceChangeListener { private final StatsUtilities utils; private UIUpdateThread thread; private ContentObserver observer; - private LocationManager locationManager; /** * The id of the currently selected track. @@ -96,7 +94,6 @@ public class StatsActivity extends Activity private boolean showCurrentSegment = false; private final MyTracksProviderUtils providerUtils; - private Track track = null; /** * A runnable for posting to the UI thread. Will update the total time field. @@ -179,11 +176,9 @@ public class StatsActivity extends Activity preferences.getBoolean(getString(R.string.report_speed_key), true); checkLiveTrack(); restoreStats(); - updateLocation(null); + showUnknownLocation(); preferences.registerOnSharedPreferenceChangeListener(this); } - locationManager = - (LocationManager) getSystemService(Context.LOCATION_SERVICE); utils.setMetricUnits(metricUnits); utils.setReportSpeed(displaySpeed); utils.updateUnits(); @@ -214,7 +209,7 @@ public class StatsActivity extends Activity activityOnTop = true; checkLiveTrack(); restoreStats(); - updateLocation(null); + showUnknownLocation(); super.onResume(); } @@ -234,7 +229,7 @@ public class StatsActivity extends Activity -1); checkLiveTrack(); restoreStats(); - updateLocation(null); + showUnknownLocation(); } else if (key.equals(getString(R.string.recording_track_key))) { recordingTrackId = sharedPreferences.getLong( @@ -242,7 +237,7 @@ public class StatsActivity extends Activity -1); checkLiveTrack(); restoreStats(); - updateLocation(null); + showUnknownLocation(); } else if (key.equals(getString(R.string.metric_units_key))) { metricUnits = sharedPreferences.getBoolean( @@ -299,29 +294,36 @@ public class StatsActivity extends Activity return super.onOptionsItemSelected(item); } - @Override - public void onLocationChanged(Location l) { - if (selectedTrackIsRecording()) { - updateLocation(l); + private final LocationListener locationListener = new LocationListener() { + @Override + public void onLocationChanged(Location l) { + if (selectedTrackIsRecording()) { + showLocation(l); + } } - } - - @Override - public void onProviderDisabled(String provider) { - } - - @Override - public void onProviderEnabled(String provider) { - } - - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - } + + @Override + public void onProviderDisabled(String provider) { + // Do nothing + } + + @Override + public void onProviderEnabled(String provider) { + // Do nothing + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + // Do nothing + } + }; /** * Registers to receive location updates from the GPS location provider. */ private void registerLocationListener() { + LocationManager locationManager = + (LocationManager) getSystemService(Context.LOCATION_SERVICE); if (locationManager != null) { LocationProvider gpsProvider = locationManager.getProvider(MyTracksConstants.GPS_PROVIDER); @@ -334,7 +336,7 @@ public class StatsActivity extends Activity + gpsProvider.getName()); } locationManager.requestLocationUpdates(gpsProvider.getName(), - 0/*minTime*/, 0/*minDist*/, this); + 0/*minTime*/, 0/*minDist*/, locationListener); } } @@ -342,8 +344,10 @@ public class StatsActivity extends Activity * Unregisters all location listener. */ private void unregisterLocationListener() { + LocationManager locationManager = + (LocationManager) getSystemService(Context.LOCATION_SERVICE); if (locationManager != null) { - locationManager.removeUpdates(this); + locationManager.removeUpdates(locationListener); } } @@ -364,7 +368,7 @@ public class StatsActivity extends Activity return; } - track = providerUtils.getTrack(selectedTrackId); + Track track = providerUtils.getTrack(selectedTrackId); if (track == null || track.getStatistics() == null) { utils.setAllToUnknown(); return; @@ -421,17 +425,17 @@ public class StatsActivity extends Activity * * @param l may be null (will set location fields to unknown) */ - private void updateLocation(Location l) { - if (l == null) { - utils.setUnknown(R.id.elevation_register); - utils.setUnknown(R.id.latitude_register); - utils.setUnknown(R.id.longitude_register); - utils.setUnknown(R.id.speed_register); - } else { - utils.setAltitude(R.id.elevation_register, l.getAltitude()); - utils.setLatLong(R.id.latitude_register, l.getLatitude()); - utils.setLatLong(R.id.longitude_register, l.getLongitude()); - utils.setSpeed(R.id.speed_register, l.getSpeed() * 3.6); - } + private void showLocation(Location l) { + utils.setAltitude(R.id.elevation_register, l.getAltitude()); + utils.setLatLong(R.id.latitude_register, l.getLatitude()); + utils.setLatLong(R.id.longitude_register, l.getLongitude()); + utils.setSpeed(R.id.speed_register, l.getSpeed() * 3.6); + } + + private void showUnknownLocation() { + utils.setUnknown(R.id.elevation_register); + utils.setUnknown(R.id.latitude_register); + utils.setUnknown(R.id.longitude_register); + utils.setUnknown(R.id.speed_register); } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/StatsUtilities.java b/MyTracks/src/com/google/android/apps/mytracks/StatsUtilities.java index b0c3c8b14..c37ecab65 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/StatsUtilities.java +++ b/MyTracks/src/com/google/android/apps/mytracks/StatsUtilities.java @@ -20,13 +20,13 @@ import com.google.android.apps.mytracks.util.StringUtils; import com.google.android.apps.mytracks.util.UnitConversions; import com.google.android.maps.mytracks.R; +import java.text.DecimalFormat; +import java.text.NumberFormat; + import android.app.Activity; import android.util.Log; import android.widget.TextView; -import java.text.DecimalFormat; -import java.text.NumberFormat; - /** * Various utility functions for views that display statistics information. * @@ -218,7 +218,7 @@ public class StatsUtilities { setGrade(R.id.max_grade_register, maxGrade); } - public void setAllStats(TripStatistics stats) { + public void setAllStats(TripStatistics stats) { setTime(R.id.moving_time_register, stats.getMovingTime()); setDistance(R.id.total_distance_register, stats.getTotalDistance() / 1000); setSpeed(R.id.average_speed_register, stats.getAverageSpeed() * 3.6); @@ -230,6 +230,8 @@ public class StatsUtilities { setAltitude(R.id.elevation_gain_register, stats.getTotalElevationGain()); setGrade(R.id.min_grade_register, stats.getMinGrade()); setGrade(R.id.max_grade_register, stats.getMaxGrade()); + setTime(R.id.total_time_register, stats.getTotalTime()); + } public void setSpeedLabel(int id, int speedString, int paceString) { diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java b/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java index b71e8440a..f8ba71f6a 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java +++ b/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java @@ -21,6 +21,8 @@ import android.database.Cursor; import android.location.Location; import android.net.Uri; +import java.util.List; + /** * Utility to access data from the mytracks content provider. * @@ -184,6 +186,15 @@ public interface MyTracksProviderUtils { * @return a Track object, or null if not found */ Track getTrack(long id); + + /** + * Retrieves all tracks without track points. If no tracks exist an empty + * list will be returned. Use {@link #getTrackPoints(Track, int)} to load + * the track points. + * + * @return a list of all the recorded tracks + */ + List getAllTracks(); /** * Loads the track points for a given track. @@ -203,9 +214,24 @@ public interface MyTracksProviderUtils { * * @param track to load locations for * @param buffer an array of locations to fill + * @deprecated use {@link #fillTrackPoints} instead */ + @Deprecated void getTrackPoints(Track track, TrackBuffer buffer); + /** + * Fetches some number of locations for the given track. + * + * This is designed to be used to stream through large tracks without loading + * all points into memory. + * This method will reuse the Location objects in the buffer. If you need a + * Location object copy the object. + * + * @param track to load locations for + * @param buffer an array of locations to fill + */ + void fillTrackPoints(Track track, TrackBuffer buffer); + /** * Creates a cursor over the tracks provider with a given selection. * Caller gets to own the returned cursor. Don't forget to close it. @@ -293,7 +319,15 @@ public interface MyTracksProviderUtils { * @return a new location object */ Location createLocation(Cursor cursor); - + + /** + * Fill a location object with values from a given cursor. + * + * @param cursor a cursor pointing at a db or provider with locations + * @param location a location object to be overwritten + */ + void fillLocation(Cursor cursor, Location location); + /** * Creates a waypoint object from a given cursor. * diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java b/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java index e0e940bbe..28dd11dd6 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java +++ b/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java @@ -24,6 +24,9 @@ import android.location.Location; import android.net.Uri; import android.util.Log; +import java.util.ArrayList; +import java.util.List; + /** * Helper class providing easy access to locations and tracks in the * MyTracksProvider. All static members. @@ -168,6 +171,15 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { @Override public Location createLocation(Cursor cursor) { + Location location = new Location(""); + fillLocation(cursor, location); + return location; + } + + @Override + public void fillLocation(Cursor cursor, Location location) { + location.reset(); + int idxLatitude = cursor.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE); int idxLongitude = cursor.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE); @@ -177,7 +189,6 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { int idxAccuracy = cursor.getColumnIndexOrThrow(TrackPointsColumns.ACCURACY); int idxSpeed = cursor.getColumnIndexOrThrow(TrackPointsColumns.SPEED); - Location location = new Location(""); if (!cursor.isNull(idxLatitude)) { location.setLatitude(1. * cursor.getInt(idxLatitude) / 1E6); } @@ -199,7 +210,6 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { if (!cursor.isNull(idxAccuracy)) { location.setAccuracy(cursor.getFloat(idxAccuracy)); } - return location; } @Override @@ -790,6 +800,21 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { String select = TracksColumns._ID + "=" + id; return findTrackBy(select); } + + @Override + public List getAllTracks() { + Cursor cursor = getTracksCursor(null); + if (cursor == null || !cursor.moveToFirst()) { + return new ArrayList(); + } + + List tracks = new ArrayList(cursor.getCount()); + do { + tracks.add(createTrack(cursor)); + } while(cursor.moveToNext()); + + return tracks; + } @Override public long getTrackPoints(Track track, int maxPoints) { @@ -822,6 +847,16 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { @Override public void getTrackPoints(Track track, TrackBuffer buffer) { + getTrackPoints(track, buffer, false); + } + + @Override + public void fillTrackPoints(Track track, TrackBuffer buffer) { + getTrackPoints(track, buffer, true); + } + + public void getTrackPoints(Track track, TrackBuffer buffer, + boolean reuseLocations) { long startingPoint = buffer.getLastLocationRead() == 0 ? track.getStartId() : buffer.getLastLocationRead(); buffer.reset(); @@ -849,13 +884,17 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { final int idColumnIdx = cursor.getColumnIndexOrThrow(TrackPointsColumns._ID); do { - Location location = createLocation(cursor); - if (location == null) { - continue; + if (reuseLocations) { + fillLocation(cursor, buffer.add(cursor.getLong(idColumnIdx))); + } else { + Location location = createLocation(cursor); + if (location == null) { + continue; + } + buffer.add(location, cursor.getLong(idColumnIdx)); } - buffer.add(location, cursor.getLong(idColumnIdx)); } while (cursor.moveToNext()); - + if (buffer.getLocationsLoaded() == 0) { Log.w(MyTracksProvider.TAG, "No locations read."); buffer.resetAt(startingPoint + buffer.getSize()); diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/TrackBuffer.java b/MyTracks/src/com/google/android/apps/mytracks/content/TrackBuffer.java index 88d569002..9cc5fdead 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/content/TrackBuffer.java +++ b/MyTracks/src/com/google/android/apps/mytracks/content/TrackBuffer.java @@ -94,6 +94,22 @@ public class TrackBuffer { buffer[locationsLoaded++] = location; lastLocationRead = Math.max(lastLocationRead, id); } + + /** + * Get a location from the end of the buffer. + * This may reuse Location objects already allocated to the buffer. + * @param locationId The of the location to be added. + * @return The location object associated with the give id. + */ + public Location add(long locationId) { + lastLocationRead = Math.max(lastLocationRead, locationId); + if (buffer[locationsLoaded] == null) { + buffer[locationsLoaded] = new Location(""); + } else { + buffer[locationsLoaded].reset(); + } + return buffer[locationsLoaded++]; + } /** * @return The id of the last location loaded into the buffer diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/TrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/TrackWriter.java index 0554c2c02..4c3638c3a 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/TrackWriter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/TrackWriter.java @@ -264,7 +264,7 @@ public class TrackWriter { while (buffer.getLastLocationRead() < track.getStopId()) { Log.d(MyTracksConstants.TAG, "Reading track points starting at: " + buffer.getLastLocationRead()); - providerUtils.getTrackPoints(track, buffer); + providerUtils.fillTrackPoints(track, buffer); if (!wroteFirst) { Location first = buffer.findStartLocation(); writer.writeBeginTrack(first); diff --git a/MyTracks/src/com/google/android/apps/mytracks/stats/ExtremityMonitor.java b/MyTracks/src/com/google/android/apps/mytracks/stats/ExtremityMonitor.java index 5b7aa855d..fafcf9ae2 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/stats/ExtremityMonitor.java +++ b/MyTracks/src/com/google/android/apps/mytracks/stats/ExtremityMonitor.java @@ -103,4 +103,9 @@ public class ExtremityMonitor { public void setMax(double max) { this.max = max; } + + @Override + public String toString() { + return "Min: " + min + " Max: " + max; + } } diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackWriterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackWriterTest.java index ecb05224d..2d9c2ff54 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackWriterTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackWriterTest.java @@ -326,7 +326,7 @@ public class TrackWriterTest extends AndroidTestCase { * will be fed into the track buffer on each call */ private void stubBufferFill(final Location[]... feeds) { - providerUtils.getTrackPoints(same(track), isA(TrackBuffer.class)); + providerUtils.fillTrackPoints(same(track), isA(TrackBuffer.class)); EasyMock.expectLastCall().andStubAnswer(new IAnswer() { private int lastId = 1; private int reads = 0;