Merge with 25704964a526fcfe09919af8f29c1e526b5409ec

This commit is contained in:
Bartlomiej Niechwiej
2010-10-04 16:34:38 -07:00
parent 256dac3a82
commit 34bb467b97
55 changed files with 942 additions and 486 deletions
@@ -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
+4 -1
View File
@@ -56,8 +56,11 @@
<activity android:name="com.google.android.apps.mytracks.MyTracksSettings"
android:label="@string/mytracks_settings" />
<activity android:name="com.google.android.apps.mytracks.AggregatedStatsActivity"
android:label="@string/aggregated_stats" />
<activity android:name="com.google.android.apps.mytracks.StatsActivity"
android:label="@string/statistics"/>
android:label="@string/statistics" />
<activity android:name="com.google.android.apps.mytracks.ChartActivity"
android:label="@string/elevation_label"/>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

@@ -17,11 +17,11 @@ limitations under the License.
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false"
android:drawable="@drawable/btn_arrow_left_default" />
android:drawable="@drawable/left_arrow_chart" />
<item android:state_pressed="true"
android:drawable="@drawable/btn_arrow_left_press" />
android:drawable="@drawable/left_arrow_chart_press" />
<item android:drawable="@drawable/btn_arrow_left_default" />
<item android:drawable="@drawable/left_arrow_chart" />
</selector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

