forked from upstream-mirrors/OpenTracks
Merging from head.
This commit is contained in:
@@ -38,7 +38,6 @@ limitations under the License.
|
|||||||
<string name="share_url_only_key">shareUrlOnly</string>
|
<string name="share_url_only_key">shareUrlOnly</string>
|
||||||
<string name="signal_sampling_frequency_key">signalSamplingFrequency</string>
|
<string name="signal_sampling_frequency_key">signalSamplingFrequency</string>
|
||||||
<string name="split_frequency_key">splitFrequency</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 -->
|
<!-- Preference keys which keep transient state (not user preferences) - DO NOT TRANSLATE -->
|
||||||
<string name="auto_resume_track_current_retry_key">autoResumeTrackCurrentRetry</string>
|
<string name="auto_resume_track_current_retry_key">autoResumeTrackCurrentRetry</string>
|
||||||
|
|||||||
@@ -133,13 +133,4 @@ limitations under the License.
|
|||||||
android:persistent="false" />
|
android:persistent="false" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
</PreferenceScreen>
|
</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>
|
</PreferenceScreen>
|
||||||
|
|||||||
@@ -21,8 +21,10 @@ import com.google.android.apps.mytracks.content.Track;
|
|||||||
import com.google.android.apps.mytracks.content.TrackPointsColumns;
|
import com.google.android.apps.mytracks.content.TrackPointsColumns;
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
||||||
|
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
|
||||||
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
||||||
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
|
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
|
||||||
|
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
@@ -35,7 +37,6 @@ import android.location.Location;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.speech.tts.TextToSpeech;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
@@ -243,7 +244,9 @@ public class ChartActivity extends Activity implements
|
|||||||
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
||||||
|
|
||||||
// The volume we want to control is the Text-To-Speech volume
|
// The volume we want to control is the Text-To-Speech volume
|
||||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
int volumeStream =
|
||||||
|
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
|
||||||
|
setVolumeControlStream(volumeStream);
|
||||||
|
|
||||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
setContentView(R.layout.mytracks_elevation);
|
setContentView(R.layout.mytracks_elevation);
|
||||||
|
|||||||
@@ -26,11 +26,14 @@ import com.google.android.apps.mytracks.io.AuthManagerFactory;
|
|||||||
import com.google.android.apps.mytracks.io.GpxImporter;
|
import com.google.android.apps.mytracks.io.GpxImporter;
|
||||||
import com.google.android.apps.mytracks.io.SendToDocs;
|
import com.google.android.apps.mytracks.io.SendToDocs;
|
||||||
import com.google.android.apps.mytracks.io.SendToMyMaps;
|
import com.google.android.apps.mytracks.io.SendToMyMaps;
|
||||||
|
import com.google.android.apps.mytracks.io.SendToMyMaps.OnSendCompletedListener;
|
||||||
import com.google.android.apps.mytracks.io.TrackWriter;
|
import com.google.android.apps.mytracks.io.TrackWriter;
|
||||||
import com.google.android.apps.mytracks.io.TrackWriterFactory;
|
import com.google.android.apps.mytracks.io.TrackWriterFactory;
|
||||||
import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
|
import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
|
||||||
import com.google.android.apps.mytracks.services.ITrackRecordingService;
|
import com.google.android.apps.mytracks.services.ITrackRecordingService;
|
||||||
|
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
|
||||||
import com.google.android.apps.mytracks.services.TrackRecordingService;
|
import com.google.android.apps.mytracks.services.TrackRecordingService;
|
||||||
|
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||||
import com.google.android.apps.mytracks.util.FileUtils;
|
import com.google.android.apps.mytracks.util.FileUtils;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
@@ -53,17 +56,18 @@ import android.location.LocationManager;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.HandlerThread;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.speech.tts.TextToSpeech;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.Window;
|
|
||||||
import android.view.View.OnTouchListener;
|
import android.view.View.OnTouchListener;
|
||||||
import android.view.ViewGroup.LayoutParams;
|
import android.view.ViewGroup.LayoutParams;
|
||||||
|
import android.view.Window;
|
||||||
import android.view.WindowManager.BadTokenException;
|
import android.view.WindowManager.BadTokenException;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TabHost;
|
import android.widget.TabHost;
|
||||||
@@ -258,7 +262,9 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
|||||||
menuManager = new MenuManager(this);
|
menuManager = new MenuManager(this);
|
||||||
|
|
||||||
// The volume we want to control is the Text-To-Speech volume
|
// The volume we want to control is the Text-To-Speech volume
|
||||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
int volumeStream =
|
||||||
|
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
|
||||||
|
setVolumeControlStream(volumeStream);
|
||||||
|
|
||||||
// We don't need a window title bar:
|
// We don't need a window title bar:
|
||||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
@@ -520,8 +526,8 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
|||||||
if (results.hasExtra("mapid")) {
|
if (results.hasExtra("mapid")) {
|
||||||
sendToMyMapsMapId = results.getStringExtra("mapid");
|
sendToMyMapsMapId = results.getStringExtra("mapid");
|
||||||
}
|
}
|
||||||
setProgressMessage(getString(
|
setProgressMessage(
|
||||||
R.string.progress_message_authenticating_mymaps));
|
R.string.progress_message_authenticating_mymaps);
|
||||||
authenticate(results, MyTracksConstants.SEND_TO_GOOGLE,
|
authenticate(results, MyTracksConstants.SEND_TO_GOOGLE,
|
||||||
MyMapsConstants.MAPSHOP_SERVICE);
|
MyMapsConstants.MAPSHOP_SERVICE);
|
||||||
} else {
|
} else {
|
||||||
@@ -587,7 +593,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
|||||||
if (resultCode == RESULT_OK) {
|
if (resultCode == RESULT_OK) {
|
||||||
setProgressValue(0);
|
setProgressValue(0);
|
||||||
setProgressMessage(
|
setProgressMessage(
|
||||||
getString(R.string.progress_message_authenticating_docs));
|
R.string.progress_message_authenticating_docs);
|
||||||
authenticate(results,
|
authenticate(results,
|
||||||
MyTracksConstants.AUTHENTICATE_TO_TRIX, "writely");
|
MyTracksConstants.AUTHENTICATE_TO_TRIX, "writely");
|
||||||
} else {
|
} else {
|
||||||
@@ -599,7 +605,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
|||||||
if (resultCode == RESULT_OK) {
|
if (resultCode == RESULT_OK) {
|
||||||
setProgressValue(30);
|
setProgressValue(30);
|
||||||
setProgressMessage(
|
setProgressMessage(
|
||||||
getString(R.string.progress_message_authenticating_docs));
|
R.string.progress_message_authenticating_docs);
|
||||||
authenticate(results, MyTracksConstants.SEND_TO_DOCS, "wise");
|
authenticate(results, MyTracksConstants.SEND_TO_DOCS, "wise");
|
||||||
} else {
|
} else {
|
||||||
dismissDialogSafely(DIALOG_PROGRESS);
|
dismissDialogSafely(DIALOG_PROGRESS);
|
||||||
@@ -610,7 +616,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
|||||||
if (results != null && resultCode == RESULT_OK) {
|
if (results != null && resultCode == RESULT_OK) {
|
||||||
Log.d(MyTracksConstants.TAG, "Sending to Docs....");
|
Log.d(MyTracksConstants.TAG, "Sending to Docs....");
|
||||||
setProgressValue(50);
|
setProgressValue(50);
|
||||||
setProgressMessage(getString(R.string.progress_message_sending_docs));
|
setProgressMessage(R.string.progress_message_sending_docs);
|
||||||
final long trackId = results.getLongExtra("trackid", selectedTrackId);
|
final long trackId = results.getLongExtra("trackid", selectedTrackId);
|
||||||
final SendToDocs sender = new SendToDocs(this, authMap.get("wise"),
|
final SendToDocs sender = new SendToDocs(this, authMap.get("wise"),
|
||||||
authMap.get("writely"), trackId);
|
authMap.get("writely"), trackId);
|
||||||
@@ -641,30 +647,30 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
|||||||
}
|
}
|
||||||
case MyTracksConstants.SEND_TO_GOOGLE: {
|
case MyTracksConstants.SEND_TO_GOOGLE: {
|
||||||
if (results != null && resultCode == RESULT_OK) {
|
if (results != null && resultCode == RESULT_OK) {
|
||||||
final String mapid;
|
final String mapId;
|
||||||
final long trackId;
|
final long trackId;
|
||||||
if (results.hasExtra("mapid")) {
|
if (results.hasExtra("mapid")) {
|
||||||
mapid = results.getStringExtra("mapid");
|
mapId = results.getStringExtra("mapid");
|
||||||
} else {
|
} else {
|
||||||
mapid = "new";
|
mapId = "new";
|
||||||
}
|
}
|
||||||
if (results.hasExtra("trackid")) {
|
if (results.hasExtra("trackid")) {
|
||||||
trackId = results.getLongExtra("trackid", -1);
|
trackId = results.getLongExtra("trackid", -1);
|
||||||
} else {
|
} else {
|
||||||
trackId = selectedTrackId;
|
trackId = selectedTrackId;
|
||||||
}
|
}
|
||||||
final SendToMyMaps sender = new SendToMyMaps(this, mapid, auth,
|
|
||||||
trackId, this/*progressIndicator*/);
|
OnSendCompletedListener onCompletion = new OnSendCompletedListener() {
|
||||||
Runnable onCompletion = new Runnable() {
|
@Override
|
||||||
public void run() {
|
public void onSendCompleted(String mapId, boolean success, int statusMessage) {
|
||||||
sendToMyMapsMessage = sender.getStatusMessage();
|
sendToMyMapsMessage = getString(statusMessage);
|
||||||
sendToMyMapsSuccess = sender.wasSuccess();
|
sendToMyMapsSuccess = success;
|
||||||
if (sendToMyMapsSuccess) {
|
if (sendToMyMapsSuccess) {
|
||||||
sendToMyMapsMapId = sender.getMapId();
|
sendToMyMapsMapId = mapId;
|
||||||
// Update the map id for this track:
|
// Update the map id for this track:
|
||||||
try {
|
try {
|
||||||
Track track = providerUtils.getTrack(trackId);
|
Track track = providerUtils.getTrack(trackId);
|
||||||
track.setMapId(sender.getMapId());
|
track.setMapId(mapId);
|
||||||
providerUtils.updateTrack(track);
|
providerUtils.updateTrack(track);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
// If that fails whatever reasons we'll just log an error, but
|
// If that fails whatever reasons we'll just log an error, but
|
||||||
@@ -685,8 +691,13 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
sender.setOnCompletion(onCompletion);
|
final SendToMyMaps sender = new SendToMyMaps(this, mapId, auth,
|
||||||
sender.run();
|
trackId, this/*progressIndicator*/, onCompletion);
|
||||||
|
|
||||||
|
HandlerThread handlerThread = new HandlerThread("SendToMyMaps");
|
||||||
|
handlerThread.start();
|
||||||
|
Handler handler = new Handler(handlerThread.getLooper());
|
||||||
|
handler.post(sender);
|
||||||
} else {
|
} else {
|
||||||
dismissDialogSafely(DIALOG_PROGRESS);
|
dismissDialogSafely(DIALOG_PROGRESS);
|
||||||
}
|
}
|
||||||
@@ -950,18 +961,31 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setProgressMessage(final String text) {
|
// ProgressIndicator implementation
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProgressMessage(int resId) {
|
||||||
|
setProgressMessage(getString(resId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setProgressMessage(final String message) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (progressDialog != null) {
|
if (progressDialog != null) {
|
||||||
progressDialog.setMessage(text);
|
progressDialog.setMessage(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearProgressMessage() {
|
||||||
|
setProgressMessage("");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void setProgressValue(final int percent) {
|
public void setProgressValue(final int percent) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -1275,7 +1299,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setProgressValue(0);
|
setProgressValue(0);
|
||||||
setProgressMessage("");
|
clearProgressMessage();
|
||||||
showDialogSafely(DIALOG_PROGRESS);
|
showDialogSafely(DIALOG_PROGRESS);
|
||||||
if (sendToGoogleDialog.getSendToMyMaps()) {
|
if (sendToGoogleDialog.getSendToMyMaps()) {
|
||||||
if (!sendToGoogleDialog.getCreateNewMap()) {
|
if (!sendToGoogleDialog.getCreateNewMap()) {
|
||||||
@@ -1283,8 +1307,8 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
|||||||
startActivityForResult(listIntent, MyTracksConstants.GET_MAP);
|
startActivityForResult(listIntent, MyTracksConstants.GET_MAP);
|
||||||
} else {
|
} else {
|
||||||
setProgressValue(0);
|
setProgressValue(0);
|
||||||
setProgressMessage(getString(
|
setProgressMessage(
|
||||||
R.string.progress_message_authenticating_mymaps));
|
R.string.progress_message_authenticating_mymaps);
|
||||||
authenticate(new Intent(), MyTracksConstants.SEND_TO_GOOGLE,
|
authenticate(new Intent(), MyTracksConstants.SEND_TO_GOOGLE,
|
||||||
MyMapsConstants.MAPSHOP_SERVICE);
|
MyMapsConstants.MAPSHOP_SERVICE);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks;
|
package com.google.android.apps.mytracks;
|
||||||
|
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constants used by the MyTracks application.
|
* Constants used by the MyTracks application.
|
||||||
*
|
*
|
||||||
@@ -40,11 +38,6 @@ public abstract class MyTracksConstants {
|
|||||||
*/
|
*/
|
||||||
public static final String SDCARD_TOP_DIR = "MyTracks";
|
public static final String SDCARD_TOP_DIR = "MyTracks";
|
||||||
|
|
||||||
/**
|
|
||||||
* The API level of the Android version we're being run under.
|
|
||||||
*/
|
|
||||||
public static final int ANDROID_API_LEVEL = Integer.parseInt(Build.VERSION.SDK);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* onActivityResult request codes:
|
* onActivityResult request codes:
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ import com.google.android.apps.mytracks.content.Track;
|
|||||||
import com.google.android.apps.mytracks.content.TrackPointsColumns;
|
import com.google.android.apps.mytracks.content.TrackPointsColumns;
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
||||||
|
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
|
||||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||||
|
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||||
import com.google.android.maps.GeoPoint;
|
import com.google.android.maps.GeoPoint;
|
||||||
import com.google.android.maps.MapActivity;
|
import com.google.android.maps.MapActivity;
|
||||||
@@ -47,17 +49,16 @@ import android.os.Bundle;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.speech.tts.TextToSpeech;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
import android.view.ContextMenu.ContextMenuInfo;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.SubMenu;
|
import android.view.SubMenu;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnCreateContextMenuListener;
|
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
|
import android.view.ContextMenu.ContextMenuInfo;
|
||||||
|
import android.view.View.OnCreateContextMenuListener;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
@@ -139,14 +140,16 @@ public class MyTracksMap extends MapActivity
|
|||||||
*/
|
*/
|
||||||
private HandlerThread updateTrackThread;
|
private HandlerThread updateTrackThread;
|
||||||
|
|
||||||
/** Handler for updateTrackThread */
|
/**
|
||||||
|
* Handler for updateTrackThread.
|
||||||
|
*/
|
||||||
private Handler updateTrackHandler;
|
private Handler updateTrackHandler;
|
||||||
|
|
||||||
private MyTracksProviderUtils providerUtils;
|
private MyTracksProviderUtils providerUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A runnable that updates the track from the provider (looking for points
|
* A runnable that updates the track from the provider (looking for points
|
||||||
* added after "lastSeenLocationId".
|
* added after "lastSeenLocationId").
|
||||||
*/
|
*/
|
||||||
private final Runnable updateTrackRunnable = new Runnable() {
|
private final Runnable updateTrackRunnable = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -154,6 +157,7 @@ public class MyTracksMap extends MapActivity
|
|||||||
if (!isATrackSelected()) {
|
if (!isATrackSelected()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
readAllNewTrackPoints();
|
readAllNewTrackPoints();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -167,6 +171,7 @@ public class MyTracksMap extends MapActivity
|
|||||||
if (!isATrackSelected()) {
|
if (!isATrackSelected()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mapOverlay.clearPoints();
|
mapOverlay.clearPoints();
|
||||||
firstSeenLocationId = -1;
|
firstSeenLocationId = -1;
|
||||||
lastSeenLocationId = -1;
|
lastSeenLocationId = -1;
|
||||||
@@ -192,13 +197,10 @@ public class MyTracksMap extends MapActivity
|
|||||||
cursor = providerUtils.getWaypointsCursor(
|
cursor = providerUtils.getWaypointsCursor(
|
||||||
selectedTrackId, 0,
|
selectedTrackId, 0,
|
||||||
MyTracksConstants.MAX_DISPLAYED_WAYPOINTS_POINTS);
|
MyTracksConstants.MAX_DISPLAYED_WAYPOINTS_POINTS);
|
||||||
if (cursor != null) {
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
if (cursor.moveToFirst()) {
|
do {
|
||||||
do {
|
mapOverlay.addWaypoint(providerUtils.createWaypoint(cursor));
|
||||||
Waypoint wpt = providerUtils.createWaypoint(cursor);
|
} while (cursor.moveToNext());
|
||||||
mapOverlay.addWaypoint(wpt);
|
|
||||||
} while (cursor.moveToNext());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
Log.w(MyTracksConstants.TAG, "Caught an unexpected exception.", e);
|
Log.w(MyTracksConstants.TAG, "Caught an unexpected exception.", e);
|
||||||
@@ -212,9 +214,9 @@ public class MyTracksMap extends MapActivity
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A runnable intended to be posted to the updateTrackThread after the
|
* A runnable intended to be posted to the {@code #updateTrackThread} after
|
||||||
* selected track changes. It will post to the ui thread to update the screen
|
* the selected track changes. It will post to the UI thread to update
|
||||||
* elements and move the map to show the selected track.
|
* the screen elements and move the map to show the selected track.
|
||||||
*/
|
*/
|
||||||
private final Runnable setSelectedTrackRunnable = new Runnable() {
|
private final Runnable setSelectedTrackRunnable = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -282,7 +284,9 @@ public class MyTracksMap extends MapActivity
|
|||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
|
|
||||||
// The volume we want to control is the Text-To-Speech volume
|
// The volume we want to control is the Text-To-Speech volume
|
||||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
int volumeStream =
|
||||||
|
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
|
||||||
|
setVolumeControlStream(volumeStream);
|
||||||
|
|
||||||
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
||||||
|
|
||||||
@@ -372,12 +376,20 @@ public class MyTracksMap extends MapActivity
|
|||||||
setSelectedTrack(selectedTrackId);
|
setSelectedTrack(selectedTrackId);
|
||||||
}
|
}
|
||||||
updateOptionsButton();
|
updateOptionsButton();
|
||||||
mapOverlay.setDrawBounds(preferences.getBoolean(
|
|
||||||
getString(R.string.debug_draw_bounds_key), false));
|
|
||||||
preferences.registerOnSharedPreferenceChangeListener(this);
|
preferences.registerOnSharedPreferenceChangeListener(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
Log.d(MyTracksConstants.TAG, "MyTracksMap.onDestroy");
|
||||||
|
if (updateTrackThread != null) {
|
||||||
|
updateTrackThread.quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether there's a track currently selected for display.
|
* Returns whether there's a track currently selected for display.
|
||||||
*/
|
*/
|
||||||
@@ -893,8 +905,7 @@ public class MyTracksMap extends MapActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(
|
public void onSharedPreferenceChanged(
|
||||||
final SharedPreferences sharedPreferences,
|
final SharedPreferences sharedPreferences, final String key) {
|
||||||
final String key) {
|
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
uiHandler.post(new Runnable() {
|
uiHandler.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -918,10 +929,6 @@ public class MyTracksMap extends MapActivity
|
|||||||
mapOverlay.setShowEndMarker(!isRecordingSelected());
|
mapOverlay.setShowEndMarker(!isRecordingSelected());
|
||||||
mapView.postInvalidate();
|
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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -950,8 +957,8 @@ public class MyTracksMap extends MapActivity
|
|||||||
public void onLocationChanged(Location location) {
|
public void onLocationChanged(Location location) {
|
||||||
if (location.getProvider().equals(MyTracksConstants.GPS_PROVIDER)) {
|
if (location.getProvider().equals(MyTracksConstants.GPS_PROVIDER)) {
|
||||||
// Recalculate the variation if there was a jump in location > 1km:
|
// Recalculate the variation if there was a jump in location > 1km:
|
||||||
if (currentLocation == null
|
if (currentLocation == null ||
|
||||||
|| location.distanceTo(currentLocation) > 1000) {
|
location.distanceTo(currentLocation) > 1000) {
|
||||||
setVariation(location);
|
setVariation(location);
|
||||||
}
|
}
|
||||||
currentLocation = location;
|
currentLocation = location;
|
||||||
@@ -1039,8 +1046,8 @@ public class MyTracksMap extends MapActivity
|
|||||||
// Now we already have at least one point, calculate the sampling
|
// Now we already have at least one point, calculate the sampling
|
||||||
// frequency
|
// frequency
|
||||||
long numTotalPoints = lastStoredLocationId - firstSeenLocationId;
|
long numTotalPoints = lastStoredLocationId - firstSeenLocationId;
|
||||||
samplingFrequency = (int) (1 +
|
samplingFrequency = (int) (1 + numTotalPoints
|
||||||
numTotalPoints / MyTracksConstants.TARGET_DISPLAYED_TRACK_POINTS);
|
/ MyTracksConstants.TARGET_DISPLAYED_TRACK_POINTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
providerUtils.fillLocation(cursor, location);
|
providerUtils.fillLocation(cursor, location);
|
||||||
@@ -1052,8 +1059,8 @@ public class MyTracksMap extends MapActivity
|
|||||||
if (numPoints % samplingFrequency == 0 ||
|
if (numPoints % samplingFrequency == 0 ||
|
||||||
(!isRecordingSelected() && locationId == lastStoredLocationId) ||
|
(!isRecordingSelected() && locationId == lastStoredLocationId) ||
|
||||||
!MyTracksUtils.isValidLocation(location)) {
|
!MyTracksUtils.isValidLocation(location)) {
|
||||||
// Only allocate a new location if it is going to be kept around.
|
// No need to allocate a new location (we can safely reuse the existing).
|
||||||
mapOverlay.addLocation(new Location(location));
|
mapOverlay.addLocation(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
numPoints++;
|
numPoints++;
|
||||||
|
|||||||
@@ -20,20 +20,24 @@ import com.google.android.apps.mytracks.util.MyTracksUtils;
|
|||||||
import com.google.android.maps.GeoPoint;
|
import com.google.android.maps.GeoPoint;
|
||||||
import com.google.android.maps.MapView;
|
import com.google.android.maps.MapView;
|
||||||
import com.google.android.maps.Overlay;
|
import com.google.android.maps.Overlay;
|
||||||
|
import com.google.android.maps.Projection;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.res.Resources;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.Region;
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map overlay that displays a "MyLocation" arrow, an error circle, the
|
* A map overlay that displays a "MyLocation" arrow, an error circle, the
|
||||||
@@ -43,7 +47,7 @@ import java.util.ArrayList;
|
|||||||
*/
|
*/
|
||||||
public class MyTracksOverlay extends Overlay {
|
public class MyTracksOverlay extends Overlay {
|
||||||
|
|
||||||
private final Drawable arrow[] = new Drawable[18];
|
private final Drawable[] arrows;
|
||||||
private final int arrowWidth, arrowHeight;
|
private final int arrowWidth, arrowHeight;
|
||||||
private final Drawable statsMarker;
|
private final Drawable statsMarker;
|
||||||
private final Drawable waypointMarker;
|
private final Drawable waypointMarker;
|
||||||
@@ -53,68 +57,94 @@ public class MyTracksOverlay extends Overlay {
|
|||||||
private final Paint selectedTrackPaint;
|
private final Paint selectedTrackPaint;
|
||||||
private final Paint errorCirclePaint;
|
private final Paint errorCirclePaint;
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final ArrayList<Waypoint> waypoints = new ArrayList<Waypoint>();
|
private final List<Waypoint> waypoints;
|
||||||
private final ArrayList<Location> points = new ArrayList<Location>(1024);
|
private final List<CachedLocation> points;
|
||||||
|
private final BlockingQueue<CachedLocation> pendingPoints;
|
||||||
|
|
||||||
private boolean trackDrawingEnabled;
|
private boolean trackDrawingEnabled;
|
||||||
private int lastHeading = 0;
|
private int lastHeading = 0;
|
||||||
private Location myLocation;
|
private Location myLocation;
|
||||||
private boolean showEndMarker = true;
|
private boolean showEndMarker = true;
|
||||||
private boolean drawBounds;
|
// TODO: Remove it completely after completing performance tests.
|
||||||
|
private boolean alwaysVisible = true;
|
||||||
|
|
||||||
|
private GeoPoint lastReferencePoint;
|
||||||
|
private Rect lastViewRect;
|
||||||
|
private Path lastPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a pre-processed {@code Location} to speed up drawing.
|
||||||
|
* This class is more like a data object and doesn't provide accessors.
|
||||||
|
*/
|
||||||
|
private static class CachedLocation {
|
||||||
|
public final boolean valid;
|
||||||
|
public final GeoPoint geoPoint;
|
||||||
|
|
||||||
|
public CachedLocation(Location location) {
|
||||||
|
this.valid = MyTracksUtils.isValidLocation(location);
|
||||||
|
this.geoPoint = valid ? MyTracksUtils.getGeoPoint(location) : null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public MyTracksOverlay(Context context) {
|
public MyTracksOverlay(Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
|
||||||
|
this.waypoints = new ArrayList<Waypoint>();
|
||||||
|
this.points = new ArrayList<CachedLocation>(1024);
|
||||||
|
this.pendingPoints = new ArrayBlockingQueue<CachedLocation>(
|
||||||
|
MyTracksConstants.MAX_DISPLAYED_TRACK_POINTS, true);
|
||||||
|
|
||||||
// TODO: Can we use a FrameAnimation or similar here rather
|
// TODO: Can we use a FrameAnimation or similar here rather
|
||||||
// than individual resources for each arrow direction?
|
// than individual resources for each arrow direction?
|
||||||
|
final Resources resources = context.getResources();
|
||||||
arrow[0] = context.getResources().getDrawable(R.drawable.arrow_0);
|
arrows = new Drawable[] {
|
||||||
arrow[1] = context.getResources().getDrawable(R.drawable.arrow_20);
|
resources.getDrawable(R.drawable.arrow_0),
|
||||||
arrow[2] = context.getResources().getDrawable(R.drawable.arrow_40);
|
resources.getDrawable(R.drawable.arrow_20),
|
||||||
arrow[3] = context.getResources().getDrawable(R.drawable.arrow_60);
|
resources.getDrawable(R.drawable.arrow_40),
|
||||||
arrow[4] = context.getResources().getDrawable(R.drawable.arrow_80);
|
resources.getDrawable(R.drawable.arrow_60),
|
||||||
arrow[5] = context.getResources().getDrawable(R.drawable.arrow_100);
|
resources.getDrawable(R.drawable.arrow_80),
|
||||||
arrow[6] = context.getResources().getDrawable(R.drawable.arrow_120);
|
resources.getDrawable(R.drawable.arrow_100),
|
||||||
arrow[7] = context.getResources().getDrawable(R.drawable.arrow_140);
|
resources.getDrawable(R.drawable.arrow_120),
|
||||||
arrow[8] = context.getResources().getDrawable(R.drawable.arrow_160);
|
resources.getDrawable(R.drawable.arrow_140),
|
||||||
arrow[9] = context.getResources().getDrawable(R.drawable.arrow_180);
|
resources.getDrawable(R.drawable.arrow_160),
|
||||||
arrow[10] = context.getResources().getDrawable(R.drawable.arrow_200);
|
resources.getDrawable(R.drawable.arrow_180),
|
||||||
arrow[11] = context.getResources().getDrawable(R.drawable.arrow_220);
|
resources.getDrawable(R.drawable.arrow_200),
|
||||||
arrow[12] = context.getResources().getDrawable(R.drawable.arrow_240);
|
resources.getDrawable(R.drawable.arrow_220),
|
||||||
arrow[13] = context.getResources().getDrawable(R.drawable.arrow_260);
|
resources.getDrawable(R.drawable.arrow_240),
|
||||||
arrow[14] = context.getResources().getDrawable(R.drawable.arrow_280);
|
resources.getDrawable(R.drawable.arrow_260),
|
||||||
arrow[15] = context.getResources().getDrawable(R.drawable.arrow_300);
|
resources.getDrawable(R.drawable.arrow_280),
|
||||||
arrow[16] = context.getResources().getDrawable(R.drawable.arrow_320);
|
resources.getDrawable(R.drawable.arrow_300),
|
||||||
arrow[17] = context.getResources().getDrawable(R.drawable.arrow_340);
|
resources.getDrawable(R.drawable.arrow_320),
|
||||||
arrowWidth = arrow[lastHeading].getIntrinsicWidth();
|
resources.getDrawable(R.drawable.arrow_340)
|
||||||
arrowHeight = arrow[lastHeading].getIntrinsicHeight();
|
};
|
||||||
for (int i = 0; i <= 17; i++) {
|
arrowWidth = arrows[lastHeading].getIntrinsicWidth();
|
||||||
arrow[i].setBounds(0, 0, arrowWidth, arrowHeight);
|
arrowHeight = arrows[lastHeading].getIntrinsicHeight();
|
||||||
|
for (Drawable arrow : arrows) {
|
||||||
|
arrow.setBounds(0, 0, arrowWidth, arrowHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
statsMarker = context.getResources().getDrawable(R.drawable.ylw_pushpin);
|
statsMarker = resources.getDrawable(R.drawable.ylw_pushpin);
|
||||||
markerWidth = statsMarker.getIntrinsicWidth();
|
markerWidth = statsMarker.getIntrinsicWidth();
|
||||||
markerHeight = statsMarker.getIntrinsicHeight();
|
markerHeight = statsMarker.getIntrinsicHeight();
|
||||||
statsMarker.setBounds(0, 0, markerWidth, markerHeight);
|
statsMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||||
|
|
||||||
startMarker = context.getResources().getDrawable(R.drawable.green_dot);
|
startMarker = resources.getDrawable(R.drawable.green_dot);
|
||||||
startMarker.setBounds(0, 0, markerWidth, markerHeight);
|
startMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||||
|
|
||||||
endMarker = context.getResources().getDrawable(R.drawable.red_dot);
|
endMarker = resources.getDrawable(R.drawable.red_dot);
|
||||||
endMarker.setBounds(0, 0, markerWidth, markerHeight);
|
endMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||||
|
|
||||||
waypointMarker =
|
waypointMarker = resources.getDrawable(R.drawable.blue_pushpin);
|
||||||
context.getResources().getDrawable(R.drawable.blue_pushpin);
|
|
||||||
waypointMarker.setBounds(0, 0, markerWidth, markerHeight);
|
waypointMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||||
|
|
||||||
selectedTrackPaint = new Paint();
|
selectedTrackPaint = new Paint();
|
||||||
selectedTrackPaint.setColor(context.getResources().getColor(R.color.red));
|
selectedTrackPaint.setColor(resources.getColor(R.color.red));
|
||||||
selectedTrackPaint.setStrokeWidth(3);
|
selectedTrackPaint.setStrokeWidth(3);
|
||||||
selectedTrackPaint.setStyle(Paint.Style.STROKE);
|
selectedTrackPaint.setStyle(Paint.Style.STROKE);
|
||||||
selectedTrackPaint.setAntiAlias(true);
|
selectedTrackPaint.setAntiAlias(true);
|
||||||
|
|
||||||
errorCirclePaint = new Paint();
|
errorCirclePaint = new Paint();
|
||||||
errorCirclePaint.setColor(context.getResources().getColor(R.color.blue));
|
errorCirclePaint.setColor(resources.getColor(R.color.blue));
|
||||||
errorCirclePaint.setStyle(Paint.Style.STROKE);
|
errorCirclePaint.setStyle(Paint.Style.STROKE);
|
||||||
errorCirclePaint.setStrokeWidth(3);
|
errorCirclePaint.setStrokeWidth(3);
|
||||||
errorCirclePaint.setAlpha(127);
|
errorCirclePaint.setAlpha(127);
|
||||||
@@ -124,35 +154,50 @@ public class MyTracksOverlay extends Overlay {
|
|||||||
/**
|
/**
|
||||||
* Add a location to the map overlay.
|
* Add a location to the map overlay.
|
||||||
*
|
*
|
||||||
* NOTE: this method takes ownership of this location and may change it.
|
* NOTE: This method doesn't take ownership of the given location, so it is
|
||||||
|
* safe to reuse the same location while calling this method.
|
||||||
*
|
*
|
||||||
* @param l the location to add
|
* @param l the location to add.
|
||||||
*/
|
*/
|
||||||
public void addLocation(Location l) {
|
public void addLocation(Location l) {
|
||||||
if (l != null) {
|
// Queue up in the pending queue until it's merged with {@code #points}.
|
||||||
synchronized (points) {
|
pendingPoints.offer(new CachedLocation(l));
|
||||||
points.add(l);
|
}
|
||||||
|
|
||||||
|
public void addWaypoint(Waypoint wpt) {
|
||||||
|
// Note: We don't cache waypoints, because it's not worth the effort.
|
||||||
|
if (wpt != null && wpt.getLocation() != null) {
|
||||||
|
synchronized (waypoints) {
|
||||||
|
waypoints.add(wpt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addWaypoint(Waypoint wpt) {
|
|
||||||
waypoints.add(wpt);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getNumLocations() {
|
public int getNumLocations() {
|
||||||
synchronized (points) {
|
synchronized (points) {
|
||||||
return points.size();
|
return points.size() + pendingPoints.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearWaypoints() {
|
// Visible for testing.
|
||||||
waypoints.clear();
|
int getNumWaypoints() {
|
||||||
|
synchronized (waypoints) {
|
||||||
|
return waypoints.size();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearPoints() {
|
public void clearPoints() {
|
||||||
synchronized (points) {
|
synchronized (points) {
|
||||||
points.clear();
|
points.clear();
|
||||||
|
pendingPoints.clear();
|
||||||
|
lastPath = null;
|
||||||
|
lastViewRect = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearWaypoints() {
|
||||||
|
synchronized (waypoints) {
|
||||||
|
waypoints.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,230 +215,175 @@ public class MyTracksOverlay extends Overlay {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// It's safe to keep projection within a single draw operation.
|
||||||
|
final Projection projection = getMapProjection(mapView);
|
||||||
|
// Get the current viewing window.
|
||||||
if (trackDrawingEnabled) {
|
if (trackDrawingEnabled) {
|
||||||
|
Rect viewRect = getMapViewRect(mapView);
|
||||||
|
|
||||||
// Draw the selected track:
|
// Draw the selected track:
|
||||||
drawTrack(canvas, mapView);
|
drawTrack(canvas, projection, viewRect);
|
||||||
|
|
||||||
// Draw the waypoints:
|
// Draw the waypoints:
|
||||||
drawWaypoints(canvas, mapView);
|
drawWaypoints(canvas, projection);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the current location
|
// Draw the current location
|
||||||
drawMyLocation(canvas, mapView);
|
drawMyLocation(canvas, projection);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawWaypoints(Canvas canvas, MapView mapView) {
|
// Visible for testing.
|
||||||
ArrayList<Waypoint> currentWaypoints = waypoints;
|
Projection getMapProjection(MapView mapView) {
|
||||||
for (int i = 1; i < currentWaypoints.size(); i++) {
|
return mapView.getProjection();
|
||||||
Waypoint wpt = currentWaypoints.get(i);
|
|
||||||
if (wpt == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Location loc = wpt.getLocation();
|
|
||||||
if (loc == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
GeoPoint geoPoint = MyTracksUtils.getGeoPoint(loc);
|
|
||||||
Point pt = new Point();
|
|
||||||
mapView.getProjection().toPixels(geoPoint, pt);
|
|
||||||
canvas.save();
|
|
||||||
canvas.translate(pt.x - (markerWidth / 2) + 3, pt.y - (markerHeight));
|
|
||||||
if (wpt.getType() == Waypoint.TYPE_STATISTICS) {
|
|
||||||
statsMarker.draw(canvas);
|
|
||||||
} else {
|
|
||||||
waypointMarker.draw(canvas);
|
|
||||||
}
|
|
||||||
canvas.restore();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawMyLocation(Canvas canvas, MapView mapView) {
|
// Visible for testing.
|
||||||
// Draw the arrow icon:
|
Rect getMapViewRect(MapView mapView) {
|
||||||
if (myLocation == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GeoPoint geoPoint = new GeoPoint(
|
|
||||||
(int) (myLocation.getLatitude() * 1E6),
|
|
||||||
(int) (myLocation.getLongitude() * 1E6));
|
|
||||||
Point pt = new Point();
|
|
||||||
mapView.getProjection().toPixels(geoPoint, pt);
|
|
||||||
canvas.save();
|
|
||||||
canvas.translate(pt.x - (arrowWidth / 2), pt.y - (arrowHeight / 2));
|
|
||||||
arrow[lastHeading].draw(canvas);
|
|
||||||
canvas.restore();
|
|
||||||
|
|
||||||
// Draw the error circle:
|
|
||||||
float radius =
|
|
||||||
mapView.getProjection().metersToEquatorPixels(myLocation.getAccuracy());
|
|
||||||
canvas.drawCircle(pt.x, pt.y, radius, errorCirclePaint);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 w = mapView.getLongitudeSpan();
|
||||||
int h = mapView.getLatitudeSpan();
|
int h = mapView.getLatitudeSpan();
|
||||||
int cx = mapView.getMapCenter().getLongitudeE6();
|
int cx = mapView.getMapCenter().getLongitudeE6();
|
||||||
int cy = mapView.getMapCenter().getLatitudeE6();
|
int cy = mapView.getMapCenter().getLatitudeE6();
|
||||||
Rect viewRect = new Rect(cx - w, cy - h, cx + w, cy + h);
|
return new Rect(cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2);
|
||||||
|
}
|
||||||
|
|
||||||
// Global bounding box, including points not visible
|
// Visible for testing.
|
||||||
int allMinLat, allMinLon, allMaxLat, allMaxLon;
|
Path newPath() {
|
||||||
|
return new Path();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visible for testing.
|
||||||
|
Path getLastPath() {
|
||||||
|
return lastPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawWaypoints(Canvas canvas, Projection projection) {
|
||||||
|
synchronized (waypoints) {;
|
||||||
|
for (Waypoint wpt : waypoints) {
|
||||||
|
Location loc = wpt.getLocation();
|
||||||
|
drawElement(canvas, projection, MyTracksUtils.getGeoPoint(loc),
|
||||||
|
wpt.getType() == Waypoint.TYPE_STATISTICS ? statsMarker
|
||||||
|
: waypointMarker, -(markerWidth / 2) + 3, -markerHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawMyLocation(Canvas canvas, Projection projection) {
|
||||||
|
// Draw the arrow icon.
|
||||||
|
if (myLocation == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Point pt = drawElement(canvas, projection,
|
||||||
|
MyTracksUtils.getGeoPoint(myLocation), arrows[lastHeading],
|
||||||
|
-(arrowWidth / 2) + 3, -(arrowHeight / 2));
|
||||||
|
// Draw the error circle.
|
||||||
|
float radius = projection.metersToEquatorPixels(myLocation.getAccuracy());
|
||||||
|
canvas.drawCircle(pt.x, pt.y, radius, errorCirclePaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawTrack(Canvas canvas, Projection projection, Rect viewRect) {
|
||||||
|
Path path;
|
||||||
synchronized (points) {
|
synchronized (points) {
|
||||||
int numPoints = points.size();
|
// Merge the pending points with the list of cached locations.
|
||||||
if (numPoints < 2) {
|
final GeoPoint referencePoint = projection.fromPixels(0, 0);
|
||||||
return;
|
int newPoints = pendingPoints.drainTo(points);
|
||||||
}
|
boolean newProjection = !viewRect.equals(lastViewRect) ||
|
||||||
|
!referencePoint.equals(lastReferencePoint);
|
||||||
GeoPoint geoPoint;
|
if (newPoints == 0 && lastPath != null && !newProjection) {
|
||||||
Location loc;
|
// No need to recreate path (same points and viewing area).
|
||||||
int minLon, maxLon, minLat, maxLat;
|
path = lastPath;
|
||||||
lastValidLocation = points.get(0);
|
} else {
|
||||||
int lastLocLon = allMinLon = allMaxLon = (int) (lastValidLocation.getLongitude() * 1E6);
|
int numPoints = points.size();
|
||||||
int lastLocLat = allMinLat = allMaxLat = (int) (lastValidLocation.getLatitude() * 1E6);
|
if (numPoints < 2) {
|
||||||
lastLocValid = MyTracksUtils.isValidLocation(lastValidLocation);
|
// Not enough points to draw a path.
|
||||||
boolean lastLocVisible = false;
|
path = null;
|
||||||
path = new Path();
|
} else if (lastPath != null && !newProjection) {
|
||||||
|
// Incremental update of the path, without repositioning the view.
|
||||||
// Loop over track points:
|
path = lastPath;
|
||||||
path.incReserve(numPoints);
|
updatePath(projection, viewRect, path, numPoints - newPoints);
|
||||||
for (int i = 1; i < numPoints; i++) {
|
} else {
|
||||||
loc = points.get(i);
|
// The view has changed so we have to start from scratch.
|
||||||
|
path = newPath();
|
||||||
boolean locValid = MyTracksUtils.isValidLocation(loc);
|
path.incReserve(numPoints);
|
||||||
boolean locVisible = false;
|
updatePath(projection, viewRect, path, 0);
|
||||||
if (locValid) {
|
}
|
||||||
locLon = (int) (loc.getLongitude() * 1E6);
|
lastPath = path;
|
||||||
locLat = (int) (loc.getLatitude() * 1E6);
|
}
|
||||||
|
lastReferencePoint = referencePoint;
|
||||||
if (firstGeoPoint == null) {
|
lastViewRect = viewRect;
|
||||||
// Found the starting point
|
}
|
||||||
firstGeoPoint = new GeoPoint(locLat, locLon);
|
if (path != null) {
|
||||||
}
|
canvas.drawPath(path, selectedTrackPaint);
|
||||||
|
}
|
||||||
// If both the current and previous locations were valid
|
|
||||||
if (lastLocValid) {
|
// Draw the "End" marker.
|
||||||
lastValidLocation = loc;
|
if (showEndMarker) {
|
||||||
|
for (int i = points.size() - 1; i >= 0; --i) {
|
||||||
// Get the bounding box of the segment about to be drawn
|
if (points.get(i).valid) {
|
||||||
if (locLon > lastLocLon) {
|
drawElement(canvas, projection, points.get(i).geoPoint, endMarker,
|
||||||
minLon = lastLocLon;
|
-markerWidth / 2, -markerHeight);
|
||||||
maxLon = locLon;
|
break;
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
lastLocLon = locLon;
|
|
||||||
lastLocLat = locLat;
|
|
||||||
lastLocValid = locValid;
|
|
||||||
lastLocVisible = locVisible;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.drawPath(path, selectedTrackPaint);
|
// Draw the "Start" marker.
|
||||||
|
for (int i = 0; i < points.size(); ++i) {
|
||||||
if (drawBounds) {
|
if (points.get(i).valid) {
|
||||||
drawBoundingBox(canvas, mapView,
|
drawElement(canvas, projection, points.get(i).geoPoint, startMarker,
|
||||||
allMinLat, allMinLon, allMaxLat, allMaxLon);
|
-markerWidth / 2, -markerHeight);
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
// Draw the "End" marker:
|
|
||||||
if (showEndMarker && lastValidLocation != null) {
|
|
||||||
canvas.save();
|
|
||||||
GeoPoint geoPoint = MyTracksUtils.getGeoPoint(lastValidLocation);
|
|
||||||
mapView.getProjection().toPixels(geoPoint, pt);
|
|
||||||
canvas.translate(pt.x - (markerWidth / 2), pt.y - markerHeight);
|
|
||||||
endMarker.draw(canvas);
|
|
||||||
canvas.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the "Start" marker:
|
|
||||||
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,
|
private void updatePath(Projection projection, Rect viewRect, Path path,
|
||||||
int allMinLon, int allMaxLat, int allMaxLon) {
|
int startLocationIdx) {
|
||||||
// Transform coordinates
|
// Whether to start a new segment on new valid and visible point.
|
||||||
GeoPoint maxPoint = new GeoPoint(allMaxLat, allMaxLon);
|
boolean newSegment =
|
||||||
GeoPoint minPoint = new GeoPoint(allMinLat, allMinLon);
|
startLocationIdx > 0 ? !points.get(startLocationIdx - 1).valid : true;
|
||||||
Point minPt = new Point();
|
boolean lastVisible = !newSegment;
|
||||||
Point maxPt = new Point();
|
final Point pt = new Point();
|
||||||
mapView.getProjection().toPixels(minPoint, minPt);
|
// Loop over track points.
|
||||||
mapView.getProjection().toPixels(maxPoint, maxPt);
|
for (int i = startLocationIdx; i < points.size(); ++i) {
|
||||||
Rect allBounds = new Rect(minPt.x, minPt.y, maxPt.x, maxPt.y);
|
CachedLocation loc = points.get(i);
|
||||||
|
// Check if valid, if not then indicate a new segment.
|
||||||
|
if (!loc.valid) {
|
||||||
|
newSegment = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare a green paint
|
final GeoPoint geoPoint = loc.geoPoint;
|
||||||
Paint boundingBoxPaint = new Paint();
|
// Check if this breaks the existing segment.
|
||||||
boundingBoxPaint.setColor(context.getResources().getColor(R.color.green));
|
boolean visible = alwaysVisible || viewRect.contains(
|
||||||
boundingBoxPaint.setStrokeWidth(2);
|
geoPoint.getLongitudeE6(), geoPoint.getLatitudeE6());
|
||||||
boundingBoxPaint.setStyle(Paint.Style.STROKE);
|
if (!visible && !lastVisible) {
|
||||||
boundingBoxPaint.setAntiAlias(true);
|
// This is a point outside view not connected to a visible one.
|
||||||
|
newSegment = true;
|
||||||
|
}
|
||||||
|
lastVisible = visible;
|
||||||
|
|
||||||
// Disable clipping
|
// Either move to beginning of a new segment or continue the old one.
|
||||||
|
projection.toPixels(geoPoint, pt);
|
||||||
|
if (newSegment) {
|
||||||
|
path.moveTo(pt.x, pt.y);
|
||||||
|
newSegment = false;
|
||||||
|
} else {
|
||||||
|
path.lineTo(pt.x, pt.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visible for testing.
|
||||||
|
Point drawElement(Canvas canvas, Projection projection, GeoPoint geoPoint,
|
||||||
|
Drawable element, int offsetX, int offsetY) {
|
||||||
|
Point pt = new Point();
|
||||||
|
projection.toPixels(geoPoint, pt);
|
||||||
canvas.save();
|
canvas.save();
|
||||||
canvas.clipRect(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY,
|
canvas.translate(pt.x + offsetX, pt.y + offsetY);
|
||||||
Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
|
element.draw(canvas);
|
||||||
Region.Op.REPLACE);
|
|
||||||
|
|
||||||
// Draw the bounding box
|
|
||||||
canvas.drawRect(allBounds, boundingBoxPaint);
|
|
||||||
|
|
||||||
// Re-enable clipping
|
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
}
|
return pt;
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean draw(Canvas canvas, MapView mapView, boolean shadow,
|
|
||||||
long when) {
|
|
||||||
draw(canvas, mapView, shadow);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -436,20 +426,22 @@ public class MyTracksOverlay extends Overlay {
|
|||||||
final Location tapLocation = MyTracksUtils.getLocation(p);
|
final Location tapLocation = MyTracksUtils.getLocation(p);
|
||||||
double dmin = Double.MAX_VALUE;
|
double dmin = Double.MAX_VALUE;
|
||||||
Waypoint waypoint = null;
|
Waypoint waypoint = null;
|
||||||
for (int i = 0; i < waypoints.size(); i++) {
|
synchronized (waypoints) {
|
||||||
final Location waypointLocation = waypoints.get(i).getLocation();
|
for (int i = 0; i < waypoints.size(); i++) {
|
||||||
if (waypointLocation == null) {
|
final Location waypointLocation = waypoints.get(i).getLocation();
|
||||||
continue;
|
if (waypointLocation == null) {
|
||||||
}
|
continue;
|
||||||
final double d = waypointLocation.distanceTo(tapLocation);
|
}
|
||||||
if (d < dmin) {
|
final double d = waypointLocation.distanceTo(tapLocation);
|
||||||
dmin = d;
|
if (d < dmin) {
|
||||||
waypoint = waypoints.get(i);
|
dmin = d;
|
||||||
|
waypoint = waypoints.get(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (waypoint != null
|
if (waypoint != null &&
|
||||||
&& dmin < 15000000 / Math.pow(2, mapView.getZoomLevel())) {
|
dmin < 15000000 / Math.pow(2, mapView.getZoomLevel())) {
|
||||||
Intent intent = new Intent(context, MyTracksWaypointDetails.class);
|
Intent intent = new Intent(context, MyTracksWaypointDetails.class);
|
||||||
intent.putExtra("waypointid", waypoint.getId());
|
intent.putExtra("waypointid", waypoint.getId());
|
||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
@@ -457,8 +449,4 @@ public class MyTracksOverlay extends Overlay {
|
|||||||
}
|
}
|
||||||
return super.onTap(p, mapView);
|
return super.onTap(p, mapView);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDrawBounds(boolean drawBounds) {
|
|
||||||
this.drawBounds = drawBounds;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ package com.google.android.apps.mytracks;
|
|||||||
|
|
||||||
import com.google.android.apps.mytracks.io.backup.BackupActivityHelper;
|
import com.google.android.apps.mytracks.io.backup.BackupActivityHelper;
|
||||||
import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener;
|
import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener;
|
||||||
import com.google.android.apps.mytracks.services.SafeStatusAnnouncerTask;
|
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
|
||||||
|
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
@@ -25,12 +26,10 @@ import android.os.Bundle;
|
|||||||
import android.preference.CheckBoxPreference;
|
import android.preference.CheckBoxPreference;
|
||||||
import android.preference.ListPreference;
|
import android.preference.ListPreference;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
import android.preference.PreferenceActivity;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.preference.Preference.OnPreferenceChangeListener;
|
import android.preference.Preference.OnPreferenceChangeListener;
|
||||||
import android.preference.Preference.OnPreferenceClickListener;
|
import android.preference.Preference.OnPreferenceClickListener;
|
||||||
import android.speech.tts.TextToSpeech;
|
import android.preference.PreferenceActivity;
|
||||||
import android.util.Log;
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An activity that let's the user see and edit the settings.
|
* An activity that let's the user see and edit the settings.
|
||||||
@@ -53,29 +52,19 @@ public class MyTracksSettings extends PreferenceActivity {
|
|||||||
public static final int DEFAULT_MIN_REQUIRED_ACCURACY = 200;
|
public static final int DEFAULT_MIN_REQUIRED_ACCURACY = 200;
|
||||||
public static final int DEFAULT_SPLIT_FREQUENCY = 0;
|
public static final int DEFAULT_SPLIT_FREQUENCY = 0;
|
||||||
|
|
||||||
private static boolean mTTSAvailable;
|
|
||||||
private BackupPreferencesListener backupListener;
|
private BackupPreferencesListener backupListener;
|
||||||
|
|
||||||
private SharedPreferences preferences;
|
private SharedPreferences preferences;
|
||||||
|
|
||||||
/* establish whether the tts class is available to us */
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
SafeStatusAnnouncerTask.checkAvailable();
|
|
||||||
mTTSAvailable = true;
|
|
||||||
} catch (Throwable t) {
|
|
||||||
Log.d(MyTracksConstants.TAG, "TTS not available.", t);
|
|
||||||
mTTSAvailable = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Called when the activity is first created. */
|
/** Called when the activity is first created. */
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle icicle) {
|
public void onCreate(Bundle icicle) {
|
||||||
super.onCreate(icicle);
|
super.onCreate(icicle);
|
||||||
|
|
||||||
// The volume we want to control is the Text-To-Speech volume
|
// The volume we want to control is the Text-To-Speech volume
|
||||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
ApiFeatures apiFeatures = ApiFeatures.getInstance();
|
||||||
|
int volumeStream =
|
||||||
|
new StatusAnnouncerFactory(apiFeatures).getVolumeStream();
|
||||||
|
setVolumeControlStream(volumeStream);
|
||||||
|
|
||||||
// Tell it where to read/write preferences
|
// Tell it where to read/write preferences
|
||||||
PreferenceManager preferenceManager = getPreferenceManager();
|
PreferenceManager preferenceManager = getPreferenceManager();
|
||||||
@@ -83,7 +72,7 @@ public class MyTracksSettings extends PreferenceActivity {
|
|||||||
preferenceManager.setSharedPreferencesMode(0);
|
preferenceManager.setSharedPreferencesMode(0);
|
||||||
|
|
||||||
// Set up automatic preferences backup
|
// Set up automatic preferences backup
|
||||||
backupListener = BackupPreferencesListener.create(this);
|
backupListener = BackupPreferencesListener.create(this, apiFeatures);
|
||||||
preferences = preferenceManager.getSharedPreferences();
|
preferences = preferenceManager.getSharedPreferences();
|
||||||
preferences.registerOnSharedPreferenceChangeListener(backupListener);
|
preferences.registerOnSharedPreferenceChangeListener(backupListener);
|
||||||
|
|
||||||
@@ -108,7 +97,7 @@ public class MyTracksSettings extends PreferenceActivity {
|
|||||||
updatePreferenceUnits(metricUnitsPreference.isChecked());
|
updatePreferenceUnits(metricUnitsPreference.isChecked());
|
||||||
|
|
||||||
// Disable TTS announcement preference if not available
|
// Disable TTS announcement preference if not available
|
||||||
if (!mTTSAvailable) {
|
if (!apiFeatures.hasTextToSpeech()) {
|
||||||
IntegerListPreference announcementFrequency =
|
IntegerListPreference announcementFrequency =
|
||||||
(IntegerListPreference) findPreference(
|
(IntegerListPreference) findPreference(
|
||||||
getString(R.string.announcement_frequency_key));
|
getString(R.string.announcement_frequency_key));
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ package com.google.android.apps.mytracks;
|
|||||||
*/
|
*/
|
||||||
public interface ProgressIndicator {
|
public interface ProgressIndicator {
|
||||||
|
|
||||||
public void setProgressMessage(String text);
|
public void setProgressMessage(int resId);
|
||||||
|
public void clearProgressMessage();
|
||||||
|
|
||||||
public void setProgressValue(int percent);
|
public void setProgressValue(int percent);
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtilsImpl;
|
|||||||
import com.google.android.apps.mytracks.content.Track;
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
||||||
|
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
|
||||||
|
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@@ -33,7 +35,6 @@ import android.location.LocationManager;
|
|||||||
import android.location.LocationProvider;
|
import android.location.LocationProvider;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.speech.tts.TextToSpeech;
|
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@@ -143,7 +144,9 @@ public class StatsActivity extends Activity
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// The volume we want to control is the Text-To-Speech volume
|
// The volume we want to control is the Text-To-Speech volume
|
||||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
int volumeStream =
|
||||||
|
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
|
||||||
|
setVolumeControlStream(volumeStream);
|
||||||
|
|
||||||
// We don't need a window title bar:
|
// We don't need a window title bar:
|
||||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ public interface MyTracksProviderUtils {
|
|||||||
* @return A cursor over the selected range of locations
|
* @return A cursor over the selected range of locations
|
||||||
*/
|
*/
|
||||||
Cursor getWaypointsCursor(long trackId, long minWaypointId,
|
Cursor getWaypointsCursor(long trackId, long minWaypointId,
|
||||||
long maxWaypoints);
|
int maxWaypoints);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds a track by given unique track id.
|
* Finds a track by given unique track id.
|
||||||
@@ -206,32 +206,6 @@ public interface MyTracksProviderUtils {
|
|||||||
*/
|
*/
|
||||||
long getTrackPoints(Track track, int maxPoints);
|
long getTrackPoints(Track track, int maxPoints);
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* @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.
|
* Creates a cursor over the tracks provider with a given selection.
|
||||||
* Caller gets to own the returned cursor. Don't forget to close it.
|
* Caller gets to own the returned cursor. Don't forget to close it.
|
||||||
|
|||||||
+1
-62
@@ -775,7 +775,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor getWaypointsCursor(long trackId, long minWaypointId,
|
public Cursor getWaypointsCursor(long trackId, long minWaypointId,
|
||||||
long maxWaypoints) {
|
int maxWaypoints) {
|
||||||
String selection;
|
String selection;
|
||||||
if (minWaypointId > 0) {
|
if (minWaypointId > 0) {
|
||||||
selection = String.format("%s=%d AND %s>=%d",
|
selection = String.format("%s=%d AND %s>=%d",
|
||||||
@@ -845,67 +845,6 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
return lastId;
|
return lastId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@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();
|
|
||||||
Cursor cursor = getLocationsCursor(track.getId(),
|
|
||||||
startingPoint,
|
|
||||||
buffer.getSize(), false);
|
|
||||||
if (cursor == null) {
|
|
||||||
Log.w(MyTracksProvider.TAG, "Cannot get a locations cursor!");
|
|
||||||
buffer.setInvalid();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (cursor.getCount() == 0) {
|
|
||||||
Log.w(MyTracksProvider.TAG, "No matching locations found.");
|
|
||||||
buffer.resetAt(startingPoint + buffer.getSize());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cursor.moveToFirst()) {
|
|
||||||
Log.w(MyTracksProvider.TAG, "Could not move to first.");
|
|
||||||
buffer.setInvalid();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int idColumnIdx =
|
|
||||||
cursor.getColumnIndexOrThrow(TrackPointsColumns._ID);
|
|
||||||
do {
|
|
||||||
if (reuseLocations) {
|
|
||||||
fillLocation(cursor, buffer.add(cursor.getLong(idColumnIdx)));
|
|
||||||
} else {
|
|
||||||
Location location = createLocation(cursor);
|
|
||||||
if (location == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
Log.w(MyTracksProvider.TAG, "Caught unexpected exception.", e);
|
|
||||||
} finally {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor getTracksCursor(String selection) {
|
public Cursor getTracksCursor(String selection) {
|
||||||
Cursor cursor = context.getContentResolver().query(
|
Cursor cursor = context.getContentResolver().query(
|
||||||
|
|||||||
@@ -1,149 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2009 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
package com.google.android.apps.mytracks.content;
|
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
|
||||||
|
|
||||||
import android.location.Location;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A buffer of Locations from a track.
|
|
||||||
* This class contains some additional information about the loaded points.
|
|
||||||
*
|
|
||||||
* @author Sandor Dornbush
|
|
||||||
*/
|
|
||||||
public class TrackBuffer {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An array of location objects that are part of a track.
|
|
||||||
*/
|
|
||||||
private final Location[] buffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The id of the last location read from the track.
|
|
||||||
*/
|
|
||||||
private long lastLocationRead = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of locations loaded into the buffer.
|
|
||||||
*/
|
|
||||||
private int locationsLoaded = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a TrackBuffer with size elements.
|
|
||||||
*
|
|
||||||
* @param size The size of the buffer
|
|
||||||
*/
|
|
||||||
public TrackBuffer(int size) {
|
|
||||||
buffer = new Location[size];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the track to a state with no locations.
|
|
||||||
*/
|
|
||||||
public void reset() {
|
|
||||||
resetAt(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the track at the given starting location id.
|
|
||||||
*/
|
|
||||||
public void resetAt(long lastLocation) {
|
|
||||||
lastLocationRead = lastLocation;
|
|
||||||
locationsLoaded = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInvalid() {
|
|
||||||
lastLocationRead = Integer.MAX_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The number of locations that can be stored in this buffer
|
|
||||||
*/
|
|
||||||
public int getSize() {
|
|
||||||
return buffer.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param index The index of the location to fetch
|
|
||||||
* @return The location for the given index
|
|
||||||
*/
|
|
||||||
public Location get(int index) {
|
|
||||||
return buffer[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a location to the end of the buffer.
|
|
||||||
* @param location The location to add.
|
|
||||||
* @param id The id of the location to be added.
|
|
||||||
*/
|
|
||||||
public void add(Location location, long id) {
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
public long getLastLocationRead() {
|
|
||||||
return lastLocationRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The number of locations loaded into the buffer.
|
|
||||||
*/
|
|
||||||
public int getLocationsLoaded() {
|
|
||||||
return locationsLoaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the start location, i.e. the one which is the first point of a
|
|
||||||
* segment with at least two points.
|
|
||||||
*
|
|
||||||
* @return the start location
|
|
||||||
*/
|
|
||||||
public Location findStartLocation() {
|
|
||||||
int numValidLocations = 0;
|
|
||||||
for (int i = 0; i < getLocationsLoaded(); i++) {
|
|
||||||
Location location = buffer[i];
|
|
||||||
if (MyTracksUtils.isValidLocation(location)) {
|
|
||||||
numValidLocations++;
|
|
||||||
if (numValidLocations == 2) {
|
|
||||||
return buffer[i - 1];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
numValidLocations = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,7 +25,6 @@ import com.google.android.apps.mytracks.MyTracksSettings;
|
|||||||
import com.google.android.apps.mytracks.ProgressIndicator;
|
import com.google.android.apps.mytracks.ProgressIndicator;
|
||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||||
import com.google.android.apps.mytracks.content.Track;
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
import com.google.android.apps.mytracks.content.TrackBuffer;
|
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
||||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||||
@@ -44,53 +43,392 @@ import android.content.Context;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.HandlerThread;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper class used to transmit tracks to Google MyMaps.
|
* A helper class used to transmit tracks to Google MyMaps.
|
||||||
|
* A new instance should be used for each upload.
|
||||||
*
|
*
|
||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
*/
|
*/
|
||||||
public class SendToMyMaps {
|
public class SendToMyMaps implements Runnable {
|
||||||
|
|
||||||
|
public static final String NEW_MAP_ID = "new";
|
||||||
private static final String END_ICON_URL =
|
private static final String END_ICON_URL =
|
||||||
"http://maps.google.com/mapfiles/ms/micons/red-dot.png";
|
"http://maps.google.com/mapfiles/ms/micons/red-dot.png";
|
||||||
private static final String START_ICON_URL =
|
private static final String START_ICON_URL =
|
||||||
"http://maps.google.com/mapfiles/ms/micons/green-dot.png";
|
"http://maps.google.com/mapfiles/ms/micons/green-dot.png";
|
||||||
|
private static final int MAX_POINTS_PER_UPLOAD = 2048;
|
||||||
|
|
||||||
private final Activity context;
|
private final Activity context;
|
||||||
private final AuthManager auth;
|
private final AuthManager auth;
|
||||||
private final long trackId;
|
private final long trackId;
|
||||||
private final ProgressIndicator progressIndicator;
|
private final ProgressIndicator progressIndicator;
|
||||||
|
private final OnSendCompletedListener onCompletion;
|
||||||
private final StringUtils stringUtils;
|
private final StringUtils stringUtils;
|
||||||
private final MyTracksProviderUtils providerUtils;
|
private final MyTracksProviderUtils providerUtils;
|
||||||
private String mapId;
|
private String mapId;
|
||||||
|
|
||||||
private String statusMessage = "";
|
// GData service
|
||||||
private boolean success = true;
|
|
||||||
private Runnable onCompletion;
|
|
||||||
|
|
||||||
private HandlerThread handlerThread;
|
|
||||||
private Handler handler;
|
|
||||||
|
|
||||||
private String originalDescription;
|
|
||||||
|
|
||||||
private MyMapsGDataWrapper wrapper;
|
private MyMapsGDataWrapper wrapper;
|
||||||
private MyMapsGDataConverter gdataConverter;
|
private MyMapsGDataConverter gdataConverter;
|
||||||
|
|
||||||
private Location last = null;
|
// Progress status
|
||||||
|
private int totalLocationsRead;
|
||||||
|
private int totalLocationsPrepared;
|
||||||
|
private int totalLocationsUploaded;
|
||||||
|
private int totalLocations;
|
||||||
|
private int totalSegmentsUploaded;
|
||||||
|
|
||||||
private Vector<Double> distances;
|
public interface OnSendCompletedListener {
|
||||||
private Vector<Double> elevations;
|
void onSendCompleted(String mapId, boolean success, int statusMessage);
|
||||||
private DoubleBuffer elevationBuffer;
|
}
|
||||||
|
|
||||||
|
public SendToMyMaps(Activity context, String mapId, AuthManager auth,
|
||||||
|
long trackId, ProgressIndicator progressIndicator,
|
||||||
|
OnSendCompletedListener onCompletion) {
|
||||||
|
this.context = context;
|
||||||
|
this.mapId = mapId;
|
||||||
|
this.auth = auth;
|
||||||
|
this.trackId = trackId;
|
||||||
|
this.progressIndicator = progressIndicator;
|
||||||
|
this.onCompletion = onCompletion;
|
||||||
|
this.stringUtils = new StringUtils(context);
|
||||||
|
this.providerUtils = MyTracksProviderUtils.Factory.get(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Log.d(MyTracksConstants.TAG, "Sending to MyMaps: trackId = " + trackId);
|
||||||
|
doUpload();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doUpload() {
|
||||||
|
int statusMessageId = R.string.error_sending_to_mymap;
|
||||||
|
boolean success = true;
|
||||||
|
try {
|
||||||
|
gdataConverter = new MyMapsGDataConverter();
|
||||||
|
|
||||||
|
progressIndicator.setProgressValue(1);
|
||||||
|
progressIndicator.setProgressMessage(
|
||||||
|
R.string.progress_message_reading_track);
|
||||||
|
|
||||||
|
// Get the track meta-data
|
||||||
|
Track track = providerUtils.getTrack(trackId);
|
||||||
|
String originalDescription = track.getDescription();
|
||||||
|
track.setDescription("<p>" + track.getDescription() + "</p><p>"
|
||||||
|
+ stringUtils.generateTrackDescription(track, null, null) + "</p>");
|
||||||
|
wrapper = new MyMapsGDataWrapper(context);
|
||||||
|
wrapper.setAuthManager(auth);
|
||||||
|
wrapper.setRetryOnAuthFailure(true);
|
||||||
|
|
||||||
|
// Create a new map if necessary:
|
||||||
|
boolean isNewMap = mapId.equals(NEW_MAP_ID);
|
||||||
|
if (isNewMap) {
|
||||||
|
success = createNewMap(track, originalDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload all of the segments of the track plus start/end markers
|
||||||
|
if (success) {
|
||||||
|
success = uploadAllTrackPoints(track, originalDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put waypoints.
|
||||||
|
if (success) {
|
||||||
|
success = uploadWaypoints(track);
|
||||||
|
if (!success) {
|
||||||
|
Log.w(MyTracksConstants.TAG,
|
||||||
|
"SendToMyMaps: upload waypoints failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
statusMessageId = isNewMap
|
||||||
|
? R.string.status_new_mymap_has_been_created
|
||||||
|
: R.string.status_tracks_have_been_uploaded;
|
||||||
|
}
|
||||||
|
Log.d(MyTracksConstants.TAG, "SendToMyMaps: Done: " + success);
|
||||||
|
progressIndicator.setProgressValue(100);
|
||||||
|
} catch (XmlPullParserException e) {
|
||||||
|
Log.e(MyTracksConstants.TAG, "Caught an unexpected exception.", e);
|
||||||
|
} finally {
|
||||||
|
if (wrapper != null) {
|
||||||
|
wrapper.cleanUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean finalSuccess = success;
|
||||||
|
final int finalStatusMessageId = statusMessageId;
|
||||||
|
context.runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (onCompletion != null) {
|
||||||
|
onCompletion.onSendCompleted(
|
||||||
|
mapId, finalSuccess, finalStatusMessageId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new map for the given track.
|
||||||
|
*
|
||||||
|
* @param track The track that will be uploaded to this map
|
||||||
|
* @return True on success.
|
||||||
|
*/
|
||||||
|
private boolean createNewMap(final Track track, final String description) {
|
||||||
|
progressIndicator.setProgressMessage(
|
||||||
|
R.string.progress_message_creating_map);
|
||||||
|
return wrapper.runQuery(new QueryFunction() {
|
||||||
|
@Override
|
||||||
|
public void query(MapsClient client) throws IOException, Exception {
|
||||||
|
Log.d(MyTracksConstants.TAG, "Creating a new map.");
|
||||||
|
String mapFeed = MapsClient.getMapsFeed();
|
||||||
|
Log.d(MyTracksConstants.TAG, "Map feed is " + mapFeed);
|
||||||
|
MyMapsMapMetadata metaData = new MyMapsMapMetadata();
|
||||||
|
metaData.setTitle(track.getName());
|
||||||
|
metaData.setDescription(description + " - "
|
||||||
|
+ track.getCategory() + " - "
|
||||||
|
+ context.getString(R.string.new_map_description));
|
||||||
|
SharedPreferences preferences = context.getSharedPreferences(
|
||||||
|
MyTracksSettings.SETTINGS_NAME, 0);
|
||||||
|
boolean mapPublic = true;
|
||||||
|
if (preferences != null) {
|
||||||
|
mapPublic = preferences.getBoolean(
|
||||||
|
context.getString(R.string.default_map_public_key), true);
|
||||||
|
}
|
||||||
|
metaData.setSearchable(mapPublic);
|
||||||
|
Entry entry = MyMapsGDataConverter.getMapEntryForMetadata(metaData);
|
||||||
|
Log.d(MyTracksConstants.TAG, "Title: " + entry.getTitle());
|
||||||
|
Entry map = client.createEntry(mapFeed, auth.getAuthToken(), entry);
|
||||||
|
mapId = MapsClient.getMapIdFromMapEntryId(map.getId());
|
||||||
|
Log.d(MyTracksConstants.TAG, "New map id is: " + mapId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean uploadAllTrackPoints(
|
||||||
|
final Track track, String originalDescription) {
|
||||||
|
SharedPreferences preferences = context.getSharedPreferences(
|
||||||
|
MyTracksSettings.SETTINGS_NAME, 0);
|
||||||
|
boolean metricUnits = true;
|
||||||
|
if (preferences != null) {
|
||||||
|
metricUnits =
|
||||||
|
preferences.getBoolean(context.getString(R.string.metric_units_key),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor locationsCursor =
|
||||||
|
providerUtils.getLocationsCursor(track.getId(), 0, -1, false);
|
||||||
|
try {
|
||||||
|
if (!locationsCursor.moveToFirst()) {
|
||||||
|
Log.w(MyTracksConstants.TAG, "Unable to get any points to upload");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalLocationsRead = 0;
|
||||||
|
totalLocationsPrepared = 0;
|
||||||
|
totalLocationsUploaded = 0;
|
||||||
|
totalLocations = locationsCursor.getCount();
|
||||||
|
totalSegmentsUploaded = 0;
|
||||||
|
|
||||||
|
// Limit the number of elevation readings. Ideally we would want around 250.
|
||||||
|
int elevationSamplingFrequency =
|
||||||
|
Math.max(1, (int) (totalLocations / 250.0));
|
||||||
|
Log.d(MyTracksConstants.TAG,
|
||||||
|
"Using elevation sampling factor: " + elevationSamplingFrequency
|
||||||
|
+ " on " + totalLocations);
|
||||||
|
double totalDistance = 0;
|
||||||
|
|
||||||
|
Vector<Double> distances = new Vector<Double>();
|
||||||
|
Vector<Double> elevations = new Vector<Double>();
|
||||||
|
DoubleBuffer elevationBuffer =
|
||||||
|
new DoubleBuffer(MyTracksConstants.ELEVATION_SMOOTHING_FACTOR);
|
||||||
|
|
||||||
|
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
|
||||||
|
progressIndicator.setProgressMessage(
|
||||||
|
R.string.progress_message_reading_track);
|
||||||
|
Location lastLocation = null;
|
||||||
|
do {
|
||||||
|
if (totalLocationsRead % 100 == 0) {
|
||||||
|
updateProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
Location loc = providerUtils.createLocation(locationsCursor);
|
||||||
|
locations.add(loc);
|
||||||
|
|
||||||
|
if (totalLocationsRead == 0) {
|
||||||
|
// Put a marker at the first point of the first valid segment:
|
||||||
|
uploadMarker(track, loc, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to the elevation profile.
|
||||||
|
if (loc != null && MyTracksUtils.isValidLocation(loc)) {
|
||||||
|
// All points go into the smoothing buffer...
|
||||||
|
elevationBuffer.setNext(metricUnits ? loc.getAltitude()
|
||||||
|
: loc.getAltitude() * UnitConversions.M_TO_FT);
|
||||||
|
if (lastLocation != null) {
|
||||||
|
double dist = lastLocation.distanceTo(loc);
|
||||||
|
totalDistance += dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...but only a few points are really used to keep the url short.
|
||||||
|
if (totalLocationsRead % elevationSamplingFrequency == 0) {
|
||||||
|
distances.add(totalDistance);
|
||||||
|
elevations.add(elevationBuffer.getAverage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the location was not valid, it's a segment split, so make sure the
|
||||||
|
// distance between the previous segment and the new one is not accounted
|
||||||
|
// for in the next iteration.
|
||||||
|
lastLocation = loc;
|
||||||
|
|
||||||
|
// Every now and then, upload the accumulated points
|
||||||
|
if (totalLocationsRead % MAX_POINTS_PER_UPLOAD
|
||||||
|
== MAX_POINTS_PER_UPLOAD - 1) {
|
||||||
|
if (!prepareAndUploadPoints(track, locations)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
totalLocationsRead++;
|
||||||
|
} while (locationsCursor.moveToNext());
|
||||||
|
|
||||||
|
// Do a final upload with what's left
|
||||||
|
if (!prepareAndUploadPoints(track, locations)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put an end marker at the last point of the last valid segment:
|
||||||
|
if (lastLocation != null) {
|
||||||
|
track.setDescription("<p>" + originalDescription + "</p><p>"
|
||||||
|
+ stringUtils.generateTrackDescription(
|
||||||
|
track, distances, elevations)
|
||||||
|
+ "</p>");
|
||||||
|
return uploadMarker(track, lastLocation, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
locationsCursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean uploadMarker(final Track track,
|
||||||
|
final Location location,
|
||||||
|
final boolean isStart) {
|
||||||
|
boolean okay = wrapper.runQuery(new QueryFunction() {
|
||||||
|
@Override
|
||||||
|
public void query(MapsClient client)
|
||||||
|
throws AuthenticationException, IOException, Exception {
|
||||||
|
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
||||||
|
insertMarker(context, client, featureFeed, track, location, isStart);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return okay;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a place mark. Second try if 1st try fails. Will throw exception on
|
||||||
|
* 2nd failure.
|
||||||
|
*/
|
||||||
|
private void insertMarker(Context context, MapsClient client,
|
||||||
|
String featureFeed, Track track, Location loc,
|
||||||
|
boolean isStart) throws IOException, Exception {
|
||||||
|
Entry entry = gdataConverter.getEntryForFeature(
|
||||||
|
buildMyMapsPlacemarkFeature(context, track, loc, isStart));
|
||||||
|
Log.d(MyTracksConstants.TAG, "SendToMyMaps: Creating placemark "
|
||||||
|
+ entry.getTitle());
|
||||||
|
try {
|
||||||
|
client.createEntry(featureFeed, auth.getAuthToken(), entry);
|
||||||
|
Log.d(MyTracksConstants.TAG, "SendToMyMaps: createEntry success!");
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(MyTracksConstants.TAG,
|
||||||
|
"SendToMyMaps: createEntry 1st try failed. Trying again.");
|
||||||
|
// Retry once (often IOException is thrown on a timeout):
|
||||||
|
client.createEntry(featureFeed, auth.getAuthToken(), entry);
|
||||||
|
Log.d(MyTracksConstants.TAG,
|
||||||
|
"SendToMyMaps: createEntry success on 2nd try!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a placemark MyMapsFeature from a track.
|
||||||
|
*
|
||||||
|
* @param track the track
|
||||||
|
* @param isStart true if it's the start of the track, or false for end
|
||||||
|
* @return a MyMapsFeature
|
||||||
|
*/
|
||||||
|
private static MyMapsFeature buildMyMapsPlacemarkFeature(Context context,
|
||||||
|
Track track, Location loc, boolean isStart) {
|
||||||
|
MyMapsFeature myMapsFeature = new MyMapsFeature();
|
||||||
|
myMapsFeature.generateAndroidId();
|
||||||
|
myMapsFeature.setType(MyMapsFeature.MARKER);
|
||||||
|
if (isStart) {
|
||||||
|
myMapsFeature.setIconUrl(START_ICON_URL);
|
||||||
|
} else {
|
||||||
|
myMapsFeature.setIconUrl(END_ICON_URL);
|
||||||
|
}
|
||||||
|
myMapsFeature.addPoint(MyTracksUtils.getGeoPoint(loc));
|
||||||
|
String name = track.getName() + " "
|
||||||
|
+ (isStart ? context.getString(R.string.start)
|
||||||
|
: context.getString(R.string.end));
|
||||||
|
myMapsFeature.setTitle(name);
|
||||||
|
myMapsFeature.setDescription(isStart ? "" : track.getDescription());
|
||||||
|
return myMapsFeature;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean prepareAndUploadPoints(Track track, List<Location> locations) {
|
||||||
|
progressIndicator.setProgressMessage(
|
||||||
|
R.string.progress_message_preparing_track);
|
||||||
|
updateProgress();
|
||||||
|
|
||||||
|
int numLocations = locations.size();
|
||||||
|
if (numLocations < 2) {
|
||||||
|
Log.d(MyTracksConstants.TAG, "Not preparing/uploading too few points");
|
||||||
|
totalLocationsUploaded += numLocations;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare/pre-process the points
|
||||||
|
ArrayList<Track> splitTracks = prepareLocations(track, locations);
|
||||||
|
|
||||||
|
// Start uploading them
|
||||||
|
progressIndicator.setProgressMessage(
|
||||||
|
R.string.progress_message_sending_mymaps);
|
||||||
|
for (Track splitTrack : splitTracks) {
|
||||||
|
if (totalSegmentsUploaded > 1) {
|
||||||
|
splitTrack.setName(splitTrack.getName() + " "
|
||||||
|
+ String.format(
|
||||||
|
context.getString(R.string.part), totalSegmentsUploaded));
|
||||||
|
}
|
||||||
|
totalSegmentsUploaded++;
|
||||||
|
Log.d(MyTracksConstants.TAG,
|
||||||
|
"SendToMyMaps: Prepared feature for upload w/ "
|
||||||
|
+ splitTrack.getLocations().size() + " points.");
|
||||||
|
|
||||||
|
// Transmit tracks via GData feed:
|
||||||
|
// -------------------------------
|
||||||
|
Log.d(MyTracksConstants.TAG,
|
||||||
|
"SendToMyMaps: Uploading to map " + mapId + " w/ auth " + auth);
|
||||||
|
if (!uploadTrackPoints(splitTrack)) {
|
||||||
|
Log.e(MyTracksConstants.TAG, "Uploading failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
locations.clear();
|
||||||
|
totalLocationsUploaded += numLocations;
|
||||||
|
updateProgress();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares a buffer of locations for transmission to google maps.
|
* Prepares a buffer of locations for transmission to google maps.
|
||||||
@@ -100,8 +438,8 @@ public class SendToMyMaps {
|
|||||||
* @return an array of tracks each with a sub section of the points in the
|
* @return an array of tracks each with a sub section of the points in the
|
||||||
* original buffer
|
* original buffer
|
||||||
*/
|
*/
|
||||||
private ArrayList<Track> prepareLocations(Track track,
|
private ArrayList<Track> prepareLocations(
|
||||||
TrackBuffer buffer) {
|
Track track, Iterable<Location> locations) {
|
||||||
ArrayList<Track> splitTracks = new ArrayList<Track>();
|
ArrayList<Track> splitTracks = new ArrayList<Track>();
|
||||||
|
|
||||||
// Create segments from each full track:
|
// Create segments from each full track:
|
||||||
@@ -115,8 +453,10 @@ public class SendToMyMaps {
|
|||||||
segmentStats.setStartTime(trackStats.getStartTime());
|
segmentStats.setStartTime(trackStats.getStartTime());
|
||||||
segmentStats.setStopTime(trackStats.getStopTime());
|
segmentStats.setStopTime(trackStats.getStopTime());
|
||||||
boolean startNewTrackSegment = false;
|
boolean startNewTrackSegment = false;
|
||||||
for (int i = 0; i < buffer.getLocationsLoaded(); ++i) {
|
for (Location loc : locations) {
|
||||||
Location loc = buffer.get(i);
|
if (totalLocationsPrepared % 100 == 0) {
|
||||||
|
updateProgress();
|
||||||
|
}
|
||||||
if (loc.getLatitude() > 90) {
|
if (loc.getLatitude() > 90) {
|
||||||
startNewTrackSegment = true;
|
startNewTrackSegment = true;
|
||||||
}
|
}
|
||||||
@@ -141,6 +481,7 @@ public class SendToMyMaps {
|
|||||||
segmentStats.setStartTime(loc.getTime());
|
segmentStats.setStartTime(loc.getTime());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
totalLocationsPrepared++;
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareTrackSegment(segment, splitTracks);
|
prepareTrackSegment(segment, splitTracks);
|
||||||
@@ -182,237 +523,30 @@ public class SendToMyMaps {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts a place mark. Second try if 1st try fails. Will throw exception on
|
|
||||||
* 2nd failure.
|
|
||||||
*/
|
|
||||||
private void insertMarker(Context context, MapsClient client,
|
|
||||||
String featureFeed, Track track, Location loc,
|
|
||||||
boolean isStart) throws IOException, Exception {
|
|
||||||
Entry entry = gdataConverter.getEntryForFeature(
|
|
||||||
buildMyMapsPlacemarkFeature(context, track, loc, isStart));
|
|
||||||
Log.d(MyTracksConstants.TAG, "SendToMyMaps: Creating placemark "
|
|
||||||
+ entry.getTitle());
|
|
||||||
try {
|
|
||||||
client.createEntry(featureFeed, auth.getAuthToken(), entry);
|
|
||||||
Log.d(MyTracksConstants.TAG, "SendToMyMaps: createEntry success!");
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(MyTracksConstants.TAG,
|
|
||||||
"SendToMyMaps: createEntry 1st try failed. Trying again.");
|
|
||||||
// Retry once (often IOException is thrown on a timeout):
|
|
||||||
client.createEntry(featureFeed, auth.getAuthToken(), entry);
|
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"SendToMyMaps: createEntry success on 2nd try!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean uploadMarker(final Track track,
|
|
||||||
final Location location,
|
|
||||||
final boolean isStart) {
|
|
||||||
boolean okay = wrapper.runQuery(new QueryFunction() {
|
|
||||||
@Override
|
|
||||||
public void query(MapsClient client)
|
|
||||||
throws AuthenticationException, IOException, Exception {
|
|
||||||
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
|
||||||
insertMarker(context, client, featureFeed, track, location, isStart);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return okay;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the current upload progress.
|
|
||||||
*
|
|
||||||
* @param track The track being uploaded
|
|
||||||
* @param totalLocationsRead The total number of locations already read and
|
|
||||||
* uploaded
|
|
||||||
* @param segmentSize The percent of total that each segment corresponds to
|
|
||||||
* @param progressInSegment The approximate progress in this segment
|
|
||||||
*/
|
|
||||||
private void setProgress(final Track track, int totalLocationsRead,
|
|
||||||
double segmentSize, double progressInSegment) {
|
|
||||||
long totalLocations = track.getStopId() - track.getStartId();
|
|
||||||
|
|
||||||
// The percent of the total that represents the completed part of this
|
|
||||||
// segment.
|
|
||||||
double percentSegmentInTotal = (segmentSize * progressInSegment);
|
|
||||||
|
|
||||||
int progress = (int) ((100.0 * totalLocationsRead / totalLocations)
|
|
||||||
+ percentSegmentInTotal);
|
|
||||||
progress = Math.min(99, progress);
|
|
||||||
progressIndicator.setProgressValue(progress);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean uploadAllTrackPoints(final Track track) {
|
|
||||||
int totalLocationsRead = 0;
|
|
||||||
// TODO: This might not actually be accurate.
|
|
||||||
long totalLocations = track.getStopId() - track.getStartId();
|
|
||||||
|
|
||||||
// Limit the number of elevation readings. Ideally we would want around 250.
|
|
||||||
int elevationSamplingFrequency =
|
|
||||||
Math.max(1, (int) (totalLocations / 250.0));
|
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"Using elevation sampling factor: " + elevationSamplingFrequency
|
|
||||||
+ " on " + totalLocations);
|
|
||||||
double totalDistance = 0;
|
|
||||||
Location lastLocation = null;
|
|
||||||
|
|
||||||
TrackBuffer buffer = new TrackBuffer((int) Math.min(2048, totalLocations));
|
|
||||||
int part = 1;
|
|
||||||
|
|
||||||
SharedPreferences preferences = context.getSharedPreferences(
|
|
||||||
MyTracksSettings.SETTINGS_NAME, 0);
|
|
||||||
boolean metricUnits = true;
|
|
||||||
if (preferences != null) {
|
|
||||||
metricUnits =
|
|
||||||
preferences.getBoolean(context.getString(R.string.metric_units_key),
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For the progress monitor:
|
|
||||||
// For each track segment 33% = read, 33% = prep & 33% = upload
|
|
||||||
// Note: These numbers are very rough.
|
|
||||||
|
|
||||||
// The size of each segment in the 100% progress
|
|
||||||
double segmentSize = buffer.getSize() * 100.0 / totalLocations;
|
|
||||||
while (success && buffer.getLastLocationRead() < track.getStopId()) {
|
|
||||||
setProgress(track, totalLocationsRead, segmentSize, 0.0);
|
|
||||||
progressIndicator.setProgressMessage(
|
|
||||||
context.getString(R.string.progress_message_reading_track));
|
|
||||||
|
|
||||||
providerUtils.getTrackPoints(track, buffer);
|
|
||||||
if (buffer.getLocationsLoaded() <= 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (totalLocationsRead == 0) {
|
|
||||||
// Put a marker at the first point of the first valid segment:
|
|
||||||
uploadMarker(track, buffer.get(0), true);
|
|
||||||
}
|
|
||||||
last = buffer.get(buffer.getLocationsLoaded() - 1);
|
|
||||||
|
|
||||||
// Extract the elevation profile.
|
|
||||||
for (int i = 0; i < buffer.getLocationsLoaded(); i++) {
|
|
||||||
// Add all points to the smoothing buffer.
|
|
||||||
Location l = buffer.get(i);
|
|
||||||
if (l != null && MyTracksUtils.isValidLocation(l)) {
|
|
||||||
elevationBuffer.setNext(metricUnits ? l.getAltitude()
|
|
||||||
: l.getAltitude() * UnitConversions.M_TO_FT);
|
|
||||||
if (lastLocation != null) {
|
|
||||||
double dist = lastLocation.distanceTo(l);
|
|
||||||
totalDistance += dist;
|
|
||||||
}
|
|
||||||
lastLocation = l;
|
|
||||||
|
|
||||||
// Only add some data points to keep the url short.
|
|
||||||
if (i % elevationSamplingFrequency == 0) {
|
|
||||||
distances.add(totalDistance);
|
|
||||||
elevations.add(elevationBuffer.getAverage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setProgress(track, totalLocationsRead, segmentSize, 0.3333);
|
|
||||||
progressIndicator.setProgressMessage(
|
|
||||||
context.getString(R.string.progress_message_preparing_track));
|
|
||||||
|
|
||||||
ArrayList<Track> splitTracks = prepareLocations(track, buffer);
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
for (Track t : splitTracks) {
|
|
||||||
if (part > 1) {
|
|
||||||
t.setName(t.getName() + " "
|
|
||||||
+ String.format(context.getString(R.string.part), part));
|
|
||||||
}
|
|
||||||
part++;
|
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"SendToMyMaps: Prepared feature for upload w/ "
|
|
||||||
+ t.getLocations().size() + " points.");
|
|
||||||
|
|
||||||
//
|
|
||||||
// Transmit tracks via GData feed:
|
|
||||||
// -------------------------------
|
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"SendToMyMaps: Uploading to map " + mapId + " w/ auth " + auth);
|
|
||||||
if (!uploadTrackPoints(track, t,
|
|
||||||
totalLocationsRead, segmentSize)) {
|
|
||||||
success = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
totalLocationsRead += buffer.getLocationsLoaded();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uploads a given list of tracks to Google MyMaps using the maps GData feed.
|
* Uploads a given list of tracks to Google MyMaps using the maps GData feed.
|
||||||
*
|
|
||||||
* @param track the track meta-data is used for the map name
|
|
||||||
*/
|
*/
|
||||||
private boolean uploadTrackPoints(final Track track, final Track currentTrack,
|
private boolean uploadTrackPoints(final Track track) {
|
||||||
int totalLocationsRead, double segmentSize) {
|
|
||||||
setProgress(track, totalLocationsRead, segmentSize, 0.6666);
|
|
||||||
progressIndicator.setProgressMessage(
|
|
||||||
context.getString(R.string.progress_message_sending_mymaps));
|
|
||||||
return wrapper.runQuery(new QueryFunction() {
|
return wrapper.runQuery(new QueryFunction() {
|
||||||
@Override
|
@Override
|
||||||
public void query(MapsClient client)
|
public void query(MapsClient client)
|
||||||
throws AuthenticationException, IOException, Exception {
|
throws AuthenticationException, IOException, Exception {
|
||||||
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
||||||
Log.d(MyTracksConstants.TAG, "Feature feed url: " + featureFeed);
|
Log.d(MyTracksConstants.TAG, "Feature feed url: " + featureFeed);
|
||||||
uploadTrackPoints(track, currentTrack, client, featureFeed);
|
uploadTrackPoints(track, client, featureFeed);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private boolean uploadTrackPoints(Track splitTrack,
|
||||||
* Creates a new map for the given track.
|
|
||||||
*
|
|
||||||
* @param track The track that will be uploaded to this map
|
|
||||||
* @return True on success.
|
|
||||||
*/
|
|
||||||
private boolean createNewMap(final Track track) {
|
|
||||||
progressIndicator.setProgressMessage(context
|
|
||||||
.getString(R.string.progress_message_creating_map));
|
|
||||||
return wrapper.runQuery(new QueryFunction() {
|
|
||||||
@Override
|
|
||||||
public void query(MapsClient client) throws IOException, Exception {
|
|
||||||
Log.d(MyTracksConstants.TAG, "Creating a new map.");
|
|
||||||
String mapFeed = MapsClient.getMapsFeed();
|
|
||||||
Log.d(MyTracksConstants.TAG, "Map feed is " + mapFeed);
|
|
||||||
MyMapsMapMetadata metaData = new MyMapsMapMetadata();
|
|
||||||
metaData.setTitle(track.getName());
|
|
||||||
metaData.setDescription(originalDescription + " - "
|
|
||||||
+ track.getCategory() + " - "
|
|
||||||
+ context.getString(R.string.new_map_description));
|
|
||||||
SharedPreferences preferences = context.getSharedPreferences(
|
|
||||||
MyTracksSettings.SETTINGS_NAME, 0);
|
|
||||||
boolean mapPublic = true;
|
|
||||||
if (preferences != null) {
|
|
||||||
mapPublic = preferences.getBoolean(
|
|
||||||
context.getString(R.string.default_map_public_key), true);
|
|
||||||
}
|
|
||||||
metaData.setSearchable(mapPublic);
|
|
||||||
Entry entry = MyMapsGDataConverter.getMapEntryForMetadata(metaData);
|
|
||||||
Log.d(MyTracksConstants.TAG, "Title: " + entry.getTitle());
|
|
||||||
Entry map = client.createEntry(mapFeed, auth.getAuthToken(), entry);
|
|
||||||
mapId = MapsClient.getMapIdFromMapEntryId(map.getId());
|
|
||||||
Log.d(MyTracksConstants.TAG, "New map id is: " + mapId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean uploadTrackPoints(final Track track,
|
|
||||||
Track splitTrack,
|
|
||||||
MapsClient client,
|
MapsClient client,
|
||||||
String featureFeed)
|
String featureFeed)
|
||||||
throws IOException, Exception {
|
throws IOException, Exception {
|
||||||
Entry entry = null;
|
Entry entry = null;
|
||||||
if (splitTrack.getId() != track.getId()
|
int numLocations = splitTrack.getLocations().size();
|
||||||
|| splitTrack.getLocations().size() < 2) {
|
if (numLocations < 2) {
|
||||||
// Need at least two points for a polyline:
|
// Need at least two points for a polyline:
|
||||||
|
Log.w(MyTracksConstants.TAG, "Not uploading too few points");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,6 +569,29 @@ public class SendToMyMaps {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a MyMapsFeature from a track.
|
||||||
|
*
|
||||||
|
* @param track the track
|
||||||
|
* @return a MyMapsFeature
|
||||||
|
*/
|
||||||
|
private static MyMapsFeature buildMyMapsLineFeature(Track track) {
|
||||||
|
MyMapsFeature myMapsFeature = new MyMapsFeature();
|
||||||
|
myMapsFeature.generateAndroidId();
|
||||||
|
myMapsFeature.setType(MyMapsFeature.LINE);
|
||||||
|
if (track.getName().length() < 1) {
|
||||||
|
// Features must have a name (otherwise GData upload may fail):
|
||||||
|
myMapsFeature.setTitle("-");
|
||||||
|
} else {
|
||||||
|
myMapsFeature.setTitle(track.getName());
|
||||||
|
}
|
||||||
|
myMapsFeature.setColor(0x80FF0000);
|
||||||
|
for (Location loc : track.getLocations()) {
|
||||||
|
myMapsFeature.addPoint(MyTracksUtils.getGeoPoint(loc));
|
||||||
|
}
|
||||||
|
return myMapsFeature;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uploads all of the waypoints associated with this track to map.
|
* Uploads all of the waypoints associated with this track to map.
|
||||||
*
|
*
|
||||||
@@ -494,185 +651,6 @@ public class SendToMyMaps {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public SendToMyMaps(Activity context, String mapId, AuthManager auth,
|
|
||||||
long trackId, ProgressIndicator progressIndicator) {
|
|
||||||
this.context = context;
|
|
||||||
this.mapId = mapId;
|
|
||||||
this.auth = auth;
|
|
||||||
this.trackId = trackId;
|
|
||||||
this.progressIndicator = progressIndicator;
|
|
||||||
this.stringUtils = new StringUtils(context);
|
|
||||||
this.providerUtils = MyTracksProviderUtils.Factory.get(context);
|
|
||||||
|
|
||||||
Log.d(MyTracksConstants.TAG, "Sending to MyMap: trackId = " + trackId);
|
|
||||||
handlerThread = new HandlerThread("SendToMyMaps");
|
|
||||||
handlerThread.start();
|
|
||||||
handler = new Handler(handlerThread.getLooper());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run() {
|
|
||||||
handler.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
doUpload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOnCompletion(Runnable onCompletion) {
|
|
||||||
this.onCompletion = onCompletion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMapId() {
|
|
||||||
return mapId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean wasSuccess() {
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStatusMessage() {
|
|
||||||
return statusMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doUpload() {
|
|
||||||
statusMessage = context.getString(R.string.error_sending_to_mymap);
|
|
||||||
success = true;
|
|
||||||
try {
|
|
||||||
gdataConverter = new MyMapsGDataConverter();
|
|
||||||
|
|
||||||
progressIndicator.setProgressValue(1);
|
|
||||||
progressIndicator.setProgressMessage(
|
|
||||||
context.getString(R.string.progress_message_reading_track));
|
|
||||||
|
|
||||||
// Get the track meta-data
|
|
||||||
Track track = providerUtils.getTrack(trackId);
|
|
||||||
originalDescription = track.getDescription();
|
|
||||||
track.setDescription("<p>" + track.getDescription() + "</p><p>"
|
|
||||||
+ stringUtils.generateTrackDescription(track, null, null) + "</p>");
|
|
||||||
wrapper = new MyMapsGDataWrapper(context);
|
|
||||||
wrapper.setAuthManager(auth);
|
|
||||||
wrapper.setRetryOnAuthFailure(true);
|
|
||||||
|
|
||||||
distances = new Vector<Double>();
|
|
||||||
elevations = new Vector<Double>();
|
|
||||||
elevationBuffer =
|
|
||||||
new DoubleBuffer(MyTracksConstants.ELEVATION_SMOOTHING_FACTOR);
|
|
||||||
|
|
||||||
// Create a new map if necessary:
|
|
||||||
if (mapId.equals("new")) {
|
|
||||||
if (!createNewMap(track)) {
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload all of the segments of the track.
|
|
||||||
if (success) {
|
|
||||||
if (!uploadAllTrackPoints(track)) {
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start end end markers.
|
|
||||||
if (success) {
|
|
||||||
// Put an end marker at the last point of the last valid segment:
|
|
||||||
if (last != null) {
|
|
||||||
track.setDescription("<p>" + originalDescription + "</p><p>"
|
|
||||||
+ stringUtils.generateTrackDescription(
|
|
||||||
track, distances, elevations)
|
|
||||||
+ "</p>");
|
|
||||||
if (!uploadMarker(track, last, false)) {
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put waypoints.
|
|
||||||
if (success) {
|
|
||||||
if (!uploadWaypoints(track)) {
|
|
||||||
Log.w(MyTracksConstants.TAG,
|
|
||||||
"SendToMyMaps: upload waypoints failed.");
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
if (mapId.equals("new")) {
|
|
||||||
statusMessage =
|
|
||||||
context.getString(R.string.status_new_mymap_has_been_created);
|
|
||||||
} else {
|
|
||||||
statusMessage =
|
|
||||||
context.getString(R.string.status_tracks_have_been_uploaded);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.d(MyTracksConstants.TAG, "SendToMyMaps: Done: " + success);
|
|
||||||
progressIndicator.setProgressValue(100);
|
|
||||||
} catch (XmlPullParserException e) {
|
|
||||||
Log.e(MyTracksConstants.TAG, "Caught an unexpected exception.", e);
|
|
||||||
} finally {
|
|
||||||
if (wrapper != null) {
|
|
||||||
wrapper.cleanUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
context.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
if (onCompletion != null) {
|
|
||||||
onCompletion.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a MyMapsFeature from a track.
|
|
||||||
*
|
|
||||||
* @param track the track
|
|
||||||
* @return a MyMapsFeature
|
|
||||||
*/
|
|
||||||
private static MyMapsFeature buildMyMapsLineFeature(Track track) {
|
|
||||||
MyMapsFeature myMapsFeature = new MyMapsFeature();
|
|
||||||
myMapsFeature.generateAndroidId();
|
|
||||||
myMapsFeature.setType(MyMapsFeature.LINE);
|
|
||||||
if (track.getName().length() < 1) {
|
|
||||||
// Features must have a name (otherwise GData upload may fail):
|
|
||||||
myMapsFeature.setTitle("-");
|
|
||||||
} else {
|
|
||||||
myMapsFeature.setTitle(track.getName());
|
|
||||||
}
|
|
||||||
myMapsFeature.setColor(0x80FF0000);
|
|
||||||
for (Location loc : track.getLocations()) {
|
|
||||||
myMapsFeature.addPoint(MyTracksUtils.getGeoPoint(loc));
|
|
||||||
}
|
|
||||||
return myMapsFeature;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a placemark MyMapsFeature from a track.
|
|
||||||
*
|
|
||||||
* @param track the track
|
|
||||||
* @param isStart true if it's the start of the track, or false for end
|
|
||||||
* @return a MyMapsFeature
|
|
||||||
*/
|
|
||||||
private static MyMapsFeature buildMyMapsPlacemarkFeature(Context context,
|
|
||||||
Track track, Location loc, boolean isStart) {
|
|
||||||
MyMapsFeature myMapsFeature = new MyMapsFeature();
|
|
||||||
myMapsFeature.generateAndroidId();
|
|
||||||
myMapsFeature.setType(MyMapsFeature.MARKER);
|
|
||||||
if (isStart) {
|
|
||||||
myMapsFeature.setIconUrl(START_ICON_URL);
|
|
||||||
} else {
|
|
||||||
myMapsFeature.setIconUrl(END_ICON_URL);
|
|
||||||
}
|
|
||||||
myMapsFeature.addPoint(MyTracksUtils.getGeoPoint(loc));
|
|
||||||
String name = track.getName() + " "
|
|
||||||
+ (isStart ? context.getString(R.string.start)
|
|
||||||
: context.getString(R.string.end));
|
|
||||||
myMapsFeature.setTitle(name);
|
|
||||||
myMapsFeature.setDescription(isStart ? "" : track.getDescription());
|
|
||||||
return myMapsFeature;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a MyMapsFeature from a track.
|
* Builds a MyMapsFeature from a track.
|
||||||
*
|
*
|
||||||
@@ -695,4 +673,17 @@ public class SendToMyMaps {
|
|||||||
myMapsFeature.setDescription(wpt.getDescription().replaceAll("\n", "<br>"));
|
myMapsFeature.setDescription(wpt.getDescription().replaceAll("\n", "<br>"));
|
||||||
return myMapsFeature;
|
return myMapsFeature;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current upload progress.
|
||||||
|
*/
|
||||||
|
private void updateProgress() {
|
||||||
|
// The percent of the total that represents the completed part of this
|
||||||
|
// segment.
|
||||||
|
int totalPercentage =
|
||||||
|
(totalLocationsRead + totalLocationsPrepared + totalLocationsUploaded)
|
||||||
|
/ (totalLocations * 3);
|
||||||
|
totalPercentage = Math.min(99, totalPercentage);
|
||||||
|
progressIndicator.setProgressValue(totalPercentage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ package com.google.android.apps.mytracks.io;
|
|||||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||||
import com.google.android.apps.mytracks.content.Track;
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
import com.google.android.apps.mytracks.content.TrackBuffer;
|
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.util.FileUtils;
|
import com.google.android.apps.mytracks.util.FileUtils;
|
||||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||||
@@ -254,55 +253,8 @@ public class TrackWriter {
|
|||||||
void writeDocument() {
|
void writeDocument() {
|
||||||
Log.d(MyTracksConstants.TAG, "Started writing track.");
|
Log.d(MyTracksConstants.TAG, "Started writing track.");
|
||||||
writer.writeHeader();
|
writer.writeHeader();
|
||||||
TrackBuffer buffer = new TrackBuffer(1024);
|
// TODO: Fix ordering (in GPX waypoints should come first)
|
||||||
Location last = null;
|
writeLocations();
|
||||||
boolean wroteFirst = false;
|
|
||||||
boolean segmentOpen = false;
|
|
||||||
int nValidLocations = 0;
|
|
||||||
|
|
||||||
// Fetch small pieces of the track.
|
|
||||||
while (buffer.getLastLocationRead() < track.getStopId()) {
|
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"Reading track points starting at: " + buffer.getLastLocationRead());
|
|
||||||
providerUtils.fillTrackPoints(track, buffer);
|
|
||||||
if (!wroteFirst) {
|
|
||||||
Location first = buffer.findStartLocation();
|
|
||||||
writer.writeBeginTrack(first);
|
|
||||||
wroteFirst = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"Reading " + buffer.getLocationsLoaded()
|
|
||||||
+ " Ending at: " + buffer.getLastLocationRead());
|
|
||||||
for (int i = 0; i < buffer.getLocationsLoaded(); i++) {
|
|
||||||
Location location = buffer.get(i);
|
|
||||||
if (MyTracksUtils.isValidLocation(location)) {
|
|
||||||
nValidLocations++;
|
|
||||||
if (!segmentOpen) {
|
|
||||||
writer.writeOpenSegment();
|
|
||||||
segmentOpen = true;
|
|
||||||
}
|
|
||||||
writer.writeLocation(location);
|
|
||||||
if (nValidLocations >= 2) {
|
|
||||||
last = location;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
nValidLocations = 0;
|
|
||||||
last = null;
|
|
||||||
if (segmentOpen) {
|
|
||||||
writer.writeCloseSegment();
|
|
||||||
segmentOpen = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (segmentOpen) {
|
|
||||||
writer.writeCloseSegment();
|
|
||||||
segmentOpen = false;
|
|
||||||
}
|
|
||||||
if (wroteFirst) {
|
|
||||||
writer.writeEndTrack(last);
|
|
||||||
}
|
|
||||||
writeWaypoints(track.getId());
|
writeWaypoints(track.getId());
|
||||||
writer.writeFooter();
|
writer.writeFooter();
|
||||||
writer.close();
|
writer.close();
|
||||||
@@ -310,4 +262,66 @@ public class TrackWriter {
|
|||||||
Log.d(MyTracksConstants.TAG, "Done writing track.");
|
Log.d(MyTracksConstants.TAG, "Done writing track.");
|
||||||
errorMessage = R.string.io_write_finished;
|
errorMessage = R.string.io_write_finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void writeLocations() {
|
||||||
|
boolean wroteFirst = false;
|
||||||
|
boolean segmentOpen = false;
|
||||||
|
Location lastLoc = null, loc = null;
|
||||||
|
boolean isLastValid = false;
|
||||||
|
Cursor locationsCursor =
|
||||||
|
providerUtils.getLocationsCursor(track.getId(), 0, -1, false);
|
||||||
|
|
||||||
|
if (locationsCursor == null || !locationsCursor.moveToFirst()) {
|
||||||
|
Log.w(MyTracksConstants.TAG, "Unable to get any points to write");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (loc == null) loc = new Location("");
|
||||||
|
providerUtils.fillLocation(locationsCursor, loc);
|
||||||
|
|
||||||
|
boolean isValid = MyTracksUtils.isValidLocation(loc);
|
||||||
|
boolean validSegment = isValid && isLastValid;
|
||||||
|
if (!wroteFirst && validSegment) {
|
||||||
|
// Found the first two consecutive points which are valid
|
||||||
|
writer.writeBeginTrack(lastLoc);
|
||||||
|
wroteFirst = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validSegment) {
|
||||||
|
if (!segmentOpen) {
|
||||||
|
// Start a segment for this point
|
||||||
|
writer.writeOpenSegment();
|
||||||
|
segmentOpen = true;
|
||||||
|
|
||||||
|
// Write the previous point, which we had previously skipped
|
||||||
|
writer.writeLocation(lastLoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the current point
|
||||||
|
writer.writeLocation(loc);
|
||||||
|
} else {
|
||||||
|
if (segmentOpen) {
|
||||||
|
writer.writeCloseSegment();
|
||||||
|
segmentOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap loc and lastLoc (so lastLoc is reused)
|
||||||
|
Location tmp = lastLoc;
|
||||||
|
lastLoc = loc;
|
||||||
|
loc = tmp;
|
||||||
|
if (loc != null) loc.reset();
|
||||||
|
|
||||||
|
isLastValid = isValid;
|
||||||
|
} while (locationsCursor.moveToNext());
|
||||||
|
|
||||||
|
if (segmentOpen) {
|
||||||
|
writer.writeCloseSegment();
|
||||||
|
segmentOpen = false;
|
||||||
|
}
|
||||||
|
if (wroteFirst) {
|
||||||
|
writer.writeEndTrack(lastLoc);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ public class TrackWriterFactory {
|
|||||||
* @param format the output format to write in
|
* @param format the output format to write in
|
||||||
* @return the new track writer
|
* @return the new track writer
|
||||||
*/
|
*/
|
||||||
public static TrackWriter newWriter(Context context,
|
private static TrackWriter newWriter(Context context,
|
||||||
MyTracksProviderUtils providerUtils,
|
MyTracksProviderUtils providerUtils,
|
||||||
Track track, TrackFileFormat format) {
|
Track track, TrackFileFormat format) {
|
||||||
TrackFormatWriter writer = format.newFormatWriter(context);
|
TrackFormatWriter writer = format.newFormatWriter(context);
|
||||||
|
|||||||
+4
-3
@@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks.io.backup;
|
package com.google.android.apps.mytracks.io.backup;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||||
|
|
||||||
import android.app.backup.BackupManager;
|
import android.app.backup.BackupManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -66,8 +66,9 @@ public abstract class BackupPreferencesListener
|
|||||||
/**
|
/**
|
||||||
* Creates and returns a proper instance of the listener for this device.
|
* Creates and returns a proper instance of the listener for this device.
|
||||||
*/
|
*/
|
||||||
public static BackupPreferencesListener create(Context context) {
|
public static BackupPreferencesListener create(
|
||||||
if (MyTracksConstants.ANDROID_API_LEVEL >= 8) {
|
Context context, ApiFeatures apiFeatures) {
|
||||||
|
if (apiFeatures.hasBackup()) {
|
||||||
return new BackupPreferencesListenerImpl(context);
|
return new BackupPreferencesListenerImpl(context);
|
||||||
} else {
|
} else {
|
||||||
return new DummyBackupPreferencesListener();
|
return new DummyBackupPreferencesListener();
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2009 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.android.apps.mytracks.services;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class will periodically announce the user's trip statistics. This class
|
|
||||||
* is a wrapper for cupcake devices.
|
|
||||||
*
|
|
||||||
* @author Sandor Dornbush
|
|
||||||
*/
|
|
||||||
public class SafeStatusAnnouncerTask implements PeriodicTask {
|
|
||||||
|
|
||||||
private StatusAnnouncerTask announcer;
|
|
||||||
|
|
||||||
/* class initialization fails when this throws an exception */
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
Class.forName(
|
|
||||||
"com.google.android.apps.mytracks.services.StatusAnnouncerTask");
|
|
||||||
} catch (ClassNotFoundException ex) {
|
|
||||||
throw new RuntimeException(ex);
|
|
||||||
} catch (LinkageError er) {
|
|
||||||
throw new RuntimeException(er);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* calling here forces class initialization */
|
|
||||||
public static void checkAvailable() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public SafeStatusAnnouncerTask(Context context) {
|
|
||||||
announcer = new StatusAnnouncerTask(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run(TrackRecordingService service) {
|
|
||||||
announcer.run(service);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void shutdown() {
|
|
||||||
announcer.shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
package com.google.android.apps.mytracks.services;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||||
|
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for producing a proper {@link SignalStrengthTask} according to the
|
||||||
|
* current API level.
|
||||||
|
*
|
||||||
|
* @author Rodrigo Damazio
|
||||||
|
*/
|
||||||
|
public class SignalStrengthTaskFactory {
|
||||||
|
private final boolean hasModernSignalStrength;
|
||||||
|
|
||||||
|
SignalStrengthTaskFactory(ApiFeatures apiFeatures) {
|
||||||
|
this.hasModernSignalStrength = apiFeatures.hasModernSignalStrength();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PeriodicTask create(Context context) {
|
||||||
|
if (hasModernSignalStrength) {
|
||||||
|
Log.d(MyTracksConstants.TAG,
|
||||||
|
"TrackRecordingService using modern signal strength api.");
|
||||||
|
return new SignalStrengthTaskModern(context);
|
||||||
|
} else {
|
||||||
|
Log.w(MyTracksConstants.TAG,
|
||||||
|
"TrackRecordingService using legacy signal strength api.");
|
||||||
|
return new SignalStrengthTask(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
package com.google.android.apps.mytracks.services;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.media.AudioManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory which wraps construction and setup of text-to-speech announcements in
|
||||||
|
* an API-level-safe way.
|
||||||
|
*
|
||||||
|
* @author Rodrigo Damazio
|
||||||
|
*/
|
||||||
|
public class StatusAnnouncerFactory {
|
||||||
|
|
||||||
|
private final boolean hasTts;
|
||||||
|
|
||||||
|
public StatusAnnouncerFactory(ApiFeatures apiFeatures) {
|
||||||
|
this.hasTts = apiFeatures.hasTextToSpeech();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a periodic task which does voice announcements.
|
||||||
|
*
|
||||||
|
* @return the task, or null if announcements are not supported
|
||||||
|
*/
|
||||||
|
public PeriodicTask create(Context context) {
|
||||||
|
if (hasTts) {
|
||||||
|
return new StatusAnnouncerTask(context);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the appropriate volume stream for controlling announcement
|
||||||
|
* volume.
|
||||||
|
*/
|
||||||
|
public int getVolumeStream() {
|
||||||
|
if (hasTts) {
|
||||||
|
return StatusAnnouncerTask.getVolumeStream();
|
||||||
|
} else {
|
||||||
|
return AudioManager.USE_DEFAULT_STREAM_TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ import com.google.android.maps.mytracks.R;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.speech.tts.TextToSpeech;
|
import android.speech.tts.TextToSpeech;
|
||||||
|
import android.speech.tts.TextToSpeech.OnInitListener;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -35,8 +36,7 @@ import java.util.Locale;
|
|||||||
*
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
public class StatusAnnouncerTask
|
public class StatusAnnouncerTask implements PeriodicTask {
|
||||||
implements TextToSpeech.OnInitListener, PeriodicTask {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The rate at which announcements are spoken.
|
* The rate at which announcements are spoken.
|
||||||
@@ -56,12 +56,12 @@ public class StatusAnnouncerTask
|
|||||||
/**
|
/**
|
||||||
* The interface to the text to speech engine.
|
* The interface to the text to speech engine.
|
||||||
*/
|
*/
|
||||||
private TextToSpeech tts = null;
|
private final TextToSpeech tts;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The response recieved from the TTS engine ater initialization.
|
* The response recieved from the TTS engine ater initialization.
|
||||||
*/
|
*/
|
||||||
private int ttsStatus = TextToSpeech.ERROR;
|
private boolean ready = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs the announcer and start the TTS engine.
|
* Constructs the announcer and start the TTS engine.
|
||||||
@@ -70,19 +70,40 @@ public class StatusAnnouncerTask
|
|||||||
this.context = context;
|
this.context = context;
|
||||||
this.stringUtils = new StringUtils(context);
|
this.stringUtils = new StringUtils(context);
|
||||||
|
|
||||||
tts = new TextToSpeech(context, this);
|
// We can't have this class also be the listener, otherwise it's unsafe to
|
||||||
// Force the language to be the same as the string we will be speaking.
|
// reference it in Cupcake (even if we don't instantiate it).
|
||||||
tts.setLanguage(Locale.getDefault());
|
tts = new TextToSpeech(context, new OnInitListener() {
|
||||||
// Slow down the speed just a bit as it is hard to hear when exercising.
|
@Override
|
||||||
tts.setSpeechRate(TTS_SPEECH_RATE);
|
public void onInit(int status) {
|
||||||
|
onTtsInit(status);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies that the tts engine is done with initialization.
|
* Called when the TTS engine is initialized.
|
||||||
*/
|
*/
|
||||||
public void onInit(int status) {
|
private void onTtsInit(int status) {
|
||||||
this.ttsStatus = status;
|
|
||||||
Log.i(MyTracksConstants.TAG, "TrackRecordingService.TTS init: " + status);
|
Log.i(MyTracksConstants.TAG, "TrackRecordingService.TTS init: " + status);
|
||||||
|
this.ready = (status == TextToSpeech.SUCCESS);
|
||||||
|
|
||||||
|
if (ready) {
|
||||||
|
// Force the language to be the same as the string we will be speaking,
|
||||||
|
// if that's available.
|
||||||
|
Locale speechLanguage = Locale.getDefault();
|
||||||
|
int languageAvailability = tts.isLanguageAvailable(speechLanguage);
|
||||||
|
if (languageAvailability == TextToSpeech.LANG_MISSING_DATA ||
|
||||||
|
languageAvailability == TextToSpeech.LANG_NOT_SUPPORTED) {
|
||||||
|
// English is probably supported
|
||||||
|
// TODO: Somehow use announcement strings from English too
|
||||||
|
Log.w(MyTracksConstants.TAG, "Default language not available, using English.");
|
||||||
|
speechLanguage = Locale.ENGLISH;
|
||||||
|
}
|
||||||
|
tts.setLanguage(speechLanguage);
|
||||||
|
|
||||||
|
// Slow down the speed just a bit as it is hard to hear when exercising.
|
||||||
|
tts.setSpeechRate(TTS_SPEECH_RATE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -90,8 +111,8 @@ public class StatusAnnouncerTask
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void run(TrackRecordingService service) {
|
public void run(TrackRecordingService service) {
|
||||||
if (ttsStatus != TextToSpeech.SUCCESS && tts != null) {
|
if (!ready) {
|
||||||
Log.e(MyTracksConstants.TAG, "StatusAnnouncer Tts not initialized.");
|
Log.e(MyTracksConstants.TAG, "StatusAnnouncer Tts not ready.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,4 +200,8 @@ public class StatusAnnouncerTask
|
|||||||
@Override
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getVolumeStream() {
|
||||||
|
return TextToSpeech.Engine.DEFAULT_STREAM;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import com.google.android.apps.mytracks.content.Waypoint;
|
|||||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
||||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||||
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
|
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
|
||||||
|
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||||
import com.google.android.apps.mytracks.util.StringUtils;
|
import com.google.android.apps.mytracks.util.StringUtils;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
@@ -106,7 +107,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
/**
|
/**
|
||||||
* Status announcer executer.
|
* Status announcer executer.
|
||||||
*/
|
*/
|
||||||
private PeriodicTaskExecuter executer;
|
private PeriodicTaskExecuter announcementExecuter;
|
||||||
private TaskExecuterManager signalManager;
|
private TaskExecuterManager signalManager;
|
||||||
private SplitManager splitManager;
|
private SplitManager splitManager;
|
||||||
|
|
||||||
@@ -181,18 +182,6 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
*/
|
*/
|
||||||
private int announcementFrequency = -1;
|
private int announcementFrequency = -1;
|
||||||
|
|
||||||
private static boolean mTTSAvailable;
|
|
||||||
|
|
||||||
/* establish whether the "new" class is available to us */
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
SafeStatusAnnouncerTask.checkAvailable();
|
|
||||||
mTTSAvailable = true;
|
|
||||||
} catch (Throwable t) {
|
|
||||||
mTTSAvailable = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Utility functions
|
* Utility functions
|
||||||
*/
|
*/
|
||||||
@@ -645,17 +634,12 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||||
locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
|
locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
|
||||||
splitManager = new SplitManager(this);
|
splitManager = new SplitManager(this);
|
||||||
try {
|
|
||||||
signalManager =
|
SignalStrengthTaskFactory strengthTaskFactory =
|
||||||
new TaskExecuterManager(-1, new SignalStrengthTaskModern(this), this);
|
new SignalStrengthTaskFactory(ApiFeatures.getInstance());
|
||||||
Log.d(MyTracksConstants.TAG,
|
signalManager =
|
||||||
"TrackRecordingService using modern signal strength api.");
|
new TaskExecuterManager(-1, strengthTaskFactory.create(this), this);
|
||||||
} catch (LinkageError e) {
|
|
||||||
Log.w(MyTracksConstants.TAG,
|
|
||||||
"TrackRecordingService could not load modern signal strength.", e);
|
|
||||||
signalManager =
|
|
||||||
new TaskExecuterManager(-1, new SignalStrengthTask(this), this);
|
|
||||||
}
|
|
||||||
prefManager = new PreferenceManager(this);
|
prefManager = new PreferenceManager(this);
|
||||||
prefManager.onSharedPreferenceChanged(null);
|
prefManager.onSharedPreferenceChanged(null);
|
||||||
registerLocationListener();
|
registerLocationListener();
|
||||||
@@ -682,12 +666,16 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
* the announcements, otherwise this method is no-op.
|
* the announcements, otherwise this method is no-op.
|
||||||
*/
|
*/
|
||||||
private void setUpAnnouncer() {
|
private void setUpAnnouncer() {
|
||||||
if (mTTSAvailable && announcementFrequency != -1) {
|
if (announcementFrequency != -1) {
|
||||||
if (executer == null) {
|
if (announcementExecuter == null) {
|
||||||
SafeStatusAnnouncerTask announcer = new SafeStatusAnnouncerTask(this);
|
StatusAnnouncerFactory statusAnnouncerFactory =
|
||||||
executer = new PeriodicTaskExecuter(announcer, this);
|
new StatusAnnouncerFactory(ApiFeatures.getInstance());
|
||||||
|
PeriodicTask announcer = statusAnnouncerFactory.create(this);
|
||||||
|
if (announcer == null) return;
|
||||||
|
|
||||||
|
announcementExecuter = new PeriodicTaskExecuter(announcer, this);
|
||||||
}
|
}
|
||||||
executer.scheduleTask(announcementFrequency * 60000);
|
announcementExecuter.scheduleTask(announcementFrequency * 60000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -702,8 +690,8 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
isRecording = false;
|
isRecording = false;
|
||||||
showNotification();
|
showNotification();
|
||||||
unregisterLocationListener();
|
unregisterLocationListener();
|
||||||
if (executer != null) {
|
if (announcementExecuter != null) {
|
||||||
executer.shutdown();
|
announcementExecuter.shutdown();
|
||||||
}
|
}
|
||||||
splitManager.shutdown();
|
splitManager.shutdown();
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
@@ -1014,15 +1002,13 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
|
|
||||||
public void setAnnouncementFrequency(int announcementFrequency) {
|
public void setAnnouncementFrequency(int announcementFrequency) {
|
||||||
this.announcementFrequency = announcementFrequency;
|
this.announcementFrequency = announcementFrequency;
|
||||||
if (mTTSAvailable) {
|
if (announcementFrequency == -1) {
|
||||||
if (announcementFrequency == -1) {
|
if (announcementExecuter != null) {
|
||||||
if (executer != null) {
|
announcementExecuter.shutdown();
|
||||||
executer.shutdown();
|
announcementExecuter = null;
|
||||||
executer = null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setUpAnnouncer();
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
setUpAnnouncer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
package com.google.android.apps.mytracks.util;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for determining if newer-API features are available on the
|
||||||
|
* current device.
|
||||||
|
*
|
||||||
|
* @author Rodrigo Damazio
|
||||||
|
*/
|
||||||
|
public class ApiFeatures {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The API level of the Android version we're being run under.
|
||||||
|
*/
|
||||||
|
public static final int ANDROID_API_LEVEL = Integer.parseInt(Build.VERSION.SDK);
|
||||||
|
|
||||||
|
private static ApiFeatures instance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the singleton instance of this class.
|
||||||
|
*/
|
||||||
|
public static ApiFeatures getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new ApiFeatures();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injects a specific singleton instance, to be used for unit tests.
|
||||||
|
*/
|
||||||
|
public static void injectInstance(ApiFeatures instance) {
|
||||||
|
ApiFeatures.instance = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow subclasses for mocking, but no direct instantiation.
|
||||||
|
*/
|
||||||
|
protected ApiFeatures() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether cloud backup (a.k.a. Froyo backup) is available.
|
||||||
|
*/
|
||||||
|
public boolean hasBackup() {
|
||||||
|
return getApiLevel() >= 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether text-to-speech is available.
|
||||||
|
*/
|
||||||
|
public boolean hasTextToSpeech() {
|
||||||
|
if (getApiLevel() < 4) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class.forName("android.speech.tts.TextToSpeech");
|
||||||
|
} catch (ClassNotFoundException ex) {
|
||||||
|
return false;
|
||||||
|
} catch (LinkageError er) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasModernSignalStrength() {
|
||||||
|
return getApiLevel() >= 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visible for testing.
|
||||||
|
protected int getApiLevel() {
|
||||||
|
return ANDROID_API_LEVEL;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,7 +37,7 @@ public class FileUtils {
|
|||||||
* A set of characters that are prohibited from being in file names.
|
* A set of characters that are prohibited from being in file names.
|
||||||
*/
|
*/
|
||||||
private static final Pattern PROHIBITED_CHAR_PATTERN =
|
private static final Pattern PROHIBITED_CHAR_PATTERN =
|
||||||
Pattern.compile("[^ A-Za-z0-9_.()]+");
|
Pattern.compile("[^ A-Za-z0-9_.()-]+");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a path inside the My Tracks directory in the SD card.
|
* Builds a path inside the My Tracks directory in the SD card.
|
||||||
@@ -69,8 +69,6 @@ public class FileUtils {
|
|||||||
* Normalizes the input string and make sure it is a valid fat32 file name.
|
* Normalizes the input string and make sure it is a valid fat32 file name.
|
||||||
*
|
*
|
||||||
* @param name the name to normalize
|
* @param name the name to normalize
|
||||||
* @param overheadSize the number of additional characters that will be added
|
|
||||||
* to the name after sanitization
|
|
||||||
* @return the sanitized name
|
* @return the sanitized name
|
||||||
*/
|
*/
|
||||||
String sanitizeName(String name) {
|
String sanitizeName(String name) {
|
||||||
|
|||||||
@@ -0,0 +1,243 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
package com.google.android.apps.mytracks;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
|
import com.google.android.maps.GeoPoint;
|
||||||
|
import com.google.android.maps.MapView;
|
||||||
|
import com.google.android.maps.Projection;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Path;
|
||||||
|
import android.graphics.Point;
|
||||||
|
import android.graphics.PointF;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.test.AndroidTestCase;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for the MyTracks map overlay.
|
||||||
|
*
|
||||||
|
* @author Bartlomiej Niechwiej
|
||||||
|
*/
|
||||||
|
public class MyTracksOverlayTest extends AndroidTestCase {
|
||||||
|
private Canvas canvas;
|
||||||
|
private MockMyTracksOverlay myTracksOverlay;
|
||||||
|
private MapView mockView;
|
||||||
|
private Projection mockProjection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mock version of {@code MyTracksOverlay} that does not use
|
||||||
|
* {@class MapView}.
|
||||||
|
*/
|
||||||
|
private class MockMyTracksOverlay extends MyTracksOverlay {
|
||||||
|
public MockMyTracksOverlay(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
Projection getMapProjection(MapView mapView) {
|
||||||
|
return mockProjection;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
Rect getMapViewRect(MapView mapView) {
|
||||||
|
return new Rect(0, 0, 100, 100);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
Path newPath() {
|
||||||
|
return new MockPath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mock class that intercepts {@code Path}'s and records calls to
|
||||||
|
* {@code #moveTo()} and {@code #lineTo()}.
|
||||||
|
*/
|
||||||
|
private static class MockPath extends Path {
|
||||||
|
/** A list of disjoined path segments. */
|
||||||
|
public final List<List<PointF>> segments = new LinkedList<List<PointF>>();
|
||||||
|
/** The total number of points in this path. */
|
||||||
|
public int totalPoints;
|
||||||
|
|
||||||
|
private List<PointF> currentSegment;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void lineTo(float x, float y) {
|
||||||
|
super.lineTo(x, y);
|
||||||
|
assertNotNull(currentSegment);
|
||||||
|
currentSegment.add(new PointF(x, y));
|
||||||
|
totalPoints++;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void moveTo(float x, float y) {
|
||||||
|
super.moveTo(x, y);
|
||||||
|
segments.add(currentSegment =
|
||||||
|
new ArrayList<PointF>(Arrays.asList(new PointF(x, y))));
|
||||||
|
totalPoints++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mock {@code Projection} that acts as the identity matrix.
|
||||||
|
*/
|
||||||
|
private static class MockProjection implements Projection {
|
||||||
|
@Override
|
||||||
|
public Point toPixels(GeoPoint in, Point out) {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public float metersToEquatorPixels(float meters) {
|
||||||
|
return meters;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public GeoPoint fromPixels(int x, int y) {
|
||||||
|
return new GeoPoint(y, x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
canvas = new Canvas();
|
||||||
|
myTracksOverlay = new MockMyTracksOverlay(getContext());
|
||||||
|
// Enable drawing.
|
||||||
|
myTracksOverlay.setTrackDrawingEnabled(true);
|
||||||
|
mockView = null;
|
||||||
|
mockProjection = new MockProjection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAddLocation() throws Exception {
|
||||||
|
Location location = new Location("gps");
|
||||||
|
location.setLatitude(10);
|
||||||
|
location.setLongitude(20);
|
||||||
|
myTracksOverlay.addLocation(location);
|
||||||
|
assertEquals(1, myTracksOverlay.getNumLocations());
|
||||||
|
assertEquals(0, myTracksOverlay.getNumWaypoints());
|
||||||
|
|
||||||
|
location.setLatitude(20);
|
||||||
|
location.setLongitude(30);
|
||||||
|
myTracksOverlay.addLocation(location);
|
||||||
|
assertEquals(2, myTracksOverlay.getNumLocations());
|
||||||
|
assertEquals(0, myTracksOverlay.getNumWaypoints());
|
||||||
|
assertNull(myTracksOverlay.getLastPath());
|
||||||
|
|
||||||
|
// Draw and make sure that we don't lose any point.
|
||||||
|
myTracksOverlay.draw(canvas, mockView, false);
|
||||||
|
assertEquals(2, myTracksOverlay.getNumLocations());
|
||||||
|
assertEquals(0, myTracksOverlay.getNumWaypoints());
|
||||||
|
assertNotNull(myTracksOverlay.getLastPath());
|
||||||
|
assertEquals(2, ((MockPath)myTracksOverlay.getLastPath()).totalPoints);
|
||||||
|
|
||||||
|
myTracksOverlay.draw(canvas, mockView, true);
|
||||||
|
assertEquals(2, myTracksOverlay.getNumLocations());
|
||||||
|
assertEquals(0, myTracksOverlay.getNumWaypoints());
|
||||||
|
assertNotNull(myTracksOverlay.getLastPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testClearPoints() throws Exception {
|
||||||
|
Location location = new Location("gps");
|
||||||
|
location.setLatitude(10);
|
||||||
|
location.setLongitude(20);
|
||||||
|
myTracksOverlay.addLocation(location);
|
||||||
|
assertEquals(1, myTracksOverlay.getNumLocations());
|
||||||
|
myTracksOverlay.clearPoints();
|
||||||
|
assertEquals(0, myTracksOverlay.getNumLocations());
|
||||||
|
|
||||||
|
// Same after drawing on canvas.
|
||||||
|
final int locations = 100;
|
||||||
|
for (int i = 0; i < locations; ++i) {
|
||||||
|
myTracksOverlay.addLocation(location);
|
||||||
|
}
|
||||||
|
assertEquals(locations, myTracksOverlay.getNumLocations());
|
||||||
|
myTracksOverlay.draw(canvas, mockView, false);
|
||||||
|
myTracksOverlay.draw(canvas, mockView, true);
|
||||||
|
myTracksOverlay.clearPoints();
|
||||||
|
assertEquals(0, myTracksOverlay.getNumLocations());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAddWaypoint() throws Exception {
|
||||||
|
Location location = new Location("gps");
|
||||||
|
location.setLatitude(10);
|
||||||
|
location.setLongitude(20);
|
||||||
|
Waypoint waypoint = new Waypoint();
|
||||||
|
waypoint.setLocation(location);
|
||||||
|
myTracksOverlay.addWaypoint(waypoint);
|
||||||
|
assertEquals(1, myTracksOverlay.getNumWaypoints());
|
||||||
|
assertEquals(0, myTracksOverlay.getNumLocations());
|
||||||
|
assertNull(myTracksOverlay.getLastPath());
|
||||||
|
|
||||||
|
final int waypoints = 10;
|
||||||
|
for (int i = 0; i < waypoints; ++i) {
|
||||||
|
waypoint = new Waypoint();
|
||||||
|
waypoint.setLocation(location);
|
||||||
|
myTracksOverlay.addWaypoint(waypoint);
|
||||||
|
}
|
||||||
|
assertEquals(1 + waypoints, myTracksOverlay.getNumWaypoints());
|
||||||
|
assertEquals(0, myTracksOverlay.getNumLocations());
|
||||||
|
assertNull(myTracksOverlay.getLastPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testClearWaypoints() throws Exception {
|
||||||
|
Location location = new Location("gps");
|
||||||
|
location.setLatitude(10);
|
||||||
|
location.setLongitude(20);
|
||||||
|
Waypoint waypoint = new Waypoint();
|
||||||
|
waypoint.setLocation(location);
|
||||||
|
myTracksOverlay.addWaypoint(waypoint);
|
||||||
|
assertEquals(1, myTracksOverlay.getNumWaypoints());
|
||||||
|
myTracksOverlay.clearWaypoints();
|
||||||
|
assertEquals(0, myTracksOverlay.getNumWaypoints());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testDrawing() {
|
||||||
|
Location location = new Location("gps");
|
||||||
|
location.setLatitude(10);
|
||||||
|
for (int i = 0; i < 40; ++i) {
|
||||||
|
location.setLongitude(20 + i);
|
||||||
|
Waypoint waypoint = new Waypoint();
|
||||||
|
waypoint.setLocation(location);
|
||||||
|
myTracksOverlay.addWaypoint(waypoint);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 100; ++i) {
|
||||||
|
location = new Location("gps");
|
||||||
|
location.setLatitude(20 + i / 2);
|
||||||
|
location.setLongitude(150 - i);
|
||||||
|
myTracksOverlay.addLocation(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shadow.
|
||||||
|
myTracksOverlay.draw(canvas, mockView, true);
|
||||||
|
// We don't expect to do anything if
|
||||||
|
assertNull(myTracksOverlay.getLastPath());
|
||||||
|
assertEquals(40, myTracksOverlay.getNumWaypoints());
|
||||||
|
assertEquals(100, myTracksOverlay.getNumLocations());
|
||||||
|
|
||||||
|
// No shadow.
|
||||||
|
myTracksOverlay.draw(canvas, mockView, false);
|
||||||
|
assertNotNull(myTracksOverlay.getLastPath());
|
||||||
|
assertTrue(myTracksOverlay.getLastPath() instanceof MockPath);
|
||||||
|
MockPath path = (MockPath) myTracksOverlay.getLastPath();
|
||||||
|
assertEquals(40, myTracksOverlay.getNumWaypoints());
|
||||||
|
assertEquals(100, myTracksOverlay.getNumLocations());
|
||||||
|
assertEquals(100, path.totalPoints);
|
||||||
|
// TODO: Check the points from the path (and the segments).
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,21 +2,23 @@
|
|||||||
|
|
||||||
package com.google.android.apps.mytracks.io;
|
package com.google.android.apps.mytracks.io;
|
||||||
|
|
||||||
|
import static org.easymock.EasyMock.eq;
|
||||||
import static org.easymock.EasyMock.expect;
|
import static org.easymock.EasyMock.expect;
|
||||||
import static org.easymock.EasyMock.isA;
|
import static org.easymock.EasyMock.isA;
|
||||||
|
import static org.easymock.EasyMock.leq;
|
||||||
import static org.easymock.EasyMock.same;
|
import static org.easymock.EasyMock.same;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
|
||||||
import com.google.android.apps.mytracks.content.Track;
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
import com.google.android.apps.mytracks.content.TrackBuffer;
|
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.testing.TestingProviderUtilsFactory;
|
import com.google.android.apps.mytracks.testing.TestingProviderUtilsFactory;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.MatrixCursor;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
import android.provider.BaseColumns;
|
||||||
import android.test.AndroidTestCase;
|
import android.test.AndroidTestCase;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@@ -25,6 +27,7 @@ import java.io.OutputStream;
|
|||||||
|
|
||||||
import org.easymock.EasyMock;
|
import org.easymock.EasyMock;
|
||||||
import org.easymock.IAnswer;
|
import org.easymock.IAnswer;
|
||||||
|
import org.easymock.IArgumentMatcher;
|
||||||
import org.easymock.IMocksControl;
|
import org.easymock.IMocksControl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -226,6 +229,8 @@ public class TrackWriterTest extends AndroidTestCase {
|
|||||||
expect(providerUtils.getWaypointsCursor(
|
expect(providerUtils.getWaypointsCursor(
|
||||||
TRACK_ID, 0, MyTracksConstants.MAX_LOADED_WAYPOINTS_POINTS))
|
TRACK_ID, 0, MyTracksConstants.MAX_LOADED_WAYPOINTS_POINTS))
|
||||||
.andStubReturn(null);
|
.andStubReturn(null);
|
||||||
|
expect(providerUtils.getLocationsCursor(
|
||||||
|
eq(TRACK_ID), leq(0L), leq(0), eq(false))).andStubReturn(null);
|
||||||
|
|
||||||
// Set expected mock behavior
|
// Set expected mock behavior
|
||||||
formatWriter.writeHeader();
|
formatWriter.writeHeader();
|
||||||
@@ -242,61 +247,78 @@ public class TrackWriterTest extends AndroidTestCase {
|
|||||||
public void testWriteDocument() {
|
public void testWriteDocument() {
|
||||||
writer = new TrackWriter(getContext(), providerUtils, track, formatWriter);
|
writer = new TrackWriter(getContext(), providerUtils, track, formatWriter);
|
||||||
|
|
||||||
Location l1 = new Location("fake1");
|
final Location[] locs = {
|
||||||
Location l2 = new Location("fake2");
|
new Location("fake0"),
|
||||||
Location l3 = new Location("fake3");
|
new Location("fake1"),
|
||||||
Location l4 = new Location("fake4");
|
new Location("fake2"),
|
||||||
Location l5 = new Location("fake5");
|
new Location("fake3"),
|
||||||
Location l6 = new Location("fake6");
|
new Location("fake4"),
|
||||||
Waypoint p1 = new Waypoint();
|
new Location("fake5"),
|
||||||
Waypoint p2 = new Waypoint();
|
};
|
||||||
|
Waypoint[] wps = { new Waypoint(), new Waypoint(), new Waypoint() };
|
||||||
|
|
||||||
addLocations(l1, l2, l3, l4, l5, l6);
|
// Fill locations with valid values
|
||||||
stubBufferFill(
|
fillLocations(locs);
|
||||||
new Location[] { l1, l2, l3, l4 },
|
|
||||||
new Location[] { l5, l6 });
|
|
||||||
|
|
||||||
track.setStopId(6L);
|
|
||||||
|
|
||||||
// Make location 3 invalid
|
// Make location 3 invalid
|
||||||
l3.setLatitude(100);
|
locs[2].setLatitude(100);
|
||||||
|
|
||||||
|
// Set up cursors
|
||||||
|
// We use fake columns since the cursor is only read by the provider utils
|
||||||
|
final MatrixCursor locCursor =
|
||||||
|
new MatrixCursor(new String[] { BaseColumns._ID }, 6);
|
||||||
|
for (int i = 1; i <= 6; i++) {
|
||||||
|
locCursor.newRow().add(i);
|
||||||
|
}
|
||||||
|
expect(providerUtils.getLocationsCursor(
|
||||||
|
eq(TRACK_ID), leq(0L), leq(0), eq(false))).andStubReturn(locCursor);
|
||||||
|
providerUtils.fillLocation(same(locCursor), isA(Location.class));
|
||||||
|
EasyMock.expectLastCall().andStubAnswer(new IAnswer<Void>() {
|
||||||
|
@Override
|
||||||
|
public Void answer() throws Throwable {
|
||||||
|
Location loc = (Location) EasyMock.getCurrentArguments()[1];
|
||||||
|
loc.set(locs[locCursor.getPosition()]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
MatrixCursor wpCursor =
|
||||||
|
new MatrixCursor(new String[] { BaseColumns._ID }, 3);
|
||||||
|
wpCursor.newRow().add(1);
|
||||||
|
wpCursor.newRow().add(2);
|
||||||
|
wpCursor.newRow().add(3);
|
||||||
|
expect(providerUtils.getWaypointsCursor(
|
||||||
|
eq(TRACK_ID), leq(0L),
|
||||||
|
eq(MyTracksConstants.MAX_LOADED_WAYPOINTS_POINTS)))
|
||||||
|
.andStubReturn(wpCursor);
|
||||||
|
expect(providerUtils.createWaypoint(wpCursor))
|
||||||
|
.andStubAnswer(stubCursorToArray(wpCursor, wps));
|
||||||
|
|
||||||
// Begin the track
|
// Begin the track
|
||||||
formatWriter.writeHeader();
|
formatWriter.writeHeader();
|
||||||
formatWriter.writeBeginTrack(l1);
|
formatWriter.writeBeginTrack(locEq(locs[0]));
|
||||||
|
|
||||||
// Write locations 1-2
|
// Write locations 1-2
|
||||||
formatWriter.writeOpenSegment();
|
formatWriter.writeOpenSegment();
|
||||||
formatWriter.writeLocation(l1);
|
formatWriter.writeLocation(locEq(locs[0]));
|
||||||
formatWriter.writeLocation(l2);
|
formatWriter.writeLocation(locEq(locs[1]));
|
||||||
formatWriter.writeCloseSegment();
|
formatWriter.writeCloseSegment();
|
||||||
|
|
||||||
// Location 3 is not written - it's invalid
|
// Location 3 is not written - it's invalid
|
||||||
|
|
||||||
// Write locations 4-6
|
// Write locations 4-6
|
||||||
formatWriter.writeOpenSegment();
|
formatWriter.writeOpenSegment();
|
||||||
formatWriter.writeLocation(l4);
|
formatWriter.writeLocation(locEq(locs[3]));
|
||||||
formatWriter.writeLocation(l5);
|
formatWriter.writeLocation(locEq(locs[4]));
|
||||||
formatWriter.writeLocation(l6);
|
formatWriter.writeLocation(locEq(locs[5]));
|
||||||
formatWriter.writeCloseSegment();
|
formatWriter.writeCloseSegment();
|
||||||
|
|
||||||
// End the track
|
// End the track
|
||||||
formatWriter.writeEndTrack(l6);
|
formatWriter.writeEndTrack(locEq(locs[5]));
|
||||||
|
|
||||||
// Expect reading/writing of the waypoints
|
// Expect reading/writing of the waypoints (except the first)
|
||||||
Cursor cursor = mocksControl.createMock(Cursor.class);
|
formatWriter.writeWaypoint(wps[1]);
|
||||||
expect(providerUtils.getWaypointsCursor(
|
formatWriter.writeWaypoint(wps[2]);
|
||||||
TRACK_ID, 0, MyTracksConstants.MAX_LOADED_WAYPOINTS_POINTS))
|
|
||||||
.andStubReturn(cursor);
|
|
||||||
expect(cursor.moveToFirst()).andReturn(true);
|
|
||||||
expect(cursor.moveToNext()).andReturn(true);
|
|
||||||
expect(providerUtils.createWaypoint(cursor)).andReturn(p1);
|
|
||||||
formatWriter.writeWaypoint(p1);
|
|
||||||
expect(cursor.moveToNext()).andReturn(true);
|
|
||||||
expect(providerUtils.createWaypoint(cursor)).andReturn(p2);
|
|
||||||
formatWriter.writeWaypoint(p2);
|
|
||||||
expect(cursor.moveToNext()).andReturn(false).anyTimes();
|
|
||||||
cursor.close();
|
|
||||||
|
|
||||||
formatWriter.writeFooter();
|
formatWriter.writeFooter();
|
||||||
formatWriter.close();
|
formatWriter.close();
|
||||||
@@ -308,7 +330,47 @@ public class TrackWriterTest extends AndroidTestCase {
|
|||||||
mocksControl.verify();
|
mocksControl.verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addLocations(Location... locs) {
|
private static Location locEq(final Location loc) {
|
||||||
|
EasyMock.reportMatcher(new IArgumentMatcher() {
|
||||||
|
@Override
|
||||||
|
public boolean matches(Object locObj2) {
|
||||||
|
if (locObj2 == null || loc == null) return loc == locObj2;
|
||||||
|
Location loc2 = (Location) locObj2;
|
||||||
|
|
||||||
|
return loc.hasAccuracy() == loc2.hasAccuracy()
|
||||||
|
&& (!loc.hasAccuracy() || loc.getAccuracy() == loc2.getAccuracy())
|
||||||
|
&& loc.hasAltitude() == loc2.hasAltitude()
|
||||||
|
&& (!loc.hasAltitude() || loc.getAltitude() == loc2.getAltitude())
|
||||||
|
&& loc.hasBearing() == loc2.hasBearing()
|
||||||
|
&& (!loc.hasBearing() || loc.getBearing() == loc2.getBearing())
|
||||||
|
&& loc.hasSpeed() == loc2.hasSpeed()
|
||||||
|
&& (!loc.hasSpeed() || loc.getSpeed() == loc2.getSpeed())
|
||||||
|
&& loc.getLatitude() == loc2.getLatitude()
|
||||||
|
&& loc.getLongitude() == loc2.getLongitude()
|
||||||
|
&& loc.getTime() == loc2.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appendTo(StringBuffer buffer) {
|
||||||
|
buffer.append("locEq(");
|
||||||
|
buffer.append(loc);
|
||||||
|
buffer.append(")");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> IAnswer<T> stubCursorToArray(
|
||||||
|
final MatrixCursor cursor, final T[] values) {
|
||||||
|
return new IAnswer<T>() {
|
||||||
|
@Override
|
||||||
|
public T answer() throws Throwable {
|
||||||
|
return values[cursor.getPosition()];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillLocations(Location... locs) {
|
||||||
assertTrue(locs.length < 90);
|
assertTrue(locs.length < 90);
|
||||||
for (int i = 0; i < locs.length; i++) {
|
for (int i = 0; i < locs.length; i++) {
|
||||||
Location location = locs[i];
|
Location location = locs[i];
|
||||||
@@ -316,46 +378,4 @@ public class TrackWriterTest extends AndroidTestCase {
|
|||||||
location.setLongitude(i + 1);
|
location.setLongitude(i + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the behaviour of filling the track buffer when a read is
|
|
||||||
* requested.
|
|
||||||
* The IDs of the locations will be their sequential number.
|
|
||||||
*
|
|
||||||
* @param feeds is a list of location arrays, each element of which
|
|
||||||
* will be fed into the track buffer on each call
|
|
||||||
*/
|
|
||||||
private void stubBufferFill(final Location[]... feeds) {
|
|
||||||
providerUtils.fillTrackPoints(same(track), isA(TrackBuffer.class));
|
|
||||||
EasyMock.expectLastCall().andStubAnswer(new IAnswer<Void>() {
|
|
||||||
private int lastId = 1;
|
|
||||||
private int reads = 0;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Void answer() throws Throwable {
|
|
||||||
// Get the buffer from the arguments
|
|
||||||
Object[] args = EasyMock.getCurrentArguments();
|
|
||||||
assertEquals(2, args.length);
|
|
||||||
TrackBuffer buffer = (TrackBuffer) args[1];
|
|
||||||
assertNotNull(buffer);
|
|
||||||
|
|
||||||
// Check that we still have data to feed to the buffer
|
|
||||||
if (reads >= feeds.length) {
|
|
||||||
fail("More buffer reads than expected");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill the buffer
|
|
||||||
buffer.reset();
|
|
||||||
Location[] locations = feeds[reads];
|
|
||||||
for (int i = 0; i < locations.length; i++) {
|
|
||||||
buffer.add(locations[i], lastId + i);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update internal state
|
|
||||||
lastId += locations.length;
|
|
||||||
reads++;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+72
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
package com.google.android.apps.mytracks.services;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||||
|
|
||||||
|
import android.test.AndroidTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link SignalStrengthTaskFactoryTest}.
|
||||||
|
* These tests require Eclair+ (API level 7) to run.
|
||||||
|
*
|
||||||
|
* @author Rodrigo Damazio
|
||||||
|
*/
|
||||||
|
public class SignalStrengthTaskFactoryTest extends AndroidTestCase {
|
||||||
|
/**
|
||||||
|
* Mock version of the {@link ApiFeatures} class.
|
||||||
|
*/
|
||||||
|
private static class MockApiFeatures extends ApiFeatures {
|
||||||
|
private boolean hasModernStrength;
|
||||||
|
|
||||||
|
public void setHasModernSignalStrength(boolean hasModernStrength) {
|
||||||
|
this.hasModernStrength = hasModernStrength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasModernSignalStrength() {
|
||||||
|
return hasModernStrength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MockApiFeatures apiFeatures;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
|
||||||
|
apiFeatures = new MockApiFeatures();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCreate() {
|
||||||
|
apiFeatures.setHasModernSignalStrength(true);
|
||||||
|
SignalStrengthTaskFactory factory =
|
||||||
|
new SignalStrengthTaskFactory(apiFeatures);
|
||||||
|
|
||||||
|
PeriodicTask task = factory.create(getContext());
|
||||||
|
assertTrue(task.getClass().getName(),
|
||||||
|
task instanceof SignalStrengthTaskModern);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCreate_legacy() {
|
||||||
|
apiFeatures.setHasModernSignalStrength(false);
|
||||||
|
SignalStrengthTaskFactory factory =
|
||||||
|
new SignalStrengthTaskFactory(apiFeatures);
|
||||||
|
|
||||||
|
PeriodicTask task = factory.create(getContext());
|
||||||
|
assertTrue(task.getClass().getName(), task instanceof SignalStrengthTask);
|
||||||
|
}
|
||||||
|
}
|
||||||
+89
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
package com.google.android.apps.mytracks.services;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||||
|
|
||||||
|
import android.media.AudioManager;
|
||||||
|
import android.speech.tts.TextToSpeech;
|
||||||
|
import android.test.AndroidTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link StatusAnnouncerFactory}.
|
||||||
|
* These tests require Donut+ to run.
|
||||||
|
*
|
||||||
|
* @author Rodrigo Damazio
|
||||||
|
*/
|
||||||
|
public class StatusAnnouncerFactoryTest extends AndroidTestCase {
|
||||||
|
/**
|
||||||
|
* Mock version of the {@link ApiFeatures} class.
|
||||||
|
*/
|
||||||
|
private class MockApiFeatures extends ApiFeatures {
|
||||||
|
private boolean hasTts;
|
||||||
|
|
||||||
|
public void setHasTextToSpeech(boolean hasTts) {
|
||||||
|
this.hasTts = hasTts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasTextToSpeech() {
|
||||||
|
return hasTts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MockApiFeatures apiFeatures;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
|
||||||
|
apiFeatures = new MockApiFeatures();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCreate() {
|
||||||
|
apiFeatures.setHasTextToSpeech(true);
|
||||||
|
StatusAnnouncerFactory factory =
|
||||||
|
new StatusAnnouncerFactory(apiFeatures);
|
||||||
|
PeriodicTask task = factory.create(getContext());
|
||||||
|
assertTrue(task instanceof StatusAnnouncerTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCreate_notAvailable() {
|
||||||
|
apiFeatures.setHasTextToSpeech(false);
|
||||||
|
StatusAnnouncerFactory factory =
|
||||||
|
new StatusAnnouncerFactory(apiFeatures);
|
||||||
|
PeriodicTask task = factory.create(getContext());
|
||||||
|
assertNull(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetVolumeStream() {
|
||||||
|
apiFeatures.setHasTextToSpeech(true);
|
||||||
|
StatusAnnouncerFactory factory =
|
||||||
|
new StatusAnnouncerFactory(apiFeatures);
|
||||||
|
assertEquals(
|
||||||
|
TextToSpeech.Engine.DEFAULT_STREAM,
|
||||||
|
factory.getVolumeStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetVolumeStream_notAvailable() {
|
||||||
|
apiFeatures.setHasTextToSpeech(false);
|
||||||
|
StatusAnnouncerFactory factory =
|
||||||
|
new StatusAnnouncerFactory(apiFeatures);
|
||||||
|
assertEquals(
|
||||||
|
AudioManager.USE_DEFAULT_STREAM_TYPE,
|
||||||
|
factory.getVolumeStream());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
package com.google.android.apps.mytracks.util;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the API feature detection code in {@link ApiFeatures}.
|
||||||
|
* This test requires Froyo+ to run.
|
||||||
|
*
|
||||||
|
* @author Rodrigo Damazio
|
||||||
|
*/
|
||||||
|
public class ApiFeaturesTest extends TestCase {
|
||||||
|
|
||||||
|
private TestableApiFeatures features;
|
||||||
|
|
||||||
|
private class TestableApiFeatures extends ApiFeatures {
|
||||||
|
private int apiLevel;
|
||||||
|
|
||||||
|
public void setApiLevel(int apiLevel) {
|
||||||
|
this.apiLevel = apiLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getApiLevel() {
|
||||||
|
return apiLevel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
|
||||||
|
features = new TestableApiFeatures();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testHasBackup() {
|
||||||
|
for (int i = 3; i <= 7; i++) {
|
||||||
|
features.setApiLevel(i);
|
||||||
|
assertFalse(features.hasBackup());
|
||||||
|
}
|
||||||
|
features.setApiLevel(8);
|
||||||
|
assertTrue(features.hasBackup());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testHasTextToSpeech() {
|
||||||
|
features.setApiLevel(3);
|
||||||
|
assertFalse(features.hasTextToSpeech());
|
||||||
|
for (int i = 4; i <= 8; i++) {
|
||||||
|
features.setApiLevel(i);
|
||||||
|
assertTrue(features.hasTextToSpeech());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testHasModernSignalStrength() {
|
||||||
|
for (int i = 3; i <= 6; i++) {
|
||||||
|
features.setApiLevel(i);
|
||||||
|
assertFalse(features.hasModernSignalStrength());
|
||||||
|
}
|
||||||
|
features.setApiLevel(7);
|
||||||
|
assertTrue(features.hasModernSignalStrength());
|
||||||
|
features.setApiLevel(8);
|
||||||
|
assertTrue(features.hasModernSignalStrength());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,8 +31,8 @@ import junit.framework.TestCase;
|
|||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public class FileUtilsTest extends TestCase {
|
public class FileUtilsTest extends TestCase {
|
||||||
private static final String ORIGINAL_NAME = "Swim\10ming ^across: the/ pacific (ocean).";
|
private static final String ORIGINAL_NAME = "Swim\10ming-^across: the/ pacific (ocean).";
|
||||||
private static final String SANITIZED_NAME = "Swimming across the pacific (ocean).";
|
private static final String SANITIZED_NAME = "Swimming-across the pacific (ocean).";
|
||||||
|
|
||||||
private FileUtils fileUtils;
|
private FileUtils fileUtils;
|
||||||
private Set<String> existingFiles;
|
private Set<String> existingFiles;
|
||||||
|
|||||||
Reference in New Issue
Block a user