diff --git a/MyTracks/AndroidManifest.xml b/MyTracks/AndroidManifest.xml
index 166ec86ff..b2c4d90cd 100755
--- a/MyTracks/AndroidManifest.xml
+++ b/MyTracks/AndroidManifest.xml
@@ -44,6 +44,110 @@ limitations under the License.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -52,44 +156,6 @@ limitations under the License.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/MyTracks/res/values-ja/strings.xml b/MyTracks/res/values-ja/strings.xml
index 5732e571f..1a6a34106 100644
--- a/MyTracks/res/values-ja/strings.xml
+++ b/MyTracks/res/values-ja/strings.xml
@@ -159,8 +159,8 @@ limitations under the License.
$2 : Attempted imports
$3 : MyTracks gpx path
-->
- %3$s からGPXファイル %2$d件のうち %1$d件をインポートしました
- %s ではGPXファイルがありません.SDカードを読み込めないかディレクトリにGPXファイルが無いかも知れません.
+ %3$s からGPXファイル %2$d件のうち %1$d件をインポートしました
+ %s ではGPXファイルがありません.SDカードを読み込めないかディレクトリにGPXファイルが無いかも知れません.
ファイル %s のインポートエラー
diff --git a/MyTracks/res/values-ru/strings.xml b/MyTracks/res/values-ru/strings.xml
index 5e3068b2f..0598737ce 100644
--- a/MyTracks/res/values-ru/strings.xml
+++ b/MyTracks/res/values-ru/strings.xml
@@ -162,8 +162,8 @@ limitations under the License.
$2 : Attempted imports
$3 : MyTracks gpx path
-->
- Импортировано %1$d из %2$d GPX файлов с %3$s
- GPX файлы не найденны в %s. Возможно не читается SD-карта или каталог не содержит GPX файлы.
+ Импортировано %1$d из %2$d GPX файлов с %3$s
+ GPX файлы не найденны в %s. Возможно не читается SD-карта или каталог не содержит GPX файлы.
Ошибка импорта файла %s
diff --git a/MyTracks/res/values/do_not_translate.xml b/MyTracks/res/values/do_not_translate.xml
index 9b586e743..f6252d1db 100644
--- a/MyTracks/res/values/do_not_translate.xml
+++ b/MyTracks/res/values/do_not_translate.xml
@@ -42,4 +42,7 @@ limitations under the License.
UA-7222692-2
+
+
+ android.intent.action.SAVE
diff --git a/MyTracks/res/values/strings.xml b/MyTracks/res/values/strings.xml
index 694c2520d..5a5e33fba 100644
--- a/MyTracks/res/values/strings.xml
+++ b/MyTracks/res/values/strings.xml
@@ -163,9 +163,12 @@ limitations under the License.
$2 : Attempted imports
$3 : MyTracks gpx path
-->
- Imported %1$d of %2$d GPX files from %3$s
- No GPX files found in %s. SD card may be unreadable or directory may not contain GPX files.
+ Imported %1$d of %2$d GPX files from %3$s
+ No GPX files found in %s. SD card may be unreadable or directory may not contain GPX files.
Import error for file %s
+ Import into My Tracks
+ Successfully imported %1$d track(s) from file.
+ Show track
Delete all markers
diff --git a/MyTracks/src/com/dsi/ant/AntInterface.java b/MyTracks/src/com/dsi/ant/AntInterface.java
index 0e425e57d..5fd5ee345 100644
--- a/MyTracks/src/com/dsi/ant/AntInterface.java
+++ b/MyTracks/src/com/dsi/ant/AntInterface.java
@@ -61,10 +61,10 @@ public class AntInterface {
private static Object INSTANCE_LOCK = new Object();
/** The context to use. */
- private static Context sContext = null;
+ private Context sContext = null;
/** Listens to changes to service connection status. */
- private static ServiceListener sServiceListener;
+ private ServiceListener sServiceListener;
/** Is the ANT Radio Proxy Service connected. */
private static boolean sServiceConnected = false;
@@ -210,7 +210,7 @@ public class AntInterface {
/**
* Class for interacting with the ANT interface.
*/
- private static ServiceConnection sIAntConnection = new ServiceConnection() {
+ private final ServiceConnection sIAntConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName pClassName, IBinder pService) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
@@ -1223,7 +1223,7 @@ public class AntInterface {
*/
public boolean requestForceClaimInterface(String appName) throws AntInterfaceException
{
- if((null == appName) || ("" == appName))
+ if((null == appName) || ("".equals(appName)))
{
throw new IllegalArgumentException();
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/AccountChooser.java b/MyTracks/src/com/google/android/apps/mytracks/AccountChooser.java
index 05ba6ad01..ff2a2d0ea 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/AccountChooser.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/AccountChooser.java
@@ -83,15 +83,19 @@ public class AccountChooser {
builder.setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+
if (selectedAccountIndex >= 0) {
selectedAccount = accounts[selectedAccountIndex];
- handler.handleAccountSelected(selectedAccount);
}
+
+ handler.handleAccountSelected(selectedAccount);
}
});
builder.setNegativeButton(R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
handler.handleAccountSelected(null);
}
});
@@ -122,6 +126,7 @@ public class AccountChooser {
builder.setNegativeButton(R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
handler.handleAccountSelected(null);
}
});
diff --git a/MyTracks/src/com/google/android/apps/mytracks/AggregatedStatsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/AggregatedStatsActivity.java
index cb385f35c..3e2767bf2 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/AggregatedStatsActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/AggregatedStatsActivity.java
@@ -19,9 +19,9 @@ import java.util.List;
/**
* Activity for viewing the combined statistics for all the recorded tracks.
- *
+ *
* Other features to add - menu items to change setings.
- *
+ *
* @author Fergus Nelson
*/
public class AggregatedStatsActivity extends Activity implements
@@ -43,7 +43,7 @@ public class AggregatedStatsActivity extends Activity implements
Log.d(Constants.TAG, "StatsActivity: onSharedPreferences changed "
+ key);
if (key != null) {
- if (key.equals(R.string.metric_units_key)) {
+ if (key.equals(getString(R.string.metric_units_key))) {
metricUnits = sharedPreferences.getBoolean(
getString(R.string.metric_units_key), true);
utils.setMetricUnits(metricUnits);
@@ -86,8 +86,8 @@ public class AggregatedStatsActivity extends Activity implements
}
/**
- * 1. Reads tracks from the db
- * 2. Merges the trip stats from the tracks
+ * 1. Reads tracks from the db
+ * 2. Merges the trip stats from the tracks
* 3. Updates the view
*/
private void loadAggregatedStats() {
diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java b/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java
index 87efb8d58..1d9ba4e4c 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java
@@ -35,6 +35,8 @@ import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
import android.location.Location;
import android.os.Bundle;
import android.util.Log;
@@ -105,8 +107,6 @@ public class ChartActivity extends Activity implements TrackDataListener {
protected void onCreate(Bundle savedInstanceState) {
Log.w(TAG, "ChartActivity.onCreate");
super.onCreate(savedInstanceState);
- MyTracks.getInstance().setChartActivity(this);
- dataHub = MyTracks.getInstance().getDataHub();
// The volume we want to control is the Text-To-Speech volume
int volumeStream =
@@ -142,6 +142,7 @@ public class ChartActivity extends Activity implements TrackDataListener {
protected void onResume() {
super.onResume();
+ dataHub = TrackDataHub.getStartedInstance();
dataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED,
ListenerDataType.TRACK_UPDATES,
@@ -154,6 +155,7 @@ public class ChartActivity extends Activity implements TrackDataListener {
@Override
protected void onPause() {
dataHub.unregisterTrackDataListener(this);
+ dataHub = null;
super.onPause();
}
@@ -187,10 +189,6 @@ public class ChartActivity extends Activity implements TrackDataListener {
runOnUiThread(updateChart);
}
- public boolean isSeriesEnabled(int index) {
- return chartView.getChartValueSeries(index).isEnabled();
- }
-
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
@@ -205,13 +203,36 @@ public class ChartActivity extends Activity implements TrackDataListener {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case Constants.MENU_CHART_SETTINGS:
- MyTracks.getInstance().getDialogManager().showDialogSafely(
- DialogManager.DIALOG_CHART_SETTINGS);
+ showSettingsDialog();
return true;
}
return super.onOptionsItemSelected(item);
}
+ private void showSettingsDialog() {
+ final ChartSettingsDialog settingsDialog = new ChartSettingsDialog(this);
+ settingsDialog.setOwnerActivity(this);
+
+ settingsDialog.setMode(mode);
+ for (int i = 0; i < ChartView.NUM_SERIES; i++) {
+ settingsDialog.setSeriesEnabled(i, chartView.getChartValueSeries(i).isEnabled());
+ }
+
+ settingsDialog.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface arg0, int which) {
+ if (which != DialogInterface.BUTTON_POSITIVE) return;
+
+ for (int i = 0; i < ChartView.NUM_SERIES; i++) {
+ chartView.getChartValueSeries(i).setEnabled(settingsDialog.isSeriesEnabled(i));
+ }
+ setMode(settingsDialog.getMode());
+ }
+ });
+
+ settingsDialog.show();
+ }
+
/**
* Given a location, creates a new data point for the chart. A data point is
* an array double[3 or 6], where:
diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartSettingsDialog.java b/MyTracks/src/com/google/android/apps/mytracks/ChartSettingsDialog.java
index 34d64e82f..228cee046 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/ChartSettingsDialog.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/ChartSettingsDialog.java
@@ -36,6 +36,7 @@ import android.widget.RadioGroup;
public class ChartSettingsDialog extends Dialog {
private RadioButton distance;
private CheckBox[] series;
+ private OnClickListener clickListener;
public ChartSettingsDialog(Context context) {
super(context);
@@ -44,23 +45,31 @@ public class ChartSettingsDialog extends Dialog {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.chart_settings);
Button cancel = (Button) findViewById(R.id.chart_settings_cancel);
cancel.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
+ if (clickListener != null) {
+ clickListener.onClick(ChartSettingsDialog.this, BUTTON_NEGATIVE);
+ }
dismiss();
}
});
- Button ok = (Button) findViewById(R.id.chart_settings_ok);
- ok.setOnClickListener(new View.OnClickListener() {
+
+ Button okButton = (Button) findViewById(R.id.chart_settings_ok);
+ okButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
- handleOk();
+ if (clickListener != null) {
+ clickListener.onClick(ChartSettingsDialog.this, BUTTON_POSITIVE);
+ }
+ dismiss();
}
});
distance = (RadioButton) findViewById(R.id.chart_settings_by_distance);
-
+
series = new CheckBox[ChartView.NUM_SERIES];
series[ChartView.ELEVATION_SERIES] =
(CheckBox) findViewById(R.id.chart_settings_elevation);
@@ -74,29 +83,30 @@ public class ChartSettingsDialog extends Dialog {
(CheckBox) findViewById(R.id.chart_settings_heart_rate);
}
- public void setup(ChartActivity chart) {
- if (chart == null) {
- return;
- }
-
+ public void setMode(Mode mode) {
RadioGroup rd = (RadioGroup) findViewById(R.id.chart_settings_x);
- rd.check(chart.getMode() == Mode.BY_DISTANCE
+ rd.check(mode == Mode.BY_DISTANCE
? R.id.chart_settings_by_distance
: R.id.chart_settings_by_time);
- for (int i = 0; i < ChartView.NUM_SERIES; i++) {
- series[i].setChecked(chart.isSeriesEnabled(i));
- }
}
- private void handleOk() {
- ChartActivity chart = MyTracks.getInstance().getChartActivity();
- chart.setMode(distance.isChecked() ? Mode.BY_DISTANCE : Mode.BY_TIME);
+ public void setSeriesEnabled(int seriesIdx, boolean enabled) {
+ series[seriesIdx].setChecked(enabled);
+ }
- // TODO: check that something is visible.
- for (int i = 0; i < ChartView.NUM_SERIES; i++) {
- chart.setSeriesEnabled(i, series[i].isChecked());
- }
+ public Mode getMode() {
+ if (distance == null) return Mode.BY_DISTANCE;
- dismiss();
+ return distance.isSelected() ? Mode.BY_DISTANCE : Mode.BY_TIME;
+ }
+
+ public boolean isSeriesEnabled(int seriesIdx) {
+ if (series == null) return true;
+
+ return series[seriesIdx].isChecked();
+ }
+
+ public void setOnClickListener(OnClickListener clickListener) {
+ this.clickListener = clickListener;
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartView.java b/MyTracks/src/com/google/android/apps/mytracks/ChartView.java
index 0a6e665ad..e9e308508 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/ChartView.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/ChartView.java
@@ -49,6 +49,7 @@ import java.util.ArrayList;
* @author Leif Hendrik Wilden
*/
public class ChartView extends View {
+ private static final int MIN_ZOOM_LEVEL = 1;
/*
* Scrolling logic:
@@ -62,7 +63,6 @@ public class ChartView extends View {
* Zoom logic:
*/
private int zoomLevel = 1;
- private final int minZoomLevel = 1;
private int maxZoomLevel = 10;
private static final int MAX_INTERVALS = 5;
@@ -333,7 +333,7 @@ public class ChartView extends View {
* @return true if the chart can be zoomed out
*/
public boolean canZoomOut() {
- return zoomLevel > minZoomLevel;
+ return zoomLevel > MIN_ZOOM_LEVEL;
}
/**
@@ -593,7 +593,7 @@ public class ChartView extends View {
final float x = getWaypointX(waypoint);
c.drawLine(x, h - bottomBorder, x, topBorder, gridPaint);
- c.translate(x - markerWidth / 2, markerHeight);
+ c.translate(x - (float) markerWidth / 2.0f, (float) markerHeight);
if (waypoints.get(i).getType() == Waypoint.TYPE_STATISTICS) {
statsMarker.draw(c);
} else {
diff --git a/MyTracks/src/com/google/android/apps/mytracks/Constants.java b/MyTracks/src/com/google/android/apps/mytracks/Constants.java
index c3fc58acf..063bb6679 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/Constants.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/Constants.java
@@ -50,7 +50,7 @@ public abstract class Constants {
public static final int AUTHENTICATE_TO_DOCLIST = 10;
public static final int AUTHENTICATE_TO_TRIX = 11;
public static final int DELETE_TRACK = 13;
- public static final int SEND_TO_GOOGLE_DIALOG = 14;
+ public static final int SEND_TO_GOOGLE = 14;
public static final int SHARE_LINK = 15;
public static final int SHARE_GPX_FILE = 16;
public static final int SHARE_KML_FILE = 17;
@@ -190,7 +190,7 @@ public abstract class Constants {
public static int getActionFromMenuId(int menuId) {
switch (menuId) {
case Constants.MENU_SEND_TO_GOOGLE:
- return Constants.SEND_TO_GOOGLE_DIALOG;
+ return Constants.SEND_TO_GOOGLE;
case Constants.MENU_EDIT:
return Constants.EDIT_DETAILS;
case Constants.MENU_DELETE:
diff --git a/MyTracks/src/com/google/android/apps/mytracks/DialogManager.java b/MyTracks/src/com/google/android/apps/mytracks/DialogManager.java
index 2d2d17312..01eba2c3e 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/DialogManager.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/DialogManager.java
@@ -17,14 +17,12 @@ package com.google.android.apps.mytracks;
import static com.google.android.apps.mytracks.Constants.TAG;
-import com.google.android.apps.mytracks.io.sendtogoogle.SendDialog;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
-import android.app.ProgressDialog;
-import android.os.Bundle;
+import android.content.DialogInterface;
import android.util.Log;
import android.view.WindowManager.BadTokenException;
@@ -35,148 +33,22 @@ import android.view.WindowManager.BadTokenException;
*/
public class DialogManager {
- public static final int DIALOG_CHART_SETTINGS = 1;
- public static final int DIALOG_IMPORT_PROGRESS = 2;
- public static final int DIALOG_PROGRESS = 3;
- public static final int DIALOG_SEND_TO_GOOGLE = 4;
-
- private ProgressDialog progressDialog;
- private ProgressDialog importProgressDialog;
- private SendDialog sendToGoogleDialog;
- private ChartSettingsDialog chartSettingsDialog;
-
- private MyTracks activity;
-
- public DialogManager(MyTracks activity) {
- this.activity = activity;
- }
-
- protected Dialog onCreateDialog(int id, Bundle args) {
- switch (id) {
- case DIALOG_CHART_SETTINGS:
- chartSettingsDialog = new ChartSettingsDialog(activity);
- return chartSettingsDialog;
- case DIALOG_IMPORT_PROGRESS:
- importProgressDialog = new ProgressDialog(activity);
- importProgressDialog.setIcon(android.R.drawable.ic_dialog_info);
- importProgressDialog.setTitle(
- activity.getString(R.string.progress_title));
- importProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
- importProgressDialog.setMessage(
- activity.getString(R.string.import_progress_message));
- return importProgressDialog;
- case DIALOG_PROGRESS:
- progressDialog = new ProgressDialog(activity);
- progressDialog.setIcon(android.R.drawable.ic_dialog_info);
- progressDialog.setTitle(activity.getString(R.string.progress_title));
- progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
- progressDialog.setMessage("");
- progressDialog.setMax(100);
- progressDialog.setProgress(10);
- return progressDialog;
- case DIALOG_SEND_TO_GOOGLE:
- sendToGoogleDialog = new SendDialog(activity);
- return sendToGoogleDialog;
- }
- return null;
- }
-
- protected void onPrepareDialog(int id, Dialog dialog) {
- switch (id) {
- case DIALOG_SEND_TO_GOOGLE:
- activity.resetSendToGoogleStatus();
- break;
- case DIALOG_CHART_SETTINGS:
- Log.d(TAG, "MyTracks.onPrepare chart dialog");
- chartSettingsDialog.setup(activity.getChartActivity());
- break;
- }
- }
-
- public void setProgressMessage(final String message) {
- activity.runOnUiThread(new Runnable() {
- public void run() {
- synchronized (this) {
- if (progressDialog != null) {
- progressDialog.setMessage(message);
- }
- }
- }
- });
- }
-
- public void setProgressValue(final int percent) {
- activity.runOnUiThread(new Runnable() {
- public void run() {
- synchronized (this) {
- if (progressDialog != null) {
- progressDialog.setProgress(percent);
- }
- }
- }
- });
- }
-
- /**
- * @return the sendToGoogleDialog
- */
- public SendDialog getSendToGoogleDialog() {
- return sendToGoogleDialog;
- }
-
- /**
- * Shows a dialog with the given message.
- * Does it on the UI thread.
- *
- * @param success if true, displays an info icon/title, otherwise an error
- * icon/title
- * @param message resource string id
- */
- public void showMessageDialog(final int message, final boolean success) {
- if (activity.isFinishing()) {
+ public static void showMessageDialog(
+ Activity ctx, int message, boolean success, DialogInterface.OnClickListener okListener) {
+ if (ctx.isFinishing()) {
Log.w(TAG, "Activity finishing - not showing dialog");
return;
}
- activity.runOnUiThread(new Runnable() {
- public void run() {
- AlertDialog dialog = null;
- AlertDialog.Builder builder = new AlertDialog.Builder(activity);
- builder.setMessage(activity.getString(message));
- builder.setNegativeButton(activity.getString(R.string.ok), null);
- builder.setIcon(success ? android.R.drawable.ic_dialog_info :
- android.R.drawable.ic_dialog_alert);
- builder.setTitle(success ? R.string.success : R.string.error);
- dialog = builder.create();
- dialog.show();
- }
- });
- }
-
- /**
- * Just like showDialog, but will catch a {@link BadTokenException} that
- * sometimes (very rarely) gets thrown. This might happen if the user hits
- * the "back" button immediately after sending tracks to google.
- *
- * @param id the dialog id
- */
- public void showDialogSafely(final int id) {
- if (activity.isFinishing()) {
- Log.w(TAG, "Activity finishing - not showing dialog");
- return;
- }
-
- activity.runOnUiThread(new Runnable() {
- public void run() {
- try {
- activity.showDialog(id);
- } catch (BadTokenException e) {
- Log.w(TAG, "Could not display dialog with id " + id, e);
- } catch (IllegalStateException e) {
- Log.w(TAG, "Could not display dialog with id " + id, e);
- }
- }
- });
+ AlertDialog dialog = null;
+ AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
+ builder.setMessage(message);
+ builder.setNeutralButton(R.string.ok, okListener);
+ builder.setIcon(success ? android.R.drawable.ic_dialog_info :
+ android.R.drawable.ic_dialog_alert);
+ builder.setTitle(success ? R.string.success : R.string.error);
+ dialog = builder.create();
+ dialog.show();
}
/**
@@ -202,26 +74,6 @@ public class DialogManager {
});
}
- /**
- * Dismisses the progress dialog if it is showing. Executed on the UI thread.
- */
- public void dismissDialogSafely(final int id) {
- if (activity.isFinishing()) {
- Log.w(TAG, "Activity finishing - not dismissing dialog");
- return;
- }
-
- activity.runOnUiThread(new Runnable() {
- public void run() {
- try {
- activity.dismissDialog(id);
- } catch (IllegalArgumentException e) {
- // This will be thrown if this dialog was not shown before.
- }
- }
- });
- }
-
/**
* The equivalent of {@link #dismissDialogSafely(int)}, but for a specific
* dialog instance.
@@ -242,4 +94,6 @@ public class DialogManager {
}
});
}
+
+ private DialogManager() {}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/ImportAllTracks.java b/MyTracks/src/com/google/android/apps/mytracks/ImportAllTracks.java
index 8826e40c7..0e23f3d8a 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/ImportAllTracks.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/ImportAllTracks.java
@@ -104,9 +104,9 @@ public class ImportAllTracks {
Log.i(Constants.TAG, "ImportAllTracks: Done");
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
if (gpxFileCount == 0) {
- builder.setMessage(activity.getString(R.string.import_empty, gpxPath + "/"));
+ builder.setMessage(activity.getString(R.string.import_multi_empty, gpxPath + "/"));
} else {
- builder.setMessage(activity.getString(R.string.import_done, importSuccessCount, gpxFileCount,
+ builder.setMessage(activity.getString(R.string.import_multi_done, importSuccessCount, gpxFileCount,
gpxPath + "/"));
}
builder.setPositiveButton(R.string.ok, null);
diff --git a/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java b/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java
index 1ac9c1f05..d7391a211 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java
@@ -23,6 +23,8 @@ import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint;
+import com.google.android.apps.mytracks.io.file.SaveActivity;
+import com.google.android.apps.mytracks.io.sendtogoogle.SendActivity;
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.ApiFeatures;
@@ -122,7 +124,7 @@ public class MapActivity extends com.google.android.maps.MapActivity
// ------------------------
@Override
- public void onCreate(Bundle bundle) {
+ protected void onCreate(Bundle bundle) {
Log.d(TAG, "MapActivity.onCreate");
super.onCreate(bundle);
@@ -131,8 +133,6 @@ public class MapActivity extends com.google.android.maps.MapActivity
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
setVolumeControlStream(volumeStream);
- dataHub = MyTracks.getInstance().getDataHub();
-
// We don't need a window title bar:
requestWindowFeature(Window.FEATURE_NO_TITLE);
@@ -179,9 +179,10 @@ public class MapActivity extends com.google.android.maps.MapActivity
@Override
protected void onResume() {
- Log.d(TAG, "MapActivity.onStart");
+ Log.d(TAG, "MapActivity.onResume");
super.onResume();
+ dataHub = TrackDataHub.getStartedInstance();
dataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED,
ListenerDataType.POINT_UPDATES,
@@ -202,9 +203,10 @@ public class MapActivity extends com.google.android.maps.MapActivity
@Override
protected void onPause() {
- Log.d(TAG, "MapActivity.onStop");
+ Log.d(TAG, "MapActivity.onPause");
dataHub.unregisterTrackDataListener(this);
+ dataHub = null;
super.onPause();
}
@@ -392,13 +394,27 @@ public class MapActivity extends com.google.android.maps.MapActivity
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
- if (!super.onMenuItemSelected(featureId, item)) {
- MyTracks.getInstance().onActivityResult(
- Constants.getActionFromMenuId(item.getItemId()), RESULT_OK,
- new Intent());
- return true;
+ switch (item.getItemId()) {
+ case Constants.MENU_SEND_TO_GOOGLE:
+ SendActivity.sendToGoogle(this, dataHub.getSelectedTrackId(), false);
+ return true;
+ case Constants.MENU_SHARE_LINK:
+ SendActivity.sendToGoogle(this, dataHub.getSelectedTrackId(), true);
+ return true;
+ case Constants.MENU_SAVE_GPX_FILE:
+ case Constants.MENU_SAVE_KML_FILE:
+ case Constants.MENU_SAVE_CSV_FILE:
+ case Constants.MENU_SAVE_TCX_FILE:
+ case Constants.MENU_SHARE_GPX_FILE:
+ case Constants.MENU_SHARE_KML_FILE:
+ case Constants.MENU_SHARE_CSV_FILE:
+ case Constants.MENU_SHARE_TCX_FILE:
+ SaveActivity.handleExportTrackAction(this, dataHub.getSelectedTrackId(),
+ Constants.getActionFromMenuId(item.getItemId()));
+ return true;
+ default:
+ return super.onMenuItemSelected(featureId, item);
}
- return false;
}
@Override
diff --git a/MyTracks/src/com/google/android/apps/mytracks/MapOverlay.java b/MyTracks/src/com/google/android/apps/mytracks/MapOverlay.java
index 403b1abe5..0203d8d6b 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/MapOverlay.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/MapOverlay.java
@@ -175,14 +175,18 @@ public class MapOverlay extends Overlay {
*/
public void addLocation(Location l) {
// Queue up in the pending queue until it's merged with {@code #points}.
- pendingPoints.offer(new CachedLocation(l));
+ if (!pendingPoints.offer(new CachedLocation(l))) {
+ Log.e(TAG, "Unable to add pending points");
+ }
}
/**
* Adds a segment split to the map overlay.
*/
public void addSegmentSplit() {
- pendingPoints.offer(new CachedLocation());
+ if (!pendingPoints.offer(new CachedLocation())) {
+ Log.e(TAG, "Unable to add pending points");
+ }
}
public void addWaypoint(Waypoint wpt) {
diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyMapsList.java b/MyTracks/src/com/google/android/apps/mytracks/MyMapsList.java
index 7b74ac606..3e3f0096c 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/MyMapsList.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/MyMapsList.java
@@ -1,12 +1,12 @@
/*
* Copyright 2011 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -25,11 +25,7 @@ import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.MenuItem;
import android.view.View;
-import android.view.View.OnCreateContextMenuListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
@@ -42,29 +38,12 @@ import android.widget.TextView;
* @author Rodrigo Damazio
*/
public class MyMapsList extends Activity implements MapsFacade.MapsListCallback {
- private static final int MENU_OPEN = 0;
- private static final int MENU_SHARE = 2;
private static final int GET_LOGIN = 1;
private MapsFacade mapsClient;
private AuthManager auth;
private MyMapsListAdapter listAdapter;
- private int contextPosition;
-
- private final OnCreateContextMenuListener contextMenuListener =
- new OnCreateContextMenuListener() {
- @Override
- public void onCreateContextMenu(ContextMenu menu, View v,
- ContextMenuInfo menuInfo) {
- AdapterView.AdapterContextMenuInfo info =
- (AdapterView.AdapterContextMenuInfo) menuInfo;
- contextPosition = info.position;
- menu.add(0, MENU_OPEN, 0, R.string.open_map);
- menu.add(0, MENU_SHARE, 0, R.string.share_map);
- }
- };
-
private final OnItemClickListener clickListener =
new OnItemClickListener() {
@Override
@@ -91,7 +70,6 @@ public class MyMapsList extends Activity implements MapsFacade.MapsListCallback
ListView list = (ListView) findViewById(R.id.maplist);
list.setOnItemClickListener(clickListener);
- list.setOnCreateContextMenuListener(contextMenuListener);
list.setAdapter(listAdapter);
startLogin();
@@ -101,7 +79,8 @@ public class MyMapsList extends Activity implements MapsFacade.MapsListCallback
// Starts in the UI thread.
// TODO fix this for non-froyo devices.
if (AuthManagerFactory.useModernAuthManager()) {
- MyTracks.getInstance().getAccountChooser().chooseAccount(
+ AccountChooser accountChooser = new AccountChooser();
+ accountChooser.chooseAccount(
MyMapsList.this,
new AccountChooser.AccountHandler() {
@Override
@@ -111,6 +90,8 @@ public class MyMapsList extends Activity implements MapsFacade.MapsListCallback
// The user did not quit and there was a valid google
// account.
doLogin(account);
+ } else {
+ finish();
}
}
});
@@ -183,27 +164,4 @@ public class MyMapsList extends Activity implements MapsFacade.MapsListCallback
}
super.onActivityResult(requestCode, resultCode, data);
}
-
- @Override
- public boolean onMenuItemSelected(int featureId, MenuItem item) {
- switch (item.getItemId()) {
- case MENU_OPEN:
- clickListener.onItemClick(null, null, contextPosition, 0);
- return true;
- case MENU_SHARE:
- Intent shareIntent = new Intent(Intent.ACTION_SEND);
- shareIntent.setType("text/plain");
- shareIntent.putExtra(Intent.EXTRA_SUBJECT,
- getText(R.string.share_map_subject));
- String[] listItem = (String[]) listAdapter.getMapListingArray(contextPosition);
- shareIntent.putExtra(Intent.EXTRA_TEXT, String.format(
- getText(R.string.share_map_body_format).toString(),
- listItem[1],
- MapsFacade.buildMapUrl(listItem[0])));
- startActivity(Intent.createChooser(shareIntent,
- getText(R.string.share_map).toString()));
- return true;
- }
- return false;
- }
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java
index 11fab5f83..ed3914120 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java
@@ -16,58 +16,33 @@
package com.google.android.apps.mytracks;
import static com.google.android.apps.mytracks.Constants.TAG;
-import static com.google.android.apps.mytracks.DialogManager.DIALOG_IMPORT_PROGRESS;
-import static com.google.android.apps.mytracks.DialogManager.DIALOG_PROGRESS;
-import static com.google.android.apps.mytracks.DialogManager.DIALOG_SEND_TO_GOOGLE;
-import com.google.android.accounts.Account;
import com.google.android.apps.analytics.GoogleAnalyticsTracker;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
-import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub;
+import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
-import com.google.android.apps.mytracks.io.AuthManager;
-import com.google.android.apps.mytracks.io.AuthManagerFactory;
-import com.google.android.apps.mytracks.io.SendToDocs;
-import com.google.android.apps.mytracks.io.SendToFusionTables;
-import com.google.android.apps.mytracks.io.SendToFusionTables.OnSendCompletedListener;
-import com.google.android.apps.mytracks.io.SendToMyMaps;
-import com.google.android.apps.mytracks.io.file.GpxImporter;
import com.google.android.apps.mytracks.io.file.TempFileCleaner;
-import com.google.android.apps.mytracks.io.file.TrackWriter;
-import com.google.android.apps.mytracks.io.file.TrackWriterFactory;
-import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
-import com.google.android.apps.mytracks.io.mymaps.MapsFacade;
-import com.google.android.apps.mytracks.io.mymaps.MyMapsConstants;
-import com.google.android.apps.mytracks.io.sendtogoogle.ResultDialogFactory;
-import com.google.android.apps.mytracks.io.sendtogoogle.SendDialog;
-import com.google.android.apps.mytracks.io.sendtogoogle.SendResult;
-import com.google.android.apps.mytracks.io.sendtogoogle.SendType;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
-import com.google.android.apps.mytracks.services.TrackRecordingService;
+import com.google.android.apps.mytracks.services.ServiceUtils;
+import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.util.ApiFeatures;
-import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.SystemUtils;
+import com.google.android.apps.mytracks.util.UriUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
-import android.app.Dialog;
import android.app.TabActivity;
-import android.content.ComponentName;
+import android.content.ContentUris;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
import android.os.RemoteException;
-import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
@@ -80,65 +55,24 @@ import android.widget.RelativeLayout;
import android.widget.TabHost;
import android.widget.Toast;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-import javax.xml.parsers.ParserConfigurationException;
-
-import org.xml.sax.SAXException;
-
/**
* The super activity that embeds our sub activities.
*
* @author Leif Hendrik Wilden
+ * @author Rodrigo Damazio
*/
-public class MyTracks extends TabActivity implements OnTouchListener,
- ProgressIndicator {
- /**
- * Singleton instance
- */
- private static MyTracks instance;
-
+public class MyTracks extends TabActivity implements OnTouchListener {
private TrackDataHub dataHub;
- private ChartActivity chartActivity;
- /*
- * Authentication
- */
- private AuthManager auth;
- private final HashMap authMap =
- new HashMap();
- private final AccountChooser accountChooser = new AccountChooser();
-
- /*
- * Dialogs manager.
- */
- private DialogManager dialogManager;
-
- /*
+ /**
* Menu manager.
*/
private MenuManager menuManager;
- /*
- * Information on upload success to MyMaps/Docs.
- * Used by SendToGoogleResultDialog.
+ /**
+ * Preferences.
*/
-
- public long sendToTrackId = -1;
- public boolean sendToMyMapsSuccess = false;
- public boolean sendToFusionTablesSuccess = false;
- public boolean sendToDocsSuccess = false;
- public String sendToMyMapsMapId;
- public String sendToMyMapsMessage = "";
- public String sendToFusionTablesTableId;
- public String sendToFusionTablesMessage = "";
- public String sendToDocsMessage = "";
+ private SharedPreferences preferences;
/**
* True if a new track should be created after the track recording service
@@ -146,58 +80,15 @@ public class MyTracks extends TabActivity implements OnTouchListener,
*/
private boolean startNewTrackRequested = false;
- private ITrackRecordingService trackRecordingService;
-
- /**
- * Does the user want to share the current track.
- */
- private boolean shareRequested = false;
-
/**
* Utilities to deal with the database.
*/
private MyTracksProviderUtils providerUtils;
- private SharedPreferences sharedPreferences;
-
/**
* Google Analytics tracker
*/
private GoogleAnalyticsTracker tracker;
-
- /**
- * The connection to the track recording service.
- */
- private final ServiceConnection serviceConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName className, IBinder service) {
- Log.d(Constants.TAG, "MyTracks: Service now connected.");
- // Delay setting the service until we are done with initialization.
- ITrackRecordingService trackRecordingService =
- ITrackRecordingService.Stub.asInterface(service);
- try {
- // TODO: Send a start service intent and broadcast service started
- // message to avoid the hack below and a race condition.
- if (startNewTrackRequested) {
- startNewTrackRequested = false;
- startRecordingNewTrack(trackRecordingService);
- }
- } finally {
- MyTracks.this.trackRecordingService = trackRecordingService;
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName className) {
- Log.d(TAG, "MyTracks: Service now disconnected.");
- trackRecordingService = null;
- }
- };
-
- /**
- * Whether {@link #serviceConnection} is bound or not.
- */
- private boolean isBound = false;
/*
* Tabs/View navigation:
@@ -211,35 +102,27 @@ public class MyTracks extends TabActivity implements OnTouchListener,
}
};
- public static MyTracks getInstance() {
- return instance;
- }
-
- /**
- * 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 or in case the service is down,
- * based on settings from the shared preferences.
+ /*
+ * Recording service interaction:
*/
- public boolean isRecording() {
- if (trackRecordingService == null) {
- // Fall back to alternative check method.
- return dataHub.isRecording();
- }
- try {
- return trackRecordingService.isRecording();
- // TODO: We catch Exception, because after eliminating the service process
- // all exceptions it may throw are no longer wrapped in a RemoteException.
- } catch (Exception e) {
- Log.e(TAG, "MyTracks: Remote exception.", e);
- // Fall back to alternative check method.
- return dataHub.isRecording();
+ private final Runnable serviceBindCallback = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (serviceConnection) {
+ ITrackRecordingService service = serviceConnection.getServiceIfBound();
+ if (startNewTrackRequested && service != null) {
+ Log.i(TAG, "Starting recording");
+ startNewTrackRequested = false;
+ startRecordingNewTrack(service);
+ } else if (startNewTrackRequested) {
+ Log.w(TAG, "Not yet starting recording");
+ }
+ }
}
- }
+ };
+
+ private TrackRecordingServiceConnection serviceConnection;
/*
* Application lifetime events:
@@ -250,7 +133,6 @@ public class MyTracks extends TabActivity implements OnTouchListener,
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "MyTracks.onCreate");
super.onCreate(savedInstanceState);
- instance = this;
ApiFeatures apiFeatures = ApiFeatures.getInstance();
if (!SystemUtils.isRelease(this)) {
apiFeatures.getApiPlatformAdapter().enableStrictMode();
@@ -258,16 +140,16 @@ public class MyTracks extends TabActivity implements OnTouchListener,
tracker = GoogleAnalyticsTracker.getInstance();
// Start the tracker in manual dispatch mode...
- tracker.start(this.getString(R.string.google_analytics_id), this.getApplicationContext());
+ tracker.start(getString(R.string.google_analytics_id), getApplicationContext());
tracker.setProductVersion("android-mytracks", SystemUtils.getMyTracksVersion(this));
tracker.trackPageView("/appstart");
tracker.dispatch();
providerUtils = MyTracksProviderUtils.Factory.get(this);
- sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
- dataHub = new TrackDataHub(this, sharedPreferences, providerUtils);
+ preferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
+ dataHub = TrackDataHub.newInstance(this);
menuManager = new MenuManager(this);
- dialogManager = new DialogManager(this);
+ serviceConnection = new TrackRecordingServiceConnection(this, serviceBindCallback);
// The volume we want to control is the Text-To-Speech volume
int volumeStream =
@@ -309,67 +191,6 @@ public class MyTracks extends TabActivity implements OnTouchListener,
// This will show the eula until the user accepts or quits the app.
Eula.showEulaRequireAcceptance(this);
-
- // Check if we got invoked via the VIEW intent:
- Intent intent = getIntent();
- String action;
- if (intent != null && (action = intent.getAction()) != null) {
- if (action.equals(Intent.ACTION_MAIN)) {
- // Do nothing.
- } else if (action.equals(Intent.ACTION_VIEW)) {
- if (intent.getScheme() != null && intent.getScheme().equals("file")) {
- Log.w(TAG, "Received a VIEW intent with file scheme. Importing.");
- importGpxFile(intent.getData().getPath());
- } else {
- Log.w(TAG, "Received a VIEW intent with unsupported scheme " + intent.getScheme());
- }
- } else {
- Log.w(TAG, "Received an intent with unsupported action " + action);
- }
- } else {
- Log.d(TAG, "Received an intent with no action.");
- }
- }
-
- @Override
- protected void onDestroy() {
- Log.d(TAG, "MyTracks.onDestroy");
-
- dataHub.destroy();
-
- tryUnbindTrackRecordingService();
- tracker.dispatch();
- tracker.stop();
- super.onDestroy();
- }
-
- @Override
- protected void onStop() {
- Log.d(TAG, "MyTracks.onStop");
-
- dataHub.stop();
-
- // Clean up any temporary track files.
- TempFileCleaner.clean();
- super.onStop();
- }
-
- @Override
- protected void onPause() {
- // Called when activity is going into the background, but has not (yet) been
- // killed. Shouldn't block longer than approx. 2 seconds.
- Log.d(TAG, "MyTracks.onPause");
- tryUnbindTrackRecordingService();
- super.onPause();
- }
-
- @Override
- protected void onResume() {
- // Called when the current activity is being displayed or re-displayed
- // to the user.
- Log.d(TAG, "MyTracks.onResume");
- tryBindTrackRecordingService();
- super.onResume();
}
@Override
@@ -378,17 +199,60 @@ public class MyTracks extends TabActivity implements OnTouchListener,
super.onStart();
dataHub.start();
- // Ensure that service is running if we're supposed to be recording
- if (dataHub.isRecording()) {
- Intent startIntent = new Intent(this, TrackRecordingService.class);
- startService(startIntent);
+ // Ensure that service is running and bound if we're supposed to be recording
+ if (ServiceUtils.isRecording(this, null, preferences)) {
+ serviceConnection.startAndBind();
+ }
+
+ Intent intent = getIntent();
+ String action = intent.getAction();
+ Uri data = intent.getData();
+ if ((Intent.ACTION_VIEW.equals(action) || Intent.ACTION_EDIT.equals(action))
+ && TracksColumns.CONTENT_ITEMTYPE.equals(intent.getType())
+ && UriUtils.matchesContentUri(data, TracksColumns.CONTENT_URI)) {
+ long trackId = ContentUris.parseId(data);
+ dataHub.loadTrack(trackId);
}
}
- /*
- * Menu events:
- * ============
- */
+ @Override
+ protected void onResume() {
+ // Called when the current activity is being displayed or re-displayed
+ // to the user.
+ Log.d(TAG, "MyTracks.onResume");
+ serviceConnection.bindIfRunning();
+ super.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ // Called when activity is going into the background, but has not (yet) been
+ // killed. Shouldn't block longer than approx. 2 seconds.
+ Log.d(TAG, "MyTracks.onPause");
+ super.onPause();
+ }
+
+ @Override
+ protected void onStop() {
+ Log.d(TAG, "MyTracks.onStop");
+
+ dataHub.stop();
+
+ tracker.dispatch();
+ tracker.stop();
+
+ // Clean up any temporary track files.
+ TempFileCleaner.clean();
+ super.onStop();
+ }
+
+ @Override
+ protected void onDestroy() {
+ Log.d(TAG, "MyTracks.onDestroy");
+ serviceConnection.unbind();
+
+ super.onDestroy();
+ }
@Override
public boolean onCreateOptionsMenu(Menu menu) {
@@ -399,7 +263,8 @@ public class MyTracks extends TabActivity implements OnTouchListener,
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menuManager.onPrepareOptionsMenu(menu, providerUtils.getLastTrack() != null,
- isRecording(), dataHub.isATrackSelected());
+ ServiceUtils.isRecording(this, serviceConnection.getServiceIfBound(), preferences),
+ dataHub.isATrackSelected());
return super.onPrepareOptionsMenu(menu);
}
@@ -410,27 +275,6 @@ public class MyTracks extends TabActivity implements OnTouchListener,
: super.onOptionsItemSelected(item);
}
- /*
- * Dialog events:
- * ==============
- */
-
- @Override
- protected Dialog onCreateDialog(int id, Bundle args) {
- return dialogManager.onCreateDialog(id, args);
- }
-
- @Override
- protected Dialog onCreateDialog(int id) {
- return dialogManager.onCreateDialog(id, null);
- }
-
- @Override
- protected void onPrepareDialog(int id, Dialog dialog) {
- super.onPrepareDialog(id, dialog);
- dialogManager.onPrepareDialog(id, dialog);
- }
-
/*
* Key events:
* ===========
@@ -438,8 +282,8 @@ public class MyTracks extends TabActivity implements OnTouchListener,
@Override
public boolean onTrackballEvent(MotionEvent event) {
- if (isRecording()) {
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ if (ServiceUtils.isRecording(this, serviceConnection.getServiceIfBound(), preferences)) {
try {
insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
} catch (RemoteException e) {
@@ -450,6 +294,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
return true;
}
}
+
return super.onTrackballEvent(event);
}
@@ -457,19 +302,12 @@ public class MyTracks extends TabActivity implements OnTouchListener,
public void onActivityResult(int requestCode, int resultCode,
final Intent results) {
Log.d(TAG, "MyTracks.onActivityResult");
- TrackFileFormat exportFormat = null;
long trackId = dataHub.getSelectedTrackId();
if (results != null) {
trackId = results.getLongExtra("trackid", trackId);
}
switch (requestCode) {
- case Constants.GET_LOGIN: {
- if (resultCode != RESULT_OK || auth == null || !auth.authResult(resultCode, results)) {
- dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
- }
- break;
- }
case Constants.SHOW_TRACK: {
if (results != null) {
if (trackId >= 0) {
@@ -512,121 +350,6 @@ public class MyTracks extends TabActivity implements OnTouchListener,
}
break;
}
- case Constants.SEND_TO_GOOGLE_DIALOG: {
- shareRequested = false;
- dialogManager.showDialogSafely(DIALOG_SEND_TO_GOOGLE);
- break;
- }
- case Constants.GET_MAP: {
- // User picked a map to upload to
- if (resultCode == RESULT_OK) {
- results.putExtra("trackid", dataHub.getSelectedTrackId());
- if (results.hasExtra("mapid")) {
- sendToMyMapsMapId = results.getStringExtra("mapid");
- }
- authenticateToGoogleMaps(results);
- } else {
- onSendToGoogleDone();
- }
- break;
- }
- case Constants.AUTHENTICATE_TO_MY_MAPS: {
- // Authenticated with Google My Maps
- if (results != null && resultCode == RESULT_OK) {
- final String mapId;
- if (results.hasExtra("mapid")) {
- mapId = results.getStringExtra("mapid");
- } else {
- mapId = "new";
- }
-
- sendToGoogleMaps(trackId, mapId);
- } else {
- onSendToGoogleDone();
- }
- break;
- }
- case Constants.AUTHENTICATE_TO_FUSION_TABLES: {
- // Authenticated with Google Fusion Tables
- if (results != null && resultCode == RESULT_OK) {
- sendToFusionTables(trackId);
- } else {
- onSendToGoogleDone();
- }
- break;
- }
- case Constants.AUTHENTICATE_TO_DOCLIST: {
- // Authenticated with Google Docs
- if (resultCode == RESULT_OK) {
- authenticateToGoogleTrix();
- } else {
- onSendToGoogleDone();
- }
- break;
- }
- case Constants.AUTHENTICATE_TO_TRIX: {
- // Authenticated with Trix
- if (resultCode == RESULT_OK) {
- sendToGoogleDocs(trackId);
- } else {
- onSendToGoogleDone();
- }
- break;
- }
- case Constants.SAVE_GPX_FILE:
- if (exportFormat == null) { exportFormat = TrackFileFormat.GPX; }
- //$FALL-THROUGH$
- case Constants.SAVE_KML_FILE:
- if (exportFormat == null) { exportFormat = TrackFileFormat.KML; }
- //$FALL-THROUGH$
- case Constants.SAVE_CSV_FILE:
- if (exportFormat == null) { exportFormat = TrackFileFormat.CSV; }
- //$FALL-THROUGH$
- case Constants.SAVE_TCX_FILE:
- if (exportFormat == null) { exportFormat = TrackFileFormat.TCX; }
-
- if (results != null && resultCode == Activity.RESULT_OK) {
- if (trackId >= 0) {
- saveTrack(trackId, exportFormat);
- }
- }
- break;
- case Constants.SHARE_LINK: {
- Track selectedTrack = providerUtils.getTrack(dataHub.getSelectedTrackId());
- if (selectedTrack != null) {
- if (!TextUtils.isEmpty(selectedTrack.getMapId())) {
- shareLinkToMap(MapsFacade.buildMapUrl(selectedTrack.getMapId()));
- } else if (!TextUtils.isEmpty(selectedTrack.getTableId())) {
- String url = getFusionTablesUrl(dataHub.getSelectedTrackId());
- if (url != null) {
- shareLinkToMap(url);
- }
- } else {
- shareRequested = true;
- dialogManager.showDialogSafely(DIALOG_SEND_TO_GOOGLE);
- }
- }
- break;
- }
- case Constants.SHARE_GPX_FILE:
- if (exportFormat == null) { exportFormat = TrackFileFormat.GPX; }
- //$FALL-THROUGH$
- case Constants.SHARE_KML_FILE:
- if (exportFormat == null) { exportFormat = TrackFileFormat.KML; }
- //$FALL-THROUGH$
- case Constants.SHARE_CSV_FILE:
- if (exportFormat == null) { exportFormat = TrackFileFormat.CSV; }
- //$FALL-THROUGH$
- case Constants.SHARE_TCX_FILE: {
- if (exportFormat == null) { exportFormat = TrackFileFormat.TCX; }
-
- if (results != null && resultCode == Activity.RESULT_OK) {
- if (trackId >= 0) {
- sendTrack(trackId, exportFormat);
- }
- }
- break;
- }
case Constants.CLEAR_MAP: {
dataHub.unloadCurrentTrack();
break;
@@ -650,107 +373,6 @@ public class MyTracks extends TabActivity implements OnTouchListener,
return false;
}
- /**
- * Resets status information for sending to MyMaps/Docs.
- */
- public void resetSendToGoogleStatus() {
- sendToMyMapsMapId = null;
- sendToMyMapsMessage = "";
- sendToMyMapsSuccess = true;
- sendToFusionTablesMessage = "";
- sendToFusionTablesSuccess = true;
- sendToDocsMessage = "";
- sendToDocsSuccess = true;
- sendToFusionTablesTableId = null;
- }
-
- private void importGpxFile(final String fileName) {
- dialogManager.showDialogSafely(DIALOG_IMPORT_PROGRESS);
- Thread t = new Thread() {
- @Override
- public void run() {
- int message = R.string.success;
-
- long[] trackIdsImported = null;
-
- try {
- try {
- InputStream is = new FileInputStream(fileName);
- trackIdsImported = GpxImporter.importGPXFile(is, providerUtils);
- } catch (SAXException e) {
- Log.e(TAG, "Caught an unexpected exception.", e);
- message = R.string.error_generic;
- } catch (ParserConfigurationException e) {
- Log.e(TAG, "Caught an unexpected exception.", e);
- message = R.string.error_generic;
- } catch (IOException e) {
- Log.e(TAG, "Caught an unexpected exception.", e);
- message = R.string.error_unable_to_read_file;
- } catch (NullPointerException e) {
- Log.e(TAG, "Caught an unexpected exception.", e);
- message = R.string.error_invalid_gpx_format;
- } catch (OutOfMemoryError e) {
- Log.e(TAG, "Caught an unexpected exception.", e);
- message = R.string.error_out_of_memory;
- }
- if (trackIdsImported != null && trackIdsImported.length > 0) {
- // select last track from import file
- dataHub.loadTrack(trackIdsImported[trackIdsImported.length - 1]);
- } else {
- dialogManager.showMessageDialog(message, false/* success */);
- }
- } finally {
- runOnUiThread(new Runnable() {
- public void run() {
- dismissDialog(DIALOG_IMPORT_PROGRESS);
- }
- });
- }
- }
- };
- t.start();
- }
-
- // ProgressIndicator implementation
-
- @Override
- public void setProgressMessage(int resId) {
- dialogManager.setProgressMessage(getString(resId));
- }
-
- @Override
- public void clearProgressMessage() {
- dialogManager.setProgressMessage("");
- }
-
- @Override
- public void setProgressValue(final int percent) {
- dialogManager.setProgressValue(percent);
- }
-
- /**
- * Shares a link to a My Map or Fusion Table via external app (email, gmail, ...)
- * A chooser with apps that support text/plain will be shown to the user.
- */
- public void shareLinkToMap(String url) {
- Intent shareIntent = new Intent(Intent.ACTION_SEND);
- shareIntent.setType("text/plain");
- shareIntent.putExtra(Intent.EXTRA_SUBJECT,
- getResources().getText(R.string.share_map_subject).toString());
-
- boolean shareUrlOnly = true;
- if (sharedPreferences != null) {
- shareUrlOnly = sharedPreferences.getBoolean(
- getString(R.string.share_url_only_key), false);
- }
-
- 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()));
- }
-
/**
* Deletes the track with the given id.
* Prompts the user if he want to really delete the track first.
@@ -784,10 +406,13 @@ public class MyTracks extends TabActivity implements OnTouchListener,
/**
* Inserts a waypoint marker.
*
+ * TODO: Merge with WaypointsList#insertWaypoint.
+ *
* @return Id of the inserted statistics marker.
* @throws RemoteException If the call on the service failed.
*/
- public long insertWaypoint(WaypointCreationRequest request) throws RemoteException {
+ private long insertWaypoint(WaypointCreationRequest request) throws RemoteException {
+ ITrackRecordingService trackRecordingService = serviceConnection.getServiceIfBound();
if (trackRecordingService == null) {
throw new IllegalStateException("The recording service is not bound.");
}
@@ -805,55 +430,6 @@ public class MyTracks extends TabActivity implements OnTouchListener,
}
}
- /**
- * Initializes the authentication manager which obtains an authentication
- * token, prompting the user for a login and password if needed.
- */
- private void authenticate(final Intent results, final int requestCode,
- final String service) {
- auth = authMap.get(service);
- if (auth == null) {
- Log.i(TAG, "Creating a new authentication for service: " + service);
- auth = AuthManagerFactory.getAuthManager(this,
- Constants.GET_LOGIN,
- null,
- true,
- service);
- authMap.put(service, auth);
- }
- Log.d(TAG, "Logging in to " + service + "...");
- if (AuthManagerFactory.useModernAuthManager()) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- accountChooser.chooseAccount(MyTracks.this,
- new AccountChooser.AccountHandler() {
- @Override
- public void handleAccountSelected(Account account) {
- if (account == null) {
- dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
- return;
- }
- doLogin(results, requestCode, service, account);
- }
- });
- }
- });
- } else {
- doLogin(results, requestCode, service, null);
- }
- }
-
- private void doLogin(final Intent results, final int requestCode,
- final String service, final Account account) {
- auth.doLogin(new Runnable() {
- public void run() {
- Log.d(TAG, "Loggin success for " + service + "!");
- onActivityResult(requestCode, RESULT_OK, results);
- }
- }, account);
- }
-
private void startRecordingNewTrack(
ITrackRecordingService trackRecordingService) {
try {
@@ -876,14 +452,16 @@ public class MyTracks extends TabActivity implements OnTouchListener,
* Starts the track recording service (if not already running) and binds to
* it. Starts recording a new track.
*/
- public void startRecording() {
- if (trackRecordingService == null) {
+ void startRecording() {
+ synchronized (serviceConnection) {
startNewTrackRequested = true;
- Intent startIntent = new Intent(this, TrackRecordingService.class);
- startService(startIntent);
- tryBindTrackRecordingService();
- } else {
- startRecordingNewTrack(trackRecordingService);
+ serviceConnection.startAndBind();
+
+ // Binding was already requested before, it either already happened
+ // (in which case running the callback manually triggers the actual recording start)
+ // or it will happen in the future
+ // (in which case running the callback now will have no effect).
+ serviceBindCallback.run();
}
}
@@ -891,327 +469,28 @@ public class MyTracks extends TabActivity implements OnTouchListener,
* Stops the track recording service and unbinds from it. Will display a toast
* "Stopped recording" and pop up the Track Details activity.
*/
- public void stopRecording() {
+ void stopRecording() {
+ // Save the track id as the shared preference will overwrite the recording track id.
+ SharedPreferences sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
+ long currentTrackId = sharedPreferences.getLong(getString(R.string.recording_track_key), -1);
+
+ ITrackRecordingService trackRecordingService = serviceConnection.getServiceIfBound();
if (trackRecordingService != null) {
- // Save the track id as the shared preference will overwrite the recording track id.
- long currentTrackId = sharedPreferences.getLong(getString(R.string.recording_track_key), -1);
try {
trackRecordingService.endCurrentTrack();
- // TODO: We catch Exception, because after eliminating the service process
- // all exceptions it may throw are no longer wrapped in a RemoteException.
} catch (Exception e) {
Log.e(TAG, "Unable to stop recording.", e);
}
-
- if (currentTrackId > 0) {
- Intent intent = new Intent(MyTracks.this, TrackDetails.class);
- intent.putExtra("trackid", currentTrackId);
- intent.putExtra("hasCancelButton", false);
- startActivity(intent);
- }
- }
- tryUnbindTrackRecordingService();
- try {
- stopService(new Intent(MyTracks.this, TrackRecordingService.class));
- } catch (SecurityException e) {
- Log.e(TAG, "Encountered a security exception when trying to stop service.", e);
- }
- trackRecordingService = null;
- }
-
- /**
- * Initiates the process to send tracks to google.
- * This is called once the user has selected sending options via the
- * SendToGoogleDialog.
- *
- * TODO: Change this whole flow to an actual state machine.
- */
- public void sendToGoogle() {
- SendDialog sendToGoogleDialog =
- dialogManager.getSendToGoogleDialog();
- if (sendToGoogleDialog == null) {
- return;
- }
- setProgressValue(0);
- clearProgressMessage();
- dialogManager.showDialogSafely(DIALOG_PROGRESS);
-
- if (sendToGoogleDialog.getSendToMyMaps()) {
- sendToGoogleMapsOrPickMap(sendToGoogleDialog);
- } else if (sendToGoogleDialog.getSendToFusionTables()) {
- authenticateToFusionTables(null);
- } else if (sendToGoogleDialog.getSendToDocs()) {
- authenticateToGoogleDocs();
- } else {
- Log.w(TAG, "Nowhere to upload to");
- onSendToGoogleDone();
- }
- }
-
- private void sendToGoogleMapsOrPickMap(SendDialog sendToGoogleDialog) {
- tracker.trackPageView("/send/maps");
- if (!sendToGoogleDialog.getCreateNewMap()) {
- // Ask the user to choose a map to upload into
- Intent listIntent = new Intent(this, MyMapsList.class);
- startActivityForResult(listIntent, Constants.GET_MAP);
- // The callback for GET_MAP calls authenticateToGoogleMaps
- } else {
- authenticateToGoogleMaps(null);
- }
- }
-
- private void authenticateToGoogleMaps(Intent results) {
- if (results == null) { results = new Intent(); }
-
- setProgressValue(0);
- setProgressMessage(
- R.string.progress_message_authenticating_mymaps);
- authenticate(results, Constants.AUTHENTICATE_TO_MY_MAPS,
- MyMapsConstants.SERVICE_NAME);
- // AUTHENTICATE_TO_MY_MAPS callback calls sendToGoogleMaps
- }
-
- private void sendToGoogleMaps(final long trackId, String mapId) {
- SendToMyMaps.OnSendCompletedListener onCompletion = new SendToMyMaps.OnSendCompletedListener() {
- @Override
- public void onSendCompleted(String mapId, boolean success, int statusMessage) {
- sendToMyMapsMessage = getString(statusMessage);
- sendToMyMapsSuccess = success;
- if (sendToMyMapsSuccess) {
- sendToMyMapsMapId = mapId;
- // Update the map id for this track:
- try {
- Track track = providerUtils.getTrack(trackId);
- if (track != null) {
- track.setMapId(mapId);
- providerUtils.updateTrack(track);
- } else {
- Log.w(TAG, "Updating map id failed.");
- }
- } catch (RuntimeException e) {
- // If that fails whatever reasons we'll just log an error, but
- // continue.
- Log.w(TAG, "Updating map id failed.", e);
- }
- }
-
- onSendToGoogleMapsDone();
- }
- };
- final SendToMyMaps sender = new SendToMyMaps(this, mapId, auth,
- trackId, this /*progressIndicator*/, onCompletion);
-
- HandlerThread handlerThread = new HandlerThread("SendToMyMaps");
- handlerThread.start();
- Handler handler = new Handler(handlerThread.getLooper());
- handler.post(sender);
- }
-
- private void onSendToGoogleMapsDone() {
- SendDialog sendToGoogleDialog = dialogManager.getSendToGoogleDialog();
- if (sendToGoogleDialog.getSendToFusionTables()) {
- authenticateToFusionTables(null);
- } else if (sendToGoogleDialog.getSendToDocs()) {
- authenticateToGoogleDocs();
- } else {
- onSendToGoogleDone();
- }
- }
-
- private void authenticateToFusionTables(Intent results) {
- if (results == null) { results = new Intent(); }
-
- setProgressValue(0);
- setProgressMessage(R.string.progress_message_authenticating_fusiontables);
- authenticate(results, Constants.AUTHENTICATE_TO_FUSION_TABLES,
- SendToFusionTables.SERVICE_ID);
- // AUTHENTICATE_TO_FUSION_TABLES callback calls sendToFusionTables
- }
-
- private void sendToFusionTables(final long trackId) {
- tracker.trackPageView("/send/fusion_tables");
- OnSendCompletedListener onCompletion = new OnSendCompletedListener() {
- @Override
- public void onSendCompleted(String tableId, boolean success,
- int statusMessage) {
- sendToFusionTablesMessage = getString(statusMessage);
- sendToFusionTablesSuccess = success;
- if (sendToFusionTablesSuccess) {
- sendToFusionTablesTableId = tableId;
- // Update the table id for this track:
- try {
- Track track = providerUtils.getTrack(trackId);
- if (track != null) {
- track.setTableId(tableId);
- providerUtils.updateTrack(track);
- } else {
- Log.w(TAG, "Updating table id failed.");
- }
- } catch (RuntimeException e) {
- // If that fails whatever reasons we'll just log an error, but
- // continue.
- Log.w(TAG, "Updating table id failed.", e);
- }
- }
-
- onSendToFusionTablesDone();
- }
- };
- sendToTrackId = trackId;
- final SendToFusionTables sender = new SendToFusionTables(this, auth,
- trackId, this/*progressIndicator*/, onCompletion);
-
- HandlerThread handlerThread = new HandlerThread("SendToFusionTables");
- handlerThread.start();
- Handler handler = new Handler(handlerThread.getLooper());
- handler.post(sender);
- }
-
- private void onSendToFusionTablesDone() {
- SendDialog sendToGoogleDialog = dialogManager.getSendToGoogleDialog();
- if (sendToGoogleDialog.getSendToDocs()) {
- authenticateToGoogleDocs();
- } else {
- onSendToGoogleDone();
- }
- }
-
- private void authenticateToGoogleDocs() {
- setProgressValue(0);
- setProgressMessage(
- R.string.progress_message_authenticating_docs);
- authenticate(new Intent(),
- Constants.AUTHENTICATE_TO_DOCLIST,
- SendToDocs.GDATA_SERVICE_NAME_DOCLIST);
- // AUTHENTICATE_TO_DOCLIST callback calls authenticateToGoogleTrix
- }
-
- private void authenticateToGoogleTrix() {
- setProgressValue(30);
- setProgressMessage(
- R.string.progress_message_authenticating_docs);
- authenticate(new Intent(),
- Constants.AUTHENTICATE_TO_TRIX,
- SendToDocs.GDATA_SERVICE_NAME_TRIX);
- // AUTHENTICATE_TO_TRIX callback calls sendToGoogleDocs
- }
-
- private void sendToGoogleDocs(final long trackId) {
- Log.d(TAG, "Sending to Docs....");
- tracker.trackPageView("/send/docs");
- setProgressValue(50);
- setProgressMessage(R.string.progress_message_sending_docs);
- final SendToDocs sender = new SendToDocs(this,
- authMap.get(SendToDocs.GDATA_SERVICE_NAME_TRIX),
- authMap.get(SendToDocs.GDATA_SERVICE_NAME_DOCLIST), trackId);
- sendToTrackId = trackId;
- Runnable onCompletion = new Runnable() {
- public void run() {
- setProgressValue(100);
- dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
- sendToDocsMessage = sender.getStatusMessage();
- sendToDocsSuccess = sender.wasSuccess();
-
- onSendToGoogleDocsDone();
- }
- };
- sender.setOnCompletion(onCompletion);
- sender.run();
- }
-
- private void onSendToGoogleDocsDone() {
- onSendToGoogleDone();
- }
-
- private void onSendToGoogleDone() {
- tracker.dispatch();
- SendDialog sendToGoogleDialog = dialogManager.getSendToGoogleDialog();
- final boolean sentToMyMaps = sendToGoogleDialog.getSendToMyMaps();
- final boolean sentToFusionTables = sendToGoogleDialog.getSendToFusionTables();
- dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
-
- // We've finished sending the track to the user-selected services. Now
- // we tell them the results of the upload, and optionally share the track.
- // There are a few different paths through this code:
- //
- // 1. The user pre-requested a share (shareRequested == true). We're going
- // to display the result dialog *without* the share button (the share
- // listener will be null). The OK button listener will initiate the
- // share.
- //
- // 2. The user did not pre-request a share, and the set of services to
- // which we succeeded in uploading the track are compatible with
- // sharing. We'll display a share button (the share listener will be
- // non-null), and will share the link if the user clicks it.
- //
- // 3. The user did not pre-request a share, and the set of services to
- // which we succeeded in uploading the track are incompatible with
- // sharing. We won't display a share button.
-
- final boolean canShare = getSendToFusionTablesTableId() != null
- || getSendToMyMapsMapId() != null;
-
- DialogInterface.OnClickListener doShareListener = null;
- if (canShare) {
- doShareListener = new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- shareLinkToMap(sentToMyMaps, sentToFusionTables);
- }
- };
}
- DialogInterface.OnClickListener onOkListener = (canShare && shareRequested)
- ? doShareListener : null;
- DialogInterface.OnClickListener onShareListener = (canShare && !shareRequested)
- ? doShareListener : null;
+ serviceConnection.stop();
- AlertDialog sendToGoogleResultDialog = ResultDialogFactory.makeDialog(this,
- makeSendToGoogleResults(sendToGoogleDialog), onOkListener, onShareListener);
- DialogManager.showDialogSafely(this, sendToGoogleResultDialog);
- }
-
- boolean shareLinkToMap(boolean sentToMyMaps, boolean sentToFusionTables) {
- String url = null;
- if (sentToMyMaps && sendToMyMapsSuccess) {
- // Prefer a link to My Maps
- url = MapsFacade.buildMapUrl(sendToMyMapsMapId);
- } else if (sentToFusionTables && sendToFusionTablesSuccess) {
- // Otherwise try using the link to fusion tables
- url = getFusionTablesUrl(sendToTrackId);
+ if (currentTrackId > 0) {
+ Intent intent = new Intent(MyTracks.this, TrackDetails.class);
+ intent.putExtra("trackid", currentTrackId);
+ intent.putExtra("hasCancelButton", false);
+ startActivity(intent);
}
-
- if (url != null) {
- shareLinkToMap(url);
- return true;
- }
-
- return false;
- }
-
- protected String getFusionTablesUrl(long trackId) {
- Track track = providerUtils.getTrack(trackId);
- return SendToFusionTables.getMapVisualizationUrl(track);
- }
-
- /**
- * Creates a list of {@link SendResult} instances based on the set of
- * services selected in {@link SendDialog} and the results as known to
- * this class.
- */
- private List makeSendToGoogleResults(SendDialog dialog) {
- List results = new ArrayList();
- if (dialog.getSendToMyMaps()) {
- results.add(new SendResult(SendType.MYMAPS, sendToMyMapsSuccess));
- }
- if (dialog.getSendToFusionTables()) {
- results.add(new SendResult(SendType.FUSION_TABLES, sendToFusionTablesSuccess));
- }
- if (dialog.getSendToDocs()) {
- results.add(new SendResult(SendType.DOCS, sendToDocsSuccess));
- }
-
- return results;
}
void clearSelectedTrack() {
@@ -1221,154 +500,4 @@ public class MyTracks extends TabActivity implements OnTouchListener,
long getSelectedTrackId() {
return dataHub.getSelectedTrackId();
}
-
- /**
- * Binds to track recording service if it is running.
- */
- private void tryBindTrackRecordingService() {
- Log.d(TAG,
- "MyTracks: Trying to bind to track recording service...");
- bindService(new Intent(this, TrackRecordingService.class),
- serviceConnection, 0);
- Log.d(TAG, "MyTracks: ...bind finished!");
- isBound = true;
- }
-
- /**
- * Tries to unbind the track recording service. Catches exception silently in
- * case service is not registered anymore.
- */
- private void tryUnbindTrackRecordingService() {
- if (isBound) {
- Log.d(TAG, "MyTracks: Trying to unbind from track recording service...");
- try {
- unbindService(serviceConnection);
- Log.d(TAG, "MyTracks: ...unbind finished!");
- } catch (IllegalArgumentException e) {
- Log.d(TAG, "MyTracks: Tried unbinding, but service was not registered.", e);
- }
- isBound = false;
- }
- }
-
- /**
- * Saves the track with the given id to the SD card.
- *
- * @param trackId The id of the track to be sent
- */
- public void saveTrack(long trackId, TrackFileFormat format) {
- TrackWriter writer = TrackWriterFactory.newWriter(this, providerUtils, trackId, format);
- if (writer == null) {
- dialogManager.showMessageDialog(R.string.error_track_does_not_exist, false);
- return;
- }
-
- WriteProgressController controller = new WriteProgressController(this, writer);
- controller.setOnCompletionListener(new WriteProgressController.OnCompletionListener() {
- @Override
- public void onComplete(TrackWriter writer) {
- dialogManager.showMessageDialog(writer.getErrorMessage(), writer.wasSuccess());
- }
- });
- controller.startWrite();
- }
-
- /**
- * Sends the requested track as an email attachment.
- * This will leave the gpx file on the SD card for at least one hour.
- * Temporary gpx files will be deleted in onStop.
- *
- * @param trackId The id of the track to be sent
- */
- public void sendTrack(long trackId, final TrackFileFormat format) {
- TrackWriter writer = TrackWriterFactory.newWriter(this, providerUtils, trackId, format);
- if (writer == null) {
- dialogManager.showMessageDialog(R.string.error_track_does_not_exist, false);
- return;
- }
-
- FileUtils fileUtils = new FileUtils();
- String extension = format.getExtension();
- String dirName = fileUtils.buildExternalDirectoryPath(extension, "tmp");
-
- File dir = new File(dirName);
- writer.setDirectory(dir);
-
- WriteProgressController controller = new WriteProgressController(this, writer);
- controller.setOnCompletionListener(new WriteProgressController.OnCompletionListener() {
- @Override
- public void onComplete(TrackWriter writer) {
- if (!writer.wasSuccess()) {
- dialogManager.showMessageDialog(writer.getErrorMessage(), writer.wasSuccess());
- return;
- }
-
- Intent shareIntent = new Intent(Intent.ACTION_SEND);
- shareIntent.putExtra(Intent.EXTRA_SUBJECT,
- getResources().getText(R.string.send_track_subject).toString());
- shareIntent.putExtra(Intent.EXTRA_TEXT,
- getResources().getText(R.string.send_track_body_format)
- .toString());
- shareIntent.setType(format.getMimeType());
- Uri u = Uri.fromFile(new File(writer.getAbsolutePath()));
- shareIntent.putExtra(Intent.EXTRA_STREAM, u);
- startActivity(Intent.createChooser(shareIntent,
- getResources().getText(R.string.share_track).toString()));
- }
- });
-
- controller.startWrite();
- }
-
- public AccountChooser getAccountChooser() {
- return accountChooser;
- }
-
- public ChartActivity getChartActivity() {
- return chartActivity;
- }
-
- public void setChartActivity(ChartActivity chartActivity) {
- this.chartActivity = chartActivity;
- }
-
- public DialogManager getDialogManager() {
- return dialogManager;
- }
-
- public String getSendToMyMapsMapId() {
- return sendToMyMapsMapId;
- }
-
- public String getSendToFusionTablesTableId() {
- return sendToFusionTablesTableId;
- }
-
- public boolean getSendToGoogleSuccess() {
- return sendToFusionTablesSuccess && sendToDocsSuccess;
- }
-
- // @VisibleForTesting
- long getRecordingTrackId() {
- return sharedPreferences.getLong(getString(R.string.recording_track_key), -1);
- }
-
- // @VisibleForTesting
- SharedPreferences getSharedPreferences() {
- return sharedPreferences;
- }
-
- // @VisibleForTesting
- static void clearInstance() {
- instance = null;
- }
-
- // @VisibleForTesting
- ITrackRecordingService getTrackRecordingService() {
- return trackRecordingService;
- }
-
- public TrackDataHub getDataHub() {
- return dataHub;
- }
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/SensorStateActivity.java b/MyTracks/src/com/google/android/apps/mytracks/SensorStateActivity.java
index 5302ad80b..08251014c 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/SensorStateActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/SensorStateActivity.java
@@ -16,8 +16,10 @@
package com.google.android.apps.mytracks;
import static com.google.android.apps.mytracks.Constants.TAG;
+
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
+import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.services.sensors.SensorUtils;
import com.google.android.maps.mytracks.R;
import com.google.protobuf.InvalidProtocolBufferException;
@@ -57,6 +59,11 @@ public class SensorStateActivity extends Activity {
}
};
+ /**
+ * Connection to the recording service.
+ */
+ private TrackRecordingServiceConnection serviceConnection;
+
/**
* A task which will update the U/I.
*/
@@ -79,35 +86,45 @@ public class SensorStateActivity extends Activity {
setContentView(R.layout.sensor_state);
+ // TODO: Allow reading from sensors even if service is not running.
+ serviceConnection = new TrackRecordingServiceConnection(this, stateUpdater);
+ serviceConnection.bindIfRunning();
updateState();
}
@Override
protected void onResume() {
super.onResume();
+
+ serviceConnection.bindIfRunning();
+
timer = new Timer();
timer.schedule(new RefreshTask(), REFRESH_PERIOD_MS, REFRESH_PERIOD_MS);
}
@Override
protected void onPause() {
- super.onPause();
timer.cancel();
timer.purge();
timer = null;
+ super.onPause();
+ }
+
+ @Override
+ protected void onDestroy() {
+ serviceConnection.unbind();
+ super.onDestroy();
}
protected void updateState() {
- MyTracks mt = MyTracks.getInstance();
-
- ITrackRecordingService service =
- mt == null ? null : mt.getTrackRecordingService();
+ ITrackRecordingService service = serviceConnection.getServiceIfBound();
if (service == null) {
Log.d(Constants.TAG, "Could not get track recording service.");
updateSensorState(Sensor.SensorState.NONE);
updateSensorData(null);
return;
}
+
Sensor.SensorDataSet sds = null;
try {
byte[] buff = service.getSensorData();
@@ -153,8 +170,8 @@ public class SensorStateActivity extends Activity {
if (sds.hasPower() && sds.getPower().hasValue()
&& sds.getPower().getState() == Sensor.SensorState.SENDING) {
- utils.setText(R.id.power_state_register,
- Integer.toString(sds.getPower().getValue()));
+ utils.setText(R.id.power_state_register,
+ Integer.toString(sds.getPower().getValue()));
} else {
utils.setText(R.id.power_state_register,
SensorUtils.getStateAsString(
@@ -180,14 +197,14 @@ public class SensorStateActivity extends Activity {
if (sds.hasHeartRate() && sds.getHeartRate().hasValue()
&& sds.getHeartRate().getState() == Sensor.SensorState.SENDING) {
utils.setText(R.id.heart_rate_register,
- Integer.toString(sds.getHeartRate().getValue()));
+ Integer.toString(sds.getHeartRate().getValue()));
} else {
- utils.setText(R.id.heart_rate_register,
- SensorUtils.getStateAsString(
- sds.hasHeartRate()
- ? sds.getHeartRate().getState()
- : Sensor.SensorState.NONE,
- this));
+ utils.setText(R.id.heart_rate_register,
+ SensorUtils.getStateAsString(
+ sds.hasHeartRate()
+ ? sds.getHeartRate().getState()
+ : Sensor.SensorState.NONE,
+ this));
}
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java
index e73111291..d8d9b4bef 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java
@@ -22,11 +22,13 @@ import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint;
+import com.google.android.apps.mytracks.services.ServiceUtils;
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
+import android.content.SharedPreferences;
import android.location.Location;
import android.os.Bundle;
import android.util.DisplayMetrics;
@@ -46,6 +48,18 @@ import java.util.EnumSet;
* @author Rodrigo Damazio
*/
public class StatsActivity extends Activity implements TrackDataListener {
+ /**
+ * A runnable for posting to the UI thread. Will update the total time field.
+ */
+ private final Runnable updateResults = new Runnable() {
+ public void run() {
+ if (dataHub.isRecordingSelected()) {
+ utils.setTime(R.id.total_time_register,
+ System.currentTimeMillis() - startTime);
+ }
+ }
+ };
+
private StatsUtilities utils;
private UIUpdateThread thread;
@@ -61,18 +75,7 @@ public class StatsActivity extends Activity implements TrackDataListener {
private boolean showCurrentSegment = false;
private TrackDataHub dataHub;
-
- /**
- * A runnable for posting to the UI thread. Will update the total time field.
- */
- private final Runnable updateResults = new Runnable() {
- public void run() {
- if (dataHub.isRecordingSelected()) {
- utils.setTime(R.id.total_time_register,
- System.currentTimeMillis() - startTime);
- }
- }
- };
+ private SharedPreferences preferences;
/**
* A thread that updates the total time field every second.
@@ -87,7 +90,7 @@ public class StatsActivity extends Activity implements TrackDataListener {
@Override
public void run() {
Log.i(TAG, "Started UI update thread");
- while (MyTracks.getInstance().isRecording()) {
+ while (ServiceUtils.isRecording(StatsActivity.this, null, preferences)) {
runOnUiThread(updateResults);
try {
Thread.sleep(1000L);
@@ -102,10 +105,10 @@ public class StatsActivity extends Activity implements TrackDataListener {
/** Called when the activity is first created. */
@Override
- public void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- dataHub = MyTracks.getInstance().getDataHub();
+ preferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
utils = new StatsUtilities(this);
// The volume we want to control is the Text-To-Speech volume
@@ -132,18 +135,20 @@ public class StatsActivity extends Activity implements TrackDataListener {
@Override
protected void onResume() {
+ super.onResume();
+
+ dataHub = TrackDataHub.getStartedInstance();
dataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED,
ListenerDataType.TRACK_UPDATES,
ListenerDataType.LOCATION_UPDATES,
ListenerDataType.DISPLAY_PREFERENCES));
-
- super.onResume();
}
@Override
protected void onPause() {
dataHub.unregisterTrackDataListener(this);
+ dataHub = null;
if (thread != null) {
thread.interrupt();
@@ -171,7 +176,7 @@ public class StatsActivity extends Activity implements TrackDataListener {
utils.setReportSpeed(displaySpeed);
updateLabels();
-
+
return true; // Reload data
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/TrackList.java b/MyTracks/src/com/google/android/apps/mytracks/TrackList.java
index d39fbc032..17e927f28 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/TrackList.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/TrackList.java
@@ -1,12 +1,12 @@
/*
* Copyright 2008 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -16,6 +16,10 @@
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.TracksColumns;
+import com.google.android.apps.mytracks.io.file.SaveActivity;
+import com.google.android.apps.mytracks.io.sendtogoogle.SendActivity;
+import com.google.android.apps.mytracks.services.ServiceUtils;
+import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
@@ -76,8 +80,7 @@ public class TrackList extends ListActivity
R.string.tracklist_show_track);
menu.add(0, Constants.MENU_EDIT, 0,
R.string.tracklist_edit_track);
- if (!MyTracks.getInstance().isRecording()
- || trackId != recordingTrackId) {
+ if (!isRecording() || trackId != recordingTrackId) {
menu.add(0, Constants.MENU_SEND_TO_GOOGLE, 0,
R.string.tracklist_send_to_google);
SubMenu share = menu.addSubMenu(0, Constants.MENU_SHARE, 0,
@@ -109,6 +112,16 @@ public class TrackList extends ListActivity
}
};
+ private final Runnable serviceBindingChanged = new Runnable() {
+ @Override
+ public void run() {
+ updateButtonsEnabled();
+ }
+ };
+
+ private TrackRecordingServiceConnection serviceConnection;
+ private SharedPreferences preferences;
+
@Override
public void onSharedPreferenceChanged(
SharedPreferences sharedPreferences, String key) {
@@ -138,32 +151,46 @@ public class TrackList extends ListActivity
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
- if (!super.onMenuItemSelected(featureId, item)) {
- switch (item.getItemId()) {
- case Constants.MENU_SHOW: {
- onListItemClick(null, null, 0, trackId);
- return true;
- }
- case Constants.MENU_EDIT: {
- Intent intent = new Intent(this, TrackDetails.class);
- intent.putExtra("trackid", trackId);
- startActivity(intent);
- return true;
- }
- case Constants.MENU_SHARE:
- case Constants.MENU_WRITE_TO_SD_CARD:
- return false;
- default: {
- Intent result = new Intent();
- result.putExtra("trackid", trackId);
- setResult(
- Constants.getActionFromMenuId(item.getItemId()), result);
- finish();
- return true;
- }
+ switch (item.getItemId()) {
+ case Constants.MENU_SHOW: {
+ onListItemClick(null, null, 0, trackId);
+ return true;
+ }
+ case Constants.MENU_EDIT: {
+ Intent intent = new Intent(this, TrackDetails.class);
+ intent.putExtra("trackid", trackId);
+ startActivity(intent);
+ return true;
+ }
+ case Constants.MENU_SHARE:
+ case Constants.MENU_WRITE_TO_SD_CARD:
+ return false;
+ case Constants.MENU_SEND_TO_GOOGLE:
+ SendActivity.sendToGoogle(this, trackId, false);
+ return true;
+ case Constants.MENU_SHARE_LINK:
+ SendActivity.sendToGoogle(this, trackId, true);
+ return true;
+ case Constants.MENU_SAVE_GPX_FILE:
+ case Constants.MENU_SAVE_KML_FILE:
+ case Constants.MENU_SAVE_CSV_FILE:
+ case Constants.MENU_SAVE_TCX_FILE:
+ case Constants.MENU_SHARE_GPX_FILE:
+ case Constants.MENU_SHARE_KML_FILE:
+ case Constants.MENU_SHARE_CSV_FILE:
+ case Constants.MENU_SHARE_TCX_FILE:
+ SaveActivity.handleExportTrackAction(this, trackId,
+ Constants.getActionFromMenuId(item.getItemId()));
+ return true;
+ default: {
+ Intent result = new Intent();
+ result.putExtra("trackid", trackId);
+ setResult(
+ Constants.getActionFromMenuId(item.getItemId()), result);
+ finish();
+ return true;
}
}
- return false;
}
@Override
@@ -197,17 +224,19 @@ public class TrackList extends ListActivity
listView = getListView();
listView.setOnCreateContextMenuListener(contextMenuListener);
+ preferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
+ serviceConnection = new TrackRecordingServiceConnection(this, serviceBindingChanged);
+
View deleteAll = findViewById(R.id.tracklist_btn_delete_all);
- View exportAll = findViewById(R.id.tracklist_btn_export_all);
- boolean notRecording = !MyTracks.getInstance().isRecording();
deleteAll.setOnClickListener(this);
- deleteAll.setEnabled(notRecording);
+
+ View exportAll = findViewById(R.id.tracklist_btn_export_all);
exportAll.setOnClickListener(this);
- exportAll.setEnabled(notRecording);
+
+ updateButtonsEnabled();
+
findViewById(R.id.tracklist_btn_import_all).setOnClickListener(this);
- SharedPreferences preferences =
- getSharedPreferences(Constants.SETTINGS_NAME, 0);
preferences.registerOnSharedPreferenceChangeListener(this);
metricUnits =
preferences.getBoolean(getString(R.string.metric_units_key), true);
@@ -220,6 +249,29 @@ public class TrackList extends ListActivity
setListAdapter();
}
+ @Override
+ protected void onStart() {
+ super.onStart();
+
+ serviceConnection.bindIfRunning();
+ }
+
+ @Override
+ protected void onDestroy() {
+ serviceConnection.unbind();
+
+ super.onDestroy();
+ }
+
+ private void updateButtonsEnabled() {
+ View deleteAll = findViewById(R.id.tracklist_btn_delete_all);
+ View exportAll = findViewById(R.id.tracklist_btn_export_all);
+
+ boolean notRecording = !isRecording();
+ deleteAll.setEnabled(notRecording);
+ exportAll.setEnabled(notRecording);
+ }
+
private void setListAdapter() {
// Get a cursor with all tracks
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
@@ -283,4 +335,8 @@ public class TrackList extends ListActivity
});
setListAdapter(adapter);
}
+
+ private boolean isRecording() {
+ return ServiceUtils.isRecording(TrackList.this, serviceConnection.getServiceIfBound(), preferences);
+ }
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/WaypointsList.java b/MyTracks/src/com/google/android/apps/mytracks/WaypointsList.java
index 9dc0c4295..8c96698f1 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/WaypointsList.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/WaypointsList.java
@@ -15,10 +15,14 @@
*/
package com.google.android.apps.mytracks;
+import static com.google.android.apps.mytracks.Constants.TAG;
+
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
import com.google.android.apps.mytracks.content.WaypointsColumns;
+import com.google.android.apps.mytracks.services.ITrackRecordingService;
+import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
@@ -43,6 +47,7 @@ import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
+import android.widget.Toast;
/**
* Activity which shows the list of waypoints in a track.
@@ -60,6 +65,7 @@ public class WaypointsList extends ListActivity
private Button insertStatisticsButton = null;
private long recordingTrackId = -1;
private MyTracksProviderUtils providerUtils;
+ private TrackRecordingServiceConnection serviceConnection;
private Cursor waypointsCursor = null;
@@ -125,6 +131,7 @@ public class WaypointsList extends ListActivity
super.onCreate(savedInstanceState);
providerUtils = MyTracksProviderUtils.Factory.get(this);
+ serviceConnection = new TrackRecordingServiceConnection(this, null);
// We don't need a window title bar:
requestWindowFeature(Window.FEATURE_NO_TITLE);
@@ -171,6 +178,20 @@ public class WaypointsList extends ListActivity
setListAdapter();
}
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ serviceConnection.bindIfRunning();
+ }
+
+ @Override
+ protected void onDestroy() {
+ serviceConnection.unbind();
+
+ super.onDestroy();
+ }
+
@Override
public void onClick(View v) {
WaypointCreationRequest request;
@@ -184,17 +205,10 @@ public class WaypointsList extends ListActivity
default:
return;
}
- long id;
- try {
- id = MyTracks.getInstance().insertWaypoint(request);
- } catch (RemoteException e) {
- Log.e(Constants.TAG, "Cannot insert marker.", e);
- return;
- } catch (IllegalStateException e) {
- Log.e(Constants.TAG, "Cannot insert marker.", e);
- return;
- }
+ long id = insertWaypoint(request);
if (id < 0) {
+ Toast.makeText(this, R.string.error_unable_to_insert_marker,
+ Toast.LENGTH_LONG).show();
Log.e(Constants.TAG, "Failed to insert marker.");
return;
}
@@ -203,6 +217,28 @@ public class WaypointsList extends ListActivity
startActivity(intent);
}
+ private long insertWaypoint(WaypointCreationRequest request) {
+ try {
+ ITrackRecordingService trackRecordingService = serviceConnection.getServiceIfBound();
+ if (trackRecordingService != null) {
+ long waypointId = trackRecordingService.insertWaypoint(request);
+ if (waypointId >= 0) {
+ Toast.makeText(this, R.string.status_statistics_inserted,
+ Toast.LENGTH_LONG).show();
+ return waypointId;
+ }
+ } else {
+ Log.e(TAG, "Not connected to service, not inserting waypoint");
+ }
+ } catch (RemoteException e) {
+ Log.e(Constants.TAG, "Cannot insert marker.", e);
+ } catch (IllegalStateException e) {
+ Log.e(Constants.TAG, "Cannot insert marker.", e);
+ }
+
+ return -1;
+ }
+
private void setListAdapter() {
// Get a cursor with all tracks
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java b/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java
index 5ee3e7b5d..5b4e1017d 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java
@@ -22,6 +22,7 @@ import static com.google.android.apps.mytracks.Constants.MAX_NETWORK_AGE_MS;
import static com.google.android.apps.mytracks.Constants.TAG;
import static com.google.android.apps.mytracks.Constants.TARGET_DISPLAYED_TRACK_POINTS;
+import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.DataSourceManager.DataSourceListener;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.DoubleBufferedLocationFactory;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
@@ -149,24 +150,21 @@ public class TrackDataHub {
// Get content notifications on the main thread, send listener callbacks in another.
// This ensures listener calls are serialized.
- private final HandlerThread listenerHandlerThread;
- private final Handler listenerHandler;
+ private HandlerThread listenerHandlerThread;
+ private Handler listenerHandler;
/** Manager for external listeners (those from activities). */
private final TrackDataListeners listeners;
/** Wrapper for interacting with system data managers. */
- private final DataSourcesWrapper dataSources;
+ private DataSourcesWrapper dataSources;
/** Manager for system data listener registrations. */
- private final DataSourceManager dataSourceManager;
+ private DataSourceManager dataSourceManager;
/** Condensed listener for system data listener events. */
private final DataSourceListener dataSourceListener = new HubDataSourceListener();
- /** Whether we've been started. */
- private boolean started;
-
// Cached preference values
private int minRequiredAccuracy;
private boolean useMetricUnits;
@@ -191,28 +189,34 @@ public class TrackDataHub {
private int lastSamplingFrequency;
private DoubleBufferedLocationFactory locationFactory;
+ private static TrackDataHub startedInstance;
+
/**
- * Default constructor.
+ * Builds a new {@link TrackDataHub} instance.
*/
- public TrackDataHub(Context ctx, SharedPreferences preferences,
- MyTracksProviderUtils providerUtils) {
- this(ctx, new DataSourcesWrapperImpl(ctx, preferences), new TrackDataListeners(),
- preferences, providerUtils, TARGET_DISPLAYED_TRACK_POINTS);
+ public synchronized static TrackDataHub newInstance(Context context) {
+ // Ensure our singleton is never bound to an activity, to avoid memory leaks.
+ context = context.getApplicationContext();
+
+ SharedPreferences preferences = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
+ MyTracksProviderUtils providerUtils = MyTracksProviderUtils.Factory.get(context);
+ return new TrackDataHub(context,
+ new TrackDataListeners(),
+ preferences, providerUtils,
+ TARGET_DISPLAYED_TRACK_POINTS);
}
/**
* Injection constructor.
*/
// @VisibleForTesting
- TrackDataHub(Context ctx, DataSourcesWrapper dataSources, TrackDataListeners listeners,
- SharedPreferences preferences, MyTracksProviderUtils providerUtils, int targetNumPoints) {
+ TrackDataHub(Context ctx, TrackDataListeners listeners, SharedPreferences preferences,
+ MyTracksProviderUtils providerUtils, int targetNumPoints) {
this.context = ctx;
this.listeners = listeners;
this.preferences = preferences;
this.providerUtils = providerUtils;
- this.dataSources = dataSources;
this.targetNumPoints = targetNumPoints;
- this.dataSourceManager = new DataSourceManager(dataSourceListener, dataSources);
this.locationFactory = new DoubleBufferedLocationFactory();
SELECTED_TRACK_KEY = context.getString(R.string.selected_track_key);
@@ -221,10 +225,6 @@ public class TrackDataHub {
METRIC_UNITS_KEY = context.getString(R.string.metric_units_key);
SPEED_REPORTING_KEY = context.getString(R.string.report_speed_key);
- listenerHandlerThread = new HandlerThread("trackDataContentThread");
- listenerHandlerThread.start();
- listenerHandler = new Handler(listenerHandlerThread.getLooper());
-
resetState();
}
@@ -234,11 +234,17 @@ public class TrackDataHub {
*/
public void start() {
Log.i(TAG, "TrackDataHub.start");
- if (started) {
+ if (startedInstance != null) {
Log.w(TAG, "Already started, ignoring");
return;
}
- started = true;
+ startedInstance = this;
+
+ listenerHandlerThread = new HandlerThread("trackDataContentThread");
+ listenerHandlerThread.start();
+ listenerHandler = new Handler(listenerHandlerThread.getLooper());
+ dataSources = newDataSources();
+ dataSourceManager = new DataSourceManager(dataSourceListener, dataSources);
// This may or may not register internal listeners, depending on whether
// we already had external listeners.
@@ -249,12 +255,23 @@ public class TrackDataHub {
loadDataForAllListeners();
}
- private void loadSharedPreferences() {
- selectedTrackId = preferences.getLong(SELECTED_TRACK_KEY, -1);
- useMetricUnits = preferences.getBoolean(METRIC_UNITS_KEY, true);
- reportSpeed = preferences.getBoolean(SPEED_REPORTING_KEY, true);
- minRequiredAccuracy = preferences.getInt(MIN_REQUIRED_ACCURACY_KEY,
- DEFAULT_MIN_REQUIRED_ACCURACY);
+ // @VisibleForTesting
+ protected DataSourcesWrapper newDataSources() {
+ return new DataSourcesWrapperImpl(context, preferences);
+ }
+
+ /**
+ * If there's an instance for which {@link start} has been called, returns it.
+ *
+ * @return the started instance
+ * @throws IllegalStateException if there isn't a started instance
+ */
+ public static TrackDataHub getStartedInstance() {
+ if (startedInstance == null) {
+ throw new IllegalStateException("Data hub not started");
+ }
+
+ return startedInstance;
}
/**
@@ -263,25 +280,42 @@ public class TrackDataHub {
*/
public void stop() {
Log.i(TAG, "TrackDataHub.stop");
- if (!started) {
+ if (!isStarted()) {
Log.w(TAG, "Not started, ignoring");
return;
}
// Unregister internal listeners even if there are external listeners registered.
dataSourceManager.unregisterAllListeners();
+ listenerHandlerThread.getLooper().quit();
- started = false;
+ startedInstance = null;
+
+ dataSources = null;
+ dataSourceManager = null;
+ listenerHandlerThread = null;
+ listenerHandler = null;
}
- /** Permanently invalidates and throws away all resources used by this class. */
- public void destroy() {
- if (started) {
- throw new IllegalStateException("Can only destroy the data hub after it's been stopped");
+ private boolean isStarted() {
+ return startedInstance != null;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (isStarted() || listenerHandlerThread.isAlive()) {
+ Log.e(TAG, "Forgot to stop() TrackDataHub");
}
- ApiFeatures.getInstance().getApiPlatformAdapter()
- .stopHandlerThread(listenerHandlerThread);
+ super.finalize();
+ }
+
+ private void loadSharedPreferences() {
+ selectedTrackId = preferences.getLong(SELECTED_TRACK_KEY, -1);
+ useMetricUnits = preferences.getBoolean(METRIC_UNITS_KEY, true);
+ reportSpeed = preferences.getBoolean(SPEED_REPORTING_KEY, true);
+ minRequiredAccuracy = preferences.getInt(MIN_REQUIRED_ACCURACY_KEY,
+ DEFAULT_MIN_REQUIRED_ACCURACY);
}
/** Updates known magnetic declination if needed. */
@@ -324,6 +358,10 @@ public class TrackDataHub {
* is not available or doesn't have a fix.
*/
public void forceUpdateLocation() {
+ if (!isStarted()) {
+ Log.w(TAG, "Not started, not forcing location update");
+ return;
+ }
Log.i(TAG, "Forcing location update");
Location loc = dataSources.getLastKnownLocation();
@@ -335,7 +373,7 @@ public class TrackDataHub {
/** Returns the ID of the currently-selected track. */
public long getSelectedTrackId() {
- if (!started) {
+ if (!isStarted()) {
loadSharedPreferences();
}
return selectedTrackId;
@@ -346,17 +384,9 @@ public class TrackDataHub {
return getSelectedTrackId() > 0;
}
- /** Returns whether we're currently recording a track. */
- public boolean isRecording() {
- if (!started) {
- loadSharedPreferences();
- }
- return preferences.getLong(RECORDING_TRACK_KEY, -1) > 0;
- }
-
/** Returns whether the selected track is still being recorded. */
public boolean isRecordingSelected() {
- if (!started) {
+ if (!isStarted()) {
loadSharedPreferences();
}
long recordingTrackId = preferences.getLong(RECORDING_TRACK_KEY, -1);
@@ -412,7 +442,7 @@ public class TrackDataHub {
// Don't load any data or start internal listeners if start() hasn't been
// called. When it is called, we'll do both things.
- if (!started) return;
+ if (!isStarted()) return;
reloadDataForListener(registration);
@@ -426,7 +456,7 @@ public class TrackDataHub {
// Don't load any data or start internal listeners if start() hasn't been
// called. When it is called, we'll do both things.
- if (!started) return;
+ if (!isStarted()) return;
dataSourceManager.updateAllListeners(getNeededListenerTypes());
}
@@ -449,7 +479,7 @@ public class TrackDataHub {
* Assumes it's called from a block that synchronizes on {@link #listeners}.
*/
private void reloadDataForListener(final ListenerRegistration registration) {
- if (!started) {
+ if (!isStarted()) {
Log.w(TAG, "Not started, not reloading");
return;
}
@@ -541,7 +571,7 @@ public class TrackDataHub {
* Reloads all track data received so far into the specified listeners.
*/
private void loadDataForAllListeners() {
- if (!started) {
+ if (!isStarted()) {
Log.w(TAG, "Not started, not reloading");
return;
}
@@ -608,7 +638,7 @@ public class TrackDataHub {
/** Called when the speed/pace reporting preference changes. */
private void notifySpeedReportingChanged() {
- if (!started) return;
+ if (!isStarted()) return;
runInListenerThread(new Runnable() {
@Override
@@ -630,7 +660,7 @@ public class TrackDataHub {
/** Called when the metric units setting changes. */
private void notifyUnitsChanged() {
- if (!started) return;
+ if (!isStarted()) return;
runInListenerThread(new Runnable() {
@Override
@@ -1064,6 +1094,12 @@ public class TrackDataHub {
// @VisibleForTesting
protected void runInListenerThread(Runnable runnable) {
+ if (listenerHandler == null) {
+ // Use a Throwable to ensure the stack trace is logged.
+ Log.e(TAG, "Tried to use listener thread before start()", new Throwable());
+ return;
+ }
+
listenerHandler.post(runnable);
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/ModernAuthManager.java b/MyTracks/src/com/google/android/apps/mytracks/io/ModernAuthManager.java
index 24c721bc4..8048f4fab 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/ModernAuthManager.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/ModernAuthManager.java
@@ -22,7 +22,6 @@ import com.google.android.accounts.AccountManagerFuture;
import com.google.android.accounts.AuthenticatorException;
import com.google.android.accounts.OperationCanceledException;
import com.google.android.apps.mytracks.AccountChooser;
-import com.google.android.apps.mytracks.MyTracks;
import com.google.android.apps.mytracks.Constants;
import android.app.Activity;
@@ -183,7 +182,9 @@ public class ModernAuthManager implements AuthManager {
public void run() {
accountManager.invalidateAuthToken(Constants.ACCOUNT_TYPE,
authToken);
- MyTracks.getInstance().getAccountChooser().chooseAccount(activity,
+
+ AccountChooser accountChooser = new AccountChooser();
+ accountChooser.chooseAccount(activity,
new AccountChooser.AccountHandler() {
@Override
public void handleAccountSelected(Account account) {
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/SendToDocs.java b/MyTracks/src/com/google/android/apps/mytracks/io/SendToDocs.java
index 5105285e4..1b0047740 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/SendToDocs.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/SendToDocs.java
@@ -15,8 +15,8 @@
*/
package com.google.android.apps.mytracks.io;
-import com.google.android.apps.mytracks.MyTracks;
import com.google.android.apps.mytracks.Constants;
+import com.google.android.apps.mytracks.ProgressIndicator;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.io.docs.DocsHelper;
@@ -25,10 +25,10 @@ import com.google.android.apps.mytracks.io.gdata.GDataWrapper;
import com.google.android.common.gdata.AndroidXmlParserFactory;
import com.google.android.maps.mytracks.R;
import com.google.wireless.gdata.client.GDataClient;
+import com.google.wireless.gdata.client.GDataServiceClient;
import com.google.wireless.gdata.docs.DocumentsClient;
import com.google.wireless.gdata.docs.SpreadsheetsClient;
import com.google.wireless.gdata.docs.XmlDocsGDataParserFactory;
-import com.google.wireless.gdata.client.GDataServiceClient;
import android.app.Activity;
import android.content.SharedPreferences;
@@ -49,11 +49,11 @@ public class SendToDocs {
/** The GData service name for the Google Docs Document List */
public static final String GDATA_SERVICE_NAME_DOCLIST = "writely";
-
+
private final Activity activity;
private final AuthManager trixAuth;
private final AuthManager docListAuth;
- private final long trackId;
+ private final ProgressIndicator progressIndicator;
private final boolean metricUnits;
private final HandlerThread handlerThread;
private final Handler handler;
@@ -64,12 +64,13 @@ public class SendToDocs {
private String statusMessage = "";
private Runnable onCompletion = null;
+
public SendToDocs(Activity activity, AuthManager trixAuth,
- AuthManager docListAuth, long trackId) {
+ AuthManager docListAuth, ProgressIndicator progressIndicator) {
this.activity = activity;
this.trixAuth = trixAuth;
this.docListAuth = docListAuth;
- this.trackId = trackId;
+ this.progressIndicator = progressIndicator;
SharedPreferences preferences = activity.getSharedPreferences(
Constants.SETTINGS_NAME, 0);
@@ -81,23 +82,23 @@ public class SendToDocs {
metricUnits = true;
}
- Log.d(Constants.TAG,
- "Sending to Google Docs: trackId = " + trackId);
handlerThread = new HandlerThread("SendToGoogleDocs");
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
}
- public void run() {
+ public void sendToDocs(final long trackId) {
+ Log.d(Constants.TAG,
+ "Sending to Google Docs: trackId = " + trackId);
handler.post(new Runnable() {
@Override
public void run() {
- doUpload();
+ doUpload(trackId);
}
});
}
- private void doUpload() {
+ private void doUpload(long trackId) {
// TODO
statusMessage = activity.getString(R.string.error_sending_to_fusiontables);
success = false;
@@ -198,7 +199,7 @@ public class SendToDocs {
}
if (spreadsheetId == null) {
- MyTracks.getInstance().setProgressValue(65);
+ progressIndicator.setProgressValue(65);
// Waiting a few seconds and trying again. Maybe the server just had a
// hickup (unfortunately that happens quite a lot...).
try {
@@ -217,7 +218,7 @@ public class SendToDocs {
}
// We were unable to find an existing spreadsheet, so create a new one.
- MyTracks.getInstance().setProgressValue(70);
+ progressIndicator.setProgressValue(70);
if (spreadsheetId == null) {
Log.i(Constants.TAG, "Creating new spreadsheet: " + sheetTitle);
@@ -229,11 +230,11 @@ public class SendToDocs {
+ sheetTitle, e);
return false;
}
- MyTracks.getInstance().setProgressValue(80);
+ progressIndicator.setProgressValue(80);
createdNewSpreadSheet = true;
if (spreadsheetId == null) {
- MyTracks.getInstance().setProgressValue(85);
+ progressIndicator.setProgressValue(85);
// The previous creation might have succeeded even though GData
// reported an error. Seems to be a know bug,
// see http://code.google.com/p/gdata-issues/issues/detail?id=929
@@ -255,7 +256,7 @@ public class SendToDocs {
}
if (spreadsheetId == null) {
- MyTracks.getInstance().setProgressValue(87);
+ progressIndicator.setProgressValue(87);
// Re-try
try {
Thread.sleep(5000);
@@ -290,7 +291,7 @@ public class SendToDocs {
return false;
}
- MyTracks.getInstance().setProgressValue(90);
+ progressIndicator.setProgressValue(90);
docsHelper.addTrackRow(activity, trixAuth, spreadsheetId, worksheetId,
track, metricUnits);
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/SendToFusionTables.java b/MyTracks/src/com/google/android/apps/mytracks/io/SendToFusionTables.java
index 134305b90..5e7ae4977 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/SendToFusionTables.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/SendToFusionTables.java
@@ -50,6 +50,7 @@ import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URLEncoder;
+import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -432,10 +433,10 @@ public class SendToFusionTables implements Runnable {
* @return the kml.
*/
private String getKmlPoint(Location location) {
- StringBuilder builder = new StringBuilder("");
- appendCoordinate(location, builder);
- builder.append("");
- return builder.toString();
+ StringBuilder builder = new StringBuilder("");
+ appendCoordinate(location, builder);
+ builder.append("");
+ return builder.toString();
}
/**
@@ -611,7 +612,7 @@ public class SendToFusionTables implements Runnable {
// I am leaving the number of waypoints very high which should not be a
// problem because we don't try to load them into objects all at the
// same time.
- boolean success = true;
+ boolean success = true;
Cursor c = null;
try {
c = providerUtils.getWaypointsCursor(
@@ -625,9 +626,9 @@ public class SendToFusionTables implements Runnable {
Waypoint wpt = providerUtils.createWaypoint(c);
Log.d(Constants.TAG, "SendToFusionTables: Creating waypoint.");
success = createNewPoint(wpt.getName(), wpt.getDescription(), wpt.getLocation(),
- MARKER_TYPE_WAYPOINT);
+ MARKER_TYPE_WAYPOINT);
if (!success) {
- break;
+ break;
}
}
}
@@ -649,7 +650,7 @@ public class SendToFusionTables implements Runnable {
// to fit the completion percentage range alloted to track data upload.
double totalPercentage =
(totalLocationsRead + totalLocationsPrepared + totalLocationsUploaded)
- / (totalLocations * 3);
+ / (totalLocations * 3.0);
double scaledPercentage = totalPercentage
* (PROGRESS_UPLOAD_DATA_MAX - PROGRESS_UPLOAD_DATA_MIN) + PROGRESS_UPLOAD_DATA_MIN;
@@ -693,8 +694,8 @@ public class SendToFusionTables implements Runnable {
boolean success = response.isSuccessStatusCode;
if (success) {
byte[] result = new byte[1024];
- response.getContent().read(result);
- String s = Strings.fromBytesUtf8(result);
+ int read = response.getContent().read(result);
+ String s = new String(result, 0, read, Charset.forName("UTF8"));
String[] lines = s.split(Strings.LINE_SEPARATOR);
if (lines[0].equals("tableid")) {
tableId = lines[1];
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/ExternalFileBackup.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/ExternalFileBackup.java
index 1fbcf9485..16f18d0df 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/backup/ExternalFileBackup.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/ExternalFileBackup.java
@@ -15,6 +15,8 @@
*/
package com.google.android.apps.mytracks.io.backup;
+import static com.google.android.apps.mytracks.Constants.TAG;
+
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.TrackPointsColumns;
import com.google.android.apps.mytracks.content.TracksColumns;
@@ -205,7 +207,9 @@ class ExternalFileBackup {
} catch (IOException e) {
// We tried to delete the partially created file, but do nothing
// if that also fails.
- outputFile.delete();
+ if (!outputFile.delete()) {
+ Log.w(TAG, "Failed to delete file " + outputFile.getAbsolutePath());
+ }
throw e;
} finally {
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/ImportActivity.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/ImportActivity.java
new file mode 100644
index 000000000..7fe66f3be
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/ImportActivity.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.android.apps.mytracks.io.file;
+
+import static com.google.android.apps.mytracks.Constants.TAG;
+
+import com.google.android.apps.mytracks.DialogManager;
+import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
+import com.google.android.apps.mytracks.content.TracksColumns;
+import com.google.android.apps.mytracks.util.UriUtils;
+import com.google.android.maps.mytracks.R;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
+import android.app.ProgressDialog;
+import android.content.ContentUris;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.xml.sax.SAXException;
+
+/**
+ * Helper activity which imports tracks from a file.
+ *
+ * @author Rodrigo Damazio
+ */
+public class ImportActivity extends Activity {
+
+ private ProgressDialog progressDialog;
+ private MyTracksProviderUtils providerUtils;
+
+ @Override
+ public void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+
+ providerUtils = MyTracksProviderUtils.Factory.get(this);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ Intent intent = getIntent();
+ String action = intent.getAction();
+ Uri data = intent.getData();
+ if (!(Intent.ACTION_VIEW.equals(action) || Intent.ACTION_ATTACH_DATA.equals(action))
+ || !UriUtils.isFileUri(data)) {
+ Log.e(TAG, "Received an intent with unsupported action or data: " + intent);
+ finish();
+ return;
+ }
+
+ Log.i(TAG, "Importing GPX file at " + data);
+ startTrackImport(data.getPath());
+ }
+
+ private void startTrackImport(final String fileName) {
+ progressDialog = new ProgressDialog(this);
+ progressDialog.setOwnerActivity(this);
+ progressDialog.setIcon(android.R.drawable.ic_dialog_info);
+ progressDialog.setTitle(R.string.progress_title);
+ progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+ progressDialog.setMessage(getString(R.string.import_progress_message));
+ progressDialog.show();
+
+ Thread t = new Thread() {
+ @Override
+ public void run() {
+ int message = R.string.success;
+
+ long[] trackIdsImported = null;
+
+ try {
+ try {
+ InputStream is = new FileInputStream(fileName);
+ trackIdsImported = GpxImporter.importGPXFile(is, providerUtils);
+ } catch (SAXException e) {
+ Log.e(TAG, "Caught an unexpected exception.", e);
+ message = R.string.error_generic;
+ } catch (ParserConfigurationException e) {
+ Log.e(TAG, "Caught an unexpected exception.", e);
+ message = R.string.error_generic;
+ } catch (IOException e) {
+ Log.e(TAG, "Caught an unexpected exception.", e);
+ message = R.string.error_unable_to_read_file;
+ } catch (NullPointerException e) {
+ Log.e(TAG, "Caught an unexpected exception.", e);
+ message = R.string.error_invalid_gpx_format;
+ } catch (OutOfMemoryError e) {
+ Log.e(TAG, "Caught an unexpected exception.", e);
+ message = R.string.error_out_of_memory;
+ }
+
+ boolean success = (trackIdsImported != null && trackIdsImported.length > 0);
+ onImportDone(success, message, trackIdsImported);
+ } finally {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ progressDialog.dismiss();
+ }
+ });
+ }
+ }
+ };
+ t.start();
+ }
+
+ private void onImportDone(boolean success, int message, final long[] trackIds) {
+ OnClickListener finishOnClick = new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface arg0, int arg1) {
+ finish();
+ }
+ };
+
+ if (!success) {
+ DialogManager.showMessageDialog(this, message, false /* success */,
+ finishOnClick);
+ return;
+ }
+
+ // Show a dialog telling the user about the import, and asking if he wishes
+ // to open the track right away.
+ final Builder dialogBuilder = new AlertDialog.Builder(this);
+ dialogBuilder.setCancelable(true);
+ dialogBuilder.setMessage(getString(R.string.import_success, trackIds.length));
+ dialogBuilder.setPositiveButton(android.R.string.ok, finishOnClick);
+ dialogBuilder.setNeutralButton(R.string.import_show_track, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface arg0, int arg1) {
+ long lastTrackId = trackIds[trackIds.length - 1];
+ Uri trackUri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, lastTrackId);
+
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(trackUri, TracksColumns.CONTENT_ITEMTYPE);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ finish();
+ }
+ });
+
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ dialogBuilder.show();
+ }
+ });
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/SaveActivity.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/SaveActivity.java
new file mode 100644
index 000000000..d825c482c
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/SaveActivity.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.android.apps.mytracks.io.file;
+
+import static com.google.android.apps.mytracks.Constants.TAG;
+
+import com.google.android.apps.mytracks.Constants;
+import com.google.android.apps.mytracks.DialogManager;
+import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
+import com.google.android.apps.mytracks.content.TracksColumns;
+import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
+import com.google.android.apps.mytracks.util.FileUtils;
+import com.google.android.apps.mytracks.util.UriUtils;
+import com.google.android.maps.mytracks.R;
+
+import android.app.Activity;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.io.File;
+
+/**
+ * Activity for saving a track to a file (and optionally sending that file).
+ *
+ * @author Rodrigo Damazio
+ */
+public class SaveActivity extends Activity {
+ public static final String EXTRA_SHARE_FILE = "share_file";
+ public static final String EXTRA_FILE_FORMAT = "file_format";
+
+ private MyTracksProviderUtils providerUtils;
+ private long trackId;
+ private TrackWriter writer;
+ private boolean shareFile;
+ private TrackFileFormat format;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ providerUtils = MyTracksProviderUtils.Factory.get(this);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+
+ Intent intent = getIntent();
+ String action = intent.getAction();
+ String type = intent.getType();
+ Uri data = intent.getData();
+ if (!getString(R.string.save_intent_action).equals(action) ||
+ !TracksColumns.CONTENT_ITEMTYPE.equals(type) ||
+ !UriUtils.matchesContentUri(data, TracksColumns.CONTENT_URI)) {
+ Log.e(TAG, "Got bad save intent: " + intent);
+ finish();
+ return;
+ }
+
+ trackId = ContentUris.parseId(data);
+
+ int formatIdx = intent.getIntExtra(EXTRA_FILE_FORMAT, -1);
+ format = TrackFileFormat.values()[formatIdx];
+ shareFile = intent.getBooleanExtra(EXTRA_SHARE_FILE, false);
+
+ writer = TrackWriterFactory.newWriter(this, providerUtils, trackId, format);
+ if (writer == null) {
+ Log.e(TAG, "Unable to build writer");
+ finish();
+ return;
+ }
+
+ if (shareFile) {
+ // If the file is for sending, save it to a temporary location instead.
+ FileUtils fileUtils = new FileUtils();
+ String extension = format.getExtension();
+ String dirName = fileUtils.buildExternalDirectoryPath(extension, "tmp");
+
+ File dir = new File(dirName);
+ writer.setDirectory(dir);
+ }
+
+ WriteProgressController controller = new WriteProgressController(this, writer);
+ controller.setOnCompletionListener(new WriteProgressController.OnCompletionListener() {
+ @Override
+ public void onComplete() {
+ onWriteComplete();
+ }
+ });
+ controller.startWrite();
+ }
+
+ private void onWriteComplete() {
+ if (shareFile) {
+ shareWrittenFile();
+ } else {
+ showResultDialog();
+ }
+ }
+
+ private void shareWrittenFile() {
+ if (!writer.wasSuccess()) {
+ showResultDialog();
+ return;
+ }
+
+ // Share the file.
+ Intent shareIntent = new Intent(Intent.ACTION_SEND);
+ shareIntent.putExtra(Intent.EXTRA_SUBJECT,
+ getResources().getText(R.string.send_track_subject).toString());
+ shareIntent.putExtra(Intent.EXTRA_TEXT,
+ getResources().getText(R.string.send_track_body_format)
+ .toString());
+ shareIntent.setType(format.getMimeType());
+ Uri u = Uri.fromFile(new File(writer.getAbsolutePath()));
+ shareIntent.putExtra(Intent.EXTRA_STREAM, u);
+ startActivity(Intent.createChooser(shareIntent,
+ getResources().getText(R.string.share_track).toString()));
+ }
+
+ private void showResultDialog() {
+ DialogManager.showMessageDialog(this, writer.getErrorMessage(), writer.wasSuccess(),
+ new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int arg1) {
+ dialog.dismiss();
+ finish();
+ }
+ });
+ }
+
+ public static void handleExportTrackAction(Context ctx, long trackId, int actionCode) {
+ if (trackId < 0) {
+ return;
+ }
+
+ TrackFileFormat exportFormat = null;
+ switch (actionCode) {
+ case Constants.SAVE_GPX_FILE:
+ case Constants.SHARE_GPX_FILE:
+ exportFormat = TrackFileFormat.GPX;
+ break;
+ case Constants.SAVE_KML_FILE:
+ case Constants.SHARE_KML_FILE:
+ exportFormat = TrackFileFormat.KML;
+ break;
+ case Constants.SAVE_CSV_FILE:
+ case Constants.SHARE_CSV_FILE:
+ exportFormat = TrackFileFormat.CSV;
+ break;
+ case Constants.SAVE_TCX_FILE:
+ case Constants.SHARE_TCX_FILE:
+ exportFormat = TrackFileFormat.TCX;
+ break;
+ default:
+ throw new IllegalArgumentException("Warning unhandled action code: " + actionCode);
+ }
+
+ boolean shareFile = false;
+ switch (actionCode) {
+ case Constants.SHARE_GPX_FILE:
+ case Constants.SHARE_KML_FILE:
+ case Constants.SHARE_CSV_FILE:
+ case Constants.SHARE_TCX_FILE:
+ shareFile = true;
+ }
+
+ Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId);
+
+ Intent intent = new Intent(ctx, SaveActivity.class);
+ intent.setAction(ctx.getString(R.string.save_intent_action));
+ intent.setDataAndType(uri, TracksColumns.CONTENT_ITEMTYPE);
+ intent.putExtra(EXTRA_FILE_FORMAT, exportFormat.ordinal());
+ intent.putExtra(EXTRA_SHARE_FILE, shareFile);
+ ctx.startActivity(intent);
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/TempFileCleaner.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/TempFileCleaner.java
index 6f590342b..ec200f823 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/TempFileCleaner.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/TempFileCleaner.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -15,7 +15,10 @@
*/
package com.google.android.apps.mytracks.io.file;
+import static com.google.android.apps.mytracks.Constants.TAG;
+
import android.os.Environment;
+import android.util.Log;
import java.io.File;
@@ -24,18 +27,18 @@ import java.io.File;
* @author Sandor Dornbush
*/
public class TempFileCleaner {
-
+
private long currentTimeMillis;
public static void clean() {
(new TempFileCleaner(System.currentTimeMillis())).cleanImpl();
}
-
+
// @VisibleForTesting
TempFileCleaner(long time) {
currentTimeMillis = time;
}
-
+
private void cleanImpl() {
if (!Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
@@ -64,7 +67,9 @@ public class TempFileCleaner {
long oldest = currentTimeMillis - 1000 * 3600;
for (File f : dir.listFiles()) {
if (f.lastModified() < oldest) {
- f.delete();
+ if (!f.delete()) {
+ Log.w(TAG, "Failed to delete file " + f.getAbsolutePath());
+ }
count++;
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterImpl.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterImpl.java
index d74a9c8f4..dd18b5a11 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterImpl.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterImpl.java
@@ -15,6 +15,8 @@
*/
package com.google.android.apps.mytracks.io.file;
+import static com.google.android.apps.mytracks.Constants.TAG;
+
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
@@ -120,7 +122,9 @@ class TrackWriterImpl implements TrackWriter {
} catch (InterruptedException e) {
Log.i(Constants.TAG, "The track write was interrupted");
if (file != null) {
- file.delete();
+ if (!file.delete()) {
+ Log.w(TAG, "Failed to delete file " + file.getAbsolutePath());
+ }
}
success = false;
errorMessage = R.string.error_operation_cancelled;
@@ -195,7 +199,7 @@ class TrackWriterImpl implements TrackWriter {
directory, track.getName(), writer.getExtension());
if (fileName == null) {
Log.e(Constants.TAG,
- "Unable to get a unique filename for " + fileName);
+ "Unable to get a unique filename for " + track.getName());
return false;
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/WriteProgressController.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/WriteProgressController.java
similarity index 95%
rename from MyTracks/src/com/google/android/apps/mytracks/WriteProgressController.java
rename to MyTracks/src/com/google/android/apps/mytracks/io/file/WriteProgressController.java
index 3f0356756..861df8ddd 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/WriteProgressController.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/WriteProgressController.java
@@ -13,9 +13,9 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.google.android.apps.mytracks;
+package com.google.android.apps.mytracks.io.file;
-import com.google.android.apps.mytracks.io.file.TrackWriter;
+import com.google.android.apps.mytracks.DialogManager;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
@@ -41,7 +41,7 @@ class WriteProgressController {
* dialog has been dismissed. Whether the write succeeded can be
* determined by examining the {@link TrackWriter}.
*/
- public void onComplete(TrackWriter writer);
+ public void onComplete();
}
private final Activity activity;
@@ -104,7 +104,7 @@ class WriteProgressController {
DialogManager.dismissDialogSafely(activity, dialog);
if (onCompletionListener != null) {
- onCompletionListener.onComplete(writer);
+ onCompletionListener.onComplete();
}
}
};
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/gdata/AndroidGDataClient.java b/MyTracks/src/com/google/android/apps/mytracks/io/gdata/AndroidGDataClient.java
index 7b037bb75..449056870 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/gdata/AndroidGDataClient.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/gdata/AndroidGDataClient.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -137,7 +137,7 @@ public class AndroidGDataClient implements GDataClient {
/*
* (non-Javadoc)
- *
+ *
* @see GDataClient#encodeUri(java.lang.String)
*/
public String encodeUri(String uri) {
@@ -155,7 +155,7 @@ public class AndroidGDataClient implements GDataClient {
/*
* (non-Javadoc)
- *
+ *
* @see com.google.wireless.gdata.client.GDataClient#createQueryParams()
*/
public QueryParams createQueryParams() {
@@ -273,7 +273,7 @@ public class AndroidGDataClient implements GDataClient {
String errorMessage = null;
HttpEntity entity = response.getEntity();
try {
- if (response != null && entity != null) {
+ if (entity != null) {
InputStream in = entity.getContent();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[8192];
@@ -329,7 +329,7 @@ public class AndroidGDataClient implements GDataClient {
/*
* (non-Javadoc)
- *
+ *
* @see GDataClient#getFeedAsStream(java.lang.String, java.lang.String)
*/
public InputStream getFeedAsStream(String feedUrl, String authToken)
@@ -358,7 +358,7 @@ public class AndroidGDataClient implements GDataClient {
/*
* (non-Javadoc)
- *
+ *
* @see GDataClient#createEntry
*/
public InputStream createEntry(String feedUrl, String authToken,
@@ -377,7 +377,7 @@ public class AndroidGDataClient implements GDataClient {
/*
* (non-Javadoc)
- *
+ *
* @see GDataClient#updateEntry
*/
public InputStream updateEntry(String editUri, String authToken,
@@ -395,7 +395,7 @@ public class AndroidGDataClient implements GDataClient {
/*
* (non-Javadoc)
- *
+ *
* @see GDataClient#deleteEntry
*/
public void deleteEntry(String editUri, String authToken)
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/mymaps/MyMapsFeature.java b/MyTracks/src/com/google/android/apps/mytracks/io/mymaps/MyMapsFeature.java
index 2cd24d641..87aa2f236 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/mymaps/MyMapsFeature.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/mymaps/MyMapsFeature.java
@@ -3,7 +3,7 @@ package com.google.android.apps.mytracks.io.mymaps;
import com.google.android.maps.GeoPoint;
-import java.io.Serializable;
+import java.util.Random;
import java.util.Vector;
/**
@@ -12,7 +12,7 @@ import java.util.Vector;
* {@link MyMapsFeatureMetadata} object so that it can be more efficiently
* transmitted to other activities.
*/
-class MyMapsFeature implements Serializable {
+class MyMapsFeature {
private static final long serialVersionUID = 8439035544430497236L;
@@ -49,6 +49,8 @@ class MyMapsFeature implements Serializable {
/** The metadata of this feature in a format efficient for transmission. */
private MyMapsFeatureMetadata featureInfo = new MyMapsFeatureMetadata();
+ private final Random random = new Random();
+
/**
* Initializes a valid but empty feature. It will default to a
* {@link #MARKER} with a blue placemark with a dot as an icon at the
@@ -62,7 +64,7 @@ class MyMapsFeature implements Serializable {
*
* @param point The new point to add
*/
- public synchronized void addPoint(GeoPoint point) {
+ public void addPoint(GeoPoint point) {
latitudeE6.add(point.getLatitudeE6());
longitudeE6.add(point.getLongitudeE6());
}
@@ -71,10 +73,10 @@ class MyMapsFeature implements Serializable {
* Generates a new local id for this feature based on the current time and
* a random number.
*/
- public synchronized void generateAndroidId() {
- Long time = System.currentTimeMillis();
- Integer random = (int) (Math.random() * 10000);
- androidId = time.toString() + "." + random.toString();
+ void generateAndroidId() {
+ long time = System.currentTimeMillis();
+ int rand = random.nextInt(10000);
+ androidId = time + "." + rand;
}
/**
@@ -82,7 +84,7 @@ class MyMapsFeature implements Serializable {
*
* @return The local id for this feature
*/
- public String getAndroidId() {
+ String getAndroidId() {
return androidId;
}
@@ -102,7 +104,7 @@ class MyMapsFeature implements Serializable {
*
* @param description The new description of this feature
*/
- public synchronized void setDescription(String description) {
+ public void setDescription(String description) {
featureInfo.setDescription(description);
}
@@ -196,7 +198,7 @@ class MyMapsFeature implements Serializable {
*
* @param title The new title of this feature
*/
- public synchronized void setTitle(String title) {
+ public void setTitle(String title) {
featureInfo.setTitle(title);
}
@@ -207,7 +209,7 @@ class MyMapsFeature implements Serializable {
* @param type The new type of the feature. That type must be one of
* {@link #MARKER}, {@link #LINE}, or {@link #SHAPE}
*/
- public synchronized void setType(int type) {
+ public void setType(int type) {
featureInfo.setType(type);
}
@@ -217,7 +219,7 @@ class MyMapsFeature implements Serializable {
*
* @param color The new ARGB color of this feature
*/
- public synchronized void setColor(int color) {
+ public void setColor(int color) {
featureInfo.setColor(color);
}
@@ -227,7 +229,7 @@ class MyMapsFeature implements Serializable {
*
* @param url The new icon url of the feature
*/
- public synchronized void setIconUrl(String url) {
+ public void setIconUrl(String url) {
featureInfo.setIconUrl(url);
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/mymaps/MyMapsFeatureMetadata.java b/MyTracks/src/com/google/android/apps/mytracks/io/mymaps/MyMapsFeatureMetadata.java
index 7e94d24b9..acb1486c7 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/mymaps/MyMapsFeatureMetadata.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/mymaps/MyMapsFeatureMetadata.java
@@ -82,6 +82,6 @@ class MyMapsFeatureMetadata {
}
public void setIconUrl(String url) {
- iconUrl = new String(url);
+ iconUrl = url;
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/mymaps/MyMapsMapMetadata.java b/MyTracks/src/com/google/android/apps/mytracks/io/mymaps/MyMapsMapMetadata.java
index 4ffb459b8..eab3b657d 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/mymaps/MyMapsMapMetadata.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/mymaps/MyMapsMapMetadata.java
@@ -23,7 +23,7 @@ class MyMapsMapMetadata {
}
public void setTitle(String title) {
- this.title = new String(title);
+ this.title = title;
}
public String getDescription() {
@@ -31,7 +31,7 @@ class MyMapsMapMetadata {
}
public void setDescription(String description) {
- this.description = new String(description);
+ this.description = description;
}
public boolean getSearchable() {
@@ -47,6 +47,6 @@ class MyMapsMapMetadata {
}
public void setGDataEditUri(String editUri) {
- this.gdataEditUri = new String(editUri);
+ this.gdataEditUri = editUri;
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/mymaps/XmlMapsGDataParserFactory.java b/MyTracks/src/com/google/android/apps/mytracks/io/mymaps/XmlMapsGDataParserFactory.java
index 15c0a884e..3bba20e56 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/mymaps/XmlMapsGDataParserFactory.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/mymaps/XmlMapsGDataParserFactory.java
@@ -85,8 +85,8 @@ class XmlMapsGDataParserFactory implements GDataParserFactory {
@Override
public GDataSerializer createSerializer(Entry en) {
- if (en.getClass() == MapFeatureEntry.class) {
- return new XmlMapsGDataSerializer(xmlFactory, en);
+ if (en instanceof MapFeatureEntry) {
+ return new XmlMapsGDataSerializer(xmlFactory, (MapFeatureEntry) en);
} else {
return new XmlEntryGDataSerializer(xmlFactory, en);
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/mymaps/XmlMapsGDataSerializer.java b/MyTracks/src/com/google/android/apps/mytracks/io/mymaps/XmlMapsGDataSerializer.java
index 80598788d..3251f2624 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/mymaps/XmlMapsGDataSerializer.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/mymaps/XmlMapsGDataSerializer.java
@@ -1,7 +1,6 @@
// Copyright 2010 Google Inc. All Rights Reserved.
package com.google.android.apps.mytracks.io.mymaps;
-import com.google.wireless.gdata.data.Entry;
import com.google.wireless.gdata.data.StringUtils;
import com.google.wireless.gdata.parser.ParseException;
import com.google.wireless.gdata.parser.xml.XmlGDataParser;
@@ -10,14 +9,14 @@ import com.google.wireless.gdata.serializer.xml.XmlEntryGDataSerializer;
import android.util.Log;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
/**
* Serializer of maps data for GData.
*/
@@ -29,11 +28,11 @@ class XmlMapsGDataSerializer extends XmlEntryGDataSerializer {
private XmlParserFactory factory;
private OutputStream stream;
- public XmlMapsGDataSerializer(XmlParserFactory factory, Entry entry) {
+ public XmlMapsGDataSerializer(XmlParserFactory factory, MapFeatureEntry entry) {
super(factory, entry);
this.factory = factory;
- this.entry = (MapFeatureEntry) entry;
+ this.entry = entry;
}
@Override
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/SendActivity.java b/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/SendActivity.java
new file mode 100644
index 000000000..d19367402
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/SendActivity.java
@@ -0,0 +1,685 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.android.apps.mytracks.io.sendtogoogle;
+
+import static com.google.android.apps.mytracks.Constants.TAG;
+
+import com.google.android.accounts.Account;
+import com.google.android.apps.analytics.GoogleAnalyticsTracker;
+import com.google.android.apps.mytracks.AccountChooser;
+import com.google.android.apps.mytracks.Constants;
+import com.google.android.apps.mytracks.DialogManager;
+import com.google.android.apps.mytracks.MyMapsList;
+import com.google.android.apps.mytracks.ProgressIndicator;
+import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
+import com.google.android.apps.mytracks.content.Track;
+import com.google.android.apps.mytracks.content.TracksColumns;
+import com.google.android.apps.mytracks.io.AuthManager;
+import com.google.android.apps.mytracks.io.AuthManagerFactory;
+import com.google.android.apps.mytracks.io.SendToDocs;
+import com.google.android.apps.mytracks.io.SendToFusionTables;
+import com.google.android.apps.mytracks.io.SendToFusionTables.OnSendCompletedListener;
+import com.google.android.apps.mytracks.io.SendToMyMaps;
+import com.google.android.apps.mytracks.io.mymaps.MapsFacade;
+import com.google.android.apps.mytracks.io.mymaps.MyMapsConstants;
+import com.google.android.apps.mytracks.util.SystemUtils;
+import com.google.android.apps.mytracks.util.UriUtils;
+import com.google.android.maps.mytracks.R;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Helper activity for managing the sending of tracks to Google services.
+ *
+ * @author Rodrigo Damazio
+ */
+public class SendActivity extends Activity implements ProgressIndicator {
+ public static final String EXTRA_SHARE_LINK = "share_link";
+
+ // Services
+ private MyTracksProviderUtils providerUtils;
+ private SharedPreferences sharedPreferences;
+
+ private GoogleAnalyticsTracker tracker;
+
+ // Authentication
+ private AuthManager lastAuth;
+ private final HashMap authMap =
+ new HashMap();
+ private final AccountChooser accountChooser = new AccountChooser();
+
+ // Send request information.
+ private boolean shareRequested = false;
+ private long sendTrackId;
+
+ // Send result information, used by SendToGoogleResultDialog.
+ private boolean sendToMyMapsSuccess = false;
+ private boolean sendToFusionTablesSuccess = false;
+ private boolean sendToDocsSuccess = false;
+ private String sendToMyMapsMapId;
+ private String sendToFusionTablesTableId;
+
+ // TODO: Make these be used for showing results
+ @SuppressWarnings("unused")
+ private String sendToMyMapsMessage;
+ @SuppressWarnings("unused")
+ private String sendToFusionTablesMessage;
+ @SuppressWarnings("unused")
+ private String sendToDocsMessage;
+
+ // State used while sending.
+ private SendDialog sendDialog;
+ private ProgressDialog progressDialog;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ providerUtils = MyTracksProviderUtils.Factory.get(this);
+ sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
+ }
+
+ @Override
+ protected void onStart() {
+ Log.d(TAG, "SendActivity.onStart");
+ super.onStart();
+ resetState();
+
+ tracker = GoogleAnalyticsTracker.getInstance();
+ // Start the tracker in manual dispatch mode...
+ tracker.start(getString(R.string.google_analytics_id), getApplicationContext());
+ tracker.setProductVersion("android-mytracks", SystemUtils.getMyTracksVersion(this));
+
+ Intent intent = getIntent();
+ String action = intent.getAction();
+ String type = intent.getType();
+ Uri data = intent.getData();
+ if (!Intent.ACTION_SEND.equals(action) ||
+ !TracksColumns.CONTENT_ITEMTYPE.equals(type) ||
+ !UriUtils.matchesContentUri(data, TracksColumns.CONTENT_URI)) {
+ Log.e(TAG, "Got bad send intent: " + intent);
+ finish();
+ return;
+ }
+
+ sendTrackId = ContentUris.parseId(data);
+
+ sendDialog = new SendDialog(this);
+ sendDialog.setOwnerActivity(this);
+ sendDialog.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface arg0, int which) {
+ if (which != DialogInterface.BUTTON_POSITIVE) {
+ finish();
+ return;
+ }
+
+ doSend();
+ }
+ });
+ sendDialog.show();
+ }
+
+ @Override
+ protected void onStop() {
+ Log.d(TAG, "SendActivity.onStop");
+ tracker.dispatch();
+ tracker.stop();
+
+ super.onStop();
+ }
+
+ /**
+ * Initiates the process to send tracks to google.
+ * This is called once the user has selected sending options via the
+ * SendToGoogleDialog.
+ *
+ * TODO: Change this whole flow to an actual state machine.
+ */
+ private void doSend() {
+ progressDialog = new ProgressDialog(this);
+ progressDialog.setIcon(android.R.drawable.ic_dialog_info);
+ progressDialog.setTitle(R.string.progress_title);
+ progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+ progressDialog.setMessage("");
+ progressDialog.setMax(100);
+ progressDialog.setProgress(0);
+ progressDialog.show();
+
+ if (sendDialog.getSendToMyMaps()) {
+ sendToGoogleMapsOrPickMap();
+ } else if (sendDialog.getSendToFusionTables()) {
+ authenticateToFusionTables(null);
+ } else if (sendDialog.getSendToDocs()) {
+ authenticateToGoogleDocs();
+ } else {
+ Log.w(TAG, "Nowhere to upload to");
+ onSendToGoogleDone();
+ }
+ }
+
+ private void sendToGoogleMapsOrPickMap() {
+ if (!sendDialog.getCreateNewMap()) {
+ // Ask the user to choose a map to upload into
+ Intent listIntent = new Intent(this, MyMapsList.class);
+ startActivityForResult(listIntent, Constants.GET_MAP);
+ // The callback for GET_MAP calls authenticateToGoogleMaps
+ } else {
+ authenticateToGoogleMaps(null);
+ }
+ }
+
+ private void authenticateToGoogleMaps(Intent results) {
+ if (results == null) { results = new Intent(); }
+
+ progressDialog.setProgress(0);
+ progressDialog.setMessage(getString(
+ R.string.progress_message_authenticating_mymaps));
+ authenticate(results, Constants.AUTHENTICATE_TO_MY_MAPS,
+ MyMapsConstants.SERVICE_NAME);
+ // AUTHENTICATE_TO_MY_MAPS callback calls sendToGoogleMaps
+ }
+
+ private void sendToGoogleMaps(String mapId) {
+ tracker.trackPageView("/send/maps");
+
+ SendToMyMaps.OnSendCompletedListener onCompletion = new SendToMyMaps.OnSendCompletedListener() {
+ @Override
+ public void onSendCompleted(String mapId, boolean success, int statusMessage) {
+ // TODO: Use this message
+ sendToMyMapsMessage = getString(statusMessage);
+ sendToMyMapsSuccess = success;
+ if (sendToMyMapsSuccess) {
+ sendToMyMapsMapId = mapId;
+ // Update the map id for this track:
+ try {
+ Track track = providerUtils.getTrack(sendTrackId);
+ if (track != null) {
+ track.setMapId(mapId);
+ providerUtils.updateTrack(track);
+ } else {
+ Log.w(TAG, "Updating map id failed.");
+ }
+ } catch (RuntimeException e) {
+ // If that fails whatever reasons we'll just log an error, but
+ // continue.
+ Log.w(TAG, "Updating map id failed.", e);
+ }
+ }
+
+ onSendToGoogleMapsDone();
+ }
+ };
+ final SendToMyMaps sender = new SendToMyMaps(this, mapId, lastAuth,
+ sendTrackId, this /*progressIndicator*/, onCompletion);
+
+ HandlerThread handlerThread = new HandlerThread("SendToMyMaps");
+ handlerThread.start();
+ Handler handler = new Handler(handlerThread.getLooper());
+ handler.post(sender);
+ }
+
+ private void onSendToGoogleMapsDone() {
+ if (sendDialog.getSendToFusionTables()) {
+ authenticateToFusionTables(null);
+ } else if (sendDialog.getSendToDocs()) {
+ authenticateToGoogleDocs();
+ } else {
+ onSendToGoogleDone();
+ }
+ }
+
+ private void authenticateToFusionTables(Intent results) {
+ if (results == null) { results = new Intent(); }
+
+ progressDialog.setProgress(0);
+ progressDialog.setMessage(getString(
+ R.string.progress_message_authenticating_fusiontables));
+ authenticate(results, Constants.AUTHENTICATE_TO_FUSION_TABLES,
+ SendToFusionTables.SERVICE_ID);
+ // AUTHENTICATE_TO_FUSION_TABLES callback calls sendToFusionTables
+ }
+
+ private void sendToFusionTables() {
+ tracker.trackPageView("/send/fusion_tables");
+
+ OnSendCompletedListener onCompletion = new OnSendCompletedListener() {
+ @Override
+ public void onSendCompleted(String tableId, boolean success,
+ int statusMessage) {
+ // TODO: Use this message
+ sendToFusionTablesMessage = getString(statusMessage);
+ sendToFusionTablesSuccess = success;
+ if (sendToFusionTablesSuccess) {
+ sendToFusionTablesTableId = tableId;
+ // Update the table id for this track:
+ try {
+ Track track = providerUtils.getTrack(sendTrackId);
+ if (track != null) {
+ track.setTableId(tableId);
+ providerUtils.updateTrack(track);
+ } else {
+ Log.w(TAG, "Updating table id failed.");
+ }
+ } catch (RuntimeException e) {
+ // If that fails whatever reasons we'll just log an error, but
+ // continue.
+ Log.w(TAG, "Updating table id failed.", e);
+ }
+ }
+
+ onSendToFusionTablesDone();
+ }
+ };
+ final SendToFusionTables sender = new SendToFusionTables(this, lastAuth,
+ sendTrackId, this /*progressIndicator*/, onCompletion);
+
+ HandlerThread handlerThread = new HandlerThread("SendToFusionTables");
+ handlerThread.start();
+ Handler handler = new Handler(handlerThread.getLooper());
+ handler.post(sender);
+ }
+
+ private void onSendToFusionTablesDone() {
+ if (sendDialog.getSendToDocs()) {
+ authenticateToGoogleDocs();
+ } else {
+ onSendToGoogleDone();
+ }
+ }
+
+ private void authenticateToGoogleDocs() {
+ setProgressValue(0);
+ setProgressMessage(
+ R.string.progress_message_authenticating_docs);
+ authenticate(new Intent(),
+ Constants.AUTHENTICATE_TO_DOCLIST,
+ SendToDocs.GDATA_SERVICE_NAME_DOCLIST);
+ // AUTHENTICATE_TO_DOCLIST callback calls authenticateToGoogleTrix
+ }
+
+ private void authenticateToGoogleTrix() {
+ setProgressValue(30);
+ setProgressMessage(
+ R.string.progress_message_authenticating_docs);
+ authenticate(new Intent(),
+ Constants.AUTHENTICATE_TO_TRIX,
+ SendToDocs.GDATA_SERVICE_NAME_TRIX);
+ // AUTHENTICATE_TO_TRIX callback calls sendToGoogleDocs
+ }
+
+ private void sendToGoogleDocs() {
+ Log.d(TAG, "Sending to Docs....");
+ tracker.trackPageView("/send/docs");
+
+ setProgressValue(50);
+ setProgressMessage(R.string.progress_message_sending_docs);
+ final SendToDocs sender = new SendToDocs(this,
+ authMap.get(SendToDocs.GDATA_SERVICE_NAME_TRIX),
+ authMap.get(SendToDocs.GDATA_SERVICE_NAME_DOCLIST),
+ this);
+ Runnable onCompletion = new Runnable() {
+ public void run() {
+ setProgressValue(100);
+
+ // TODO: Use this message
+ sendToDocsMessage = sender.getStatusMessage();
+ sendToDocsSuccess = sender.wasSuccess();
+
+ onSendToGoogleDocsDone();
+ }
+ };
+ sender.setOnCompletion(onCompletion);
+ sender.sendToDocs(sendTrackId);
+ }
+
+ private void onSendToGoogleDocsDone() {
+ onSendToGoogleDone();
+ }
+
+ private void onSendToGoogleDone() {
+ tracker.dispatch();
+
+ final boolean sentToMyMaps = sendDialog.getSendToMyMaps();
+ final boolean sentToFusionTables = sendDialog.getSendToFusionTables();
+ List results = makeSendToGoogleResults();
+
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ progressDialog.dismiss();
+ progressDialog = null;
+ sendDialog = null;
+ }
+ });
+
+ // We've finished sending the track to the user-selected services. Now
+ // we tell them the results of the upload, and optionally share the track.
+ // There are a few different paths through this code:
+ //
+ // 1. The user pre-requested a share (shareRequested == true). We're going
+ // to display the result dialog *without* the share button (the share
+ // listener will be null). The OK button listener will initiate the
+ // share.
+ //
+ // 2. The user did not pre-request a share, and the set of services to
+ // which we succeeded in uploading the track are compatible with
+ // sharing. We'll display a share button (the share listener will be
+ // non-null), and will share the link if the user clicks it.
+ //
+ // 3. The user did not pre-request a share, and the set of services to
+ // which we succeeded in uploading the track are incompatible with
+ // sharing. We won't display a share button.
+
+ final boolean canShare = sendToFusionTablesTableId != null
+ || sendToMyMapsMapId != null;
+
+ final OnClickListener finishListener = new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ finish();
+ }
+ };
+
+ DialogInterface.OnClickListener doShareListener = null;
+ if (canShare) {
+ doShareListener = new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (!shareLinkToMap(sentToMyMaps, sentToFusionTables)) {
+ Log.w(TAG, "Failed to share link");
+ }
+
+ finishListener.onClick(dialog, which);
+ }
+ };
+ }
+
+ DialogInterface.OnClickListener onOkListener = (canShare && shareRequested)
+ ? doShareListener : finishListener;
+ DialogInterface.OnClickListener onShareListener = (canShare && !shareRequested)
+ ? doShareListener : null;
+
+ AlertDialog sendToGoogleResultDialog = ResultDialogFactory.makeDialog(this,
+ results, onOkListener, onShareListener);
+ DialogManager.showDialogSafely(this, sendToGoogleResultDialog);
+ }
+
+ boolean shareLinkToMap(boolean sentToMyMaps, boolean sentToFusionTables) {
+ String url = null;
+ if (sentToMyMaps && sendToMyMapsSuccess) {
+ // Prefer a link to My Maps
+ url = MapsFacade.buildMapUrl(sendToMyMapsMapId);
+ } else if (sentToFusionTables && sendToFusionTablesSuccess) {
+ // Otherwise try using the link to fusion tables
+ url = getFusionTablesUrl(sendTrackId);
+ }
+
+ if (url != null) {
+ shareLinkToMap(url);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Shares a link to a My Map or Fusion Table via external app (email, gmail, ...)
+ * A chooser with apps that support text/plain will be shown to the user.
+ */
+ private void shareLinkToMap(String url) {
+ boolean shareUrlOnly = sharedPreferences.getBoolean(
+ getString(R.string.share_url_only_key), false);
+ String msg = shareUrlOnly ? url : String.format(
+ getResources().getText(R.string.share_map_body_format).toString(), url);
+
+ Intent shareIntent = new Intent(Intent.ACTION_SEND);
+ shareIntent.setType("text/plain");
+ shareIntent.putExtra(Intent.EXTRA_SUBJECT,
+ getResources().getText(R.string.share_map_subject).toString());
+ shareIntent.putExtra(Intent.EXTRA_TEXT, msg);
+ startActivity(Intent.createChooser(shareIntent,
+ getResources().getText(R.string.share_map).toString()));
+ }
+
+ protected String getFusionTablesUrl(long trackId) {
+ Track track = providerUtils.getTrack(trackId);
+ return SendToFusionTables.getMapVisualizationUrl(track);
+ }
+
+ /**
+ * Creates a list of {@link SendResult} instances based on the set of
+ * services selected in {@link SendDialog} and the results as known to
+ * this class.
+ */
+ private List makeSendToGoogleResults() {
+ List results = new ArrayList();
+ if (sendDialog.getSendToMyMaps()) {
+ results.add(new SendResult(SendType.MYMAPS, sendToMyMapsSuccess));
+ }
+ if (sendDialog.getSendToFusionTables()) {
+ results.add(new SendResult(SendType.FUSION_TABLES, sendToFusionTablesSuccess));
+ }
+ if (sendDialog.getSendToDocs()) {
+ results.add(new SendResult(SendType.DOCS, sendToDocsSuccess));
+ }
+
+ return results;
+ }
+
+ /**
+ * Initializes the authentication manager which obtains an authentication
+ * token, prompting the user for a login and password if needed.
+ */
+ private void authenticate(final Intent results, final int requestCode,
+ final String service) {
+ lastAuth = authMap.get(service);
+ if (lastAuth == null) {
+ Log.i(TAG, "Creating a new authentication for service: " + service);
+ lastAuth = AuthManagerFactory.getAuthManager(this,
+ Constants.GET_LOGIN,
+ null,
+ true,
+ service);
+ authMap.put(service, lastAuth);
+ }
+
+ Log.d(TAG, "Logging in to " + service + "...");
+ if (AuthManagerFactory.useModernAuthManager()) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ chooseAccount(results, requestCode, service);
+ }
+ });
+ } else {
+ doLogin(results, requestCode, service, null);
+ }
+ }
+
+ private void chooseAccount(final Intent results, final int requestCode,
+ final String service) {
+ accountChooser.chooseAccount(SendActivity.this,
+ new AccountChooser.AccountHandler() {
+ @Override
+ public void handleAccountSelected(Account account) {
+ if (account == null) {
+ progressDialog.dismiss();
+ progressDialog = null;
+ finish();
+ }
+
+ doLogin(results, requestCode, service, account);
+ }
+ });
+ }
+
+ private void doLogin(final Intent results, final int requestCode,
+ final String service, final Account account) {
+ // There's a chance that we go to a different activity, so close the dialog.
+ lastAuth.doLogin(new Runnable() {
+ public void run() {
+ Log.i(TAG, "Login success for " + service + "!");
+ onActivityResult(requestCode, RESULT_OK, results);
+ }
+ }, account);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode,
+ final Intent results) {
+ switch (requestCode) {
+ case Constants.GET_LOGIN: {
+ // TODO: This is a result from inside the auth manager,
+ // make this return path explicit.
+ if (resultCode != RESULT_OK || lastAuth == null ||
+ !lastAuth.authResult(resultCode, results)) {
+ progressDialog.dismiss();
+ }
+ break;
+ }
+ case Constants.GET_MAP: {
+ // User picked a map to upload to
+ if (resultCode == RESULT_OK) {
+ results.putExtra("trackid", sendTrackId);
+ if (results.hasExtra("mapid")) {
+ sendToMyMapsMapId = results.getStringExtra("mapid");
+ }
+ authenticateToGoogleMaps(results);
+ } else {
+ onSendToGoogleDone();
+ }
+ break;
+ }
+ case Constants.AUTHENTICATE_TO_MY_MAPS: {
+ // Authenticated with Google My Maps
+ if (results != null && resultCode == RESULT_OK) {
+ final String mapId;
+ if (results.hasExtra("mapid")) {
+ mapId = results.getStringExtra("mapid");
+ } else {
+ mapId = "new";
+ }
+
+ sendToGoogleMaps(mapId);
+ } else {
+ onSendToGoogleDone();
+ }
+ break;
+ }
+ case Constants.AUTHENTICATE_TO_FUSION_TABLES: {
+ // Authenticated with Google Fusion Tables
+ if (results != null && resultCode == RESULT_OK) {
+ sendToFusionTables();
+ } else {
+ onSendToGoogleDone();
+ }
+ break;
+ }
+ case Constants.AUTHENTICATE_TO_DOCLIST: {
+ // Authenticated with Google Docs
+ if (resultCode == RESULT_OK) {
+ authenticateToGoogleTrix();
+ } else {
+ onSendToGoogleDone();
+ }
+ break;
+ }
+ case Constants.AUTHENTICATE_TO_TRIX: {
+ // Authenticated with Trix
+ if (resultCode == RESULT_OK) {
+ sendToGoogleDocs();
+ } else {
+ onSendToGoogleDone();
+ }
+ break;
+ }
+
+ default: {
+ Log.w(TAG, "Warning unhandled request code: " + requestCode);
+ }
+ }
+ }
+
+ /**
+ * Resets status information for sending to MyMaps/Docs.
+ */
+ private void resetState() {
+ sendToMyMapsMapId = null;
+ sendToMyMapsMessage = "";
+ sendToMyMapsSuccess = true;
+ sendToFusionTablesMessage = "";
+ sendToFusionTablesSuccess = true;
+ sendToDocsMessage = "";
+ sendToDocsSuccess = true;
+ sendToFusionTablesTableId = null;
+ }
+
+ @Override
+ public void setProgressMessage(final int resId) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ if (progressDialog != null) {
+ progressDialog.setMessage(getString(resId));
+ }
+ }
+ });
+ }
+
+ @Override
+ public void setProgressValue(final int percent) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ if (progressDialog != null) {
+ progressDialog.setProgress(percent);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void clearProgressMessage() {
+ progressDialog.setMessage("");
+ }
+
+ public static void sendToGoogle(Context ctx, long trackId, boolean shareLink) {
+ Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId);
+
+ Intent intent = new Intent(ctx, SendActivity.class);
+ intent.setAction(Intent.ACTION_SEND);
+ intent.setDataAndType(uri, TracksColumns.CONTENT_ITEMTYPE);
+ intent.putExtra(SendActivity.EXTRA_SHARE_LINK, shareLink);
+ ctx.startActivity(intent);
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/SendDialog.java b/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/SendDialog.java
index 16df9c5e3..868e25ed1 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/SendDialog.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/SendDialog.java
@@ -16,8 +16,6 @@
package com.google.android.apps.mytracks.io.sendtogoogle;
import com.google.android.apps.mytracks.Constants;
-import com.google.android.apps.mytracks.DialogManager;
-import com.google.android.apps.mytracks.MyTracks;
import com.google.android.maps.mytracks.R;
import android.app.Dialog;
@@ -42,15 +40,13 @@ import android.widget.RadioGroup;
*/
public class SendDialog extends Dialog {
- private RadioGroup groupMyMaps;
private RadioButton createNewMapRadioButton;
private RadioButton pickMapRadioButton;
private CheckBox sendToMyMapsCheckBox;
private CheckBox sendToFusionTablesCheckBox;
private CheckBox sendToDocsCheckBox;
- private RadioButton sendStatsRadioButton;
private RadioButton sendStatsAndPointsRadioButton;
- private Button sendButton;
+ private OnClickListener clickListener;
public SendDialog(Context context) {
super(context);
@@ -62,18 +58,38 @@ public class SendDialog extends Dialog {
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.mytracks_send_to_google);
+ final Button sendButton = (Button) findViewById(R.id.sendtogoogle_send_now);
+ final RadioGroup groupMyMaps = (RadioGroup) findViewById(R.id.sendtogoogle_group_mymaps);
+ sendToMyMapsCheckBox =
+ (CheckBox) findViewById(R.id.sendtogoogle_google_mymaps);
+ sendToFusionTablesCheckBox =
+ (CheckBox) findViewById(R.id.sendtogoogle_google_fusiontables);
+ sendToDocsCheckBox = (CheckBox) findViewById(R.id.sendtogoogle_google_docs);
+ createNewMapRadioButton =
+ (RadioButton) findViewById(R.id.sendtogoogle_create_new_map);
+ pickMapRadioButton =
+ (RadioButton) findViewById(R.id.sendtogoogle_pick_existing_map);
+ RadioButton sendStatsRadioButton = (RadioButton) findViewById(R.id.sendtogoogle_send_stats);
+ sendStatsAndPointsRadioButton = (RadioButton) findViewById(
+ R.id.sendtogoogle_send_stats_and_points);
+
Button cancel = (Button) findViewById(R.id.sendtogoogle_cancel);
cancel.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
- MyTracks.getInstance().dismissDialog(DialogManager.DIALOG_SEND_TO_GOOGLE);
+ if (clickListener != null) {
+ clickListener.onClick(SendDialog.this, BUTTON_NEGATIVE);
+ }
+ dismiss();
}
});
Button send = (Button) findViewById(R.id.sendtogoogle_send_now);
send.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
+ if (clickListener != null) {
+ clickListener.onClick(SendDialog.this, BUTTON_POSITIVE);
+ }
dismiss();
- MyTracks.getInstance().sendToGoogle();
}
});
@@ -86,25 +102,9 @@ public class SendDialog extends Dialog {
groupMyMaps.setVisibility(sendToMyMapsCheckBox.isChecked() ? View.VISIBLE : View.INVISIBLE);
}
};
-
- sendButton = (Button) findViewById(R.id.sendtogoogle_send_now);
- groupMyMaps = (RadioGroup) findViewById(R.id.sendtogoogle_group_mymaps);
- sendToMyMapsCheckBox =
- (CheckBox) findViewById(R.id.sendtogoogle_google_mymaps);
sendToMyMapsCheckBox.setOnCheckedChangeListener(checkBoxListener);
- sendToFusionTablesCheckBox =
- (CheckBox) findViewById(R.id.sendtogoogle_google_fusiontables);
sendToFusionTablesCheckBox.setOnCheckedChangeListener(checkBoxListener);
- sendToDocsCheckBox = (CheckBox) findViewById(R.id.sendtogoogle_google_docs);
sendToDocsCheckBox.setOnCheckedChangeListener(checkBoxListener);
- createNewMapRadioButton =
- (RadioButton) findViewById(R.id.sendtogoogle_create_new_map);
- pickMapRadioButton =
- (RadioButton) findViewById(R.id.sendtogoogle_pick_existing_map);
- sendStatsRadioButton =
- (RadioButton) findViewById(R.id.sendtogoogle_send_stats);
- sendStatsAndPointsRadioButton = (RadioButton) findViewById(
- R.id.sendtogoogle_send_stats_and_points);
SharedPreferences prefs =
getContext().getSharedPreferences(Constants.SETTINGS_NAME, 0);
@@ -163,6 +163,10 @@ public class SendDialog extends Dialog {
super.onStop();
}
+ public void setOnClickListener(OnClickListener clickListener) {
+ this.clickListener = clickListener;
+ }
+
public boolean getSendToMyMaps() {
return sendToMyMapsCheckBox.isChecked();
}
@@ -182,4 +186,4 @@ public class SendDialog extends Dialog {
public boolean getSendStatsAndPoints() {
return sendStatsAndPointsRadioButton.isChecked();
}
-}
+}
\ No newline at end of file
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 3f5b666d5..efe060df5 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/PreferenceManager.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/PreferenceManager.java
@@ -93,13 +93,13 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
return;
}
if (key == null || key.equals(minRecordingDistanceKey)) {
- service.setMinRecordingDistance(
- sharedPreferences.getInt(
- minRecordingDistanceKey,
- Constants.DEFAULT_MIN_RECORDING_DISTANCE));
+ int minRecordingDistance = sharedPreferences.getInt(
+ minRecordingDistanceKey,
+ Constants.DEFAULT_MIN_RECORDING_DISTANCE);
+ service.setMinRecordingDistance(minRecordingDistance);
Log.d(Constants.TAG,
"TrackRecordingService: minRecordingDistance = "
- + service.getMinRecordingDistance());
+ + minRecordingDistance);
}
if (key == null || key.equals(maxRecordingDistanceKey)) {
service.setMaxRecordingDistance(sharedPreferences.getInt(
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/ServiceUtils.java b/MyTracks/src/com/google/android/apps/mytracks/services/ServiceUtils.java
new file mode 100644
index 000000000..5268ed7f2
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/ServiceUtils.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.android.apps.mytracks.services;
+
+import static com.google.android.apps.mytracks.Constants.TAG;
+
+import com.google.android.apps.mytracks.Constants;
+import com.google.android.maps.mytracks.R;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningServiceInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Helper for reading service state.
+ *
+ * @author Rodrigo Damazio
+ */
+public class ServiceUtils {
+
+ /**
+ * Checks whether we're currently recording.
+ * The checking is done by calling the service, if provided, or alternatively by reading
+ * recording state saved to preferences.
+ *
+ * @param ctx the current context
+ * @param service the service, or null if not bound to it
+ * @param preferences the preferences, or null if not available
+ * @return true if the service is recording (or supposed to be recording), false otherwise
+ */
+ public static boolean isRecording(Context ctx, ITrackRecordingService service, SharedPreferences preferences) {
+ if (service != null) {
+ try {
+ return service.isRecording();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to check if service is recording", e);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to check if service is recording", e);
+ }
+ }
+
+ if (preferences == null) {
+ preferences = ctx.getSharedPreferences(Constants.SETTINGS_NAME, 0);
+ }
+ return preferences.getLong(ctx.getString(R.string.recording_track_key), -1) > 0;
+ }
+
+ /**
+ * Checks whether the recording service is currently running.
+ *
+ * @param ctx the current context
+ * @return true if the service is running, false otherwise
+ */
+ public static boolean isServiceRunning(Context ctx) {
+ ActivityManager activityManager = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
+ List services = activityManager.getRunningServices(Integer.MAX_VALUE);
+
+ for (RunningServiceInfo serviceInfo : services) {
+ ComponentName componentName = serviceInfo.service;
+ String serviceName = componentName.getClassName();
+ if (serviceName.equals(TrackRecordingService.class.getName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private ServiceUtils() {}
+}
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 6dd5a76f3..d1e1a5bf7 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java
@@ -15,20 +15,20 @@
*/
package com.google.android.apps.mytracks.services;
-import static com.google.android.apps.mytracks.Constants.TAG;
import static com.google.android.apps.mytracks.Constants.RESUME_TRACK_EXTRA_NAME;
+import static com.google.android.apps.mytracks.Constants.TAG;
-import com.google.android.apps.mytracks.MyTracks;
import com.google.android.apps.mytracks.Constants;
+import com.google.android.apps.mytracks.MyTracks;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Sensor;
+import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
import com.google.android.apps.mytracks.content.WaypointsColumns;
-import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.services.sensors.SensorManager;
import com.google.android.apps.mytracks.services.sensors.SensorManagerFactory;
import com.google.android.apps.mytracks.services.tasks.PeriodicTaskExecutor;
@@ -74,7 +74,7 @@ import java.util.concurrent.Executors;
*
* @author Leif Hendrik Wilden
*/
-public class TrackRecordingService extends Service implements LocationListener {
+public class TrackRecordingService extends Service {
static final int MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS = 3;
@@ -89,8 +89,8 @@ public class TrackRecordingService extends Service implements LocationListener {
private int minRequiredAccuracy =
Constants.DEFAULT_MIN_REQUIRED_ACCURACY;
private int autoResumeTrackTimeout =
- Constants.DEFAULT_AUTO_RESUME_TRACK_TIMEOUT;
-
+ Constants.DEFAULT_AUTO_RESUME_TRACK_TIMEOUT;
+
private long recordingTrackId = -1;
private long currentWaypointId = -1;
@@ -120,9 +120,9 @@ public class TrackRecordingService extends Service implements LocationListener {
private PeriodicTaskExecutor splitExecutor;
private SensorManager sensorManager;
-
+
private PreferenceManager prefManager;
-
+
/**
* The interval in milliseconds that we have requested to be notified of gps
* readings.
@@ -135,6 +135,34 @@ public class TrackRecordingService extends Service implements LocationListener {
private LocationListenerPolicy locationListenerPolicy =
new AbsoluteLocationListenerPolicy(0);
+ private LocationListener locationListener = new LocationListener() {
+ @Override
+ public void onProviderDisabled(String provider) {
+ // Do nothing
+ }
+
+ @Override
+ public void onProviderEnabled(String provider) {
+ // Do nothing
+ }
+
+ @Override
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+ // Do nothing
+ }
+
+ @Override
+ public void onLocationChanged(final Location location) {
+ executorService.submit(
+ new Runnable() {
+ @Override
+ public void run() {
+ onLocationChangedAsync(location);
+ }
+ });
+ }
+ };
+
/**
* Task invoked by a timer periodically to make sure the location listener is
* still registered.
@@ -171,7 +199,7 @@ public class TrackRecordingService extends Service implements LocationListener {
* The most recent recording track.
*/
private Track recordingTrack;
-
+
/**
* Is the service currently recording a track?
*/
@@ -192,10 +220,556 @@ public class TrackRecordingService extends Service implements LocationListener {
*/
private ExecutorService executorService;
+ private ServiceBinder binder = new ServiceBinder(this);
+
/*
- * Utility functions
+ * Application lifetime events:
*/
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.d(TAG, "TrackRecordingService.onCreate");
+ providerUtils = MyTracksProviderUtils.Factory.get(this);
+ notificationManager =
+ (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
+
+ setUpTaskExecutors();
+ executorService = Executors.newSingleThreadExecutor();
+
+ prefManager = new PreferenceManager(this);
+
+ registerLocationListener();
+
+ /*
+ * After 5 min, check every minute that location listener still is
+ * registered and spit out additional debugging info to the logs:
+ */
+ timer.schedule(checkLocationListener, 1000 * 60 * 5, 1000 * 60);
+
+ // Try to restore previous recording state in case this service has been
+ // restarted by the system, which can sometimes happen.
+ recordingTrack = getRecordingTrack();
+ if (recordingTrack != null) {
+ restoreStats(recordingTrack);
+ isRecording = true;
+ } else {
+ if (recordingTrackId != -1) {
+ // Make sure we have consistent state in shared preferences.
+ Log.w(TAG, "TrackRecordingService.onCreate: "
+ + "Resetting an orphaned recording track = " + recordingTrackId);
+ }
+ prefManager.setRecordingTrack(recordingTrackId = -1);
+ }
+ showNotification();
+ }
+
+ @Override
+ public void onStart(Intent intent, int startId) {
+ handleStartCommand(intent, startId);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ handleStartCommand(intent, startId);
+ return START_STICKY;
+ }
+
+ private void handleStartCommand(Intent intent, int startId) {
+ Log.d(TAG,
+ "TrackRecordingService.handleStartCommand: " + startId);
+
+ // Check if called on phone reboot with resume intent.
+ if (intent != null &&
+ intent.getBooleanExtra(RESUME_TRACK_EXTRA_NAME, false)) {
+ Log.d(TAG, "TrackRecordingService: requested resume");
+
+ // Make sure that the current track exists and is fresh enough.
+ if (recordingTrack == null || !shouldResumeTrack(recordingTrack)) {
+ Log.i(TAG,
+ "TrackRecordingService: Not resuming, because the previous track ("
+ + recordingTrack + ") doesn't exist or is too old");
+ isRecording = false;
+ prefManager.setRecordingTrack(recordingTrackId = -1);
+ stopSelfResult(startId);
+ return;
+ }
+
+ Log.i(TAG, "TrackRecordingService: resuming");
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.d(TAG, "TrackRecordingService.onBind");
+ return binder;
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ Log.d(TAG, "TrackRecordingService.onUnbind");
+ return super.onUnbind(intent);
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.d(TAG, "TrackRecordingService.onDestroy");
+
+ isRecording = false;
+ showNotification();
+ prefManager.shutdown();
+ prefManager = null;
+ checkLocationListener.cancel();
+ checkLocationListener = null;
+ timer.cancel();
+ timer.purge();
+ unregisterLocationListener();
+ shutdownTaskExecutors();
+ executorService.shutdown();
+ if (sensorManager != null) {
+ sensorManager.shutdown();
+ sensorManager = null;
+ }
+
+ // Make sure we have no indirect references to this service.
+ locationManager = null;
+ notificationManager = null;
+ providerUtils = null;
+ binder.detachFromService();
+ binder = null;
+
+ // This should be the last operation.
+ releaseWakeLock();
+
+ super.onDestroy();
+ }
+
+ private void setAutoResumeTrackRetries(SharedPreferences sharedPreferences, int retryAttempts) {
+ Log.d(TAG, "Updating auto-resume retry attempts to: " + retryAttempts);
+ prefManager.setAutoResumeTrackCurrentRetry(retryAttempts);
+ }
+
+ private boolean shouldResumeTrack(Track track) {
+ Log.d(TAG, "shouldResumeTrack: autoResumeTrackTimeout = "
+ + autoResumeTrackTimeout);
+
+ // Check if we haven't exceeded the maximum number of retry attempts.
+ SharedPreferences sharedPreferences =
+ getSharedPreferences(Constants.SETTINGS_NAME, 0);
+ int retries = sharedPreferences.getInt(
+ getString(R.string.auto_resume_track_current_retry_key), 0);
+ Log.d(TAG,
+ "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(TAG,
+ "shouldResumeTrack: Not resuming because exceeded the maximum "
+ + "number of auto-resume retries");
+ return false;
+ }
+
+ // Increase number of retry attempts.
+ setAutoResumeTrackRetries(sharedPreferences, retries + 1);
+
+ // Check for special cases.
+ if (autoResumeTrackTimeout == 0) {
+ // Never resume.
+ Log.d(TAG,
+ "shouldResumeTrack: Auto-resume disabled (never resume)");
+ return false;
+ } else if (autoResumeTrackTimeout == -1) {
+ // Always resume.
+ Log.d(TAG,
+ "shouldResumeTrack: Auto-resume forced (always resume)");
+ return true;
+ }
+
+ // Check if the last modified time is within the acceptable range.
+ long lastModified =
+ track.getStatistics() != null ? track.getStatistics().getStopTime() : 0;
+ Log.d(TAG,
+ "shouldResumeTrack: lastModified = " + lastModified
+ + ", autoResumeTrackTimeout: " + autoResumeTrackTimeout);
+ return lastModified > 0 && System.currentTimeMillis() - lastModified <=
+ autoResumeTrackTimeout * 60L * 1000L;
+ }
+
+ /*
+ * Setup/shutdown methods.
+ */
+
+ /**
+ * Tries to acquire a partial wake lock if not already acquired. Logs errors
+ * and gives up trying in case the wake lock cannot be acquired.
+ */
+ private void acquireWakeLock() {
+ try {
+ PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ if (pm == null) {
+ Log.e(TAG,
+ "TrackRecordingService: Power manager not found!");
+ return;
+ }
+ if (wakeLock == null) {
+ wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ TAG);
+ if (wakeLock == null) {
+ Log.e(TAG,
+ "TrackRecordingService: Could not create wake lock (null).");
+ return;
+ }
+ }
+ if (!wakeLock.isHeld()) {
+ wakeLock.acquire();
+ if (!wakeLock.isHeld()) {
+ Log.e(TAG,
+ "TrackRecordingService: Could not acquire wake lock.");
+ }
+ }
+ } catch (RuntimeException e) {
+ Log.e(TAG,
+ "TrackRecordingService: Caught unexpected exception: "
+ + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Releases the wake lock if it's currently held.
+ */
+ private void releaseWakeLock() {
+ if (wakeLock != null && wakeLock.isHeld()) {
+ wakeLock.release();
+ wakeLock = null;
+ }
+ }
+
+ /**
+ * Shows the notification message and icon in the notification bar.
+ */
+ private void showNotification() {
+ final ApiPlatformAdapter apiPlatformAdapter =
+ ApiFeatures.getInstance().getApiPlatformAdapter();
+ if (isRecording) {
+ Notification notification = new Notification(
+ R.drawable.arrow_320, null /* tickerText */,
+ System.currentTimeMillis());
+ PendingIntent contentIntent = PendingIntent.getActivity(
+ this, 0 /* requestCode */, new Intent(this, MyTracks.class),
+ 0 /* flags */);
+ notification.setLatestEventInfo(this, getString(R.string.app_name),
+ getString(R.string.recording_your_track), contentIntent);
+ notification.flags += Notification.FLAG_NO_CLEAR;
+ apiPlatformAdapter.startForeground(this, notificationManager, 1,
+ notification);
+ } else {
+ apiPlatformAdapter.stopForeground(this, notificationManager, 1);
+ }
+ }
+
+ private void setUpTaskExecutors() {
+ announcementExecutor = new PeriodicTaskExecutor(
+ this, new StatusAnnouncerFactory(ApiFeatures.getInstance()));
+ splitExecutor = new PeriodicTaskExecutor(this, new SplitTask.Factory());
+ }
+
+ private void shutdownTaskExecutors() {
+ Log.d(TAG, "TrackRecordingService.shutdownExecuters");
+ try {
+ announcementExecutor.shutdown();
+ } finally {
+ announcementExecutor = null;
+ }
+ try {
+ splitExecutor.shutdown();
+ } finally {
+ splitExecutor = null;
+ }
+ }
+
+ private void registerLocationListener() {
+ if (locationManager == null) {
+ Log.e(TAG,
+ "TrackRecordingService: Do not have any location manager.");
+ return;
+ }
+ Log.d(TAG,
+ "Preparing to register location listener w/ TrackRecordingService...");
+ try {
+ long desiredInterval = locationListenerPolicy.getDesiredPollingInterval();
+ locationManager.requestLocationUpdates(
+ LocationManager.GPS_PROVIDER, desiredInterval,
+ locationListenerPolicy.getMinDistance(),
+ // , 0 /* minDistance, get all updates to properly time pauses */
+ locationListener);
+ currentRecordingInterval = desiredInterval;
+ Log.d(TAG,
+ "...location listener now registered w/ TrackRecordingService @ "
+ + currentRecordingInterval);
+ } catch (RuntimeException e) {
+ Log.e(TAG,
+ "Could not register location listener: " + e.getMessage(), e);
+ }
+ }
+
+ private void unregisterLocationListener() {
+ if (locationManager == null) {
+ Log.e(TAG,
+ "TrackRecordingService: Do not have any location manager.");
+ return;
+ }
+ locationManager.removeUpdates(locationListener);
+ Log.d(TAG,
+ "Location listener now unregistered w/ TrackRecordingService.");
+ }
+
+ /*
+ * Recording lifecycle.
+ */
+
+ public long startNewTrack() {
+ Log.d(TAG, "TrackRecordingService.startNewTrack");
+ if (recordingTrackId != -1 || isRecording) {
+ throw new IllegalStateException("A track is already in progress!");
+ }
+
+ long startTime = System.currentTimeMillis();
+ acquireWakeLock();
+
+ Track track = new Track();
+ TripStatistics trackStats = track.getStatistics();
+ trackStats.setStartTime(startTime);
+ track.setStartId(-1);
+ Uri trackUri = providerUtils.insertTrack(track);
+ recordingTrackId = Long.parseLong(trackUri.getLastPathSegment());
+ track.setId(recordingTrackId);
+ track.setName(new DefaultTrackNameFactory(this).newTrackName(
+ recordingTrackId, startTime));
+ isRecording = true;
+ isMoving = true;
+
+ providerUtils.updateTrack(track);
+ statsBuilder = new TripStatisticsBuilder(startTime);
+ statsBuilder.setMinRecordingDistance(minRecordingDistance);
+ waypointStatsBuilder = new TripStatisticsBuilder(startTime);
+ waypointStatsBuilder.setMinRecordingDistance(minRecordingDistance);
+ currentWaypointId = insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
+ length = 0;
+ showNotification();
+ registerLocationListener();
+ sensorManager = SensorManagerFactory.getSensorManager(this);
+ if (sensorManager != null) {
+ sensorManager.onStartTrack();
+ }
+
+ // Reset the number of auto-resume retries.
+ setAutoResumeTrackRetries(
+ getSharedPreferences(Constants.SETTINGS_NAME, 0), 0);
+ // Persist the current recording track.
+ prefManager.setRecordingTrack(recordingTrackId);
+
+ // Notify the world that we're now recording.
+ sendTrackBroadcast(
+ R.string.track_started_broadcast_action, recordingTrackId);
+ announcementExecutor.restore();
+ splitExecutor.restore();
+
+ return recordingTrackId;
+ }
+
+ private void restoreStats(Track track) {
+ Log.d(TAG,
+ "Restoring stats of track with ID: " + track.getId());
+
+ TripStatistics stats = track.getStatistics();
+ statsBuilder = new TripStatisticsBuilder(stats.getStartTime());
+ statsBuilder.setMinRecordingDistance(minRecordingDistance);
+
+ length = 0;
+ lastValidLocation = null;
+
+ Waypoint waypoint = providerUtils.getFirstWaypoint(recordingTrackId);
+ if (waypoint != null && waypoint.getStatistics() != null) {
+ currentWaypointId = waypoint.getId();
+ waypointStatsBuilder = new TripStatisticsBuilder(
+ waypoint.getStatistics());
+ } else {
+ // This should never happen, but we got to do something so life goes on:
+ waypointStatsBuilder = new TripStatisticsBuilder(stats.getStartTime());
+ currentWaypointId = -1;
+ }
+ waypointStatsBuilder.setMinRecordingDistance(minRecordingDistance);
+
+ Cursor cursor = null;
+ try {
+ cursor = providerUtils.getLocationsCursor(
+ recordingTrackId, -1, Constants.MAX_LOADED_TRACK_POINTS,
+ true);
+ if (cursor != null) {
+ if (cursor.moveToLast()) {
+ do {
+ Location location = providerUtils.createLocation(cursor);
+ if (LocationUtils.isValidLocation(location)) {
+ statsBuilder.addLocation(location, location.getTime());
+ if (lastValidLocation != null) {
+ length += location.distanceTo(lastValidLocation);
+ }
+ lastValidLocation = location;
+ }
+ } while (cursor.moveToPrevious());
+ }
+ statsBuilder.getStatistics().setMovingTime(stats.getMovingTime());
+ statsBuilder.pauseAt(stats.getStopTime());
+ statsBuilder.resumeAt(System.currentTimeMillis());
+ } else {
+ Log.e(TAG, "Could not get track points cursor.");
+ }
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Error while restoring track.", e);
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ announcementExecutor.restore();
+ splitExecutor.restore();
+ }
+
+ private void onLocationChangedAsync(Location location) {
+ Log.d(TAG, "TrackRecordingService.onLocationChanged");
+
+ try {
+ // Don't record if the service has been asked to pause recording:
+ if (!isRecording) {
+ Log.w(TAG,
+ "Not recording because recording has been paused.");
+ return;
+ }
+
+ // This should never happen, but just in case (we really don't want the
+ // service to crash):
+ if (location == null) {
+ Log.w(TAG,
+ "Location changed, but location is null.");
+ return;
+ }
+
+ // Don't record if the accuracy is too bad:
+ if (location.getAccuracy() > minRequiredAccuracy) {
+ Log.d(TAG,
+ "Not recording. Bad accuracy.");
+ return;
+ }
+
+ // At least one track must be available for appending points:
+ recordingTrack = getRecordingTrack();
+ if (recordingTrack == null) {
+ Log.d(TAG,
+ "Not recording. No track to append to available.");
+ return;
+ }
+
+ // Update the idle time if needed.
+ locationListenerPolicy.updateIdleTime(statsBuilder.getIdleTime());
+ addLocationToStats(location);
+ if (currentRecordingInterval !=
+ locationListenerPolicy.getDesiredPollingInterval()) {
+ registerLocationListener();
+ }
+
+ Location lastRecordedLocation = providerUtils.getLastLocation();
+ long lastRecordedLocationId =
+ providerUtils.getLastLocationId(recordingTrackId);
+ double distanceToLastRecorded = Double.POSITIVE_INFINITY;
+ if (lastRecordedLocation != null) {
+ distanceToLastRecorded = location.distanceTo(lastRecordedLocation);
+ }
+ double distanceToLast = Double.POSITIVE_INFINITY;
+ if (lastLocation != null) {
+ distanceToLast = location.distanceTo(lastLocation);
+ }
+ boolean hasSensorData = sensorManager != null
+ && sensorManager.isEnabled()
+ && sensorManager.getSensorDataSet() != null
+ && sensorManager.isDataValid();
+
+ // If the user has been stationary for two recording just record the first
+ // two and ignore the rest. This code will only have an effect if the
+ // maxRecordingDistance = 0
+ if (distanceToLast == 0 && !hasSensorData) {
+ if (isMoving) {
+ Log.d(TAG, "Found two identical locations.");
+ isMoving = false;
+ if (lastLocation != null && lastRecordedLocation != null
+ && !lastRecordedLocation.equals(lastLocation)) {
+ // Need to write the last location. This will happen when
+ // lastRecordedLocation.distance(lastLocation) <
+ // minRecordingDistance
+ if (!insertLocation(recordingTrack, lastLocation,
+ lastRecordedLocation, lastRecordedLocationId,
+ recordingTrackId)) {
+ return;
+ }
+ }
+ } else {
+ Log.d(TAG,
+ "Not recording. More than two identical locations.");
+ }
+ } else if (distanceToLastRecorded > minRecordingDistance
+ || hasSensorData) {
+ if (lastLocation != null && !isMoving) {
+ // Last location was the last stationary location. Need to go back and
+ // add it.
+ if (!insertLocation(recordingTrack, lastLocation,
+ lastRecordedLocation, lastRecordedLocationId, recordingTrackId)) {
+ return;
+ }
+ lastRecordedLocationId++;
+ isMoving = true;
+ }
+
+ // If separation from last recorded point is too large insert a
+ // separator to indicate end of a segment:
+ boolean startNewSegment =
+ lastRecordedLocation != null
+ && lastRecordedLocation.getLatitude() < 90
+ && distanceToLastRecorded > maxRecordingDistance
+ && recordingTrack.getStartId() >= 0;
+ if (startNewSegment) {
+ // Insert a separator point to indicate start of new track:
+ Log.d(TAG, "Inserting a separator.");
+ Location separator = new Location(LocationManager.GPS_PROVIDER);
+ separator.setLongitude(0);
+ separator.setLatitude(100);
+ separator.setTime(lastRecordedLocation.getTime());
+ providerUtils.insertTrackPoint(separator, recordingTrackId);
+ }
+
+ if (!insertLocation(recordingTrack, location, lastRecordedLocation,
+ lastRecordedLocationId, recordingTrackId)) {
+ return;
+ }
+ } else {
+ Log.d(TAG, String.format(
+ "Not recording. Distance to last recorded point (%f m) is less than"
+ + " %d m.", distanceToLastRecorded, minRecordingDistance));
+ // Return here so that the location is NOT recorded as the last location.
+ return;
+ }
+ } catch (Error e) {
+ // Probably important enough to rethrow.
+ Log.e(TAG, "Error in onLocationChanged", e);
+ throw e;
+ } catch (RuntimeException e) {
+ // Safe usually to trap exceptions.
+ Log.e(TAG,
+ "Trapping exception in onLocationChanged", e);
+ throw e;
+ }
+ lastLocation = location;
+ }
+
/**
* Inserts a new location in the track points db and updates the corresponding
* track in the track db.
@@ -308,326 +882,6 @@ public class TrackRecordingService extends Service implements LocationListener {
}
}
- /**
- * Tries to acquire a partial wake lock if not already acquired. Logs errors
- * and gives up trying in case the wake lock cannot be acquired.
- */
- private void acquireWakeLock() {
- try {
- PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- if (pm == null) {
- Log.e(TAG,
- "TrackRecordingService: Power manager not found!");
- return;
- }
- if (wakeLock == null) {
- wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
- TAG);
- if (wakeLock == null) {
- Log.e(TAG,
- "TrackRecordingService: Could not create wake lock (null).");
- return;
- }
- }
- if (!wakeLock.isHeld()) {
- wakeLock.acquire();
- if (!wakeLock.isHeld()) {
- Log.e(TAG,
- "TrackRecordingService: Could not acquire wake lock.");
- }
- }
- } catch (RuntimeException e) {
- Log.e(TAG,
- "TrackRecordingService: Caught unexpected exception: "
- + e.getMessage(), e);
- }
- }
-
- /**
- * Releases the wake lock if it's currently held.
- */
- private void releaseWakeLock() {
- if (wakeLock != null && wakeLock.isHeld()) {
- wakeLock.release();
- wakeLock = null;
- }
- }
-
- /**
- * 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 */,
- System.currentTimeMillis());
- PendingIntent contentIntent = PendingIntent.getActivity(
- this, 0 /* requestCode */, new Intent(this, MyTracks.class),
- 0 /* flags */);
- notification.setLatestEventInfo(this, getString(R.string.app_name),
- getString(R.string.recording_your_track), contentIntent);
- notification.flags += Notification.FLAG_NO_CLEAR;
- apiPlatformAdapter.startForeground(this, notificationManager, 1,
- notification);
- } else {
- apiPlatformAdapter.stopForeground(this, notificationManager, 1);
- }
- }
-
- public void registerLocationListener() {
- if (locationManager == null) {
- Log.e(TAG,
- "TrackRecordingService: Do not have any location manager.");
- return;
- }
- Log.d(TAG,
- "Preparing to register location listener w/ TrackRecordingService...");
- try {
- long desiredInterval = locationListenerPolicy.getDesiredPollingInterval();
- locationManager.requestLocationUpdates(
- LocationManager.GPS_PROVIDER, desiredInterval,
- locationListenerPolicy.getMinDistance(),
- // , 0 /* minDistance, get all updates to properly time pauses */
- TrackRecordingService.this);
- currentRecordingInterval = desiredInterval;
- Log.d(TAG,
- "...location listener now registered w/ TrackRecordingService @ "
- + currentRecordingInterval);
- } catch (RuntimeException e) {
- Log.e(TAG,
- "Could not register location listener: " + e.getMessage(), e);
- }
- }
-
- public void unregisterLocationListener() {
- if (locationManager == null) {
- Log.e(TAG,
- "TrackRecordingService: Do not have any location manager.");
- return;
- }
- locationManager.removeUpdates(this);
- Log.d(TAG,
- "Location listener now unregistered w/ TrackRecordingService.");
- }
-
- private Track getRecordingTrack() {
- if (recordingTrackId < 0) {
- return null;
- }
-
- return providerUtils.getTrack(recordingTrackId);
- }
-
- private void restoreStats(Track track) {
- Log.d(TAG,
- "Restoring stats of track with ID: " + track.getId());
-
- TripStatistics stats = track.getStatistics();
- statsBuilder = new TripStatisticsBuilder(stats.getStartTime());
- statsBuilder.setMinRecordingDistance(minRecordingDistance);
-
- length = 0;
- lastValidLocation = null;
-
- Waypoint waypoint = providerUtils.getFirstWaypoint(recordingTrackId);
- if (waypoint != null && waypoint.getStatistics() != null) {
- currentWaypointId = waypoint.getId();
- waypointStatsBuilder = new TripStatisticsBuilder(
- waypoint.getStatistics());
- } else {
- // This should never happen, but we got to do something so life goes on:
- waypointStatsBuilder = new TripStatisticsBuilder(stats.getStartTime());
- currentWaypointId = -1;
- }
- waypointStatsBuilder.setMinRecordingDistance(minRecordingDistance);
-
- Cursor cursor = null;
- try {
- cursor = providerUtils.getLocationsCursor(
- recordingTrackId, -1, Constants.MAX_LOADED_TRACK_POINTS,
- true);
- if (cursor != null) {
- if (cursor.moveToLast()) {
- do {
- Location location = providerUtils.createLocation(cursor);
- if (LocationUtils.isValidLocation(location)) {
- statsBuilder.addLocation(location, location.getTime());
- if (lastValidLocation != null) {
- length += location.distanceTo(lastValidLocation);
- }
- lastValidLocation = location;
- }
- } while (cursor.moveToPrevious());
- }
- statsBuilder.getStatistics().setMovingTime(stats.getMovingTime());
- statsBuilder.pauseAt(stats.getStopTime());
- statsBuilder.resumeAt(System.currentTimeMillis());
- } else {
- Log.e(TAG, "Could not get track points cursor.");
- }
- } catch (RuntimeException e) {
- Log.e(TAG, "Error while restoring track.", e);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
-
- announcementExecutor.restore();
- splitExecutor.restore();
- }
-
- /*
- * Location listener implementation: =================================
- */
- @Override
- public void onLocationChanged(final Location location) {
- executorService.submit(
- new Runnable() {
- @Override
- public void run() {
- onLocationChangedAsync(location);
- }
- });
- }
-
- private void onLocationChangedAsync(Location location) {
- Log.d(TAG, "TrackRecordingService.onLocationChanged");
-
- try {
- // Don't record if the service has been asked to pause recording:
- if (!isRecording) {
- Log.w(TAG,
- "Not recording because recording has been paused.");
- return;
- }
-
- // This should never happen, but just in case (we really don't want the
- // service to crash):
- if (location == null) {
- Log.w(TAG,
- "Location changed, but location is null.");
- return;
- }
-
- // Don't record if the accuracy is too bad:
- if (location.getAccuracy() > minRequiredAccuracy) {
- Log.d(TAG,
- "Not recording. Bad accuracy.");
- return;
- }
-
- // At least one track must be available for appending points:
- recordingTrack = getRecordingTrack();
- if (recordingTrack == null) {
- Log.d(TAG,
- "Not recording. No track to append to available.");
- return;
- }
-
- // Update the idle time if needed.
- locationListenerPolicy.updateIdleTime(statsBuilder.getIdleTime());
- addLocationToStats(location);
- if (currentRecordingInterval !=
- locationListenerPolicy.getDesiredPollingInterval()) {
- registerLocationListener();
- }
-
- Location lastRecordedLocation = providerUtils.getLastLocation();
- long lastRecordedLocationId =
- providerUtils.getLastLocationId(recordingTrackId);
- double distanceToLastRecorded = Double.POSITIVE_INFINITY;
- if (lastRecordedLocation != null) {
- distanceToLastRecorded = location.distanceTo(lastRecordedLocation);
- }
- double distanceToLast = Double.POSITIVE_INFINITY;
- if (lastLocation != null) {
- distanceToLast = location.distanceTo(lastLocation);
- }
- boolean hasSensorData = sensorManager != null
- && sensorManager.isEnabled()
- && sensorManager.getSensorDataSet() != null
- && sensorManager.isDataValid();
-
- // If the user has been stationary for two recording just record the first
- // two and ignore the rest. This code will only have an effect if the
- // maxRecordingDistance = 0
- if (distanceToLast == 0 && !hasSensorData) {
- if (isMoving) {
- Log.d(TAG, "Found two identical locations.");
- isMoving = false;
- if (lastLocation != null && lastRecordedLocation != null
- && !lastRecordedLocation.equals(lastLocation)) {
- // Need to write the last location. This will happen when
- // lastRecordedLocation.distance(lastLocation) <
- // minRecordingDistance
- if (!insertLocation(recordingTrack, lastLocation,
- lastRecordedLocation, lastRecordedLocationId,
- recordingTrackId)) {
- return;
- }
- lastRecordedLocationId++;
- }
- } else {
- Log.d(TAG,
- "Not recording. More than two identical locations.");
- }
- } else if (distanceToLastRecorded > minRecordingDistance
- || hasSensorData) {
- if (lastLocation != null && !isMoving) {
- // Last location was the last stationary location. Need to go back and
- // add it.
- if (!insertLocation(recordingTrack, lastLocation,
- lastRecordedLocation, lastRecordedLocationId, recordingTrackId)) {
- return;
- }
- lastRecordedLocationId++;
- isMoving = true;
- }
-
- // If separation from last recorded point is too large insert a
- // separator to indicate end of a segment:
- boolean startNewSegment =
- lastRecordedLocation != null
- && lastRecordedLocation.getLatitude() < 90
- && distanceToLastRecorded > maxRecordingDistance
- && recordingTrack.getStartId() >= 0;
- if (startNewSegment) {
- // Insert a separator point to indicate start of new track:
- Log.d(TAG, "Inserting a separator.");
- Location separator = new Location(LocationManager.GPS_PROVIDER);
- separator.setLongitude(0);
- separator.setLatitude(100);
- separator.setTime(lastRecordedLocation.getTime());
- providerUtils.insertTrackPoint(separator, recordingTrackId);
- }
-
- if (!insertLocation(recordingTrack, location, lastRecordedLocation,
- lastRecordedLocationId, recordingTrackId)) {
- return;
- }
- } else {
- Log.d(TAG, String.format(
- "Not recording. Distance to last recorded point (%f m) is less than"
- + " %d m.", distanceToLastRecorded, minRecordingDistance));
- // Return here so that the location is NOT recorded as the last location.
- return;
- }
- } catch (Error e) {
- // Probably important enough to rethrow.
- Log.e(TAG, "Error in onLocationChanged", e);
- throw e;
- } catch (RuntimeException e) {
- // Safe usually to trap exceptions.
- Log.e(TAG,
- "Trapping exception in onLocationChanged", e);
- throw e;
- }
- lastLocation = location;
- }
-
private void addLocationToStats(Location location) {
if (LocationUtils.isValidLocation(location)) {
long now = System.currentTimeMillis();
@@ -636,227 +890,10 @@ public class TrackRecordingService extends Service implements LocationListener {
}
}
- @Override
- public void onProviderDisabled(String provider) {
- // Do nothing
- }
-
- @Override
- public void onProviderEnabled(String provider) {
- // Do nothing
- }
-
- @Override
- public void onStatusChanged(String provider, int status, Bundle extras) {
- // Do nothing
- }
-
/*
* Application lifetime events: ============================
*/
- @Override
- public void onCreate() {
- super.onCreate();
- Log.d(TAG, "TrackRecordingService.onCreate");
- providerUtils = MyTracksProviderUtils.Factory.get(this);
- notificationManager =
- (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
- locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
-
- setUpTaskExecutors();
- prefManager = new PreferenceManager(this);
- registerLocationListener();
-
- /*
- * After 5 min, check every minute that location listener still is
- * registered and spit out additional debugging info to the logs:
- */
- timer.schedule(checkLocationListener, 1000 * 60 * 5, 1000 * 60);
-
- // Try to restore previous recording state in case this service has been
- // restarted by the system, which can sometimes happen.
- recordingTrack = getRecordingTrack();
- if (recordingTrack != null) {
- restoreStats(recordingTrack);
- isRecording = true;
- } else {
- if (recordingTrackId != -1) {
- // Make sure we have consistent state in shared preferences.
- Log.w(TAG, "TrackRecordingService.onCreate: "
- + "Resetting an orphaned recording track = " + recordingTrackId);
- }
- prefManager.setRecordingTrack(recordingTrackId = -1);
- }
- showNotification();
- executorService = Executors.newSingleThreadExecutor();
- }
-
- /**
- * Creates the periodic task executors.
- */
- private void setUpTaskExecutors() {
- announcementExecutor = new PeriodicTaskExecutor(
- this, new StatusAnnouncerFactory(ApiFeatures.getInstance()));
- splitExecutor = new PeriodicTaskExecutor(this, new SplitTask.Factory());
- }
-
- private void shutdownTaskExecutors() {
- Log.d(TAG, "TrackRecordingService.shutdownExecuters");
- try {
- announcementExecutor.shutdown();
- } finally {
- announcementExecutor = null;
- }
- try {
- splitExecutor.shutdown();
- } finally {
- splitExecutor = null;
- }
- }
-
- @Override
- public void onDestroy() {
- Log.d(TAG, "TrackRecordingService.onDestroy");
-
- isRecording = false;
- showNotification();
- prefManager.shutdown();
- prefManager = null;
- checkLocationListener.cancel();
- checkLocationListener = null;
- timer.cancel();
- timer.purge();
- unregisterLocationListener();
- shutdownTaskExecutors();
- if (sensorManager != null) {
- sensorManager.shutdown();
- sensorManager = null;
- }
-
- // Make sure we have no indirect references to this service.
- locationManager = null;
- notificationManager = null;
- providerUtils = null;
- binder.detachFromService();
- binder = null;
-
- // This should be the last operation.
- releaseWakeLock();
-
- super.onDestroy();
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- Log.d(TAG, "TrackRecordingService.onBind");
- return binder;
- }
-
- @Override
- public boolean onUnbind(Intent intent) {
- Log.d(TAG, "TrackRecordingService.onUnbind");
- return super.onUnbind(intent);
- }
-
- @Override
- public boolean stopService(Intent name) {
- Log.d(TAG, "TrackRecordingService.stopService");
- unregisterLocationListener();
- return super.stopService(name);
- }
-
- @Override
- public void onStart(Intent intent, int startId) {
- handleStartCommand(intent, startId);
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- handleStartCommand(intent, startId);
- return START_STICKY;
- }
-
- private void handleStartCommand(Intent intent, int startId) {
- Log.d(TAG,
- "TrackRecordingService.handleStartCommand: " + startId);
-
- // Check if called on phone reboot with resume intent.
- if (intent != null &&
- intent.getBooleanExtra(RESUME_TRACK_EXTRA_NAME, false)) {
- Log.d(TAG, "TrackRecordingService: requested resume");
-
- // Make sure that the current track exists and is fresh enough.
- if (recordingTrack == null || !shouldResumeTrack(recordingTrack)) {
- Log.i(TAG,
- "TrackRecordingService: Not resuming, because the previous track ("
- + recordingTrack + ") doesn't exist or is too old");
- isRecording = false;
- prefManager.setRecordingTrack(recordingTrackId = -1);
- stopSelfResult(startId);
- return;
- }
-
- Log.i(TAG, "TrackRecordingService: resuming");
- }
- }
-
- private void setAutoResumeTrackRetries(
- SharedPreferences sharedPreferences, int retryAttempts) {
- Log.d(TAG,
- "Updating auto-resume retry attempts to: " + retryAttempts);
- prefManager.setAutoResumeTrackCurrentRetry(retryAttempts);
- }
-
- private boolean shouldResumeTrack(Track track) {
- Log.d(TAG, "shouldResumeTrack: autoResumeTrackTimeout = "
- + autoResumeTrackTimeout);
-
- // Check if we haven't exceeded the maximum number of retry attempts.
- SharedPreferences sharedPreferences =
- getSharedPreferences(Constants.SETTINGS_NAME, 0);
- int retries = sharedPreferences.getInt(
- getString(R.string.auto_resume_track_current_retry_key), 0);
- Log.d(TAG,
- "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(TAG,
- "shouldResumeTrack: Not resuming because exceeded the maximum "
- + "number of auto-resume retries");
- return false;
- }
-
- // Increase number of retry attempts.
- setAutoResumeTrackRetries(sharedPreferences, retries + 1);
-
- // Check for special cases.
- if (autoResumeTrackTimeout == 0) {
- // Never resume.
- Log.d(TAG,
- "shouldResumeTrack: Auto-resume disabled (never resume)");
- return false;
- } else if (autoResumeTrackTimeout == -1) {
- // Always resume.
- Log.d(TAG,
- "shouldResumeTrack: Auto-resume forced (always resume)");
- return true;
- }
-
- // Check if the last modified time is within the acceptable range.
- long lastModified =
- track.getStatistics() != null ? track.getStatistics().getStopTime() : 0;
- Log.d(TAG,
- "shouldResumeTrack: lastModified = " + lastModified
- + ", autoResumeTrackTimeout: " + autoResumeTrackTimeout);
- return lastModified > 0 && System.currentTimeMillis() - lastModified <=
- autoResumeTrackTimeout * 60 * 1000;
- }
-
- public boolean isRecording() {
- return isRecording;
- }
-
public long insertWaypoint(WaypointCreationRequest request) {
if (!isRecording()) {
throw new IllegalStateException(
@@ -940,171 +977,6 @@ public class TrackRecordingService extends Service implements LocationListener {
// Create a new stats keeper for the next marker.
waypointStatsBuilder = new TripStatisticsBuilder(time);
}
-
- private ServiceBinder binder = new ServiceBinder(this);
-
- /**
- * TODO: There is a bug in Android that leaks Binder instances. This bug is
- * especially visible if we have a non-static class, as there is no way to
- * nullify reference to the outer class (the service).
- * A workaround is to use a static class and explicitly clear service
- * and detach it from the underlying Binder. With this approach, we minimize
- * the leak to 24 bytes per each service instance.
- *
- * For more details, see the following bug:
- * http://code.google.com/p/android/issues/detail?id=6426.
- */
- private static class ServiceBinder extends ITrackRecordingService.Stub {
- private TrackRecordingService service;
-
- public ServiceBinder(TrackRecordingService service) {
- this.service = service;
- }
-
- /**
- * Clears the reference to the outer class to minimize the leak.
- */
- public void detachFromService() {
- this.service = null;
- attachInterface(null, null);
- }
-
- @Override
- public boolean isRecording() {
- checkService();
- return service.isRecording();
- }
-
- private void checkService() {
- if (service == null) {
- throw new IllegalStateException("The service has been already detached!");
- }
- }
-
- @Override
- public long getRecordingTrackId() {
- checkService();
- return service.recordingTrackId;
- }
-
- @Override
- public boolean hasRecorded() {
- checkService();
- return service.providerUtils.getLastTrackId() >= 0;
- }
-
- @Override
- public long startNewTrack() {
- checkService();
- return service.startNewTrack();
- }
-
- /**
- * Inserts a waypoint marker in the track being recorded.
- *
- * @param request Details of the waypoint to insert
- * @return the unique ID of the inserted marker
- */
- public long insertWaypoint(WaypointCreationRequest request) {
- checkService();
- return service.insertWaypoint(request);
- }
-
- @Override
- public void endCurrentTrack() {
- checkService();
- service.endCurrentTrack();
- }
-
- @Override
- public void deleteAllTracks() {
- checkService();
- if (isRecording()) {
- throw new IllegalStateException("Cannot delete all tracks while recording!");
- }
- service.providerUtils.deleteAllTracks();
- }
-
- @Override
- public void recordLocation(Location loc) {
- checkService();
- service.onLocationChanged(loc);
- }
-
- @Override
- public byte[] getSensorData() {
- checkService();
- if (service.sensorManager == null) {
- Log.d(TAG, "No sensor manager for data.");
- return null;
- }
- if (service.sensorManager.getSensorDataSet() == null) {
- Log.d(TAG, "Sensor data set is null.");
- return null;
- }
- return service.sensorManager.getSensorDataSet().toByteArray();
- }
-
- @Override
- public int getSensorState() {
- checkService();
- if (service.sensorManager == null) {
- Log.d(TAG, "No sensor manager for data.");
- return Sensor.SensorState.NONE.getNumber();
- }
- return service.sensorManager.getSensorState().getNumber();
- }
- }
-
- public long startNewTrack() {
- Log.d(TAG, "TrackRecordingService.startNewTrack");
- if (recordingTrackId != -1 || isRecording) {
- throw new IllegalStateException("A track is already in progress!");
- }
-
- long startTime = System.currentTimeMillis();
- acquireWakeLock();
-
- Track track = new Track();
- TripStatistics trackStats = track.getStatistics();
- trackStats.setStartTime(startTime);
- track.setStartId(-1);
- Uri trackUri = providerUtils.insertTrack(track);
- recordingTrackId = Long.parseLong(trackUri.getLastPathSegment());
- track.setId(recordingTrackId);
- track.setName(new DefaultTrackNameFactory(this).newTrackName(
- recordingTrackId, startTime));
- isRecording = true;
- isMoving = true;
-
- providerUtils.updateTrack(track);
- statsBuilder = new TripStatisticsBuilder(startTime);
- statsBuilder.setMinRecordingDistance(minRecordingDistance);
- waypointStatsBuilder = new TripStatisticsBuilder(startTime);
- waypointStatsBuilder.setMinRecordingDistance(minRecordingDistance);
- currentWaypointId = insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
- length = 0;
- showNotification();
- registerLocationListener();
- sensorManager = SensorManagerFactory.getSensorManager(this);
- if (sensorManager != null) {
- sensorManager.onStartTrack();
- }
-
- // Reset the number of auto-resume retries.
- setAutoResumeTrackRetries(
- getSharedPreferences(Constants.SETTINGS_NAME, 0), 0);
- // Persist the current recording track.
- prefManager.setRecordingTrack(recordingTrackId);
-
- // Notify the world that we're now recording.
- sendTrackBroadcast(
- R.string.track_started_broadcast_action, recordingTrackId);
- announcementExecutor.restore();
- splitExecutor.restore();
-
- return recordingTrackId;
- }
private void endCurrentTrack() {
Log.d(TAG, "TrackRecordingService.endCurrentTrack");
@@ -1135,17 +1007,19 @@ public class TrackRecordingService extends Service implements LocationListener {
showNotification();
long recordedTrackId = recordingTrackId;
prefManager.setRecordingTrack(recordingTrackId = -1);
-
+
if (sensorManager != null) {
sensorManager.shutdown();
sensorManager = null;
}
-
+
releaseWakeLock();
// Notify the world that we're no longer recording.
sendTrackBroadcast(
R.string.track_stopped_broadcast_action, recordedTrackId);
+
+ stopSelf();
}
private void sendTrackBroadcast(int actionResId, long trackId) {
@@ -1157,6 +1031,22 @@ public class TrackRecordingService extends Service implements LocationListener {
getString(R.string.broadcast_notifications_permission));
}
+ /*
+ * Data/state access.
+ */
+
+ private Track getRecordingTrack() {
+ if (recordingTrackId < 0) {
+ return null;
+ }
+
+ return providerUtils.getTrack(recordingTrackId);
+ }
+
+ public boolean isRecording() {
+ return isRecording;
+ }
+
public TripStatistics getTripStatistics() {
return statsBuilder.getStatistics();
}
@@ -1169,23 +1059,15 @@ public class TrackRecordingService extends Service implements LocationListener {
return recordingTrackId;
}
- public void setRecordingTrackId(long recordingTrackId) {
+ void setRecordingTrackId(long recordingTrackId) {
this.recordingTrackId = recordingTrackId;
}
- public int getMaxRecordingDistance() {
- return maxRecordingDistance;
- }
-
- public void setMaxRecordingDistance(int maxRecordingDistance) {
+ void setMaxRecordingDistance(int maxRecordingDistance) {
this.maxRecordingDistance = maxRecordingDistance;
}
- public int getMinRecordingDistance() {
- return minRecordingDistance;
- }
-
- public void setMinRecordingDistance(int minRecordingDistance) {
+ void setMinRecordingDistance(int minRecordingDistance) {
this.minRecordingDistance = minRecordingDistance;
if (statsBuilder != null && waypointStatsBuilder != null) {
statsBuilder.setMinRecordingDistance(minRecordingDistance);
@@ -1193,41 +1075,163 @@ public class TrackRecordingService extends Service implements LocationListener {
}
}
- public int getMinRequiredAccuracy() {
- return minRequiredAccuracy;
- }
-
- public void setMinRequiredAccuracy(int minRequiredAccuracy) {
+ void setMinRequiredAccuracy(int minRequiredAccuracy) {
this.minRequiredAccuracy = minRequiredAccuracy;
}
- public LocationListenerPolicy getLocationListenerPolicy() {
- return locationListenerPolicy;
- }
-
- public void setLocationListenerPolicy(
- LocationListenerPolicy locationListenerPolicy) {
+ void setLocationListenerPolicy(LocationListenerPolicy locationListenerPolicy) {
this.locationListenerPolicy = locationListenerPolicy;
}
-
- public int getAutoResumeTrackTimeout() {
- return autoResumeTrackTimeout;
- }
-
- public void setAutoResumeTrackTimeout(int autoResumeTrackTimeout) {
+
+ void setAutoResumeTrackTimeout(int autoResumeTrackTimeout) {
this.autoResumeTrackTimeout = autoResumeTrackTimeout;
}
- public void setAnnouncementFrequency(int announcementFrequency) {
+ void setAnnouncementFrequency(int announcementFrequency) {
announcementExecutor.setTaskFrequency(announcementFrequency);
}
- public void setSplitFrequency(int frequency) {
+ void setSplitFrequency(int frequency) {
splitExecutor.setTaskFrequency(frequency);
}
- public void setMetricUnits(boolean metric) {
+ void setMetricUnits(boolean metric) {
announcementExecutor.setMetricUnits(metric);
splitExecutor.setMetricUnits(metric);
}
+
+
+ /**
+ * TODO: There is a bug in Android that leaks Binder instances. This bug is
+ * especially visible if we have a non-static class, as there is no way to
+ * nullify reference to the outer class (the service).
+ * A workaround is to use a static class and explicitly clear service
+ * and detach it from the underlying Binder. With this approach, we minimize
+ * the leak to 24 bytes per each service instance.
+ *
+ * For more details, see the following bug:
+ * http://code.google.com/p/android/issues/detail?id=6426.
+ */
+ private static class ServiceBinder extends ITrackRecordingService.Stub {
+ private TrackRecordingService service;
+ private DeathRecipient deathRecipient;
+
+ public ServiceBinder(TrackRecordingService service) {
+ this.service = service;
+ }
+
+ // Logic for letting the actual service go up and down.
+
+ @Override
+ public boolean isBinderAlive() {
+ // Pretend dead if the service went down.
+ return service != null;
+ }
+
+ @Override
+ public boolean pingBinder() {
+ return isBinderAlive();
+ }
+
+ @Override
+ public void linkToDeath(DeathRecipient recipient, int flags) {
+ deathRecipient = recipient;
+ }
+
+ @Override
+ public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
+ if (!isBinderAlive()) {
+ return false;
+ }
+
+ deathRecipient = null;
+ return true;
+ }
+
+ /**
+ * Clears the reference to the outer class to minimize the leak.
+ */
+ private void detachFromService() {
+ this.service = null;
+ attachInterface(null, null);
+
+ if (deathRecipient != null) {
+ deathRecipient.binderDied();
+ }
+ }
+
+ private void checkService() {
+ if (service == null) {
+ throw new IllegalStateException("The service has been already detached!");
+ }
+ }
+
+ // Service method delegates.
+
+ @Override
+ public boolean isRecording() {
+ checkService();
+ return service.isRecording();
+ }
+
+ @Override
+ public long getRecordingTrackId() {
+ checkService();
+ return service.recordingTrackId;
+ }
+
+ @Override
+ public long startNewTrack() {
+ checkService();
+ return service.startNewTrack();
+ }
+
+ /**
+ * Inserts a waypoint marker in the track being recorded.
+ *
+ * @param request Details of the waypoint to insert
+ * @return the unique ID of the inserted marker
+ */
+ public long insertWaypoint(WaypointCreationRequest request) {
+ checkService();
+ return service.insertWaypoint(request);
+ }
+
+ @Override
+ public void endCurrentTrack() {
+ checkService();
+ service.endCurrentTrack();
+ }
+
+ @Override
+ public void recordLocation(Location loc) {
+ checkService();
+ service.locationListener.onLocationChanged(loc);
+ }
+
+ @Override
+ public byte[] getSensorData() {
+ checkService();
+ if (service.sensorManager == null) {
+ Log.d(TAG, "No sensor manager for data.");
+ return null;
+ }
+ if (service.sensorManager.getSensorDataSet() == null) {
+ Log.d(TAG, "Sensor data set is null.");
+ return null;
+ }
+ return service.sensorManager.getSensorDataSet().toByteArray();
+ }
+
+ @Override
+ public int getSensorState() {
+ checkService();
+ if (service.sensorManager == null) {
+ Log.d(TAG, "No sensor manager for data.");
+ return Sensor.SensorState.NONE.getNumber();
+ }
+ return service.sensorManager.getSensorState().getNumber();
+ }
+ }
+
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingServiceConnection.java b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingServiceConnection.java
new file mode 100644
index 000000000..f646ab1cd
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingServiceConnection.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.android.apps.mytracks.services;
+
+import static com.google.android.apps.mytracks.Constants.TAG;
+
+import com.google.android.apps.mytracks.util.SystemUtils;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Wrapper for the connection to the track recording service.
+ * This handles connection/disconnection internally, only returning a real
+ * service for use if one is available and connected.
+ *
+ * @author Rodrigo Damazio
+ */
+public class TrackRecordingServiceConnection {
+ private ITrackRecordingService boundService;
+
+ private final DeathRecipient deathRecipient = new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ Log.d(TAG, "Service died");
+ setBoundService(null);
+ }
+ };
+
+ private final ServiceConnection serviceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ Log.i(TAG, "Connected to service");
+ try {
+ service.linkToDeath(deathRecipient, 0);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to bind a death recipient", e);
+ }
+
+ setBoundService(ITrackRecordingService.Stub.asInterface(service));
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ Log.i(TAG, "Disconnected from service");
+ setBoundService(null);
+ }
+ };
+
+ private final Context context;
+
+ private final Runnable bindChangedCallback;
+
+ /**
+ * Constructor.
+ *
+ * @param context the current context
+ * @param bindChangedCallback a callback to be executed when the state of the
+ * service binding changes
+ */
+ public TrackRecordingServiceConnection(Context context, Runnable bindChangedCallback) {
+ this.context = context;
+ this.bindChangedCallback = bindChangedCallback;
+ }
+
+ /**
+ * Binds to the service, starting it if necessary.
+ */
+ public void startAndBind() {
+ bindService(true);
+ }
+
+ /**
+ * Binds to the service, only if it's already running.
+ */
+ public void bindIfRunning() {
+ bindService(false);
+ }
+
+ /**
+ * Unbinds from and stops the service.
+ */
+ public void stop() {
+ unbind();
+
+ Log.d(TAG, "Stopping service");
+ Intent intent = new Intent(context, TrackRecordingService.class);
+ context.stopService(intent);
+ }
+
+ /**
+ * Unbinds from the service (but leaves it running).
+ */
+ public void unbind() {
+ Log.d(TAG, "Unbinding from the service");
+ try {
+ context.unbindService(serviceConnection);
+ } catch (IllegalArgumentException e) {
+ // Means we weren't bound, which is ok.
+ }
+
+ setBoundService(null);
+ }
+
+ /**
+ * Returns the service if connected to it, or null if not connected.
+ */
+ public ITrackRecordingService getServiceIfBound() {
+ checkBindingAlive();
+
+ return boundService;
+ }
+
+ private void checkBindingAlive() {
+ if (boundService != null &&
+ !boundService.asBinder().isBinderAlive()) {
+ setBoundService(null);
+ }
+ }
+
+ private void bindService(boolean startIfNeeded) {
+ if (boundService != null) {
+ // Already bound.
+ return;
+ }
+
+ if (!startIfNeeded && !ServiceUtils.isServiceRunning(context)) {
+ // Not running, start not requested.
+ Log.d(TAG, "Service not running, not binding to it.");
+ return;
+ }
+
+ if (startIfNeeded) {
+ Log.i(TAG, "Starting the service");
+ Intent intent = new Intent(context, TrackRecordingService.class);
+ context.startService(intent);
+ }
+
+ Log.i(TAG, "Binding to the service");
+ Intent intent = new Intent(context, TrackRecordingService.class);
+ int flags = SystemUtils.isRelease(context) ? 0 : Context.BIND_DEBUG_UNBIND;
+ context.bindService(intent, serviceConnection, flags);
+ }
+
+ private void setBoundService(ITrackRecordingService service) {
+ boundService = service;
+ if (bindChangedCallback != null) {
+ bindChangedCallback.run();
+ }
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactory.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactory.java
index 43e0a4be0..7c468be2c 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactory.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactory.java
@@ -1,12 +1,12 @@
/*
* Copyright 2009 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -45,7 +45,9 @@ public class SensorManagerFactory {
if (prefs == null) {
return null;
}
-
+
+ context = context.getApplicationContext();
+
String sensor = prefs.getString(context.getString(R.string.sensor_type_key), null);
Log.i(Constants.TAG, "Creating sensor of type: " + sensor);
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTaskExecutor.java b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTaskExecutor.java
index f307e4741..74dfaf641 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTaskExecutor.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTaskExecutor.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -67,7 +67,7 @@ public class PeriodicTaskExecutor {
if (!service.isRecording()) {
return;
}
-
+
if (!isTimeFrequency()) {
if (timerExecutor != null) {
timerExecutor.shutdown();
@@ -77,7 +77,7 @@ public class PeriodicTaskExecutor {
if (taskFrequency == 0) {
return;
}
-
+
// Try to make the task.
task = factory.create(service);
// Returning null is ok.
@@ -90,7 +90,7 @@ public class PeriodicTaskExecutor {
if (timerExecutor == null) {
timerExecutor = new TimerTaskExecutor(task, service);
}
- timerExecutor.scheduleTask(taskFrequency * 60000);
+ timerExecutor.scheduleTask(taskFrequency * 60000L);
} else {
// For distance based splits.
calculateNextTaskDistance();
@@ -119,7 +119,7 @@ public class PeriodicTaskExecutor {
if (!service.isRecording() || task == null) {
return;
}
-
+
if (!isDistanceFrequency()) {
nextTaskDistance = Double.MAX_VALUE;
Log.d(TAG, "SplitManager: Distance splits disabled.");
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/ApiPlatformAdapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/ApiPlatformAdapter.java
index c68832735..f432fe8fa 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/util/ApiPlatformAdapter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/ApiPlatformAdapter.java
@@ -19,7 +19,6 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.SharedPreferences;
-import android.os.HandlerThread;
/**
* A set of methods that may be implemented in a platform specific way.
@@ -51,14 +50,6 @@ public interface ApiPlatformAdapter {
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);
-
/**
* Applies all changes done to the given preferences editor.
* Changes may or may not be applied immediately.
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/CupcakePlatformAdapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/CupcakePlatformAdapter.java
index abdd9dfb7..fe6ec0b47 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/util/CupcakePlatformAdapter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/CupcakePlatformAdapter.java
@@ -16,11 +16,11 @@
package com.google.android.apps.mytracks.util;
import static com.google.android.apps.mytracks.Constants.TAG;
+
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.SharedPreferences.Editor;
-import android.os.HandlerThread;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
@@ -70,12 +70,6 @@ public class CupcakePlatformAdapter implements ApiPlatformAdapter {
}
}
- @Override
- public boolean stopHandlerThread(HandlerThread handlerThread) {
- // Do nothing, as Cupcake doesn't provide quit().
- return false;
- }
-
@Override
public void applyPreferenceChanges(Editor editor) {
editor.commit();
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/EclairPlatformAdapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/EclairPlatformAdapter.java
index 91c1ea1d3..55540e1a4 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/util/EclairPlatformAdapter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/EclairPlatformAdapter.java
@@ -19,7 +19,6 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.SharedPreferences.Editor;
-import android.os.HandlerThread;
/**
* The Eclair (API level 5) specific implementation of the
@@ -42,11 +41,6 @@ public class EclairPlatformAdapter implements ApiPlatformAdapter {
service.stopForeground(id != -1);
}
- @Override
- public boolean stopHandlerThread(HandlerThread handlerThread) {
- return handlerThread.quit();
- }
-
@Override
public void applyPreferenceChanges(Editor editor) {
editor.commit();
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/ResourceUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/ResourceUtils.java
index 884f4b95a..1d9603919 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/util/ResourceUtils.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/ResourceUtils.java
@@ -1,12 +1,12 @@
/*
* Copyright 2008 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -57,10 +57,11 @@ public class ResourceUtils {
public static void readBinaryFileToOutputStream(
Context activity, int id, OutputStream os) {
BufferedInputStream in = null;
+ BufferedOutputStream out = null;
try {
in = new BufferedInputStream(
activity.getResources().openRawResource(id));
- BufferedOutputStream out = new BufferedOutputStream(os);
+ out = new BufferedOutputStream(os);
int b;
while ((b = in.read()) != -1) {
out.write(b);
@@ -76,6 +77,14 @@ public class ResourceUtils {
// Ignore
}
}
+
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/UriUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/UriUtils.java
new file mode 100644
index 000000000..bc49f323a
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/UriUtils.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.android.apps.mytracks.util;
+
+import android.net.Uri;
+
+import java.util.List;
+
+/**
+ * Utilities for dealing with content and other types of URIs.
+ *
+ * @author Rodrigo Damazio
+ */
+public class UriUtils {
+
+ public static boolean matchesContentUri(Uri uri, Uri baseContentUri) {
+ if (uri == null) {
+ return false;
+ }
+
+ // Check that scheme and authority are the same.
+ if (!uri.getScheme().equals(baseContentUri.getScheme()) ||
+ !uri.getAuthority().equals(baseContentUri.getAuthority())) {
+ return false;
+ }
+
+ // Checks that all the base path components are in the URI.
+ List uriPathSegments = uri.getPathSegments();
+ List basePathSegments = baseContentUri.getPathSegments();
+ if (basePathSegments.size() > uriPathSegments.size()) {
+ return false;
+ }
+ for (int i = 0; i < basePathSegments.size(); i++) {
+ if (!uriPathSegments.get(i).equals(basePathSegments.get(i))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static boolean isFileUri(Uri uri) {
+ return "file".equals(uri.getScheme());
+ }
+
+ private UriUtils() {}
+}
diff --git a/MyTracksLib/src/com/google/android/apps/mytracks/services/ITrackRecordingService.aidl b/MyTracksLib/src/com/google/android/apps/mytracks/services/ITrackRecordingService.aidl
index b4ffcec0e..f6b946a4c 100644
--- a/MyTracksLib/src/com/google/android/apps/mytracks/services/ITrackRecordingService.aidl
+++ b/MyTracksLib/src/com/google/android/apps/mytracks/services/ITrackRecordingService.aidl
@@ -1,12 +1,12 @@
/*
* Copyright 2008 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -66,17 +66,6 @@ interface ITrackRecordingService {
*/
void endCurrentTrack();
- /**
- * Returns whether at least one track has been or is being recorded (and was
- * not deleted).
- */
- boolean hasRecorded();
-
- /**
- * Deletes all the stored tracks.
- */
- void deleteAllTracks();
-
/**
* The current sensor data.
* The data is returned as a byte array which is a binary version of a
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/DialogManagerTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/DialogManagerTest.java
deleted file mode 100644
index 1b23a0315..000000000
--- a/MyTracksTest/src/com/google/android/apps/mytracks/DialogManagerTest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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.io.sendtogoogle.SendDialog;
-
-import android.app.Dialog;
-import android.app.ProgressDialog;
-import android.test.ActivityInstrumentationTestCase2;
-
-/**
- * @author Sandor Dornbush
- */
-public class DialogManagerTest
- extends ActivityInstrumentationTestCase2 {
-
- public DialogManagerTest() {
- super(MyTracks.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- MyTracks.clearInstance();
- assertNull(MyTracks.getInstance());
- }
-
- public void test_onCreateChartSettings() {
- Dialog d =
- getActivity().onCreateDialog(DialogManager.DIALOG_CHART_SETTINGS, null);
- assertNotNull(d);
- assertTrue(d instanceof ChartSettingsDialog);
- }
-
- public void test_onCreateImportProgress() {
- Dialog d =
- getActivity().onCreateDialog(
- DialogManager.DIALOG_IMPORT_PROGRESS, null);
- assertNotNull(d);
- assertTrue(d instanceof ProgressDialog);
- }
-
- public void test_onCreateProgress() {
- Dialog d =
- getActivity().onCreateDialog(DialogManager.DIALOG_PROGRESS, null);
- assertNotNull(d);
- assertTrue(d instanceof ProgressDialog);
- ProgressDialog pd = (ProgressDialog) d;
- assertEquals(100, pd.getMax());
- assertEquals(10, pd.getProgress());
- }
-
- public void test_onCreateSendToGoogle() {
- Dialog d =
- getActivity().onCreateDialog(DialogManager.DIALOG_SEND_TO_GOOGLE, null);
- assertNotNull(d);
- assertTrue(d instanceof SendDialog);
- }
-}
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/MyTracksTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/MyTracksTest.java
index 7c5151944..a5581edc7 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/MyTracksTest.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/MyTracksTest.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -15,6 +15,8 @@
*/
package com.google.android.apps.mytracks;
+import com.google.android.apps.mytracks.services.ServiceUtils;
+import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
@@ -32,60 +34,53 @@ import java.util.concurrent.atomic.AtomicBoolean;
/**
* A unit test for {@link MyTracks} activity.
- *
+ *
* @author Bartlomiej Niechwiej
*/
public class MyTracksTest extends ActivityInstrumentationTestCase2{
+ private SharedPreferences sharedPreferences;
+ private TrackRecordingServiceConnection serviceConnection;
+
public MyTracksTest() {
super(MyTracks.class);
}
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- MyTracks.clearInstance();
- assertNull(MyTracks.getInstance());
- }
-
@Override
protected void tearDown() throws Exception {
clearSelectedAndRecordingTracks();
waitForIdle();
+
super.tearDown();
}
-
+
public void testInitialization_mainAction() {
// Make sure we can start MyTracks and the activity doesn't start recording.
- assertNotNull(getActivity());
- assertNotNull(MyTracks.getInstance());
- assertNotNull(getActivity().getSharedPreferences());
-
+ assertInitialized();
+
// Check if not recording.
- assertFalse(getActivity().isRecording());
- assertEquals(-1, getActivity().getRecordingTrackId());
- long selectedTrackId = getActivity().getSharedPreferences().getLong(
+ assertFalse(isRecording());
+ assertEquals(-1, getRecordingTrackId());
+ long selectedTrackId = getSharedPreferences().getLong(
getActivity().getString(R.string.selected_track_key), -1);
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
}
-
+
public void testInitialization_viewActionWithNoData() {
// Simulate start with ACTION_VIEW intent.
Intent startIntent = new Intent();
startIntent.setAction(Intent.ACTION_VIEW);
setActivityIntent(startIntent);
-
- assertNotNull(getActivity());
- assertNotNull(MyTracks.getInstance());
- assertNotNull(getActivity().getSharedPreferences());
-
+
+ assertInitialized();
+
// Check if not recording.
- assertFalse(getActivity().isRecording());
- assertEquals(-1, getActivity().getRecordingTrackId());
- long selectedTrackId = getActivity().getSharedPreferences().getLong(
+ assertFalse(isRecording());
+ assertEquals(-1, getRecordingTrackId());
+ long selectedTrackId = getSharedPreferences().getLong(
getActivity().getString(R.string.selected_track_key), -1);
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
}
-
+
public void testInitialization_viewActionWithValidData() throws Exception {
// Simulate start with ACTION_VIEW intent.
Intent startIntent = new Intent();
@@ -93,24 +88,22 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2{
Uri uri = Uri.fromFile(File.createTempFile("valid", ".gpx"));
// TODO: Add a valid GPX.
-
+
startIntent.setData(uri);
setActivityIntent(startIntent);
-
- assertNotNull(getActivity());
- assertNotNull(MyTracks.getInstance());
- assertNotNull(getActivity().getSharedPreferences());
-
+
+ assertInitialized();
+
// Check if not recording.
- assertFalse(getActivity().isRecording());
- assertEquals(-1, getActivity().getRecordingTrackId());
- long selectedTrackId = getActivity().getSharedPreferences().getLong(
+ assertFalse(isRecording());
+ assertEquals(-1, getRecordingTrackId());
+ long selectedTrackId = getSharedPreferences().getLong(
getActivity().getString(R.string.selected_track_key), -1);
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
-
- // TODO: Finish this test.
+
+ // TODO: Finish this test.
}
-
+
public void testInitialization_viewActionWithInvalidData() throws Exception {
// Simulate start with ACTION_VIEW intent.
Intent startIntent = new Intent();
@@ -118,66 +111,62 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2{
Uri uri = Uri.fromFile(File.createTempFile("invalid", ".gpx"));
startIntent.setData(uri);
setActivityIntent(startIntent);
-
- assertNotNull(getActivity());
- assertNotNull(MyTracks.getInstance());
- assertNotNull(getActivity().getSharedPreferences());
-
+
+ assertInitialized();
+
// Check if not recording.
- assertFalse(getActivity().isRecording());
- assertEquals(-1, getActivity().getRecordingTrackId());
- long selectedTrackId = getActivity().getSharedPreferences().getLong(
+ assertFalse(isRecording());
+ assertEquals(-1, getRecordingTrackId());
+ long selectedTrackId = getSharedPreferences().getLong(
getActivity().getString(R.string.selected_track_key), -1);
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
-
+
// TODO: Finish this test.
}
-
+
public void testRecording_startAndStop() throws Exception {
- // Make sure we can start MyTracks and the activity doesn't start recording.
- assertNotNull(getActivity());
- assertNotNull(MyTracks.getInstance());
- assertNotNull(getActivity().getSharedPreferences());
+ assertInitialized();
// Check if not recording.
- clearSelectedAndRecordingTracks();
+ clearSelectedAndRecordingTracks();
waitForIdle();
- assertFalse(getActivity().isRecording());
- assertEquals(-1, getActivity().getRecordingTrackId());
- long selectedTrackId = getActivity().getSharedPreferences().getLong(
+ assertFalse(isRecording());
+ assertEquals(-1, getRecordingTrackId());
+ long selectedTrackId = getSharedPreferences().getLong(
getActivity().getString(R.string.selected_track_key), -1);
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
// Start a new track.
getActivity().startRecording();
+ serviceConnection.bindIfRunning();
long recordingTrackId = awaitRecordingStatus(5000, true);
assertTrue(recordingTrackId >= 0);
-
+
// Wait until we are done and make sure that selectedTrack = recordingTrack.
waitForIdle();
- assertEquals(recordingTrackId, getActivity().getSharedPreferences().getLong(
+ assertEquals(recordingTrackId, getSharedPreferences().getLong(
getActivity().getString(R.string.recording_track_key), -1));
- selectedTrackId = getActivity().getSharedPreferences().getLong(
+ selectedTrackId = getSharedPreferences().getLong(
getActivity().getString(R.string.selected_track_key), -1);
assertEquals(recordingTrackId, selectedTrackId);
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
-
- // Watch for MyTracksDetails activity.
+
+ // Watch for MyTracksDetails activity.
ActivityMonitor monitor = getInstrumentation().addMonitor(
TrackDetails.class.getName(), null, false);
// Now, stop the track and make sure that it is still selected, but
// no longer recording.
getActivity().stopRecording();
-
- // Check if we got back MyTracksDetails activity.
+
+ // Check if we got back MyTracksDetails activity.
Activity activity = getInstrumentation().waitForMonitor(monitor);
assertTrue(activity instanceof TrackDetails);
-
+
// TODO: Update track name and other properties and test if they were
// properly saved.
-
+
// Simulate a click on Save button.
Button save = (Button) activity.findViewById(R.id.trackdetails_save);
save.performClick();
@@ -185,18 +174,24 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2{
// Check the remaining properties.
recordingTrackId = awaitRecordingStatus(5000, false);
assertEquals(-1, recordingTrackId);
- assertEquals(recordingTrackId, getActivity().getRecordingTrackId());
- assertEquals(recordingTrackId, getActivity().getSharedPreferences().getLong(
+ assertEquals(recordingTrackId, getRecordingTrackId());
+ assertEquals(recordingTrackId, getSharedPreferences().getLong(
getActivity().getString(R.string.recording_track_key), -1));
// Make sure this is the same track as the last recording track ID.
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
}
-
+
+ private void assertInitialized() {
+ assertNotNull(getActivity());
+
+ serviceConnection = new TrackRecordingServiceConnection(getActivity(), null);
+ }
+
/**
* Waits until the UI thread becomes idle.
*/
private void waitForIdle() throws InterruptedException {
- // Note: We can't use getInstrumentation().waitForIdleSync() here.
+ // Note: We can't use getInstrumentation().waitForIdleSync() here.
final Object semaphore = new Object();
synchronized (semaphore) {
final AtomicBoolean isIdle = new AtomicBoolean();
@@ -216,20 +211,20 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2{
}
/**
- * Clears {selected,recording}TrackId in the {@link SharedPreferences}.
+ * Clears {selected,recording}TrackId in the {@link getSharedPreferences()}.
*/
private void clearSelectedAndRecordingTracks() {
- Editor editor = getActivity().getSharedPreferences().edit();
+ Editor editor = getSharedPreferences().edit();
editor.putLong(getActivity().getString(R.string.selected_track_key), -1);
editor.putLong(getActivity().getString(R.string.recording_track_key), -1);
-
+
editor.clear();
editor.commit();
}
-
+
/**
* Waits until the recording state changes to the given status.
- *
+ *
* @param timeout the maximum time to wait, in milliseconds.
* @param isRecording the final status to await.
* @return the recording track ID.
@@ -237,14 +232,30 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2{
private long awaitRecordingStatus(long timeout, boolean isRecording)
throws TimeoutException, InterruptedException {
long startTime = System.nanoTime();
- while (getActivity().isRecording() != isRecording) {
+ while (isRecording() != isRecording) {
if (System.nanoTime() - startTime > timeout * 1000000) {
throw new TimeoutException("Timeout while waiting for recording!");
}
Thread.sleep(20);
}
waitForIdle();
- assertEquals(isRecording, getActivity().isRecording());
- return getActivity().getRecordingTrackId();
+ assertEquals(isRecording, isRecording());
+ return getRecordingTrackId();
+ }
+
+ private long getRecordingTrackId() {
+ return getSharedPreferences().getLong(getActivity().getString(R.string.recording_track_key), -1);
+ }
+
+ private SharedPreferences getSharedPreferences() {
+ if (sharedPreferences == null) {
+ sharedPreferences = getActivity().getSharedPreferences(Constants.SETTINGS_NAME, 0);
+ }
+ return sharedPreferences;
+ }
+
+ private boolean isRecording() {
+ return ServiceUtils.isRecording(getActivity(),
+ serviceConnection.getServiceIfBound(), getSharedPreferences());
}
}
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/content/TrackDataHubTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/content/TrackDataHubTest.java
index 9f69c79f5..ce77bb917 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/content/TrackDataHubTest.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/content/TrackDataHubTest.java
@@ -91,7 +91,12 @@ public class TrackDataHubTest extends AndroidTestCase {
dataSources = AndroidMock.createNiceMock("dataSources", DataSourcesWrapper.class);
listeners = new TrackDataListeners();
- hub = new TrackDataHub(context, dataSources, listeners, prefs, providerUtils, TARGET_POINTS) {
+ hub = new TrackDataHub(context, listeners, prefs, providerUtils, TARGET_POINTS) {
+ @Override
+ protected DataSourcesWrapper newDataSources() {
+ return dataSources;
+ }
+
@Override
protected void runInListenerThread(Runnable runnable) {
// Run everything in the same thread.
@@ -124,7 +129,7 @@ public class TrackDataHubTest extends AndroidTestCase {
AndroidMock.replay(dataSources);
hub.stop();
- hub.destroy();
+ hub = null;
super.tearDown();
}
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/WriteProgressControllerTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/WriteProgressControllerTest.java
similarity index 92%
rename from MyTracksTest/src/com/google/android/apps/mytracks/WriteProgressControllerTest.java
rename to MyTracksTest/src/com/google/android/apps/mytracks/io/file/WriteProgressControllerTest.java
index 2f4349b32..19d251281 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/WriteProgressControllerTest.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/WriteProgressControllerTest.java
@@ -13,10 +13,11 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.google.android.apps.mytracks;
+package com.google.android.apps.mytracks.io.file;
-import com.google.android.apps.mytracks.io.file.MockTrackWriter;
+import com.google.android.apps.mytracks.MyTracks;
import com.google.android.apps.mytracks.io.file.TrackWriter;
+import com.google.android.apps.mytracks.io.file.WriteProgressController;
import android.app.ProgressDialog;
import android.test.ActivityInstrumentationTestCase2;
@@ -60,7 +61,7 @@ public class WriteProgressControllerTest extends ActivityInstrumentationTestCase
getActivity(), mockWriter);
controller.setOnCompletionListener(new WriteProgressController.OnCompletionListener() {
@Override
- public void onComplete(TrackWriter writer) {
+ public void onComplete() {
controllerDoneRef.set(true);
}
});
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 0f4c13ca8..64444be9f 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java
@@ -49,9 +49,9 @@ import java.util.List;
/**
* Tests for the MyTracks track recording service.
- *
+ *
* @author Bartlomiej Niechwiej
- *
+ *
* TODO: The original class, ServiceTestCase, has a few limitations, e.g.
* it's not possible to properly shutdown the service, unless tearDown()
* is called, which prevents from testing multiple scenarios in a single
@@ -63,7 +63,7 @@ public class TrackRecordingServiceTest
private Context context;
private MyTracksProviderUtils providerUtils;
private SharedPreferences sharedPreferences;
-
+
/*
* In order to support starting and binding to the service in the same
* unit test, we provide a workaround, as the original class doesn't allow
@@ -72,19 +72,19 @@ public class TrackRecordingServiceTest
private boolean bound;
private Intent serviceIntent;
-
+
public TrackRecordingServiceTest() {
super(TrackRecordingService.class);
}
/**
* A context wrapper with the user provided {@link ContentResolver}.
- *
+ *
* TODO: Move to test utils package.
*/
public static class MockContext extends ContextWrapper {
private final ContentResolver contentResolver;
-
+
public MockContext(ContentResolver contentResolver, Context base) {
super(base);
this.contentResolver = contentResolver;
@@ -108,7 +108,7 @@ public class TrackRecordingServiceTest
return 4;
}
}
-
+
@Override
protected IBinder bindService(Intent intent) {
if (getService() != null) {
@@ -190,7 +190,7 @@ public class TrackRecordingServiceTest
startService(startIntent);
assertNotNull(getService());
- // We expect to resume the previous track.
+ // We expect to resume the previous track.
assertTrue(getService().isRecording());
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertEquals(123, service.getRecordingTrackId());
@@ -293,7 +293,7 @@ public class TrackRecordingServiceTest
assertTrue(tracks.isEmpty());
ITrackRecordingService service = bindAndGetService(createStartIntent());
- // Test if we start in no-recording mode by default.
+ // Test if we start in no-recording mode by default.
assertFalse(service.isRecording());
assertEquals(-1, service.getRecordingTrackId());
}
@@ -463,92 +463,19 @@ public class TrackRecordingServiceTest
context.getString(R.string.recording_track_key), 0));
assertEquals(-1, service.getRecordingTrackId());
}
-
+
@MediumTest
public void testIntegration_completeRecordingSession() throws Exception {
List