@@ -17,11 +17,11 @@ limitations under the License.
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false"
android:drawable="@drawable/btn_arrow_right_default" />
android:drawable="@drawable/left_arrow_stats" />
<item android:state_pressed="true"
android:drawable="@drawable/btn_arrow_right_press" />
android:drawable="@drawable/left_arrow_stats_press" />
<item android:drawable="@drawable/btn_arrow_right_default" />
<item android:drawable="@drawable/left_arrow_stats" />
</selector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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 License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false"
android:drawable="@drawable/left_arrow_track" />
<item android:state_pressed="true"
android:drawable="@drawable/left_arrow_track_press" />
<item android:drawable="@drawable/left_arrow_track" />
</selector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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 License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false"
android:drawable="@drawable/right_arrow_chart" />
<item android:state_pressed="true"
android:drawable="@drawable/right_arrow_chart_press" />
<item android:drawable="@drawable/right_arrow_chart" />
</selector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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 License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false"
android:drawable="@drawable/right_arrow_stats" />
<item android:state_pressed="true"
android:drawable="@drawable/right_arrow_stats_press" />
<item android:drawable="@drawable/right_arrow_stats" />
</selector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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 License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false"
android:drawable="@drawable/right_arrow_track" />
<item android:state_pressed="true"
android:drawable="@drawable/right_arrow_track_press" />
<item android:drawable="@drawable/right_arrow_track" />
</selector>
+6 -1
View File
@@ -20,10 +20,15 @@
android:icon="@android:drawable/ic_menu_preferences"
android:orderInCategory="1001"
android:title="@string/settings"/>
<item android:id="@+id/menu_aggregated_stats"
android:icon="@android:drawable/ic_menu_info_details"
android:orderInCategory="1002"
android:title="@string/aggregated_stats"/>
<item android:id="@+id/menu_help"
android:icon="@android:drawable/ic_menu_info_details"
android:orderInCategory="1002"
android:orderInCategory="1003"
android:title="@string/help"/>
</menu>
-1
View File
@@ -155,7 +155,6 @@ limitations under the License.
<string name="settings_announcement_frequency">Dauer zwischen Ansagen:</string>
<string name="settings_announcement_frequency_summary">Kürzeste Zeit zwichen zwei Ansagen</string>
<string name="settings_announcement_not_available_summary">Gesprochene Ansagen sind mit Ihrer Version von Android nicht verfügbar.</string>
<string name="settings_min_recording_distance">Kleinste Entfernung zwischen Punkten:</string>
<string name="settings_min_recording_distance_summary">Kleinste Entfernung zwischen zwei Positionsaufzeichnungen</string>
-1
View File
@@ -158,7 +158,6 @@ limitations under the License.
<string name="settings_announcement_frequency">Tiempo entre anuncios</string>
<string name="settings_announcement_frequency_summary">Tiempo mínimo entre dos anuncios de voz</string>
<string name="settings_announcement_not_available_summary">Conversión de anuncios de texto a voz no esta disponible en su versión de Android</string>
<string name="settings_min_recording_distance">Distancia mínima entre puntos consecutivos</string>
<string name="settings_min_recording_distance_summary">Distancia mínima entre dos grabaciones de posición</string>
-1
View File
@@ -158,7 +158,6 @@ limitations under the License.
<string name="settings_announcement_frequency">Fréquence des annonces</string>
<string name="settings_announcement_frequency_summary">Temps minimum entre eux annonces vocales</string>
<string name="settings_announcement_not_available_summary">La synthèse vocale n\'est pas disponible pour cette version d\'Android</string>
<string name="settings_min_recording_distance">Distance min. entre 2 points</string>
<string name="settings_min_recording_distance_summary">Plus petite distance entre deux enregistrements de votre position</string>
-1
View File
@@ -164,7 +164,6 @@ limitations under the License.
<string name="settings_announcement_frequency">Intervallo fra due annunci:</string>
<string name="settings_announcement_frequency_summary">Tempo minimo fra due annunci audio</string>
<string name="settings_announcement_not_available_summary">Gli annunci audio non sono disponibili in questa varsione di Android</string>
<string name="settings_min_recording_distance">Distanza minima fra due punti successivi:</string>
<string name="settings_min_recording_distance_summary">Distanza minima fra due successive registrazioni della posizione</string>
+1 -1
View File
@@ -164,7 +164,7 @@ limitations under the License.
<string name="settings_announcement_frequency">定期通知</string>
<string name="settings_announcement_frequency_summary">音声通知間隔の最小時間</string>
<string name="settings_announcement_not_available_summary">お使いのAndroidの版では音声読み上げ機能がありません</string>
<string name="settings_min_recording_distance">ポイント間の最短距離</string>
<string name="settings_min_recording_distance_summary">位置記録間の最短距離</string>
+1 -1
View File
@@ -164,7 +164,7 @@ limitations under the License.
<string name="settings_announcement_frequency">Częstotliwość komunikatów</string>
<string name="settings_announcement_frequency_summary">Minimalny czas między dwoma komunikatami głosowymi</string>
<string name="settings_announcement_not_available_summary">Brak wsparcia dla syntezy mowy w twojej wersji Androida</string>
<string name="settings_min_recording_distance">Min. odległość między punktami</string>
<string name="settings_min_recording_distance_summary">Najmniejsza odległość między dwoma zapisami twojej pozycji</string>
-1
View File
@@ -157,7 +157,6 @@ limitations under the License.
<string name="settings_sharing">Compartilhamento</string>
<string name="settings_announcement_frequency">Tempo entre anúncios</string>
<string name="settings_announcement_frequency_summary">Tempo mínimo entre dois anúncios de voz</string>
<string name="settings_announcement_not_available_summary">Anúncios de voz não estão disponíveis em sua versão do Android</string>
<string name="settings_min_recording_distance">Dist. mín. entre pontos</string>
<string name="settings_min_recording_distance_summary">Menor distância entre duas gravações da sua posição</string>
<string name="settings_max_recording_distance">Dist. máx. entre pontos</string>
+13 -13
View File
@@ -43,19 +43,19 @@ limitations under the License.
<string name="error_generic">Ett fel uppstod. Försök igen.</string>
<string name="error_track_does_not_exist">Spåret finns inte.</string>
<string name="error_out_of_memory">Den här filen är för stor (måste vara mindre än 500 KB). Avbryter.</string>
<string name="error_unable_to_insert_marker">Kan inte skapa markör. Ingen possition tillgänglig.</string>
<string name="error_unable_to_insert_marker">Kan inte skapa markör. Ingen position tillgänglig.</string>
<!-- Status messages -->
<string name="wait_for_fix">Väntar på bättre GPS-posstion. Se till att himlen är synlig.</string>
<string name="wait_for_fix">Väntar på bättre GPS-position. Se till att himlen är synlig.</string>
<string name="status_now_recording">Spelar nu in ett nytt spår.</string>
<string name="status_stopped_recording">Stoppade inspelning av ditt spår.</string>
<string name="status_new_mymap_has_been_created">En ny Google My Map har skapats på maps.google.com.</string>
<string name="status_tracks_have_been_uploaded">Ditt spår har skickats till maps.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_new_doc">Ditt spår har skockats till ett nytt kalkylblad på docs.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_new_doc">Ditt spår har skickats till ett nytt kalkylblad på docs.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_docs">Ditt spår har skickats till docs.google.com.</string>
<string name="status_mymap_info">Det kan ibland ta några minuter innan nya spår visas.</string>
<string name="status_enable_gps">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å.</string>
<string name="status_approximate_location">OBS: En ungerfärlig position som avgörs från mobilmaster och WIFI-nät visas.</string>
<string name="status_approximate_location">OBS: En ungefärlig position som avgörs från mobilmaster och WIFI-nät visas.</string>
<string name="status_no_location">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.</string>
<string name="status_waypoint_inserted">En positionsmarkör har lagts in.</string>
<string name="status_statistics_inserted">En statistikmarkör har lagts in.</string>
@@ -71,7 +71,7 @@ limitations under the License.
<string name="progress_message_sending_mymaps">Skickar spår till Google Maps...</string>
<string name="progress_message_creating_map">Skapar en ny My Map...</string>
<string name="import_progress_message">Importerar ett GPS-spår. Detta kan ta en stund.</string>
<string name="write_progress_message">Skriver til till SD-kortet. Detta kan ta en stund.</string>
<string name="write_progress_message">Skriver till SD-kortet. Detta kan ta en stund.</string>
<string name="progress_message_updating_track">Uppdaterar spårstatistik...</string>
<!-- Used by DeleteAllDataHandler -->
@@ -125,7 +125,7 @@ limitations under the License.
<!-- Export All -->
<string name="tracklist_btn_export_all">Exportera alla spår</string>
<string name="export_done">Exporteringen slutfördes</string>
<string name="tracklist_pick_one">Välj ett spår. TryckPress&amp;håll för fler alternativ.</string>
<string name="tracklist_pick_one">Välj ett spår. Tryck&amp;håll för fler alternativ.</string>
<string name="tracklist_all_tracks">Alla spår:</string>
<string name="tracklist_this_track">Det här spåret</string>
<string name="tracklist_share_track">Dela med vänner...</string>
@@ -143,13 +143,13 @@ limitations under the License.
<string name="waypointslist_show_waypoint">Visa på karta</string>
<string name="waypointslist_btn_insert_waypoint">Infoga positionspunkt</string>
<string name="waypointslist_btn_insert_statistics">Infoga statistik</string>
<string name="waypointslist_pick_one">Välj en markör. TryckPress&amp;håll för fler alternativ.</string>
<string name="waypointslist_pick_one">Välj en markör. Tryck&amp;håll för fler alternativ.</string>
<!-- MyTracks Details -->
<string name="trackdetails_title">Spårdetaljer</string>
<string name="trackdetails_name">Spårnamn</string>
<string name="trackdetails_description">Spårbeskrivning</string>
<string name="trackdetails_category">Aktivitetstyp (t ex vandring, cyckling, låpning, klättring...)</string>
<string name="trackdetails_category">Aktivitetstyp (t ex vandring, cyckling, löpning, klättring...)</string>
<!-- MyTracks Waypoint Details -->
<string name="waypointdetails_title">Markördetaljer</string>
@@ -165,7 +165,7 @@ limitations under the License.
<string name="settings_announcement_frequency">Meddelandefrekvens</string>
<string name="settings_announcement_frequency_summary">Minimumtid mellan två röstmeddelanden</string>
<string name="settings_announcement_not_available_summary">Text-till-talmeddelanden är inte tillgängligt i din version av Android</string>
<string name="settings_min_recording_distance">Min distans mellan två punkter</string>
<string name="settings_min_recording_distance_summary">Minsta distans mellan två inspelningar av din position</string>
@@ -199,11 +199,11 @@ limitations under the License.
<string name="settings_signal_sampling_frequency_summary">Tid mellan två inspelningar av telefonens signalstyrka</string>
<string name="settings_split_frequency">Delningsfrekvens</string>
<string name="settings_split_frequency_summary">Maximala tiden eller distansen varefter en ny delnginsmarkör kommer infogas</string>
<string name="settings_split_frequency_summary">Maximala tiden eller distansen varefter en ny delningsmarkör kommer infogas</string>
<string name="pace_label">Tempo</string>
<!-- MyTracksSky -->
<string name="mytracks_sky">GPS-satteliter</string>
<string name="mytracks_sky">GPS-satelliter</string>
<!-- My Maps -->
<string name="select_map_message">Välje en karta från ditt konto:</string>
@@ -240,7 +240,7 @@ limitations under the License.
<!-- Visibility -->
<string name="public_map">Publik</string>
<string name="unlisted_map">Olistat</string>
<string name="mapslist_no_maps_available">Du har inge kartor än.</string>
<string name="mapslist_no_maps_available">Du har inga kartor än.</string>
<!-- Statistics View -->
<!-- Average Moving -->
@@ -253,7 +253,7 @@ limitations under the License.
<string name="elevation_chart_name">Höjddiagram</string>
<string name="elevation_gain_label">Höjdvinst</string>
<string name="elevation_label">Höjt</string>
<string name="elevation_label">Höjd</string>
<string name="latitude_label">Latitud</string>
<string name="longitude_label">Longitud</string>
+1 -1
View File
@@ -167,7 +167,7 @@ limitations under the License.
<string name="settings_announcement_frequency">提示频度</string>
<string name="settings_announcement_frequency_summary">语音提示的最小时间间隔</string>
<string name="settings_announcement_not_available_summary">你所使用的 Android 版本不支持文本到语音转换</string>
<string name="settings_min_recording_distance">最小记录间距</string>
<string name="settings_min_recording_distance_summary">位置记录的最小间隔距离</string>
+1
View File
@@ -38,6 +38,7 @@ limitations under the License.
<string name="share_url_only_key">shareUrlOnly</string>
<string name="signal_sampling_frequency_key">signalSamplingFrequency</string>
<string name="split_frequency_key">splitFrequency</string>
<string name="debug_draw_bounds_key">debugDrawBounds</string>
<!-- Preference keys which keep transient state (not user preferences) - DO NOT TRANSLATE -->
<string name="recording_track_key">recordingTrack</string>
+1
View File
@@ -32,6 +32,7 @@ limitations under the License.
<string name="choose_track_message">Please select the track to view</string>
<string name="current_track">Current track</string>
<string name="current_segment">Last segment</string>
<string name="aggregated_stats">Aggregated Statistics</string>
<!-- Error messages -->
<string name="error_no_location_manager">No location manager was found on this device!</string>
+9
View File
@@ -133,4 +133,13 @@ limitations under the License.
android:persistent="false" />
</PreferenceCategory>
</PreferenceScreen>
<!-- Debug settings - uncomment this while debugging only -->
<!--
<PreferenceScreen android:title="Debug">
<CheckBoxPreference android:key="@string/debug_draw_bounds_key"
android:title="Draw bounding box"
android:defaultValue="false" />
</PreferenceScreen>
-->
</PreferenceScreen>
@@ -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() { }
}
@@ -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<Track> 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<Track> retrieveTracks() {
return tracksProvider.getAllTracks();
}
private void updateView(TripStatistics aggStats) {
if (aggStats != null) {
utils.setAllStats(aggStats);
}
}
}
@@ -176,11 +176,15 @@ public class ChartActivity extends Activity implements
final int idColumnIdx =
cursor.getColumnIndexOrThrow(TrackPointsColumns._ID);
ArrayList<double[]> data = new ArrayList<double[]>();
// 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<double[]> theData = new ArrayList<double[]>();
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);
}
@@ -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);
@@ -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.
@@ -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();
}
@@ -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<Waypoint> waypoints = new ArrayList<Waypoint>();
private final ArrayList<Location> points = new ArrayList<Location>(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<Waypoint> 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;
}
}
@@ -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);
}
}
@@ -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;
@@ -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]);
}
}
@@ -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);
}
}
@@ -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) {
@@ -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<Track> 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.
*
@@ -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<Track> getAllTracks() {
Cursor cursor = getTracksCursor(null);
if (cursor == null || !cursor.moveToFirst()) {
return new ArrayList<Track>();
}
List<Track> tracks = new ArrayList<Track>(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());
@@ -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
@@ -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);
@@ -103,4 +103,9 @@ public class ExtremityMonitor {
public void setMax(double max) {
this.max = max;
}
@Override
public String toString() {
return "Min: " + min + " Max: " + max;
}
}
@@ -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<Void>() {
private int lastId = 1;
private int reads = 0;