diff --git a/.hgignore b/.hgignore
index 8372fdde3..7220e4cb0 100644
--- a/.hgignore
+++ b/.hgignore
@@ -3,3 +3,4 @@ syntax:glob
*/bin
*/gen/**
.metadata
+MyTracksTest/annotations.log
diff --git a/MyTracks/.classpath b/MyTracks/.classpath
index 39a225fb5..7c5cb8f1e 100644
--- a/MyTracks/.classpath
+++ b/MyTracks/.classpath
@@ -1,10 +1,10 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/MyTracks/AndroidManifest.xml b/MyTracks/AndroidManifest.xml
index c3a1c4874..3e72ff098 100755
--- a/MyTracks/AndroidManifest.xml
+++ b/MyTracks/AndroidManifest.xml
@@ -1,6 +1,7 @@
+
+
+
1 分钟
+
+
+ - 从不
+ - 5 分钟
+ - 10 分钟
+ - 15 分钟
+ - 总是
+
- 未知
diff --git a/MyTracks/res/values-zh-rCN/strings.xml b/MyTracks/res/values-zh-rCN/strings.xml
index 00d38bcd3..648550a39 100644
--- a/MyTracks/res/values-zh-rCN/strings.xml
+++ b/MyTracks/res/values-zh-rCN/strings.xml
@@ -15,8 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-
- My Tracks
+
+
记录轨迹
@@ -32,6 +32,7 @@ limitations under the License.
请选择要查看的轨迹
当前轨迹
上一段轨迹
+ 累积统计
此设备上没有位置管理器!
@@ -122,6 +123,7 @@ limitations under the License.
保存为 GPX 文件
保存为 KML 文件
保存为 CSV 文件
+ 保存为 TCX 文件
发送到 Google My Maps
删除所有轨迹
@@ -135,6 +137,7 @@ limitations under the License.
GPX 文件
KML 文件
CSV 文件
+ TCX 文件
清除地图
@@ -165,9 +168,10 @@ limitations under the License.
记录
分享
+ 你使用的 Android 版本无法支持
+
提示频度
语音提示的最小时间间隔
-
最小记录间距
位置记录的最小间隔距离
@@ -202,7 +206,38 @@ limitations under the License.
切割频度
插入新切割标记所需要达到的最大时间或距离
- 步数
+
+ 自动恢复追踪等待时间
+ 手机重启后自动继续上次轨迹记录的最大间隔时间
+
+ 节奏
+
+ 只在 Android 2.0 及以上版本支持
+ 感应器类型
+ 要连接到的感应器类型
+ 感应器ID
+ 发送到感应器的ID.
+ ID必须是一个整数值.
+ 蓝牙感应器
+ 要连接到的蓝牙感应器
+
+
+
+
+ 备份 (测试版)
+ SD 存储卡备份
+ 立即备份
+ 将你所有轨迹和设置的完整备份写入到 SD 存储卡
+ 立即恢复
+ 从 SD 存储卡恢复所有轨迹和设置
+ 在记录轨迹时无法处理备份数据.
+
+
+ 从 SD 存储卡恢复备份数据将覆盖掉你当前的所有轨迹和设置. 确定要继续吗?
+ 请选择要恢复的备份文件
+ 没有找到备份文件.
+ 将所有轨迹和设置写入 SD 存储卡中. 这需要一些时间...
+ 从 SD 存储卡读取轨迹和设置中. 这需要一些时间...
GPS 卫星
@@ -241,7 +276,7 @@ limitations under the License.
公开
- Unlisted
+ 未公布
你还没有保存任何地图.
@@ -292,24 +327,32 @@ limitations under the License.
秒
秒
+
+
- m
- km
+
+
千米
- ft
- mi
+
+
英里
- km/h
+
千米每小时
- min/km
+
每千米
- mi/h
+
英里每小时
- min/mi
+
每英里
+
+
+
+
+
+
移动时间
总时间
@@ -320,7 +363,11 @@ limitations under the License.
统计
总距离
距离
- -
+ 电源
+ 节奏
+ 心率
+ 最大心率
+
请稍候...
@@ -336,20 +383,27 @@ limitations under the License.
技术支持
技术支持:
-
- 1.0.14
- mytracks@googlegroups.com
- mytracks.appspot.com
- Copyright 2009 Google Inc.
- Dylan Casey
- Leif Hendrik Wilden
- Rodrigo Damazio Bovendorp
- Sandor Dornbush
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
发送到...
- Google My Maps
- Google Docs
+
+
立即发送
创建新地图
选择已有地图
@@ -364,8 +418,9 @@ limitations under the License.
没有发现存贮设备. 请确认插入了 SD 卡并且你的手机没有将挂载 SD 卡到电脑.
无法写入文件.
- 无法在 SD 卡上创建目录.
+ 无法在 SD 存储卡上创建目录.
轨迹已导出到 SD 卡.
+ 从 SD 存储卡读取完毕.
检查更新
@@ -403,4 +458,29 @@ limitations under the License.
GSM 强度 %1$d
CDMA 强度 %1$d dBm
EVDO 强度 %1$d dBm
+
+
+ 蓝牙
+ 蓝牙设置
+ 设备关联
+ 打开 Android 的蓝牙设置关联设备
+
+ 感应器选项
+
+
+
+
+
+
+
+
+ 连接到 %s
+ 蓝牙感应器连接中 ...
+ 未连接蓝牙感应器
+
+
+ 感应器数据时间
+ 电源计量器已启用
+ 心率监视器已启用
+
diff --git a/MyTracks/res/values/strings.xml b/MyTracks/res/values/strings.xml
index 722f8e232..6212f9cf2 100644
--- a/MyTracks/res/values/strings.xml
+++ b/MyTracks/res/values/strings.xml
@@ -211,8 +211,8 @@ limitations under the License.
Maximum time in minutes after which a previously recording track will not be resumed on phone reboot
Timestamp as track name
- The default track name will be the date and time at the start of the track
- Track names will have numbers which increment for each new track.
+ The default track name will be the date and time at the start of the track
+ Track names will have numbers which increment for each new track.
Pace
diff --git a/MyTracks/res/xml/preferences.xml b/MyTracks/res/xml/preferences.xml
index 8de128543..848bab5a2 100644
--- a/MyTracks/res/xml/preferences.xml
+++ b/MyTracks/res/xml/preferences.xml
@@ -103,8 +103,8 @@ limitations under the License.
diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java b/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java
index 62117a82c..1e17d7a3c 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java
@@ -298,13 +298,13 @@ public class ChartActivity extends Activity implements
public void onChange(boolean selfChange) {
Log.d(MyTracksConstants.TAG, "ChartActivity: ContentObserver.onChange");
// Check for any new locations and append them to the currently
- // recording track:
- if (!MyTracks.getInstance().isRecording()) {
+ // recording track.
+ if (recordingTrackId < 0) {
// No track is being recorded. We should not be here.
return;
}
if (selectedTrackId != recordingTrackId) {
- // no track, or one other than the recording track is selected, don't
+ // No track, or one other than the recording track is selected, don't
// bother.
return;
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartValueSeries.java b/MyTracks/src/com/google/android/apps/mytracks/ChartValueSeries.java
index d2bf16d29..d5ff24834 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/ChartValueSeries.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/ChartValueSeries.java
@@ -25,7 +25,7 @@ import android.graphics.Path;
import java.text.DecimalFormat;
/**
- * This class encasulates meta data about one series of values for a chart.
+ * This class encapsulates meta data about one series of values for a chart.
*
* @author Sandor Dornbush
*/
diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java
index 449fe0f2a..8d8437d50 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java
@@ -26,9 +26,9 @@ import com.google.android.apps.mytracks.io.AuthManagerFactory;
import com.google.android.apps.mytracks.io.GpxImporter;
import com.google.android.apps.mytracks.io.SendToDocs;
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.TrackWriterFactory;
+import com.google.android.apps.mytracks.io.SendToMyMaps.OnSendCompletedListener;
import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
@@ -65,9 +65,9 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
+import android.view.Window;
import android.view.View.OnTouchListener;
import android.view.ViewGroup.LayoutParams;
-import android.view.Window;
import android.view.WindowManager.BadTokenException;
import android.widget.RelativeLayout;
import android.widget.TabHost;
@@ -97,9 +97,10 @@ public class MyTracks extends TabActivity implements OnTouchListener,
/**
* Singleton instance
*/
- private static MyTracks instance = null;
- private ChartActivity chartActivity = null;
+ private static MyTracks instance;
+ private ChartActivity chartActivity;
+
public ChartActivity getChartActivity() {
return chartActivity;
}
@@ -178,6 +179,8 @@ public class MyTracks extends TabActivity implements OnTouchListener,
* Utilities to deal with the database.
*/
private MyTracksProviderUtils providerUtils;
+
+ private SharedPreferences sharedPreferences;
/**
* The connection to the track recording service.
@@ -233,20 +236,35 @@ public class MyTracks extends TabActivity implements OnTouchListener,
}
/**
+ * Checks whether we have a track recording session in progress.
+ * In some cases, when the service has crashed or has been restarted
+ * by the system, we fall back to the shared preferences.
+ *
* @return true if the activity is bound to the track recording service and
- * the service is recording a track.
+ * the service is recording a track or in case the service is down,
+ * based on settings from the shared preferences.
*/
public boolean isRecording() {
if (trackRecordingService == null) {
- return false;
+ // Fall back to alternative check method.
+ return isRecordingBasedOnSharedPreferences();
}
try {
return trackRecordingService.isRecording();
} catch (RemoteException e) {
Log.e(MyTracksConstants.TAG, "MyTracks: Remote exception.", e);
- return false;
+
+ // Fall back to alternative check method.
+ return isRecordingBasedOnSharedPreferences();
}
}
+
+ private boolean isRecordingBasedOnSharedPreferences() {
+ // TrackRecordingServices guarantees that recordingTrackId is set to
+ // -1 if the track has been stopped.
+ // TODO: Refresh recordingTrackId.
+ return recordingTrackId >= 0;
+ }
/*
* Application lifetime events:
@@ -260,6 +278,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
instance = this;
providerUtils = MyTracksProviderUtils.Factory.get(this);
menuManager = new MenuManager(this);
+ sharedPreferences = getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
// The volume we want to control is the Text-To-Speech volume
int volumeStream =
@@ -295,14 +314,18 @@ public class MyTracks extends TabActivity implements OnTouchListener,
tabHost.addView(layout);
layout.setOnTouchListener(this);
- SharedPreferences prefs =
- getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
- if (prefs != null) {
+ if (sharedPreferences != null) {
selectedTrackId =
- prefs.getLong(getString(R.string.selected_track_key), -1);
- recordingTrackId =
- prefs.getLong(getString(R.string.recording_track_key), -1);
- prefs.registerOnSharedPreferenceChangeListener(this);
+ sharedPreferences.getLong(getString(R.string.selected_track_key), -1);
+ recordingTrackId = sharedPreferences.getLong(
+ getString(R.string.recording_track_key), -1);
+ sharedPreferences.registerOnSharedPreferenceChangeListener(this);
+ Log.d(MyTracksConstants.TAG, "recordingTrackId: " + recordingTrackId
+ + ", selectedTrackId: " + selectedTrackId);
+ if (recordingTrackId > 0) {
+ Intent startIntent = new Intent(this, TrackRecordingService.class);
+ startService(startIntent);
+ }
}
// This will show the eula until the user accepts or quits the app.
@@ -1008,20 +1031,15 @@ public class MyTracks extends TabActivity implements OnTouchListener,
shareIntent.putExtra(Intent.EXTRA_SUBJECT,
getResources().getText(R.string.share_map_subject).toString());
- SharedPreferences prefs =
- getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
boolean shareUrlOnly = true;
- if (prefs != null) {
- shareUrlOnly =
- prefs.getBoolean(getString(R.string.share_url_only_key), false);
+ if (sharedPreferences != null) {
+ shareUrlOnly = sharedPreferences.getBoolean(
+ getString(R.string.share_url_only_key), false);
}
String url = MyMapsConstants.MAPSHOP_BASE_URL + "?msa=0&msid=" + mapId;
- String msg = (shareUrlOnly
- ? url
- : String.format(
- getResources().getText(R.string.share_map_body_format).toString(),
- url));
+ String msg = shareUrlOnly ? url : String.format(
+ getResources().getText(R.string.share_map_body_format).toString(), url);
shareIntent.putExtra(Intent.EXTRA_TEXT, msg);
startActivity(Intent.createChooser(shareIntent,
getResources().getText(R.string.share_map).toString()));
@@ -1235,9 +1253,10 @@ public class MyTracks extends TabActivity implements OnTouchListener,
private void startRecordingNewTrack() {
try {
recordingTrackId = trackRecordingService.startNewTrack();
+ // Select the recording track.
+ setSelectedTrack(recordingTrackId);
Toast.makeText(this, getString(R.string.status_now_recording),
Toast.LENGTH_SHORT).show();
- setSelectedAndRecordingTrack(recordingTrackId, recordingTrackId);
} catch (RemoteException e) {
Toast.makeText(this,
getString(R.string.error_unable_to_start_recording),
@@ -1272,7 +1291,6 @@ public class MyTracks extends TabActivity implements OnTouchListener,
} catch (RemoteException e) {
Log.e(MyTracksConstants.TAG, "Unable to stop recording.", e);
}
- setRecordingTrack(-1);
Intent intent = new Intent(MyTracks.this, MyTracksDetails.class);
intent.putExtra("trackid", recordingTrackId);
intent.putExtra("hasCancelButton", false);
@@ -1327,9 +1345,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
public void setSelectedTrack(final long trackId) {
runOnUiThread(new Runnable() {
public void run() {
- SharedPreferences prefs =
- getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
- SharedPreferences.Editor editor = prefs.edit();
+ SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putLong(getString(R.string.selected_track_key), trackId);
editor.commit();
}
@@ -1340,44 +1356,6 @@ public class MyTracks extends TabActivity implements OnTouchListener,
return selectedTrackId;
}
- /**
- * Writes the recording track id to the shared preferences.
- * Executed on the UI thread.
- *
- * @param trackId the id of the track
- */
- private void setRecordingTrack(final long trackId) {
- runOnUiThread(new Runnable() {
- public void run() {
- SharedPreferences prefs =
- getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putLong(getString(R.string.recording_track_key), trackId);
- editor.commit();
- }
- });
- }
-
- /**
- * Writes the selected and the recording track id to the shared preferences.
- * Executed on UI thread.
- */
- private void setSelectedAndRecordingTrack(final long theSelectedTrackId,
- final long theRecordingTrackId) {
- runOnUiThread(new Runnable() {
- public void run() {
- SharedPreferences prefs =
- getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
- if (prefs != null) {
- SharedPreferences.Editor editor = prefs.edit();
- editor.putLong(getString(R.string.selected_track_key), theSelectedTrackId);
- editor.putLong(getString(R.string.recording_track_key), theRecordingTrackId);
- editor.commit();
- }
- }
- });
- }
-
/**
* Binds to track recording service if it is running.
*/
diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java
index 32212e145..78d7645d4 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java
@@ -118,7 +118,7 @@ public class MyTracksMap extends MapActivity
private double variation;
/**
- * From the shared preferences:
+ * From the shared preferences.
*/
private int minRequiredAccuracy =
MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY;
@@ -135,8 +135,8 @@ public class MyTracksMap extends MapActivity
private Location currentLocation;
/**
- * A thread with a looper. Post to updateTrackHandler to execute Runnables on
- * this thread.
+ * A thread with a looper. Post to updateTrackHandler to execute
+ * {@link Runnable}s on this thread.
*/
private HandlerThread updateTrackThread;
@@ -146,6 +146,8 @@ public class MyTracksMap extends MapActivity
private Handler updateTrackHandler;
private MyTracksProviderUtils providerUtils;
+
+ private SharedPreferences sharedPreferences;
/**
* A runnable that updates the track from the provider (looking for points
@@ -331,12 +333,6 @@ public class MyTracksMap extends MapActivity
@Override
public void onChange(boolean selfChange) {
Log.d(MyTracksConstants.TAG, "MyTracksMap: ContentObserver.onChange");
- // Check for any new locations and append them to the currently
- // recording track:
- if (!MyTracks.getInstance().isRecording()) {
- // No track is being recorded. We should not be here.
- return;
- }
if (!isRecordingSelected()) {
// No track, or one other than the recording track is selected,
// don't bother.
@@ -361,30 +357,25 @@ public class MyTracksMap extends MapActivity
}
};
- // Read shared preferences and register change listener:
- SharedPreferences preferences =
- getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
- if (preferences != null) {
- minRequiredAccuracy = preferences.getInt(
- getString(R.string.min_required_accuracy_key),
- MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY);
- recordingTrackId =
- preferences.getLong(getString(R.string.recording_track_key), -1);
- long selectedTrackId =
- preferences.getLong(getString(R.string.selected_track_key), -1);
- if (selectedTrackId >= 0) {
- setSelectedTrack(selectedTrackId);
- }
+ // Read shared preferences and register change listener.
+ sharedPreferences = getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
+ if (sharedPreferences != null) {
+ reloadSharedPreferences(sharedPreferences, null);
updateOptionsButton();
- preferences.registerOnSharedPreferenceChangeListener(this);
+ sharedPreferences.registerOnSharedPreferenceChangeListener(this);
}
}
@Override
protected void onDestroy() {
Log.d(MyTracksConstants.TAG, "MyTracksMap.onDestroy");
+
if (updateTrackThread != null) {
- updateTrackThread.quit();
+ ApiFeatures.getInstance().getApiPlatformAdapter().stopHandlerThread(
+ updateTrackThread);
+ }
+ if (sharedPreferences != null) {
+ sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
}
super.onDestroy();
@@ -451,6 +442,9 @@ public class MyTracksMap extends MapActivity
Log.d(MyTracksConstants.TAG, "MyTracksMap.onResume");
super.onResume();
+ // Reload all preferences as they might have changed since last run.
+ reloadSharedPreferences(sharedPreferences, null);
+
// Make sure any updates that might have happened are propagated to the
// Map overlay:
observer.onChange(false);
@@ -707,11 +701,14 @@ public class MyTracksMap extends MapActivity
* @param trackId a given track id
*/
public void setSelectedTrack(final long trackId) {
+ Log.d(MyTracksConstants.TAG, "MyTracksMap.setSelectedTrack: "
+ + "selectedtTrackId = " + selectedTrackId + ", trackId = " + trackId);
+
if (selectedTrackId == trackId) {
// Selected track did not change, nothing to do.
mapOverlay.setTrackDrawingEnabled(isATrackSelected());
- mapView.invalidate();
updateOptionsButton();
+ mapView.invalidate();
return;
}
@@ -786,7 +783,7 @@ public class MyTracksMap extends MapActivity
menu.setHeaderTitle(R.string.tracklist_this_track);
menu.add(0, MyTracksConstants.MENU_EDIT, 0,
R.string.tracklist_edit_track);
- if (!MyTracks.getInstance().isRecording() || !isRecordingSelected()) {
+ if (!isRecordingSelected()) {
menu.add(0, MyTracksConstants.MENU_SEND_TO_GOOGLE, 0,
R.string.tracklist_send_to_google);
SubMenu share = menu.addSubMenu(0, MyTracksConstants.MENU_SHARE, 0,
@@ -906,30 +903,13 @@ public class MyTracksMap extends MapActivity
@Override
public void onSharedPreferenceChanged(
final SharedPreferences sharedPreferences, final String key) {
+ Log.d(MyTracksConstants.TAG,
+ "MyTracksMap.onSharedPreferenceChanged: " + key);
if (key != null) {
uiHandler.post(new Runnable() {
@Override
public void run() {
- if (key.equals(getString(R.string.min_required_accuracy_key))) {
- minRequiredAccuracy = sharedPreferences.getInt(
- getString(R.string.min_required_accuracy_key),
- MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY);
- } else if (key.equals(getString(R.string.selected_track_key))) {
- long selectedTrackId =
- sharedPreferences.getLong(
- getString(R.string.selected_track_key),
- -1);
- setSelectedTrack(selectedTrackId);
- } else if (key.equals(getString(R.string.recording_track_key))) {
- recordingTrackId =
- sharedPreferences.getLong(
- getString(R.string.recording_track_key),
- -1);
- if (isATrackSelected()) {
- mapOverlay.setShowEndMarker(!isRecordingSelected());
- mapView.postInvalidate();
- }
- }
+ reloadSharedPreferences(sharedPreferences, key);
}
});
}
@@ -1009,6 +989,31 @@ public class MyTracksMap extends MapActivity
}
};
+ private void reloadSharedPreferences(SharedPreferences sharedPreferences,
+ String key) {
+ if (key == null ||
+ key.equals(getString(R.string.min_required_accuracy_key))) {
+ minRequiredAccuracy = sharedPreferences.getInt(
+ getString(R.string.min_required_accuracy_key),
+ MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY);
+ }
+ if (key == null || key.equals(getString(R.string.recording_track_key))) {
+ recordingTrackId = sharedPreferences.getLong(
+ getString(R.string.recording_track_key), -1);
+ }
+ if (key == null || key.equals(getString(R.string.selected_track_key))) {
+ setSelectedTrack(sharedPreferences.getLong(
+ getString(R.string.selected_track_key), -1));
+ }
+
+ // Show end marker if the track has been selected and is not recording.
+ // Note: This check must be *after* a call to setSelectedTrack(...) above.
+ if (isATrackSelected()) {
+ mapOverlay.setShowEndMarker(!isRecordingSelected());
+ mapView.postInvalidate();
+ }
+ }
+
private void readAllNewTrackPoints() {
int numPoints = mapOverlay.getNumLocations();
if (numPoints >= MyTracksConstants.MAX_DISPLAYED_TRACK_POINTS) {
diff --git a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java
index 403190a6c..9d68c9c27 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java
@@ -52,7 +52,7 @@ import android.widget.Toast;
public class StatsActivity extends Activity
implements OnSharedPreferenceChangeListener {
- private final StatsUtilities utils;
+ private StatsUtilities utils;
private UIUpdateThread thread;
private ContentObserver observer;
@@ -94,7 +94,7 @@ public class StatsActivity extends Activity
*/
private boolean showCurrentSegment = false;
- private final MyTracksProviderUtils providerUtils;
+ private MyTracksProviderUtils providerUtils;
/**
* A runnable for posting to the UI thread. Will update the total time field.
@@ -133,16 +133,14 @@ public class StatsActivity extends Activity
}
}
- public StatsActivity() {
- utils = new StatsUtilities(this);
- providerUtils = new MyTracksProviderUtilsImpl(this);
- }
-
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ utils = new StatsUtilities(this);
+ providerUtils = new MyTracksProviderUtilsImpl(getContentResolver());
+
// The volume we want to control is the Text-To-Speech volume
int volumeStream =
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java b/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java
index 1ea043aff..56c70db13 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java
@@ -345,7 +345,7 @@ public interface MyTracksProviderUtils {
* Creates an instance of {@link MyTracksProviderUtils}.
*/
protected MyTracksProviderUtils newForContext(Context context) {
- return new MyTracksProviderUtilsImpl(context);
+ return new MyTracksProviderUtilsImpl(context.getContentResolver());
}
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java b/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java
index ffbd77d9c..1e814f102 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java
@@ -17,8 +17,8 @@ package com.google.android.apps.mytracks.content;
import com.google.android.apps.mytracks.stats.TripStatistics;
+import android.content.ContentResolver;
import android.content.ContentValues;
-import android.content.Context;
import android.database.Cursor;
import android.location.Location;
import android.net.Uri;
@@ -35,10 +35,10 @@ import java.util.List;
*/
public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
- private final Context context;
+ private final ContentResolver contentResolver;
- public MyTracksProviderUtilsImpl(Context context) {
- this.context = context;
+ public MyTracksProviderUtilsImpl(ContentResolver contentResolver) {
+ this.contentResolver = contentResolver;
}
/**
@@ -448,25 +448,22 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public void deleteAllTracks() {
- context.getContentResolver().delete(TracksColumns.CONTENT_URI, null, null);
- context.getContentResolver().delete(TrackPointsColumns.CONTENT_URI,
- null, null);
- context.getContentResolver().delete(
- WaypointsColumns.CONTENT_URI, null, null);
+ contentResolver.delete(TracksColumns.CONTENT_URI, null, null);
+ contentResolver.delete(TrackPointsColumns.CONTENT_URI, null, null);
+ contentResolver.delete(WaypointsColumns.CONTENT_URI, null, null);
}
@Override
public void deleteTrack(long trackId) {
Track track = getTrack(trackId);
if (track != null) {
- context.getContentResolver().delete(TrackPointsColumns.CONTENT_URI,
+ contentResolver.delete(TrackPointsColumns.CONTENT_URI,
"_id>=" + track.getStartId() + " AND _id<=" + track.getStopId(),
null);
}
- context.getContentResolver().delete(WaypointsColumns.CONTENT_URI,
+ contentResolver.delete(WaypointsColumns.CONTENT_URI,
WaypointsColumns.TRACKID + "=" + trackId, null);
- context.getContentResolver().delete(
- TracksColumns.CONTENT_URI, "_id=" + trackId, null);
+ contentResolver.delete(TracksColumns.CONTENT_URI, "_id=" + trackId, null);
}
@Override
@@ -491,7 +488,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
"No statistics marker after the deleted one was found.");
}
}
- context.getContentResolver().delete(
+ contentResolver.delete(
WaypointsColumns.CONTENT_URI, "_id=" + waypointId, null);
}
@@ -503,7 +500,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
final String sortOrder = WaypointsColumns._ID + " LIMIT 1";
Cursor cursor = null;
try {
- cursor = context.getContentResolver().query(
+ cursor = contentResolver.query(
WaypointsColumns.CONTENT_URI,
null /*projection*/,
selection,
@@ -525,7 +522,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public boolean updateWaypoint(Waypoint waypoint) {
try {
- final int rows = context.getContentResolver().update(
+ final int rows = contentResolver.update(
WaypointsColumns.CONTENT_URI,
createContentValues(waypoint),
"_id=" + waypoint.getId(),
@@ -546,7 +543,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
private Location findLocationBy(String select) {
Cursor cursor = null;
try {
- cursor = context.getContentResolver().query(
+ cursor = contentResolver.query(
TrackPointsColumns.CONTENT_URI, null, select, null, null);
if (cursor != null && cursor.moveToNext()) {
return createLocation(cursor);
@@ -570,7 +567,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
private Track findTrackBy(String select) {
Cursor cursor = null;
try {
- cursor = context.getContentResolver().query(
+ cursor = contentResolver.query(
TracksColumns.CONTENT_URI, null, select, null, null);
if (cursor != null && cursor.moveToNext()) {
return createTrack(cursor);
@@ -592,7 +589,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public Waypoint getFirstWaypoint(long trackId) {
- Cursor cursor = context.getContentResolver().query(
+ Cursor cursor = contentResolver.query(
WaypointsColumns.CONTENT_URI,
null /*projection*/,
"trackid=" + trackId,
@@ -614,7 +611,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public Waypoint getWaypoint(long waypointId) {
- Cursor cursor = context.getContentResolver().query(
+ Cursor cursor = contentResolver.query(
WaypointsColumns.CONTENT_URI,
null /*projection*/,
"_id=" + waypointId,
@@ -637,7 +634,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public long getLastLocationId(long trackId) {
final String[] projection = {"_id"};
- Cursor cursor = context.getContentResolver().query(
+ Cursor cursor = contentResolver.query(
TrackPointsColumns.CONTENT_URI,
projection,
"_id=(select max(_id) from trackpoints WHERE trackid=" + trackId + ")",
@@ -661,7 +658,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public long getFirstWaypointId(long trackId) {
final String[] projection = {"_id"};
- Cursor cursor = context.getContentResolver().query(
+ Cursor cursor = contentResolver.query(
WaypointsColumns.CONTENT_URI,
projection,
"trackid=" + trackId,
@@ -685,7 +682,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public long getLastWaypointId(long trackId) {
final String[] projection = {"_id"};
- Cursor cursor = context.getContentResolver().query(
+ Cursor cursor = contentResolver.query(
WaypointsColumns.CONTENT_URI,
projection,
WaypointsColumns.TRACKID + "=" + trackId,
@@ -710,7 +707,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
public Track getLastTrack() {
Cursor cursor = null;
try {
- cursor = context.getContentResolver().query(
+ cursor = contentResolver.query(
TracksColumns.CONTENT_URI, null, "_id=(select max(_id) from tracks)",
null, null);
if (cursor != null && cursor.moveToNext()) {
@@ -729,7 +726,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public long getLastTrackId() {
String[] proj = { TracksColumns._ID };
- Cursor cursor = context.getContentResolver().query(
+ Cursor cursor = contentResolver.query(
TracksColumns.CONTENT_URI, proj, "_id=(select max(_id) from tracks)",
null, null);
if (cursor != null) {
@@ -769,7 +766,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
sortOrder += " LIMIT " + maxLocations;
}
- return context.getContentResolver().query(
+ return contentResolver.query(
TrackPointsColumns.CONTENT_URI, null, selection, null, sortOrder);
}
@@ -791,7 +788,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
sortOrder += " LIMIT " + maxWaypoints;
}
- return context.getContentResolver().query(
+ return contentResolver.query(
WaypointsColumns.CONTENT_URI, null, selection, null, sortOrder);
}
@@ -847,7 +844,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public Cursor getTracksCursor(String selection) {
- Cursor cursor = context.getContentResolver().query(
+ Cursor cursor = contentResolver.query(
TracksColumns.CONTENT_URI, null, selection, null, "_id");
return cursor;
}
@@ -855,14 +852,14 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public Uri insertTrack(Track track) {
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertTrack");
- return context.getContentResolver().insert(TracksColumns.CONTENT_URI,
+ return contentResolver.insert(TracksColumns.CONTENT_URI,
createContentValues(track));
}
@Override
public Uri insertTrackPoint(Location location, long trackId) {
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertTrackPoint");
- return context.getContentResolver().insert(TrackPointsColumns.CONTENT_URI,
+ return contentResolver.insert(TrackPointsColumns.CONTENT_URI,
createContentValues(location, trackId));
}
@@ -875,14 +872,14 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
values[i] = createContentValues(locations[i], trackId);
}
- return context.getContentResolver().bulkInsert(TrackPointsColumns.CONTENT_URI, values);
+ return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI, values);
}
@Override
public Uri insertWaypoint(Waypoint waypoint) {
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertWaypoint");
waypoint.setId(-1);
- return context.getContentResolver().insert(WaypointsColumns.CONTENT_URI,
+ return contentResolver.insert(WaypointsColumns.CONTENT_URI,
createContentValues(waypoint));
}
@@ -891,7 +888,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
Cursor cursor = null;
try {
final String[] projection = { TracksColumns._ID };
- cursor = context.getContentResolver().query(
+ cursor = contentResolver.query(
TracksColumns.CONTENT_URI,
projection,
TracksColumns._ID + "=" + id/*selection*/,
@@ -911,7 +908,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public void updateTrack(Track track) {
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.updateTrack");
- context.getContentResolver().update(TracksColumns.CONTENT_URI,
+ contentResolver.update(TracksColumns.CONTENT_URI,
createContentValues(track), "_id=" + track.getId(), null);
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/GpxImporter.java b/MyTracks/src/com/google/android/apps/mytracks/io/GpxImporter.java
index e89b44e01..571859cc9 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/GpxImporter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/GpxImporter.java
@@ -468,8 +468,7 @@ public class GpxImporter extends DefaultHandler {
// initialize start time with time of first track point
if (statsBuilder == null) {
- statsBuilder = new TripStatisticsBuilder();
- statsBuilder.resumeAt(time);
+ statsBuilder = new TripStatisticsBuilder(time);
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/DefaultTrackNameFactory.java b/MyTracks/src/com/google/android/apps/mytracks/services/DefaultTrackNameFactory.java
index a74c5778b..802199460 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/DefaultTrackNameFactory.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/DefaultTrackNameFactory.java
@@ -28,7 +28,7 @@ import java.util.Date;
/**
* Creates a default track name based on the current default track name policy.
*
- * @author simmonmt@google.com (Matthew Simmons)
+ * @author Matthew Simmons
*/
class DefaultTrackNameFactory {
private static final String TIMESTAMP_DATE_FORMAT = "yyyy-MM-dd HH:mm";
@@ -60,13 +60,7 @@ class DefaultTrackNameFactory {
protected boolean useTimestampTrackName() {
SharedPreferences prefs =
context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
-
- boolean useTimestamp = false;
- if (prefs != null) {
- useTimestamp = prefs.getBoolean(
- context.getString(R.string.timestamp_track_name_key), true);
- }
-
- return useTimestamp;
+ return prefs.getBoolean(
+ context.getString(R.string.timestamp_track_name_key), true);
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/PreferenceManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/PreferenceManager.java
index 77e7e7f04..311c17526 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/PreferenceManager.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/PreferenceManager.java
@@ -20,6 +20,7 @@ import com.google.android.apps.mytracks.MyTracksSettings;
import com.google.android.maps.mytracks.R;
import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
import android.util.Log;
/**
@@ -30,6 +31,7 @@ import android.util.Log;
public class PreferenceManager {
private TrackRecordingService service;
private final String announcementFrequencyKey;
+ private final String autoResumeTrackCurrentRetryKey;
private final String autoResumeTrackTimeoutKey;
private final String maxRecordingDistanceKey;
private final String metricUnitsKey;
@@ -40,11 +42,22 @@ public class PreferenceManager {
private final String signalSamplingFrequencyKey;
private final String splitFrequencyKey;
+ private final SharedPreferences sharedPreferences;
+
public PreferenceManager(TrackRecordingService service) {
this.service = service;
-
+ this.sharedPreferences =
+ service.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
+ if (sharedPreferences == null) {
+ Log.w(MyTracksConstants.TAG,
+ "TrackRecordingService: Couldn't get shared preferences.");
+ throw new IllegalStateException("Couldn't get shared preferences");
+ }
+
announcementFrequencyKey =
service.getString(R.string.announcement_frequency_key);
+ autoResumeTrackCurrentRetryKey =
+ service.getString(R.string.auto_resume_track_current_retry_key);
autoResumeTrackTimeoutKey =
service.getString(R.string.auto_resume_track_timeout_key);
maxRecordingDistanceKey =
@@ -57,12 +70,12 @@ public class PreferenceManager {
service.getString(R.string.min_recording_interval_key);
minRequiredAccuracyKey =
service.getString(R.string.min_required_accuracy_key);
- splitFrequencyKey =
- service.getString(R.string.split_frequency_key);
- signalSamplingFrequencyKey =
- service.getString(R.string.signal_sampling_frequency_key);
recordingTrackKey =
service.getString(R.string.recording_track_key);
+ signalSamplingFrequencyKey =
+ service.getString(R.string.signal_sampling_frequency_key);
+ splitFrequencyKey =
+ service.getString(R.string.split_frequency_key);
}
/**
@@ -72,14 +85,6 @@ public class PreferenceManager {
* @param key the key that changed (may be null to update all preferences)
*/
public void onSharedPreferenceChanged(String key) {
- SharedPreferences sharedPreferences =
- service.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
- if (sharedPreferences == null) {
- Log.w(MyTracksConstants.TAG,
- "TrackRecordingService: Couldn't get shared preferences.");
- return;
- }
-
if (key == null || key.equals(minRecordingDistanceKey)) {
service.setMinRecordingDistance(
sharedPreferences.getInt(
@@ -156,4 +161,16 @@ public class PreferenceManager {
sharedPreferences.getBoolean(metricUnitsKey, true));
}
}
+
+ public void setAutoResumeTrackCurrentRetry(int retryAttempts) {
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putInt(autoResumeTrackCurrentRetryKey, retryAttempts);
+ editor.commit();
+ }
+
+ public void setRecordingTrack(long id) {
+ Editor editor = sharedPreferences.edit();
+ editor.putLong(recordingTrackKey, id);
+ editor.commit();
+ }
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/StatusAnnouncerTask.java b/MyTracks/src/com/google/android/apps/mytracks/services/StatusAnnouncerTask.java
index 158a978ae..537586f87 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/StatusAnnouncerTask.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/StatusAnnouncerTask.java
@@ -27,6 +27,8 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
import android.util.Log;
import java.util.Locale;
@@ -63,6 +65,26 @@ public class StatusAnnouncerTask implements PeriodicTask {
*/
private boolean ready = false;
+ /**
+ * Whether we're allowed to speak right now.
+ */
+ private boolean speechAllowed;
+
+ /**
+ * Listener which updates {@link #speechAllowed} when the phone state changes.
+ */
+ private final PhoneStateListener phoneListener = new PhoneStateListener() {
+ @Override
+ public void onCallStateChanged(int state, String incomingNumber) {
+ speechAllowed = state == TelephonyManager.CALL_STATE_IDLE;
+
+ if (!speechAllowed && tts.isSpeaking()) {
+ // If we're already speaking, stop it.
+ tts.stop();
+ }
+ }
+ };
+
/**
* Constructs the announcer and start the TTS engine.
*/
@@ -94,8 +116,8 @@ public class StatusAnnouncerTask implements PeriodicTask {
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
+ // 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;
}
@@ -116,6 +138,12 @@ public class StatusAnnouncerTask implements PeriodicTask {
return;
}
+ if (!speechAllowed) {
+ Log.i(MyTracksConstants.TAG,
+ "Not making announcement - not allowed at this time");
+ return;
+ }
+
if (service == null || service.getTripStatistics() == null) {
Log.e(MyTracksConstants.TAG, "StatusAnnouncer stats not initialized.");
return;
@@ -193,12 +221,23 @@ public class StatusAnnouncerTask implements PeriodicTask {
context.getString(speedLabel));
}
- public void shutdown() {
- tts.shutdown();
+ @Override
+ public void start() {
+ // Register ourselves as a listener so we won't speak during a call.
+ speechAllowed = true;
+ TelephonyManager telephony =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ telephony.listen(phoneListener, PhoneStateListener.LISTEN_CALL_STATE);
}
@Override
- public void start() {
+ public void shutdown() {
+ // Stop listening to phone state.
+ TelephonyManager telephony =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ telephony.listen(phoneListener, PhoneStateListener.LISTEN_NONE);
+
+ tts.shutdown();
}
public static int getVolumeStream() {
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java
index 2ae835afe..7613e1796 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java
@@ -28,6 +28,7 @@ import com.google.android.apps.mytracks.content.WaypointsColumns;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
import com.google.android.apps.mytracks.util.ApiFeatures;
+import com.google.android.apps.mytracks.util.ApiPlatformAdapter;
import com.google.android.apps.mytracks.util.MyTracksUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
@@ -69,7 +70,7 @@ public class TrackRecordingService extends Service implements LocationListener {
private static final String STATISTICS_ICON_URL =
"http://maps.google.com/mapfiles/ms/micons/ylw-pushpin.png";
- private static final int MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS = 3;
+ static final int MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS = 3;
private NotificationManager notificationManager;
private LocationManager locationManager;
@@ -96,8 +97,8 @@ public class TrackRecordingService extends Service implements LocationListener {
*/
private MyTracksProviderUtils providerUtils;
- private TripStatisticsBuilder statsBuilder = new TripStatisticsBuilder();
- private TripStatisticsBuilder waypointStatsBuilder = new TripStatisticsBuilder();
+ private TripStatisticsBuilder statsBuilder;
+ private TripStatisticsBuilder waypointStatsBuilder;
/**
* Current length of the recorded track. This length is calculated from the
@@ -331,6 +332,8 @@ public class TrackRecordingService extends Service implements LocationListener {
* Shows the notification message and icon in the notification bar.
*/
public void showNotification() {
+ final ApiPlatformAdapter apiPlatformAdapter =
+ ApiFeatures.getInstance().getApiPlatformAdapter();
if (isRecording) {
Notification notification = new Notification(
R.drawable.arrow_320, null /* tickerText */,
@@ -341,9 +344,10 @@ public class TrackRecordingService extends Service implements LocationListener {
notification.setLatestEventInfo(this, getString(R.string.app_name),
getString(R.string.recording_your_track), contentIntent);
notification.flags += Notification.FLAG_NO_CLEAR;
- notificationManager.notify(1, notification);
+ apiPlatformAdapter.startForeground(this, notificationManager, 1,
+ notification);
} else {
- notificationManager.cancelAll();
+ apiPlatformAdapter.stopForeground(this, notificationManager, 1);
}
}
@@ -392,9 +396,11 @@ public class TrackRecordingService extends Service implements LocationListener {
}
private void restoreStats(Track track) {
+ Log.d(MyTracksConstants.TAG,
+ "Restoring stats of track with ID: " + track.getId());
+
TripStatistics stats = track.getStatistics();
- statsBuilder = new TripStatisticsBuilder();
- statsBuilder.resumeAt(stats.getStartTime());
+ statsBuilder = new TripStatisticsBuilder(stats.getStartTime());
setUpAnnouncer();
signalManager.restore();
@@ -405,11 +411,11 @@ public class TrackRecordingService extends Service implements LocationListener {
Waypoint waypoint = providerUtils.getFirstWaypoint(recordingTrackId);
if (waypoint != null) {
currentWaypointId = waypoint.getId();
- waypointStatsBuilder = new TripStatisticsBuilder(waypoint.getStatistics());
+ waypointStatsBuilder = new TripStatisticsBuilder(
+ waypoint.getStatistics());
} else {
// This should never happen, but we got to do something so life goes on:
- waypointStatsBuilder = new TripStatisticsBuilder();
- waypointStatsBuilder.resumeAt(stats.getStartTime());
+ waypointStatsBuilder = new TripStatisticsBuilder(stats.getStartTime());
currentWaypointId = -1;
}
@@ -433,7 +439,7 @@ public class TrackRecordingService extends Service implements LocationListener {
}
statsBuilder.getStatistics().setMovingTime(stats.getMovingTime());
statsBuilder.pauseAt(stats.getStopTime());
- statsBuilder.resume();
+ statsBuilder.resumeAt(System.currentTimeMillis());
} else {
Log.e(MyTracksConstants.TAG, "Could not get track points cursor.");
}
@@ -658,6 +664,11 @@ public class TrackRecordingService extends Service implements LocationListener {
if (recordingTrack != null) {
restoreStats(recordingTrack);
isRecording = true;
+ } else {
+ // Make sure we have consistent state in shared preferences.
+ Log.w(MyTracksConstants.TAG, "TrackRecordingService.onCreate: Resetting "
+ + "an orphaned recording track: " + recordingTrackId);
+ prefManager.setRecordingTrack(recordingTrackId = -1);
}
showNotification();
}
@@ -741,8 +752,10 @@ public class TrackRecordingService extends Service implements LocationListener {
// Make sure that the current track exists and is fresh enough.
if (recordingTrack == null || !shouldResumeTrack(recordingTrack)) {
Log.i(MyTracksConstants.TAG,
- "TrackRecordingService: Not resuming because the previous track "
- + "doesn't exist or is too old");
+ "TrackRecordingService: Not resuming, because the previous track ("
+ + recordingTrack + ") doesn't exist or is too old");
+ isRecording = false;
+ prefManager.setRecordingTrack(recordingTrackId = -1);
stopSelfResult(startId);
return;
}
@@ -755,16 +768,12 @@ public class TrackRecordingService extends Service implements LocationListener {
SharedPreferences sharedPreferences, int retryAttempts) {
Log.d(MyTracksConstants.TAG,
"Updating auto-resume retry attempts to: " + retryAttempts);
-
- SharedPreferences.Editor editor = sharedPreferences.edit();
- editor.putInt(
- getString(R.string.auto_resume_track_current_retry_key), retryAttempts);
- editor.commit();
+ prefManager.setAutoResumeTrackCurrentRetry(retryAttempts);
}
private boolean shouldResumeTrack(Track track) {
- Log.d(MyTracksConstants.TAG,
- "maybeResumeTrack: autoResumeTrackTimeout = " + autoResumeTrackTimeout);
+ Log.d(MyTracksConstants.TAG, "shouldResumeTrack: autoResumeTrackTimeout = "
+ + autoResumeTrackTimeout);
// Check if we haven't exceeded the maximum number of retry attempts.
SharedPreferences sharedPreferences =
@@ -772,11 +781,11 @@ public class TrackRecordingService extends Service implements LocationListener {
int retries = sharedPreferences.getInt(
getString(R.string.auto_resume_track_current_retry_key), 0);
Log.d(MyTracksConstants.TAG,
- "TrackRecordingService: Attempting to auto-resume the track ("
+ "shouldResumeTrack: Attempting to auto-resume the track ("
+ (retries + 1) + "/" + MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS + ")");
if (retries >= MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS) {
Log.i(MyTracksConstants.TAG,
- "TrackRecordingService: Not resuming because exceeded the maximum "
+ "shouldResumeTrack: Not resuming because exceeded the maximum "
+ "number of auto-resume retries");
return false;
}
@@ -787,9 +796,13 @@ public class TrackRecordingService extends Service implements LocationListener {
// Check for special cases.
if (autoResumeTrackTimeout == 0) {
// Never resume.
+ Log.d(MyTracksConstants.TAG,
+ "shouldResumeTrack: Auto-resume disabled (never resume)");
return false;
} else if (autoResumeTrackTimeout == -1) {
// Always resume.
+ Log.d(MyTracksConstants.TAG,
+ "shouldResumeTrack: Auto-resume forced (always resume)");
return true;
}
@@ -797,7 +810,8 @@ public class TrackRecordingService extends Service implements LocationListener {
long lastModified =
track.getStatistics() != null ? track.getStatistics().getStopTime() : 0;
Log.d(MyTracksConstants.TAG,
- "maybeResumeTrack: lastModified = " + lastModified);
+ "shouldResumeTrack: lastModified = " + lastModified
+ + ", autoResumeTrackTimeout: " + autoResumeTrackTimeout);
return lastModified > 0 && System.currentTimeMillis() - lastModified <=
autoResumeTrackTimeout * 60 * 1000;
}
@@ -807,6 +821,11 @@ public class TrackRecordingService extends Service implements LocationListener {
}
public long insertWaypointMarker(Waypoint waypoint) {
+ if (!isRecording()) {
+ throw new IllegalStateException(
+ "Unable to insert waypoint marker while not recording!");
+ }
+
if (waypoint.getLocation() != null) {
waypoint.setLength(length);
waypoint.setDuration(waypoint.getLocation().getTime()
@@ -825,6 +844,11 @@ public class TrackRecordingService extends Service implements LocationListener {
* @return the unique id of the inserted marker
*/
public long insertStatisticsMarker(Location location) {
+ if (!isRecording()) {
+ throw new IllegalStateException(
+ "Unable to insert statistics marker while not recording!");
+ }
+
StringUtils utils = new StringUtils(TrackRecordingService.this);
// Create a new waypoint to save
@@ -852,8 +876,7 @@ public class TrackRecordingService extends Service implements LocationListener {
Uri uri = providerUtils.insertWaypoint(waypoint);
// Create a new stats keeper for the next marker
- waypointStatsBuilder = new TripStatisticsBuilder();
- waypointStatsBuilder.resumeAt(time);
+ waypointStatsBuilder = new TripStatisticsBuilder(time);
updateCurrentWaypoint();
return Long.parseLong(uri.getLastPathSegment());
}
@@ -910,6 +933,10 @@ public class TrackRecordingService extends Service implements LocationListener {
@Override
public void endCurrentTrack() {
Log.d(MyTracksConstants.TAG, "TrackRecordingService.endCurrentTrack");
+ if (recordingTrackId == -1 || !isRecording) {
+ throw new IllegalStateException("No recording track in progress!");
+ }
+
isRecording = false;
Track recordingTrack = providerUtils.getTrack(recordingTrackId);
if (recordingTrack != null) {
@@ -929,12 +956,15 @@ public class TrackRecordingService extends Service implements LocationListener {
"_id=" + recordingTrack.getId(), null);
}
showNotification();
- recordingTrackId = -1;
+ prefManager.setRecordingTrack(recordingTrackId = -1);
}
@Override
public void deleteAllTracks() {
- endCurrentTrack();
+ if (isRecording()) {
+ throw new IllegalStateException(
+ "Cannot delete all tracks while recording!");
+ }
providerUtils.deleteAllTracks();
}
@@ -953,6 +983,10 @@ public class TrackRecordingService extends Service implements LocationListener {
public long startNewTrack() {
Log.d(MyTracksConstants.TAG, "TrackRecordingService.startNewTrack");
+ if (recordingTrackId != -1 || isRecording) {
+ throw new IllegalStateException("A track is already in progress!");
+ }
+
long startTime = System.currentTimeMillis();
Track track = new Track();
@@ -964,26 +998,29 @@ public class TrackRecordingService extends Service implements LocationListener {
track.setId(recordingTrackId);
track.setName(new DefaultTrackNameFactory(this).newTrackName(
recordingTrackId, startTime));
- providerUtils.updateTrack(track);
-
- currentWaypointId = insertStatisticsMarker(null);
isRecording = true;
isMoving = true;
- statsBuilder = new TripStatisticsBuilder();
- statsBuilder.resumeAt(startTime);
+
+ providerUtils.updateTrack(track);
+ statsBuilder = new TripStatisticsBuilder(startTime);
+ waypointStatsBuilder = new TripStatisticsBuilder(startTime);
+ currentWaypointId = insertStatisticsMarker(null);
setUpAnnouncer();
length = 0;
showNotification();
registerLocationListener();
splitManager.restore();
signalManager.restore();
+
// Reset the number of auto-resume retries.
- SharedPreferences sharedPreferences =
- getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
- setAutoResumeTrackRetries(sharedPreferences, 0);
+ setAutoResumeTrackRetries(
+ getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0), 0);
+ // Persist the current recording track.
+ prefManager.setRecordingTrack(recordingTrackId);
+
return recordingTrackId;
}
-
+
TripStatistics getTripStatistics() {
return statsBuilder.getStatistics();
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/stats/TripStatisticsBuilder.java b/MyTracks/src/com/google/android/apps/mytracks/stats/TripStatisticsBuilder.java
index f0107e832..8882ae7d2 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/stats/TripStatisticsBuilder.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/stats/TripStatisticsBuilder.java
@@ -36,13 +36,13 @@ public class TripStatisticsBuilder {
/**
* The last location that the gps reported.
*/
- private Location lastLocation = null;
+ private Location lastLocation;
/**
* The last location that contributed to the stats. It is also the last
* location the user was found to be moving.
*/
- private Location lastMovingLocation = null;
+ private Location lastMovingLocation;
/**
* The current speed in meters/second as reported by the gps.
@@ -90,10 +90,13 @@ public class TripStatisticsBuilder {
private long totalLocations = 0;
/**
- * Creates a new trip starting at the current system time.
+ * Creates a new trip starting at the given time.
+ *
+ * @param startTime the start time.
*/
- public TripStatisticsBuilder() {
+ public TripStatisticsBuilder(long startTime) {
data = new TripStatistics();
+ resumeAt(startTime);
}
/**
@@ -103,6 +106,9 @@ public class TripStatisticsBuilder {
*/
public TripStatisticsBuilder(TripStatistics statsData) {
data = new TripStatistics(statsData);
+ if (data.getStartTime() > 0) {
+ resumeAt(data.getStartTime());
+ }
}
/**
@@ -140,8 +146,8 @@ public class TripStatisticsBuilder {
// Don't do anything if we didn't move since last fix:
double distance = lastLocation.distanceTo(currentLocation);
- if (distance < MyTracksConstants.MAX_NO_MOVEMENT_DISTANCE
- && currentSpeed < MyTracksConstants.MAX_NO_MOVEMENT_SPEED) {
+ if (distance < MyTracksConstants.MAX_NO_MOVEMENT_DISTANCE &&
+ currentSpeed < MyTracksConstants.MAX_NO_MOVEMENT_SPEED) {
lastLocation = currentLocation;
return false;
}
@@ -291,13 +297,6 @@ public class TripStatisticsBuilder {
data.updateGradeExtremities(gradeBuffer.getAverage());
}
- /**
- * Pauses the track at the current time.
- */
- public void pause() {
- pauseAt(System.currentTimeMillis());
- }
-
/**
* Pauses the track at the given time.
*
@@ -312,13 +311,6 @@ public class TripStatisticsBuilder {
paused = true;
}
- /**
- * Resumes the current track at the current time.
- */
- public void resume() {
- resumeAt(System.currentTimeMillis());
- }
-
/**
* Resumes the current track at the given time.
*
@@ -364,8 +356,7 @@ public class TripStatisticsBuilder {
}
public TripStatistics getStatistics() {
- // Take a snapshot - we do't want anyone messing with our internals
+ // Take a snapshot - we don't want anyone messing with our internals
return new TripStatistics(data);
}
}
-
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java b/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java
index b6a4e02fd..7979e12b5 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java
@@ -15,7 +15,10 @@
*/
package com.google.android.apps.mytracks.util;
+import com.google.android.apps.mytracks.MyTracksConstants;
+
import android.os.Build;
+import android.util.Log;
/**
* Utility class for determining if newer-API features are available on the
@@ -28,9 +31,15 @@ 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);
-
+ public static final int ANDROID_API_LEVEL = Integer.parseInt(
+ Build.VERSION.SDK);
+
private static ApiFeatures instance;
+
+ /**
+ * The API platform adapter supported by this system.
+ */
+ private ApiPlatformAdapter apiPlatformAdapter;
/**
* Returns the singleton instance of this class.
@@ -52,7 +61,28 @@ public class ApiFeatures {
/**
* Allow subclasses for mocking, but no direct instantiation.
*/
- protected ApiFeatures() {}
+ protected ApiFeatures() {
+ if (getApiLevel() >= 5) {
+ try {
+ Class> clazz = Class.forName(
+ "com.google.android.apps.mytracks.util.EclairPlatformAdapter");
+ apiPlatformAdapter = (ApiPlatformAdapter) clazz.newInstance();
+ } catch (Exception e) {
+ Log.i(MyTracksConstants.TAG, "ApiFeatures: Unable to instantiate Eclair"
+ + " platform adapter", e);
+ }
+ }
+ if (apiPlatformAdapter == null) {
+ Log.i(MyTracksConstants.TAG,
+ "ApiFeatures: Using default platform adapter");
+ // Cupcake adapter is always supported, so it's safe to do static linkage.
+ apiPlatformAdapter = new CupcakePlatformAdapter();
+ }
+ }
+
+ public ApiPlatformAdapter getApiPlatformAdapter() {
+ return apiPlatformAdapter;
+ }
/**
* Returns whether cloud backup (a.k.a. Froyo backup) is available.
@@ -81,7 +111,7 @@ public class ApiFeatures {
public boolean hasModernSignalStrength() {
return getApiLevel() >= 7;
}
-
+
// Visible for testing.
protected int getApiLevel() {
return ANDROID_API_LEVEL;
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/ApiPlatformAdapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/ApiPlatformAdapter.java
new file mode 100644
index 000000000..1e2a5210a
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/ApiPlatformAdapter.java
@@ -0,0 +1,60 @@
+/*
+ * 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.app.Notification;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.os.HandlerThread;
+
+/**
+ * A set of methods that may be implemented in a platform specific way.
+ *
+ * @author Bartlomiej Niechwiej
+ */
+public interface ApiPlatformAdapter {
+
+ /**
+ * Puts the specified service into foreground.
+ *
+ * @param service the service to be put in foreground.
+ * @param notificationManager the notification manager used to post the given
+ * notification.
+ * @param id the ID of the notification, unique within the application.
+ * @param notification the notification to post.
+ */
+ void startForeground(Service service, NotificationManager notificationManager,
+ int id, Notification notification);
+
+ /**
+ * Puts the given service into background.
+ * @param service the service to put into background.
+ * @param notificationManager the notification manager to user when removing
+ * notifications.
+ * @param id the ID of the notification to be remove, or -1 if the
+ * notification shouldn't be removed.
+ */
+ void stopForeground(Service service, NotificationManager notificationManager,
+ int id);
+
+ /**
+ * Terminates the given handler thread.
+ *
+ * @param handlerThread the thread to be terminated.
+ * @return true whether the handler has been stopped or not.
+ */
+ boolean stopHandlerThread(HandlerThread handlerThread);
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/CupcakePlatformAdapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/CupcakePlatformAdapter.java
new file mode 100644
index 000000000..272b47218
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/CupcakePlatformAdapter.java
@@ -0,0 +1,53 @@
+/*
+ * 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.app.Notification;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.os.HandlerThread;
+
+/**
+ * The Cupcake (API level 3) specific implementation of the
+ * {@link ApiPlatformAdapter}.
+ *
+ * @author Bartlomiej Niechwiej
+ */
+public class CupcakePlatformAdapter implements ApiPlatformAdapter {
+
+ @Override
+ public void startForeground(Service service,
+ NotificationManager notificationManager, int id,
+ Notification notification) {
+ service.setForeground(true);
+ notificationManager.notify(id, notification);
+ }
+
+ @Override
+ public void stopForeground(Service service,
+ NotificationManager notificationManager, int id) {
+ service.setForeground(false);
+ if (id != -1) {
+ notificationManager.cancel(id);
+ }
+ }
+
+ @Override
+ public boolean stopHandlerThread(HandlerThread handlerThread) {
+ // Do nothing, as Cupcake doesn't provide quit().
+ return false;
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/EclairPlatformAdapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/EclairPlatformAdapter.java
new file mode 100644
index 000000000..9c32946d3
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/EclairPlatformAdapter.java
@@ -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.util;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.os.HandlerThread;
+
+/**
+ * The Eclair (API level 5) specific implementation of the
+ * {@link ApiPlatformAdapter}.
+ *
+ * @author Bartlomiej Niechwiej
+ */
+public class EclairPlatformAdapter implements ApiPlatformAdapter {
+
+ @Override
+ public void startForeground(Service service,
+ NotificationManager notificationManager, int id,
+ Notification notification) {
+ service.startForeground(id, notification);
+ }
+
+ @Override
+ public void stopForeground(Service service,
+ NotificationManager notificationManager, int id) {
+ service.stopForeground(id != -1);
+ }
+
+ @Override
+ public boolean stopHandlerThread(HandlerThread handlerThread) {
+ return handlerThread.quit();
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/FileUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/FileUtils.java
index c2a380e88..6c6ca7af0 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/util/FileUtils.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/FileUtils.java
@@ -37,7 +37,7 @@ public class FileUtils {
* A set of characters that are prohibited from being in file names.
*/
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.
@@ -69,8 +69,6 @@ public class FileUtils {
* Normalizes the input string and make sure it is a valid fat32 file name.
*
* @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
*/
String sanitizeName(String name) {
diff --git a/MyTracksTest/.factorypath b/MyTracksTest/.factorypath
index 25bd193ed..fefa9e647 100644
--- a/MyTracksTest/.factorypath
+++ b/MyTracksTest/.factorypath
@@ -1,4 +1,7 @@
+
+
+
diff --git a/MyTracksTest/.settings/org.eclipse.jdt.apt.core.prefs b/MyTracksTest/.settings/org.eclipse.jdt.apt.core.prefs
index 357ffd6b8..ac3541cd4 100644
--- a/MyTracksTest/.settings/org.eclipse.jdt.apt.core.prefs
+++ b/MyTracksTest/.settings/org.eclipse.jdt.apt.core.prefs
@@ -1,7 +1,7 @@
-#Thu Aug 12 19:30:48 BRT 2010
+#Mon Nov 01 02:42:03 BRST 2010
eclipse.preferences.version=1
org.eclipse.jdt.apt.aptEnabled=true
org.eclipse.jdt.apt.genSrcDir=.apt_generated
-org.eclipse.jdt.apt.processorOptions/bin_dir=MyTracksTest/bin
-org.eclipse.jdt.apt.processorOptions/logfile=MyTracksTest/annotations.log
+org.eclipse.jdt.apt.processorOptions/bin_dir=%PROJECT.DIR%/bin
+org.eclipse.jdt.apt.processorOptions/logfile=%PROJECT.DIR%/annotations.log
org.eclipse.jdt.apt.reconcileEnabled=false
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/ChartValueSeriesTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/ChartValueSeriesTest.java
new file mode 100644
index 000000000..afefd4910
--- /dev/null
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/ChartValueSeriesTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.maps.mytracks.R;
+
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.test.AndroidTestCase;
+
+/**
+ * @author Sandor Dornbush
+ */
+public class ChartValueSeriesTest extends AndroidTestCase {
+ private ChartValueSeries series;
+ private Paint fillPaint1;
+
+ @Override
+ protected void setUp() throws Exception {
+ fillPaint1 = new Paint();
+ fillPaint1.setStyle(Style.FILL);
+ fillPaint1.setColor(getContext().getResources().getColor(R.color.green));
+ fillPaint1.setAntiAlias(true);
+ series = new ChartValueSeries(getContext(),
+ "###,###",
+ fillPaint1,
+ null,
+ 100,
+ R.string.elevation);
+ }
+
+ public void testInitialConditions() {
+ assertEquals(25, series.getInterval());
+ assertEquals(1, series.getMaxLabelLength());
+ assertEquals(0, series.getMin());
+ assertEquals(0, series.getMax());
+ assertEquals(0.0, series.getSpread());
+ assertEquals(fillPaint1, series.getPaint());
+ assertEquals(getContext().getString(R.string.elevation),
+ series.getTitle());
+ assertTrue(series.isEnabled());
+ }
+
+ public void testEnabled() {
+ series.setEnabled(false);
+ assertFalse(series.isEnabled());
+ }
+
+ public void testSmallUpdates() {
+ series.update(0);
+ series.update(10);
+ series.updateDimension();
+ assertEquals(25, series.getInterval());
+ assertEquals(3, series.getMaxLabelLength());
+ assertEquals(0, series.getMin());
+ assertEquals(100, series.getMax());
+ assertEquals(100.0, series.getSpread());
+ }
+
+ public void testBigUpdates() {
+ series.update(0);
+ series.update(901);
+ series.updateDimension();
+ assertEquals(200, series.getInterval());
+ assertEquals(5, series.getMaxLabelLength());
+ assertEquals(0, series.getMin());
+ assertEquals(1000, series.getMax());
+ assertEquals(1000.0, series.getSpread());
+ }
+
+ public void testNotZeroBasedUpdates() {
+ series.update(500);
+ series.update(1401);
+ series.updateDimension();
+ assertEquals(200, series.getInterval());
+ assertEquals(5, series.getMaxLabelLength());
+ assertEquals(500, series.getMin());
+ assertEquals(1500, series.getMax());
+ assertEquals(1000.0, series.getSpread());
+ }
+}
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/services/DefaultTrackNameFactoryTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/services/DefaultTrackNameFactoryTest.java
index 62e529a14..855619cb7 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/services/DefaultTrackNameFactoryTest.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/DefaultTrackNameFactoryTest.java
@@ -21,7 +21,16 @@ import android.test.AndroidTestCase;
import java.text.SimpleDateFormat;
import java.util.Date;
+/**
+ * Tests {@link DefaultTrackNameFactory}
+ *
+ * @author Matthew Simmons
+ */
public class DefaultTrackNameFactoryTest extends AndroidTestCase {
+ /**
+ * A version of the factory which allows us to supply our own answer as to
+ * whether a timestamp-based track name should be used.
+ */
private static class MockDefaultTrackNameFactory
extends DefaultTrackNameFactory {
private final boolean useTimestamp;
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java
index 8a768f637..fc7ae5396 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java
@@ -15,12 +15,33 @@
*/
package com.google.android.apps.mytracks.services;
+import static com.google.android.apps.mytracks.MyTracksConstants.RESUME_TRACK_EXTRA_NAME;
+
+import com.google.android.apps.mytracks.MyTracksSettings;
+import com.google.android.apps.mytracks.content.MyTracksProvider;
+import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
+import com.google.android.apps.mytracks.content.Track;
+import com.google.android.apps.mytracks.content.Waypoint;
+import com.google.android.apps.mytracks.stats.TripStatistics;
+import com.google.android.apps.mytracks.util.ApiFeatures;
+import com.google.android.maps.mytracks.R;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.location.Location;
import android.os.IBinder;
+import android.test.RenamingDelegatingContext;
import android.test.ServiceTestCase;
+import android.test.mock.MockContentResolver;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
+import java.util.List;
+
/**
* Tests for the MyTracks track recording service.
*
@@ -28,24 +49,513 @@ import android.test.suitebuilder.annotation.SmallTest;
*/
public class TrackRecordingServiceTest
extends ServiceTestCase {
-
+
+ private Context context;
+ private MyTracksProviderUtils providerUtils;
+ private SharedPreferences sharedPreferences;
+
public TrackRecordingServiceTest() {
super(TrackRecordingService.class);
}
+
+ /**
+ * A context wrapper with the user provided {@link ContentResolver}.
+ */
+ private static class MockContext extends ContextWrapper {
+ private final ContentResolver contentResolver;
+
+ public MockContext(ContentResolver contentResolver, Context base) {
+ super(base);
+ this.contentResolver = contentResolver;
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return contentResolver;
+ }
+ }
+
+ /**
+ * A mock class that forces API level < 5 to make sure we can workaround a bug
+ * in ServiceTestCase (throwing a NPE).
+ * See http://code.google.com/p/android/issues/detail?id=12122 for more
+ * details.
+ */
+ private static class MockApiFeatures extends ApiFeatures {
+ @Override
+ protected int getApiLevel() {
+ return 4;
+ }
+ }
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ ApiFeatures.injectInstance(new MockApiFeatures());
+
+ MockContentResolver mockContentResolver = new MockContentResolver();
+ RenamingDelegatingContext targetContext = new RenamingDelegatingContext(
+ getContext(), getContext(), "test.");
+ context = new MockContext(mockContentResolver, targetContext);
+ MyTracksProvider provider = new MyTracksProvider();
+ provider.attachInfo(context, null);
+ mockContentResolver.addProvider(MyTracksProviderUtils.AUTHORITY, provider);
+ setContext(context);
+
+ providerUtils = MyTracksProviderUtils.Factory.get(context);
+
+ sharedPreferences = context.getSharedPreferences(
+ MyTracksSettings.SETTINGS_NAME, 0);
+ // Disable auto resume by default.
+ updateAutoResumePrefs(0, -1);
+ // No recording track.
+ Editor editor = sharedPreferences.edit();
+ editor.putLong(context.getString(R.string.recording_track_key), -1);
+ editor.commit();
+ }
+
@SmallTest
public void testStartable() {
- Intent startIntent = new Intent();
- startIntent.setClass(getContext(), TrackRecordingService.class);
- startService(startIntent);
+ startService(createStartIntent());
assertNotNull(getService());
}
@MediumTest
public void testBindable() {
- Intent startIntent = new Intent();
- startIntent.setClass(getContext(), TrackRecordingService.class);
- IBinder service = bindService(startIntent);
+ IBinder service = bindService(createStartIntent());
assertNotNull(service);
}
+
+ @MediumTest
+ public void testResumeAfterReboot_shouldResume() throws Exception {
+ // Insert a dummy track and mark it as recording track.
+ createDummyTrack(123, System.currentTimeMillis(), true);
+
+ // Clear the number of attempts and set the timeout to 10 min.
+ updateAutoResumePrefs(0, 10);
+
+ // Start the service in "resume" mode (simulates the on-reboot action).
+ Intent startIntent = createStartIntent();
+ startIntent.putExtra(RESUME_TRACK_EXTRA_NAME, true);
+ startService(startIntent);
+ assertNotNull(getService());
+
+ // We expect to resume the previous track.
+ assertTrue(getService().isRecording());
+ ITrackRecordingService service = bindAndGetService(createStartIntent());
+ assertEquals(123, service.getRecordingTrackId());
+ }
+
+ @MediumTest
+ public void testResumeAfterReboot_simulateReboot() throws Exception {
+ updateAutoResumePrefs(0, 10);
+ ITrackRecordingService service = bindAndGetService(createStartIntent());
+ assertFalse(service.isRecording());
+
+ // Simulate recording a track.
+ long id = service.startNewTrack();
+ assertTrue(service.isRecording());
+ assertEquals(id, service.getRecordingTrackId());
+ shutdownService();
+ assertEquals(id, sharedPreferences.getLong(
+ context.getString(R.string.recording_track_key), -1));
+
+ // Start the service in "resume" mode (simulates the on-reboot action).
+ Intent startIntent = createStartIntent();
+ startIntent.putExtra(RESUME_TRACK_EXTRA_NAME, true);
+ startService(startIntent);
+ assertNotNull(getService());
+
+ // TODO: shutdownService() has a bug and doesn't set mServiceCreated
+ // to false, thus preventing from a second call to onCreate().
+ // Report the bug to Android team. Until then, the following check
+ // must be commented out.
+
+ // assertTrue(getService().isRecording());
+ }
+
+ @MediumTest
+ public void testResumeAfterReboot_noRecordingTrack() throws Exception {
+ // Insert a dummy track and mark it as recording track.
+ createDummyTrack(123, System.currentTimeMillis(), false);
+
+ // Clear the number of attempts and set the timeout to 10 min.
+ updateAutoResumePrefs(0, 10);
+
+ // Start the service in "resume" mode (simulates the on-reboot action).
+ Intent startIntent = createStartIntent();
+ startIntent.putExtra(RESUME_TRACK_EXTRA_NAME, true);
+ startService(startIntent);
+ assertNotNull(getService());
+
+ // We don't expect to resume the previous track, because it was stopped.
+ assertFalse(getService().isRecording());
+ ITrackRecordingService service = bindAndGetService(createStartIntent());
+ assertEquals(-1, service.getRecordingTrackId());
+ }
+
+ @MediumTest
+ public void testResumeAfterReboot_expiredTrack() throws Exception {
+ // Insert a dummy track last updated 20 min ago.
+ createDummyTrack(123, System.currentTimeMillis() - 20 * 60 * 1000, true);
+
+ // Clear the number of attempts and set the timeout to 10 min.
+ updateAutoResumePrefs(0, 10);
+
+ // Start the service in "resume" mode (simulates the on-reboot action).
+ Intent startIntent = createStartIntent();
+ startIntent.putExtra(RESUME_TRACK_EXTRA_NAME, true);
+ startService(startIntent);
+ assertNotNull(getService());
+
+ // We don't expect to resume the previous track, because it has expired.
+ assertFalse(getService().isRecording());
+ ITrackRecordingService service = bindAndGetService(createStartIntent());
+ assertEquals(-1, service.getRecordingTrackId());
+ }
+
+ @MediumTest
+ public void testResumeAfterReboot_tooManyAttempts() throws Exception {
+ // Insert a dummy track.
+ createDummyTrack(123, System.currentTimeMillis(), true);
+
+ // Set the number of attempts to max.
+ updateAutoResumePrefs(
+ TrackRecordingService.MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS, 10);
+
+ // Start the service in "resume" mode (simulates the on-reboot action).
+ Intent startIntent = createStartIntent();
+ startIntent.putExtra(RESUME_TRACK_EXTRA_NAME, true);
+ startService(startIntent);
+ assertNotNull(getService());
+
+ // We don't expect to resume the previous track, because there were already
+ // too many attempts.
+ assertFalse(getService().isRecording());
+ ITrackRecordingService service = bindAndGetService(createStartIntent());
+ assertEquals(-1, service.getRecordingTrackId());
+ }
+
+ @MediumTest
+ public void testRecording_noTracks() throws Exception {
+ List