Merged to head

This commit is contained in:
Rodrigo Damazio
2011-11-14 23:27:04 -02:00
parent a4ae9e8fd5
commit c3cda97692
193 changed files with 8457 additions and 11274 deletions
@@ -78,9 +78,9 @@ public class AccountChooser {
// Let the user choose.
Log.e(Constants.TAG, "Multiple matching accounts found.");
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.choose_account_title);
builder.setTitle(R.string.send_google_choose_account_title);
builder.setCancelable(false);
builder.setPositiveButton(R.string.ok,
builder.setPositiveButton(R.string.generic_ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
@@ -92,7 +92,7 @@ public class AccountChooser {
handler.onAccountSelected(selectedAccount);
}
});
builder.setNegativeButton(R.string.cancel,
builder.setNegativeButton(R.string.generic_cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
@@ -124,10 +124,10 @@ public class AccountChooser {
final AccountHandler handler) {
Log.e(Constants.TAG, "No matching accounts found.");
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.no_account_found_title);
builder.setMessage(R.string.no_account_found);
builder.setTitle(R.string.send_google_no_account_title);
builder.setMessage(R.string.send_google_no_account_message);
builder.setCancelable(true);
builder.setNegativeButton(R.string.ok,
builder.setPositiveButton(R.string.generic_ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
@@ -74,7 +74,7 @@ public class AggregatedStatsActivity extends Activity implements
}
utils.setMetricUnits(metricUnits);
utils.updateUnits();
utils.setSpeedLabel(R.id.speed_label, R.string.speed, R.string.pace_label);
utils.setSpeedLabel(R.id.speed_label, R.string.stat_speed, R.string.stat_pace);
utils.setSpeedLabels();
DisplayMetrics metrics = new DisplayMetrics();
@@ -43,9 +43,10 @@ public class AntPreference extends Preference {
private void init() {
int sensorId = getPersistedInt(0);
if (sensorId == 0) {
setSummary(R.string.settings_ant_not_paired);
setSummary(R.string.settings_sensor_ant_not_paired);
} else {
setSummary(String.format(getContext().getString(R.string.settings_ant_paired), sensorId));
setSummary(
String.format(getContext().getString(R.string.settings_sensor_ant_paired), sensorId));
}
// Add actions to allow repairing.
@@ -54,7 +55,7 @@ public class AntPreference extends Preference {
@Override
public boolean onPreferenceClick(Preference preference) {
AntPreference.this.persistInt(0);
setSummary(R.string.settings_ant_not_paired);
setSummary(R.string.settings_sensor_ant_not_paired);
return true;
}
});
@@ -0,0 +1,64 @@
package com.google.android.apps.mytracks;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.preference.EditTextPreference;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
/**
* The {@link AutoCompleteTextPreference} class is a preference that allows for
* string input using auto complete . It is a subclass of
* {@link EditTextPreference} and shows the {@link AutoCompleteTextView} in a
* dialog.
* <p>
* This preference will store a string into the SharedPreferences.
*
* @author Rimas Trumpa (with Matt Levan)
*/
public class AutoCompleteTextPreference extends EditTextPreference {
private AutoCompleteTextView mEditText = null;
public AutoCompleteTextPreference(Context context, AttributeSet attrs) {
super(context, attrs);
mEditText = new AutoCompleteTextView(context, attrs);
mEditText.setThreshold(0);
// Gets autocomplete values for 'Default Activity' preference
if (getKey().equals(context.getString(R.string.default_activity_key))) {
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(context,
R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
mEditText.setAdapter(adapter);
}
}
@Override
protected void onBindDialogView(View view) {
AutoCompleteTextView editText = mEditText;
editText.setText(getText());
ViewParent oldParent = editText.getParent();
if (oldParent != view) {
if (oldParent != null) {
((ViewGroup) oldParent).removeView(editText);
}
onAddEditTextToDialogView(view, editText);
}
}
@Override
protected void onDialogClosed(boolean positiveResult) {
if (positiveResult) {
String value = mEditText.getText().toString();
if (callChangeListener(value)) {
setText(value);
}
}
}
}
@@ -190,7 +190,7 @@ public class ChartActivity extends Activity implements TrackDataListener {
super.onCreateOptionsMenu(menu);
chartSettingsMenuItem =
menu.add(0, Constants.MENU_CHART_SETTINGS, 0,
R.string.chart_settings);
R.string.menu_chart_view_chart_settings);
chartSettingsMenuItem.setIcon(R.drawable.chart_settings);
return true;
}
@@ -260,7 +260,6 @@ public class ChartActivity extends Activity implements TrackDataListener {
*
* @param location the location to get data for (this method takes ownership of that location)
* @param result the resulting point to fill out
* @return the previous location, now available for reuse
*/
private void fillDataPoint(Location location, double result[]) {
double timeOrDistance = Double.NaN,
@@ -20,8 +20,8 @@ import com.google.android.apps.mytracks.stats.ExtremityMonitor;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Paint.Style;
import android.graphics.Path;
import java.text.DecimalFormat;
@@ -133,9 +133,10 @@ public class ChartValueSeries {
*
* @param context The context for the chart
* @param formatString The format of the decimal format for this series
* @param fill The paint for filling the chart
* @param stroke The paint for stroking the outside the chart, optional
* @param fillColor The paint for filling the chart
* @param strokeColor The paint for stroking the outside the chart, optional
* @param zoomSettings The settings related to zooming
* @param titleId The title ID
*
* TODO: Get rid of Context and inject appropriate values instead.
*/
@@ -210,7 +210,7 @@ public class ChartView extends View {
R.color.elevation_border,
new ZoomSettings(MAX_INTERVALS,
new int[] {5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000}),
R.string.elevation);
R.string.stat_elevation);
series[SPEED_SERIES] =
new ChartValueSeries(context,
@@ -219,14 +219,14 @@ public class ChartView extends View {
R.color.speed_border,
new ZoomSettings(MAX_INTERVALS, 0, Integer.MIN_VALUE,
new int[] {1, 5, 10, 20, 50}),
R.string.speed);
R.string.stat_speed);
series[POWER_SERIES] =
new ChartValueSeries(context,
"###,###",
R.color.power_fill,
R.color.power_border,
new ZoomSettings(MAX_INTERVALS, 0, 1000, new int[] {5, 50, 100, 200}),
R.string.power);
R.string.sensor_state_power);
series[CADENCE_SERIES] =
new ChartValueSeries(context,
"###,###",
@@ -234,7 +234,7 @@ public class ChartView extends View {
R.color.cadence_border,
new ZoomSettings(MAX_INTERVALS, 0, Integer.MIN_VALUE,
new int[] {5, 10, 25, 50}),
R.string.cadence);
R.string.sensor_state_cadence);
series[HEART_RATE_SERIES] =
new ChartValueSeries(context,
"###,###",
@@ -242,7 +242,7 @@ public class ChartView extends View {
R.color.heartrate_border,
new ZoomSettings(MAX_INTERVALS, 0, Integer.MIN_VALUE,
new int[] {25, 50}),
R.string.heart_rate);
R.string.sensor_state_heart_rate);
}
public void clearWaypoints() {
@@ -269,8 +269,8 @@ public class ChartView extends View {
public void setReportSpeed(boolean reportSpeed, Context c) {
series[SPEED_SERIES].setTitle(c.getString(reportSpeed
? R.string.speed
: R.string.pace_label));
? R.string.stat_speed
: R.string.stat_pace));
}
private void addDataPointInternal(double[] theData) {
@@ -832,8 +832,8 @@ public class ChartView extends View {
canvas.drawLine(leftBorder, y, rightEdge, y, borderPaint);
Context c = getContext();
String s = mode == Mode.BY_DISTANCE
? (metricUnits ? c.getString(R.string.kilometer) : c.getString(R.string.mile))
: c.getString(R.string.min);
? (metricUnits ? c.getString(R.string.unit_kilometer) : c.getString(R.string.unit_mile))
: c.getString(R.string.unit_minute);
canvas.drawText(s, rightEdge, effectiveHeight + .2f * UNIT_BORDER + topBorder, labelPaint);
}
@@ -852,7 +852,7 @@ public class ChartView extends View {
Context c = getContext();
// TODO: This should really show units for all series.
String s = metricUnits ? c.getString(R.string.meter) : c.getString(R.string.feet);
String s = metricUnits ? c.getString(R.string.unit_meter) : c.getString(R.string.unit_feet);
canvas.drawText(s, leftBorder - UNIT_BORDER * .2f, UNIT_BORDER * .8f + topBorder, labelPaint);
}
@@ -881,7 +881,6 @@ public class ChartView extends View {
*
* @param index the time series, one of {@link #ELEVATION_SERIES},
* {@link #SPEED_SERIES}, {@link #POWER_SERIES}, etc.
* @return true to be drawn, false otherwise
*/
public void setChartValueSeriesEnabled(int index, boolean enabled) {
series[index].setEnabled(enabled);
@@ -23,73 +23,63 @@ import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import java.util.Locale;
/**
* Check to see if the units are the default for the locale.
* This comes down to warning Americans that the default is metric.
* Checks with the user if he prefers the metric units or the imperial units.
*
* @author Sandor Dornbush
*/
class CheckUnits {
private static final String PREFERENCE_UNITS_CHECKED = "checkunits.checked";
private static final String PREFERENCES_CHECK_UNITS = "checkunits";
private static final String CHECK_UNITS_PREFERENCE_FILE = "checkunits";
private static final String CHECK_UNITS_PREFERENCE_KEY = "checkunits.checked";
private static boolean metric = true;
static void check(final Context context) {
final SharedPreferences preferences =
context.getSharedPreferences(PREFERENCES_CHECK_UNITS, Activity.MODE_PRIVATE);
// Has the user already warned about the default units?
if (preferences.getBoolean(PREFERENCE_UNITS_CHECKED, false)) {
public static void check(final Context context) {
final SharedPreferences checkUnitsSharedPreferences = context.getSharedPreferences(
CHECK_UNITS_PREFERENCE_FILE, Activity.MODE_PRIVATE);
if (checkUnitsSharedPreferences.getBoolean(CHECK_UNITS_PREFERENCE_KEY, false)) {
return;
}
// Is the user in the US?
Locale current = Locale.getDefault();
Locale enUs = new Locale(Locale.ENGLISH.getLanguage(),
Locale.US.getCountry());
if (!current.equals(enUs)) {
return;
}
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.check_units_title);
builder.setCancelable(true);
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(context.getString(R.string.preferred_units_title));
CharSequence[] items = { context.getString(R.string.preferred_units_metric),
context.getString(R.string.preferred_units_imperial) };
builder.setSingleChoiceItems(items, 0, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
accept(context, preferences);
if (which == 0) {
metric = true;
} else {
metric = false;
}
}
});
builder.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
builder.setCancelable(true);
builder.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
recordCheckPerformed(preferences);
recordCheckPerformed(checkUnitsSharedPreferences);
SharedPreferences useMetricPreferences = context.getSharedPreferences(
Constants.SETTINGS_NAME, Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = useMetricPreferences.edit();
String key = context.getString(R.string.metric_units_key);
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(
editor.putBoolean(key, metric));
}
});
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
recordCheckPerformed(preferences);
recordCheckPerformed(checkUnitsSharedPreferences);
}
});
builder.setMessage(R.string.check_units_message);
builder.show();
}
private static void accept(Context context, SharedPreferences preferences) {
recordCheckPerformed(preferences);
Intent startIntent = new Intent(context, SettingsActivity.class);
startIntent.putExtra(context.getString(R.string.open_settings_screen),
context.getString(R.string.display_settings_screen_key));
context.startActivity(startIntent);
}
private static void recordCheckPerformed(SharedPreferences preferences) {
ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges(
preferences.edit().putBoolean(PREFERENCE_UNITS_CHECKED, true));
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(
preferences.edit().putBoolean(CHECK_UNITS_PREFERENCE_KEY, true));
}
private CheckUnits() {
}
private CheckUnits() {}
}
@@ -62,7 +62,6 @@ public abstract class Constants {
public static final int MENU_MY_LOCATION = 1;
public static final int MENU_TOGGLE_LAYERS = 2;
public static final int MENU_CHART_SETTINGS = 3;
public static final int MENU_CURRENT_SEGMENT = 4;
/*
* Context menu ids:
@@ -51,10 +51,10 @@ public class DeleteAllTracks extends Handler {
AlertDialog dialog = null;
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(
context.getString(R.string.all_data_will_be_permanently_deleted));
builder.setTitle(context.getString(R.string.are_you_sure_question));
context.getString(R.string.track_list_delete_all_confirm_message));
builder.setTitle(context.getString(R.string.generic_confirm_title));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setPositiveButton(context.getString(R.string.yes),
builder.setPositiveButton(context.getString(R.string.generic_yes),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
@@ -65,14 +65,14 @@ public class DeleteAllTracks extends Handler {
SharedPreferences.Editor editor = prefs.edit();
// TODO: Go through data manager
editor.putLong(context.getString(R.string.selected_track_key), -1);
ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges(editor);
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor);
if (done != null) {
Handler h = new Handler();
h.post(done);
}
}
});
builder.setNegativeButton(context.getString(R.string.no),
builder.setNegativeButton(context.getString(R.string.generic_no),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
@@ -77,11 +77,11 @@ public class DeleteTrack extends Activity
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(getString(R.string.track_will_be_permanently_deleted));
builder.setTitle(getString(R.string.are_you_sure_question));
builder.setMessage(getString(R.string.track_list_delete_track_confirm_message));
builder.setTitle(getString(R.string.generic_confirm_title));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setPositiveButton(getString(R.string.yes), this);
builder.setNegativeButton(getString(R.string.no), this);
builder.setPositiveButton(getString(R.string.generic_yes), this);
builder.setNegativeButton(getString(R.string.generic_no), this);
builder.setOnCancelListener(this);
return builder.create();
}
@@ -108,7 +108,7 @@ public class DeleteTrack extends Activity
SharedPreferences preferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
if (preferences.getLong(selectedKey, -1) == deleteTrackId) {
Editor editor = preferences.edit().putLong(selectedKey, -1);
ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges(editor);
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor);
}
}
}
@@ -30,8 +30,8 @@ import android.view.WindowManager.BadTokenException;
public class DialogManager {
/**
* The equivalent of {@link #showDialogSafely(int)}, but for a specific
* dialog instance.
* The equivalent of {@link Dialog#show()}, but for a specific dialog
* instance.
*/
public static void showDialogSafely(Activity activity, final Dialog dialog) {
if (activity.isFinishing()) {
@@ -53,8 +53,8 @@ public class DialogManager {
}
/**
* The equivalent of {@link #dismissDialogSafely(int)}, but for a specific
* dialog instance.
* The equivalent of {@link Dialog#dismiss()}, but for a specific dialog
* instance.
*/
public static void dismissDialogSafely(Activity activity, final Dialog dialog) {
if (activity.isFinishing()) {
@@ -52,14 +52,14 @@ class Eula {
}
final AlertDialog.Builder builder = initDialog(activity);
builder.setPositiveButton(R.string.accept,
builder.setPositiveButton(R.string.eula_accept,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
accept(activity, preferences);
}
});
builder.setNegativeButton(R.string.decline,
builder.setNegativeButton(R.string.eula_decline,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
@@ -82,7 +82,7 @@ class Eula {
*/
static void showEula(Context context) {
AlertDialog.Builder builder = initDialog(context);
builder.setPositiveButton(R.string.ok, null);
builder.setPositiveButton(R.string.generic_ok, null);
builder.show();
}
@@ -95,7 +95,7 @@ class Eula {
}
private static void accept(Activity activity, SharedPreferences preferences) {
ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges(
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(
preferences.edit().putBoolean(PREFERENCE_EULA_ACCEPTED, true));
Intent startIntent = new Intent(activity, WelcomeActivity.class);
activity.startActivityForResult(startIntent, Constants.WELCOME);
@@ -78,10 +78,18 @@ public class ExportAllTracks {
this.activity = activity;
Log.i(Constants.TAG, "ExportAllTracks: Starting");
String exportFileFormat = activity.getString(R.string.track_list_export_file);
String fileTypes[] = activity.getResources().getStringArray(R.array.file_types);
String[] choices = new String[fileTypes.length];
for (int i = 0; i < fileTypes.length; i++) {
choices[i] = String.format(exportFileFormat, fileTypes[i]);
}
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setSingleChoiceItems(R.array.export_formats, 0, itemClick);
builder.setPositiveButton(R.string.ok, positiveClick);
builder.setNegativeButton(R.string.cancel, null);
builder.setSingleChoiceItems(choices, 0, itemClick);
builder.setPositiveButton(R.string.generic_ok, positiveClick);
builder.setNegativeButton(R.string.generic_cancel, null);
builder.show();
}
@@ -126,11 +134,11 @@ public class ExportAllTracks {
Log.i(Constants.TAG, "ExportAllTracks: Releasing wake lock.");
}
Log.i(Constants.TAG, "ExportAllTracks: Done");
showToast(R.string.export_done, Toast.LENGTH_SHORT);
showToast(R.string.export_success, Toast.LENGTH_SHORT);
}
private void makeProgressDialog(final int trackCount) {
String exportMsg = activity.getString(R.string.tracklist_btn_export_all);
String exportMsg = activity.getString(R.string.track_list_export_all);
progress = new ProgressDialog(activity);
progress.setIcon(android.R.drawable.ic_dialog_info);
progress.setTitle(exportMsg);
@@ -183,7 +191,7 @@ public class ExportAllTracks {
TrackWriter writer =
TrackWriterFactory.newWriter(activity, providerUtils, id, format);
if (writer == null) {
showToast(R.string.error_track_does_not_exist, Toast.LENGTH_LONG);
showToast(R.string.export_error, Toast.LENGTH_LONG);
return;
}
@@ -16,6 +16,7 @@
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.io.file.GpxImporter;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.SystemUtils;
@@ -24,7 +25,11 @@ 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.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import android.widget.Toast;
@@ -49,22 +54,36 @@ import org.xml.sax.SAXException;
public class ImportAllTracks {
private final Activity activity;
private FileUtils fileUtils;
private boolean singleTrackSelected;
private String gpxPath;
private WakeLock wakeLock;
private ProgressDialog progress;
private FileUtils fileUtils;
private String gpxPath;
private int gpxFileCount;
private int importSuccessCount;
private long importedTrackIds[];
public ImportAllTracks(Activity activity) {
this.activity = activity;
Log.i(Constants.TAG, "ImportAllTracks: Starting");
fileUtils = new FileUtils();
gpxPath = fileUtils.buildExternalDirectoryPath("gpx");
new Thread(runner).start();
this(activity, null);
}
/**
* Constructor to import tracks.
*
* @param activity the activity
* @param path path of the gpx file to import and display. If null, then just
* import all the gpx files under MyTracks/gpx and do not display any
* track.
*/
public ImportAllTracks(Activity activity, String path) {
Log.i(Constants.TAG, "ImportAllTracks: Starting");
this.activity = activity;
fileUtils = new FileUtils();
singleTrackSelected = path != null;
gpxPath = path == null ? fileUtils.buildExternalDirectoryPath("gpx") : path;
new Thread(runner).start();
}
private final Runnable runner = new Runnable() {
public void run() {
aquireLocksAndImport();
@@ -81,7 +100,7 @@ public class ImportAllTracks {
if (prefs != null) {
recordingTrackId = prefs.getLong(activity.getString(R.string.recording_track_key), -1);
}
if (recordingTrackId != -1) {
if (recordingTrackId == -1) {
wakeLock = SystemUtils.acquireWakeLock(activity, wakeLock);
}
@@ -107,17 +126,31 @@ 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_multi_empty, gpxPath + "/"));
builder.setMessage(activity.getString(R.string.import_no_file, gpxPath));
} else {
builder.setMessage(activity.getString(R.string.import_multi_done, importSuccessCount, gpxFileCount,
gpxPath + "/"));
builder.setMessage(
activity.getString(R.string.import_success, importSuccessCount, gpxFileCount, gpxPath));
}
builder.setPositiveButton(R.string.ok, null);
builder.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (singleTrackSelected) {
long lastTrackId = importedTrackIds[importedTrackIds.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);
activity.startActivity(intent);
activity.finish();
}
}
});
builder.show();
}
private void makeProgressDialog(final int trackCount) {
String importMsg = activity.getString(R.string.tracklist_btn_import_all);
String importMsg = activity.getString(R.string.track_list_import_all);
progress = new ProgressDialog(activity);
progress.setIcon(android.R.drawable.ic_dialog_info);
progress.setTitle(importMsg);
@@ -185,7 +218,7 @@ public class ImportAllTracks {
private boolean importFile(File gpxFile, MyTracksProviderUtils providerUtils) {
Log.i(Constants.TAG, "ImportAllTracks: importing: " + gpxFile.getName());
try {
GpxImporter.importGPXFile(new FileInputStream(gpxFile), providerUtils);
importedTrackIds = GpxImporter.importGPXFile(new FileInputStream(gpxFile), providerUtils);
return true;
} catch (FileNotFoundException e) {
Log.w(Constants.TAG, "GPX file wasn't found/went missing: "
@@ -203,15 +236,22 @@ public class ImportAllTracks {
}
/**
* Returns a list of the GPX Files found in the GPX directory.
* Gets a list of the GPX files. If singleTrackSelected is true, returns a
* list containing just the gpxPath file. If singleTrackSelected is false,
* returns a list of GPX files under the gpxPath directory.
*/
private List<File> getGpxFiles() {
List<File> gpxFiles = new LinkedList<File>();
File[] gpxFileCandidates = new File(gpxPath).listFiles();
if (gpxFileCandidates != null) {
for (File file : gpxFileCandidates) {
if (!file.isDirectory() && file.getName().endsWith(".gpx")) {
gpxFiles.add(file);
List<File> gpxFiles = new LinkedList<File>();
File file = new File(gpxPath);
if (singleTrackSelected) {
gpxFiles.add(file);
} else {
File[] gpxFileCandidates = file.listFiles();
if (gpxFileCandidates != null) {
for (File candidate : gpxFileCandidates) {
if (!candidate.isDirectory() && candidate.getName().endsWith(".gpx")) {
gpxFiles.add(candidate);
}
}
}
}
@@ -20,10 +20,10 @@ import static com.google.android.apps.mytracks.Constants.TAG;
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.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
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;
@@ -44,14 +44,14 @@ import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.SubMenu;
import android.view.View;
import android.view.Window;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnCreateContextMenuListener;
import android.view.Window;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
@@ -358,39 +358,43 @@ public class MapActivity extends com.google.android.maps.MapActivity
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
menu.setHeaderTitle(R.string.tracklist_this_track);
menu.setHeaderTitle(R.string.track_list_context_menu_title);
menu.add(0, Constants.MENU_EDIT, 0,
R.string.tracklist_edit_track);
R.string.track_list_edit_track);
if (!dataHub.isRecordingSelected()) {
String saveFileFormat = getString(R.string.track_list_save_file);
String shareFileFormat = getString(R.string.track_list_share_file);
String fileTypes[] = getResources().getStringArray(R.array.file_types);
menu.add(0, Constants.MENU_SEND_TO_GOOGLE, 0,
R.string.tracklist_send_to_google);
R.string.track_list_send_google);
SubMenu share = menu.addSubMenu(0, Constants.MENU_SHARE, 0,
R.string.tracklist_share_track);
R.string.track_list_share_track);
share.add(0, Constants.MENU_SHARE_LINK, 0,
R.string.tracklist_share_link);
share.add(0, Constants.MENU_SHARE_GPX_FILE, 0,
R.string.tracklist_share_gpx_file);
share.add(0, Constants.MENU_SHARE_KML_FILE, 0,
R.string.tracklist_share_kml_file);
share.add(0, Constants.MENU_SHARE_CSV_FILE, 0,
R.string.tracklist_share_csv_file);
share.add(0, Constants.MENU_SHARE_TCX_FILE, 0,
R.string.tracklist_share_tcx_file);
R.string.track_list_share_url);
share.add(
0, Constants.MENU_SHARE_GPX_FILE, 0, String.format(shareFileFormat, fileTypes[0]));
share.add(
0, Constants.MENU_SHARE_KML_FILE, 0, String.format(shareFileFormat, fileTypes[1]));
share.add(
0, Constants.MENU_SHARE_CSV_FILE, 0, String.format(shareFileFormat, fileTypes[2]));
share.add(
0, Constants.MENU_SHARE_TCX_FILE, 0, String.format(shareFileFormat, fileTypes[3]));
SubMenu save = menu.addSubMenu(0,
Constants.MENU_WRITE_TO_SD_CARD, 0,
R.string.tracklist_write_to_sd);
save.add(0, Constants.MENU_SAVE_GPX_FILE, 0,
R.string.tracklist_save_as_gpx);
save.add(0, Constants.MENU_SAVE_KML_FILE, 0,
R.string.tracklist_save_as_kml);
save.add(0, Constants.MENU_SAVE_CSV_FILE, 0,
R.string.tracklist_save_as_csv);
save.add(0, Constants.MENU_SAVE_TCX_FILE, 0,
R.string.tracklist_save_as_tcx);
R.string.track_list_save_sd);
save.add(
0, Constants.MENU_SAVE_GPX_FILE, 0, String.format(saveFileFormat, fileTypes[0]));
save.add(
0, Constants.MENU_SAVE_KML_FILE, 0, String.format(saveFileFormat, fileTypes[1]));
save.add(
0, Constants.MENU_SAVE_CSV_FILE, 0, String.format(saveFileFormat, fileTypes[2]));
save.add(
0, Constants.MENU_SAVE_TCX_FILE, 0, String.format(saveFileFormat, fileTypes[3]));
menu.add(0, Constants.MENU_CLEAR_MAP, 0,
R.string.tracklist_clear_map);
R.string.track_list_clear_map);
menu.add(0, Constants.MENU_DELETE, 0,
R.string.tracklist_delete_track);
R.string.track_list_delete_track);
}
}
};
@@ -441,10 +445,10 @@ public class MapActivity extends com.google.android.maps.MapActivity
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
myLocation = menu.add(0, Constants.MENU_MY_LOCATION, 0,
R.string.mylocation);
R.string.menu_map_view_my_location);
myLocation.setIcon(android.R.drawable.ic_menu_mylocation);
toggleLayers = menu.add(0, Constants.MENU_TOGGLE_LAYERS, 0,
R.string.switch_to_sat);
R.string.menu_map_view_satellite_mode);
toggleLayers.setIcon(android.R.drawable.ic_menu_mapmode);
return true;
}
@@ -452,7 +456,7 @@ public class MapActivity extends com.google.android.maps.MapActivity
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
toggleLayers.setTitle(mapView.isSatellite() ?
R.string.switch_to_map : R.string.switch_to_sat);
R.string.menu_map_view_map_mode : R.string.menu_map_view_satellite_mode);
return super.onPrepareOptionsMenu(menu);
}
@@ -507,12 +511,12 @@ public class MapActivity extends com.google.android.maps.MapActivity
final boolean isGpsDisabled;
switch (state) {
case DISABLED:
messageId = R.string.status_enable_gps;
messageId = R.string.gps_need_to_enable;
isGpsDisabled = true;
break;
case NO_FIX:
case BAD_FIX:
messageId = R.string.wait_for_fix;
messageId = R.string.gps_wait_for_fix;
isGpsDisabled = false;
break;
case GOOD_FIX:
@@ -534,7 +538,7 @@ public class MapActivity extends com.google.android.maps.MapActivity
if (isGpsDisabled) {
// Give a warning about this state.
Toast.makeText(MapActivity.this,
R.string.error_no_gps_location_provider,
R.string.gps_not_found,
Toast.LENGTH_LONG).show();
// Make clicking take the user to the location settings.
@@ -35,7 +35,6 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -78,7 +77,6 @@ public class MapOverlay extends Overlay implements OnSharedPreferenceChangeListe
private GeoPoint lastReferencePoint;
private Rect lastViewRect;
private boolean lastPathExists;
private Location lastLocation;
private TrackPathPainter trackPathPainter;
/**
@@ -305,17 +303,17 @@ public class MapOverlay extends Overlay implements OnSharedPreferenceChangeListe
int cy = mapView.getMapCenter().getLatitudeE6();
return new Rect(cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2);
}
// Visible for testing.
Path newPath() {
return new Path();
}
// Visible for testing.
public Path getLastPath() {
return trackPathPainter.getLastPath();
// For use in testing only.
public TrackPathPainter getTrackPathPainter() {
return trackPathPainter;
}
// For use in testing only.
public void setTrackPathPainter(TrackPathPainter trackPathPainter) {
this.trackPathPainter = trackPathPainter;
}
private void drawWaypoints(Canvas canvas, Projection projection) {
synchronized (waypoints) {;
for (Waypoint wpt : waypoints) {
@@ -42,10 +42,9 @@ class MenuManager {
public void onPrepareOptionsMenu(Menu menu, boolean hasRecorded,
boolean isRecording, boolean hasSelectedTrack) {
menu.findItem(R.id.menu_list_tracks);
menu.findItem(R.id.menu_list_markers)
menu.findItem(R.id.menu_markers)
.setEnabled(hasRecorded && hasSelectedTrack);
menu.findItem(R.id.menu_start_recording)
menu.findItem(R.id.menu_record_track)
.setEnabled(!isRecording)
.setVisible(!isRecording);
menu.findItem(R.id.menu_stop_recording)
@@ -55,7 +54,7 @@ class MenuManager {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_start_recording: {
case R.id.menu_record_track: {
activity.startRecording();
return true;
}
@@ -63,12 +62,12 @@ class MenuManager {
activity.stopRecording();
return true;
}
case R.id.menu_list_tracks: {
case R.id.menu_tracks: {
activity.startActivityForResult(new Intent(activity, TrackList.class),
Constants.SHOW_TRACK);
return true;
}
case R.id.menu_list_markers: {
case R.id.menu_markers: {
Intent startIntent = new Intent(activity, WaypointsList.class);
startIntent.putExtra("trackid", activity.getSelectedTrackId());
activity.startActivityForResult(startIntent, Constants.SHOW_WAYPOINT);
@@ -80,7 +79,7 @@ class MenuManager {
case R.id.menu_settings: {
return startActivity(SettingsActivity.class);
}
case R.id.menu_aggregated_stats: {
case R.id.menu_aggregated_statistics: {
return startActivity(AggregatedStatsActivity.class);
}
case R.id.menu_help: {
@@ -99,10 +99,10 @@ public class MyMapsListAdapter implements ListAdapter {
(TextView) convertView.findViewById(R.id.maplistpublic);
if (publicList.get(position)) {
publicUnlisted.setTextColor(Color.RED);
publicUnlisted.setText(R.string.public_map);
publicUnlisted.setText(R.string.my_maps_list_public_label);
} else {
publicUnlisted.setTextColor(Color.GREEN);
publicUnlisted.setText(R.string.unlisted_map);
publicUnlisted.setText(R.string.my_maps_list_unlisted_label);
}
return convertView;
@@ -134,12 +134,12 @@ public class MyTracks extends TabActivity implements OnTouchListener {
super.onCreate(savedInstanceState);
ApiFeatures apiFeatures = ApiFeatures.getInstance();
if (!SystemUtils.isRelease(this)) {
apiFeatures.getApiPlatformAdapter().enableStrictMode();
apiFeatures.getApiAdapter().enableStrictMode();
}
tracker = GoogleAnalyticsTracker.getInstance();
// Start the tracker in manual dispatch mode...
tracker.start(getString(R.string.google_analytics_id), getApplicationContext());
tracker.start(getString(R.string.my_tracks_analytics_id), getApplicationContext());
tracker.setProductVersion("android-mytracks", SystemUtils.getMyTracksVersion(this));
tracker.trackPageView("/appstart");
tracker.dispatch();
@@ -369,13 +369,11 @@ public class MyTracks extends TabActivity implements OnTouchListener {
try {
long waypointId = trackRecordingService.insertWaypoint(request);
if (waypointId >= 0) {
Toast.makeText(this, R.string.status_statistics_inserted,
Toast.LENGTH_LONG).show();
Toast.makeText(this, R.string.marker_insert_success, Toast.LENGTH_LONG).show();
}
return waypointId;
} catch (RemoteException e) {
Toast.makeText(this, R.string.error_unable_to_insert_marker,
Toast.LENGTH_LONG).show();
Toast.makeText(this, R.string.marker_insert_error, Toast.LENGTH_LONG).show();
throw e;
}
}
@@ -386,14 +384,11 @@ public class MyTracks extends TabActivity implements OnTouchListener {
long recordingTrackId = trackRecordingService.startNewTrack();
// Select the recording track.
dataHub.loadTrack(recordingTrackId);
Toast.makeText(this, getString(R.string.status_now_recording),
Toast.LENGTH_SHORT).show();
Toast.makeText(this, getString(R.string.track_record_success), Toast.LENGTH_SHORT).show();
// 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) {
Toast.makeText(this,
getString(R.string.error_unable_to_start_recording),
Toast.LENGTH_SHORT).show();
Toast.makeText(this, getString(R.string.track_record_error), Toast.LENGTH_SHORT).show();
Log.w(TAG, "Unable to start recording.", e);
}
}
@@ -23,7 +23,7 @@ package com.google.android.apps.mytracks;
*/
public interface ProgressIndicator {
public void setProgressMessage(int resId);
public void setProgressMessage(String message);
public void clearProgressMessage();
public void setProgressValue(int percent);
@@ -20,6 +20,8 @@ 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.SensorManager;
import com.google.android.apps.mytracks.services.sensors.SensorManagerFactory;
import com.google.android.apps.mytracks.services.sensors.SensorUtils;
import com.google.android.maps.mytracks.R;
import com.google.protobuf.InvalidProtocolBufferException;
@@ -55,7 +57,8 @@ public class SensorStateActivity extends Activity {
private final Runnable stateUpdater = new Runnable() {
public void run() {
updateState();
if (isVisible) // only update when UI is visible
updateState();
}
};
@@ -73,6 +76,23 @@ public class SensorStateActivity extends Activity {
runOnUiThread(stateUpdater);
}
};
/**
* A temporary sensor manager, when none is available.
*/
SensorManager tempSensorManager = null;
/**
* A state flag set to true when the activity is active/visible,
* i.e. after resume, and before pause
*
* Used to avoid updating after the pause event, because sometimes an update
* event occurs even after the timer is cancelled. In this case,
* it could cause the {@link #tempSensorManager} to be recreated, after it
* is destroyed at the pause event.
*/
boolean isVisible = false;
public SensorStateActivity() {
utils = new StatsUtilities(this);
@@ -86,7 +106,6 @@ 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();
@@ -95,6 +114,8 @@ public class SensorStateActivity extends Activity {
@Override
protected void onResume() {
super.onResume();
isVisible = true;
serviceConnection.bindIfRunning();
@@ -104,9 +125,12 @@ public class SensorStateActivity extends Activity {
@Override
protected void onPause() {
isVisible = false;
timer.cancel();
timer.purge();
timer = null;
stopTempSensorManager();
super.onPause();
}
@@ -117,35 +141,106 @@ public class SensorStateActivity extends Activity {
}
protected void updateState() {
Log.d(TAG, "Updating SensorStateActivity");
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();
if (buff != null) {
sds = Sensor.SensorDataSet.parseFrom(buff);
updateSensorData(sds);
// Check if service is available, and recording.
boolean isRecording = false;
if (service != null) {
try {
isRecording = service.isRecording();
} catch (RemoteException e) {
Log.e(TAG, "Unable to determine if service is recording.", e);
}
} catch (RemoteException e) {
Log.e(Constants.TAG, "Could not read sensor data.", e);
} catch (InvalidProtocolBufferException e) {
Log.e(Constants.TAG, "Could not read sensor data.", e);
}
updateSensorData(sds);
// If either service isn't available, or not recording.
if (!isRecording) {
updateFromTempSensorManager();
} else {
updateFromSysSensorManager();
}
}
private void updateFromTempSensorManager()
{
// Use variables to hold the sensor state and data set.
Sensor.SensorState currentState = null;
Sensor.SensorDataSet currentDataSet = null;
// If no temp sensor manager is present, create one, and start it.
if (tempSensorManager == null) {
tempSensorManager = SensorManagerFactory.getSensorManager(this);
if (tempSensorManager != null)
tempSensorManager.onStartTrack();
}
// If a temp sensor manager is available, use states from temp sensor
// manager.
if (tempSensorManager != null) {
currentState = tempSensorManager.getSensorState();
currentDataSet = tempSensorManager.getSensorDataSet();
}
try {
int i = service.getSensorState();
updateSensorState(Sensor.SensorState.valueOf(i));
} catch (RemoteException e) {
Log.e(Constants.TAG, "Could not read sensor state.", e);
updateSensorState(Sensor.SensorState.NONE);
// Update the sensor state, and sensor data, using the variables.
updateSensorStateAndData(currentState, currentDataSet);
}
private void updateFromSysSensorManager()
{
// Use variables to hold the sensor state and data set.
Sensor.SensorState currentState = null;
Sensor.SensorDataSet currentDataSet = null;
ITrackRecordingService service = serviceConnection.getServiceIfBound();
// If a temp sensor manager is present, shut it down,
// probably recording just started.
stopTempSensorManager();
// Get sensor details from the service.
if (service == null) {
Log.d(TAG, "Could not get track recording service.");
} else {
try {
byte[] buff = service.getSensorData();
if (buff != null) {
currentDataSet = Sensor.SensorDataSet.parseFrom(buff);
}
} catch (RemoteException e) {
Log.e(TAG, "Could not read sensor data.", e);
} catch (InvalidProtocolBufferException e) {
Log.e(TAG, "Could not read sensor data.", e);
}
try {
currentState = Sensor.SensorState.valueOf(service.getSensorState());
} catch (RemoteException e) {
Log.e(TAG, "Could not read sensor state.", e);
currentState = Sensor.SensorState.NONE;
}
}
// Update the sensor state, and sensor data, using the variables.
updateSensorStateAndData(currentState, currentDataSet);
}
/**
* Stops the temporary sensor manager, if one exists.
*/
private void stopTempSensorManager()
{
if (tempSensorManager != null) {
tempSensorManager.shutdown();
tempSensorManager = null;
}
}
private void updateSensorStateAndData(Sensor.SensorState state, Sensor.SensorDataSet dataSet)
{
updateSensorState(state == null ? Sensor.SensorState.NONE : state);
updateSensorData(dataSet);
}
private void updateSensorState(Sensor.SensorState state) {
@@ -54,15 +54,44 @@ import java.util.Set;
/**
* An activity that let's the user see and edit the settings.
*
* This activity has two entry points, "root" and "display" preference screen.
* If bundle.getString("open_settings_screen") is set to "display_settings_screen_key", then
* the "display" preference screen is shown, otherwise, the "root" display preference is shown.
*
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
*/
public class SettingsActivity extends PreferenceActivity {
// Value when the task frequency is off.
private static final String TASK_FREQUENCY_OFF = "0";
// Value when the recording interval is 'Adapt battery life'.
private static final String RECORDING_INTERVAL_ADAPT_BATTERY_LIFE = "-2";
// Value when the recording interval is 'Adapt accuracy'.
private static final String RECORDING_INTERVAL_ADAPT_ACCURACY = "-1";
// Value for the recommended recording interval.
private static final String RECORDING_INTERVAL_RECOMMENDED = "0";
// Value when the auto resume timeout is never.
private static final String AUTO_RESUME_TIMEOUT_NEVER = "0";
// Value when the auto resume timeout is always.
private static final String AUTO_RESUME_TIMEOUT_ALWAYS = "-1";
// Value for the recommended recording distance.
private static final String RECORDING_DISTANCE_RECOMMENDED = "5";
// Value for the recommended track distance.
private static final String TRACK_DISTANCE_RECOMMENDED = "200";
// Value for the recommended GPS accuracy.
private static final String GPS_ACCURACY_RECOMMENDED = "200";
// Value when the GPS accuracy is for excellent GPS signal.
private static final String GPS_ACCURACY_EXCELLENT = "10";
// Value when the GPS accuracy is for poor GPS signal.
private static final String GPS_ACCURACY_POOR = "5000";
private BackupPreferencesListener backupListener;
private SharedPreferences preferences;
@@ -71,16 +100,6 @@ public class SettingsActivity extends PreferenceActivity {
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
initActivityCommons();
// If we only need the display setting screen nothing else needs to load.
if (processIntent())
return;
initActivitySpecifics();
}
private void initActivityCommons() {
// The volume we want to control is the Text-To-Speech volume
ApiFeatures apiFeatures = ApiFeatures.getInstance();
int volumeStream =
@@ -93,26 +112,26 @@ public class SettingsActivity extends PreferenceActivity {
preferenceManager.setSharedPreferencesMode(0);
// Set up automatic preferences backup
backupListener = BackupPreferencesListener.create(this, apiFeatures);
backupListener = apiFeatures.getApiAdapter().getBackupPreferencesListener(this);
preferences = preferenceManager.getSharedPreferences();
preferences.registerOnSharedPreferenceChangeListener(backupListener);
// Load the preferences to be displayed
addPreferencesFromResource(R.xml.preferences);
// Disable TTS announcement preference if not available
// Disable voice announcement if not available
if (!apiFeatures.hasTextToSpeech()) {
IntegerListPreference announcementFrequency =
(IntegerListPreference) findPreference(
getString(R.string.announcement_frequency_key));
announcementFrequency.setEnabled(false);
announcementFrequency.setValue("-1");
announcementFrequency.setSummary(
R.string.settings_not_available_summary);
announcementFrequency.setValue(TASK_FREQUENCY_OFF);
announcementFrequency.setSummary(R.string.settings_recording_voice_not_available);
}
}
private void initActivitySpecifics() {
setRecordingIntervalOptions();
setAutoResumeTimeoutOptions();
// Hook up switching of displayed list entries between metric and imperial
// units
CheckBoxPreference metricUnitsPreference =
@@ -124,11 +143,11 @@ public class SettingsActivity extends PreferenceActivity {
public boolean onPreferenceChange(Preference preference,
Object newValue) {
boolean isMetric = (Boolean) newValue;
updatePreferenceUnits(isMetric);
updateDisplayOptions(isMetric);
return true;
}
});
updatePreferenceUnits(metricUnitsPreference.isChecked());
updateDisplayOptions(metricUnitsPreference.isChecked());
customizeSensorOptionsPreferences();
customizeTrackColorModePreferences();
@@ -142,23 +161,85 @@ public class SettingsActivity extends PreferenceActivity {
return true;
}
});
// Add a confirmation dialog for the 'Allow access' preference.
final CheckBoxPreference allowAccessPreference = (CheckBoxPreference) findPreference(
getString(R.string.allow_access_key));
allowAccessPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if ((Boolean) newValue) {
AlertDialog dialog = new AlertDialog.Builder(SettingsActivity.this)
.setCancelable(true)
.setTitle(getString(R.string.settings_sharing_allow_access))
.setMessage(getString(R.string.settings_sharing_allow_access_confirm_message))
.setPositiveButton(android.R.string.ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int button) {
allowAccessPreference.setChecked(true);
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
dialog.show();
return false;
} else {
return true;
}
}
});
}
private boolean processIntent() {
boolean showDisplaySettings = false;
Bundle bundle = getIntent().getExtras();
PreferenceScreen preferenceScreen;
String intentString = getString(R.string.open_settings_screen);
if (bundle != null) {
preferenceScreen = (PreferenceScreen) findPreference(bundle.getString(intentString));
if (preferenceScreen != null) {
showDisplaySettings = true;
setPreferenceScreen(preferenceScreen);
/**
* Sets the display options for the 'Time between points' option.
*/
private void setRecordingIntervalOptions() {
String[] values = getResources().getStringArray(R.array.recording_interval_values);
String[] options = new String[values.length];
for (int i = 0; i < values.length; i++) {
if (values[i].equals(RECORDING_INTERVAL_ADAPT_BATTERY_LIFE)) {
options[i] = getString(R.string.value_adapt_battery_life);
} else if (values[i].equals(RECORDING_INTERVAL_ADAPT_ACCURACY)) {
options[i] = getString(R.string.value_adapt_accuracy);
} else if (values[i].equals(RECORDING_INTERVAL_RECOMMENDED)) {
options[i] = getString(R.string.value_smallest_recommended);
} else {
int value = Integer.parseInt(values[i]);
String format;
if (value < 60) {
format = getString(R.string.value_integer_second);
} else {
value = value / 60;
format = getString(R.string.value_integer_minute);
}
options[i] = String.format(format, value);
}
}
return showDisplaySettings;
ListPreference list = (ListPreference) findPreference(
getString(R.string.min_recording_interval_key));
list.setEntries(options);
}
/**
* Sets the display options for the 'Auto-resume timeout' option.
*/
private void setAutoResumeTimeoutOptions() {
String[] values = getResources().getStringArray(R.array.recording_auto_resume_timeout_values);
String[] options = new String[values.length];
for (int i = 0; i < values.length; i++) {
if (values[i].equals(AUTO_RESUME_TIMEOUT_NEVER)) {
options[i] = getString(R.string.value_never);
} else if (values[i].equals(AUTO_RESUME_TIMEOUT_ALWAYS)) {
options[i] = getString(R.string.value_always);
} else {
int value = Integer.parseInt(values[i]);
String format = getString(R.string.value_integer_minute);
options[i] = String.format(format, value);
}
}
ListPreference list = (ListPreference) findPreference(
getString(R.string.auto_resume_track_timeout_key));
list.setEntries(options);
}
private void customizeSensorOptionsPreferences() {
@@ -182,7 +263,7 @@ public class SettingsActivity extends PreferenceActivity {
Set<Integer> toRemove = new HashSet<Integer>();
String[] antValues = getResources().getStringArray(R.array.ant_sensor_type_values);
String[] antValues = getResources().getStringArray(R.array.sensor_type_ant_values);
for (String antValue : antValues) {
toRemove.add(sensorTypePreference.findIndexOfValue(antValue));
}
@@ -224,21 +305,19 @@ public class SettingsActivity extends PreferenceActivity {
setTrackColorModePreferenceListeners();
PreferenceCategory speedOptionsCategory =
(PreferenceCategory) findPreference(getString(R.string.track_color_mode_fixed_speed_options_key));
PreferenceCategory speedOptionsCategory = (PreferenceCategory) findPreference(
getString(R.string.track_color_mode_fixed_speed_options_key));
speedOptionsCategory.removePreference(findPreference(getString(R.string.track_color_mode_fixed_speed_slow_key)));
speedOptionsCategory.removePreference(findPreference(getString(R.string.track_color_mode_fixed_speed_medium_key)));
speedOptionsCategory.removePreference(
findPreference(getString(R.string.track_color_mode_fixed_speed_slow_key)));
speedOptionsCategory.removePreference(
findPreference(getString(R.string.track_color_mode_fixed_speed_medium_key)));
}
@Override
protected void onResume() {
super.onResume();
// If we only need the display setting screen nothing else needs to load.
if (processIntent())
return;
configureBluetoothPreferences();
Preference backupNowPreference =
findPreference(getString(R.string.backup_to_sd_key));
@@ -255,10 +334,10 @@ public class SettingsActivity extends PreferenceActivity {
resetPreference.setEnabled(!recording);
backupNowPreference.setSummary(
recording ? R.string.settings_not_while_recording
: R.string.settings_backup_to_sd_summary);
: R.string.settings_backup_now_summary);
restoreNowPreference.setSummary(
recording ? R.string.settings_not_while_recording
: R.string.settings_restore_from_sd_summary);
: R.string.settings_backup_restore_summary);
resetPreference.setSummary(
recording ? R.string.settings_not_while_recording
: R.string.settings_reset_summary);
@@ -296,8 +375,8 @@ public class SettingsActivity extends PreferenceActivity {
private void updateSensorSettings(String sensorType) {
boolean usesBluetooth =
getString(R.string.zephyr_sensor_type).equals(sensorType)
|| getString(R.string.polar_sensor_type).equals(sensorType);
getString(R.string.sensor_type_value_zephyr).equals(sensorType)
|| getString(R.string.sensor_type_value_polar).equals(sensorType);
findPreference(
getString(R.string.bluetooth_sensor_key)).setEnabled(usesBluetooth);
findPreference(
@@ -309,63 +388,168 @@ public class SettingsActivity extends PreferenceActivity {
Preference antSrm = findPreference(getString(R.string.ant_srm_bridge_sensor_id_key));
if (antHrm != null && antSrm != null) {
antHrm
.setEnabled(getString(R.string.ant_sensor_type).equals(sensorType));
.setEnabled(getString(R.string.sensor_type_value_ant).equals(sensorType));
antSrm
.setEnabled(getString(R.string.srm_ant_bridge_sensor_type).equals(sensorType));
.setEnabled(getString(R.string.sensor_type_value_srm_ant_bridge).equals(sensorType));
}
}
private void updateTrackColorModeSettings(String trackColorMode) {
boolean usesFixedSpeed = trackColorMode.equals(getString(R.string.track_color_mode_fixed));
boolean usesDynamicSpeed = trackColorMode.equals(getString(R.string.track_color_mode_dynamic));
findPreference(
getString(R.string.track_color_mode_fixed_speed_slow_display_key)).setEnabled(usesFixedSpeed);
findPreference(
getString(R.string.track_color_mode_fixed_speed_medium_display_key)).setEnabled(usesFixedSpeed);
findPreference(
getString(R.string.track_color_mode_dynamic_speed_variation_key)).setEnabled(usesDynamicSpeed);
boolean usesFixedSpeed =
trackColorMode.equals(getString(R.string.display_track_color_value_fixed));
boolean usesDynamicSpeed =
trackColorMode.equals(getString(R.string.display_track_color_value_dynamic));
findPreference(getString(R.string.track_color_mode_fixed_speed_slow_display_key))
.setEnabled(usesFixedSpeed);
findPreference(getString(R.string.track_color_mode_fixed_speed_medium_display_key))
.setEnabled(usesFixedSpeed);
findPreference(getString(R.string.track_color_mode_dynamic_speed_variation_key))
.setEnabled(usesDynamicSpeed);
}
/**
* Updates all the preferences which give options with distance units to use
* the proper unit the user has selected.
* Updates display options that depends on the preferred distance units, metric or imperial.
*
* @param isMetric true to use metric units, false to use imperial
*/
private void updatePreferenceUnits(boolean isMetric) {
final ListPreference minRecordingDistance =
(ListPreference) findPreference(
getString(R.string.min_recording_distance_key));
final ListPreference maxRecordingDistance =
(ListPreference) findPreference(
getString(R.string.max_recording_distance_key));
final ListPreference minRequiredAccuracy =
(ListPreference) findPreference(
getString(R.string.min_required_accuracy_key));
minRecordingDistance.setEntries(isMetric
? R.array.min_recording_distance_options
: R.array.min_recording_distance_options_ft);
maxRecordingDistance.setEntries(isMetric
? R.array.max_recording_distance_options
: R.array.max_recording_distance_options_ft);
minRequiredAccuracy.setEntries(isMetric
? R.array.min_required_accuracy_options
: R.array.min_required_accuracy_options_ft);
private void updateDisplayOptions(boolean isMetric) {
setTaskOptions(isMetric, R.string.announcement_frequency_key);
setTaskOptions(isMetric, R.string.split_frequency_key);
setRecordingDistanceOptions(isMetric, R.string.min_recording_distance_key);
setTrackDistanceOptions(isMetric, R.string.max_recording_distance_key);
setGpsAccuracyOptions(isMetric, R.string.min_required_accuracy_key);
}
/**
* Set the user visible options for a periodic task.
* Sets the display options for a periodic task.
*/
private void setTaskOptions(boolean isMetric, int listId) {
final ListPreference taskFrequency =
(ListPreference) findPreference(getString(listId));
taskFrequency.setEntries(isMetric
? R.array.task_frequency_options
: R.array.task_frequency_options_ft);
String[] values = getResources().getStringArray(R.array.recording_task_frequency_values);
String[] options = new String[values.length];
for (int i = 0; i < values.length; i++) {
if (values[i].equals(TASK_FREQUENCY_OFF)) {
options[i] = getString(R.string.value_off);
} else if (values[i].startsWith("-")) {
int value = Integer.parseInt(values[i].substring(1));
int stringId = isMetric ? R.string.value_integer_kilometer : R.string.value_integer_mile;
String format = getString(stringId);
options[i] = String.format(format, value);
} else {
int value = Integer.parseInt(values[i]);
String format = getString(R.string.value_integer_minute);
options[i] = String.format(format, value);
}
}
ListPreference list = (ListPreference) findPreference(getString(listId));
list.setEntries(options);
}
/**
* Sets the display options for 'Distance between points' option.
*/
private void setRecordingDistanceOptions(boolean isMetric, int listId) {
String[] values = getResources().getStringArray(R.array.recording_distance_values);
String[] options = new String[values.length];
for (int i = 0; i < values.length; i++) {
int value = Integer.parseInt(values[i]);
if (!isMetric) {
value = (int) (value * UnitConversions.M_TO_FT);
}
String format;
if (values[i].equals(RECORDING_DISTANCE_RECOMMENDED)) {
int stringId = isMetric ? R.string.value_integer_meter_recommended
: R.string.value_integer_feet_recommended;
format = getString(stringId);
} else {
int stringId = isMetric ? R.string.value_integer_meter : R.string.value_integer_feet;
format = getString(stringId);
}
options[i] = String.format(format, value);
}
ListPreference list = (ListPreference) findPreference(getString(listId));
list.setEntries(options);
}
/**
* Sets the display options for 'Distance between Tracks'.
*/
private void setTrackDistanceOptions(boolean isMetric, int listId) {
String[] values = getResources().getStringArray(R.array.recording_track_distance_values);
String[] options = new String[values.length];
for (int i = 0; i < values.length; i++) {
int value = Integer.parseInt(values[i]);
String format;
if (isMetric) {
int stringId = values[i].equals(TRACK_DISTANCE_RECOMMENDED)
? R.string.value_integer_meter_recommended : R.string.value_integer_meter;
format = getString(stringId);
options[i] = String.format(format, value);
} else {
value = (int) (value * UnitConversions.M_TO_FT);
if (value < 2000) {
int stringId = values[i].equals(TRACK_DISTANCE_RECOMMENDED)
? R.string.value_integer_feet_recommended : R.string.value_integer_feet;
format = getString(stringId);
options[i] = String.format(format, value);
} else {
double mile = value / UnitConversions.MI_TO_FEET;
format = getString(R.string.value_float_mile);
options[i] = String.format(format, mile);
}
}
}
ListPreference list = (ListPreference) findPreference(getString(listId));
list.setEntries(options);
}
/**
* Sets the display options for 'GPS accuracy'.
*/
private void setGpsAccuracyOptions(boolean isMetric, int listId) {
String[] values = getResources().getStringArray(R.array.recording_gps_accuracy_values);
String[] options = new String[values.length];
for (int i = 0; i < values.length; i++) {
int value = Integer.parseInt(values[i]);
String format;
if (isMetric) {
if (values[i].equals(GPS_ACCURACY_RECOMMENDED)) {
format = getString(R.string.value_integer_meter_recommended);
} else if (values[i].equals(GPS_ACCURACY_EXCELLENT)) {
format = getString(R.string.value_integer_meter_excellent_gps);
} else if (values[i].equals(GPS_ACCURACY_POOR)) {
format = getString(R.string.value_integer_meter_poor_gps);
} else {
format = getString(R.string.value_integer_meter);
}
options[i] = String.format(format, value);
} else {
value = (int) (value * UnitConversions.M_TO_FT);
if (value < 2000) {
if (values[i].equals(GPS_ACCURACY_RECOMMENDED)) {
format = getString(R.string.value_integer_feet_recommended);
} else if (values[i].equals(GPS_ACCURACY_EXCELLENT)) {
format = getString(R.string.value_integer_feet_excellent_gps);
} else {
format = getString(R.string.value_integer_feet);
}
options[i] = String.format(format, value);
} else {
double mile = value / UnitConversions.MI_TO_FEET;
if (values[i].equals(GPS_ACCURACY_POOR)) {
format = getString(R.string.value_float_mile_poor_gps);
} else {
format = getString(R.string.value_float_mile);
}
options[i] = String.format(format, mile);
}
}
}
ListPreference list = (ListPreference) findPreference(getString(listId));
list.setEntries(options);
}
/**
@@ -482,15 +666,15 @@ public class SettingsActivity extends PreferenceActivity {
* Saves the given edit text preference value.
* If the units are not metric convert the value before saving.
*/
private void validateTrackColorModeSettings(EditTextPreference preference,
String newValue, int id) {
private void validateTrackColorModeSettings(String newValue, int id) {
CheckBoxPreference metricUnitsPreference = (CheckBoxPreference) findPreference(
getString(R.string.metric_units_key));
String metricspeed;
if(!metricUnitsPreference.isChecked()) {
// Convert miles/h to km/h
try {
metricspeed = String.valueOf((int) (Double.parseDouble(newValue) * UnitConversions.MPH_TO_KMH) + 1);
metricspeed = String.valueOf(
(int) (Double.parseDouble(newValue) * UnitConversions.MPH_TO_KMH) + 1);
} catch (NumberFormatException e) {
metricspeed = "0";
}
@@ -522,7 +706,7 @@ public class SettingsActivity extends PreferenceActivity {
@Override
public boolean onPreferenceChange(Preference preference,
Object newValue) {
validateTrackColorModeSettings((EditTextPreference) preference, (String) newValue, metricKey);
validateTrackColorModeSettings((String) newValue, metricKey);
return true;
}
});
@@ -33,8 +33,6 @@ import android.location.Location;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Window;
import android.widget.ScrollView;
import android.widget.TextView;
@@ -68,12 +66,6 @@ public class StatsActivity extends Activity implements TrackDataListener {
*/
private long startTime = -1;
/**
* If true, the statistics for the current segment are shown, otherwise
* for the full track.
*/
private boolean showCurrentSegment = false;
private TrackDataHub dataHub;
private SharedPreferences preferences;
@@ -185,43 +177,12 @@ public class StatsActivity extends Activity implements TrackDataListener {
@Override
public void run() {
utils.updateUnits();
utils.setSpeedLabel(R.id.speed_label, R.string.speed, R.string.pace_label);
utils.setSpeedLabel(R.id.speed_label, R.string.stat_speed, R.string.stat_pace);
utils.setSpeedLabels();
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuItem currentSegment = menu.add(0,
Constants.MENU_CURRENT_SEGMENT, 0, R.string.current_segment);
currentSegment.setIcon(R.drawable.ic_menu_lastsegment);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem item = menu.findItem(Constants.MENU_CURRENT_SEGMENT);
if (item != null) {
item.setTitle(showCurrentSegment
? getString(R.string.current_track)
: getString(R.string.current_segment));
}
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case Constants.MENU_CURRENT_SEGMENT:
showCurrentSegment = !showCurrentSegment;
// TODO: Re-read only the data that interests us
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* Updates the given location fields (latitude, longitude, altitude) and all
* other fields.
@@ -20,13 +20,13 @@ import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import android.app.Activity;
import android.util.Log;
import android.widget.TextView;
import java.text.DecimalFormat;
import java.text.NumberFormat;
/**
* Various utility functions for views that display statistics information.
*
@@ -77,7 +77,7 @@ public class StatsUtilities {
}
public void setUnknown(int id) {
((TextView) activity.findViewById(id)).setText(R.string.unknown);
((TextView) activity.findViewById(id)).setText(R.string.value_unknown);
}
public void setText(int id, double d, NumberFormat format) {
@@ -128,24 +128,24 @@ public class StatsUtilities {
public void setAltitudeUnits(int unitLabelId) {
TextView unitTextView = (TextView) activity.findViewById(unitLabelId);
unitTextView.setText(metricUnits ? R.string.meter : R.string.feet);
unitTextView.setText(metricUnits ? R.string.unit_meter : R.string.unit_feet);
}
public void setDistanceUnits(int unitLabelId) {
TextView unitTextView = (TextView) activity.findViewById(unitLabelId);
unitTextView.setText(metricUnits ? R.string.kilometer : R.string.mile);
unitTextView.setText(metricUnits ? R.string.unit_kilometer : R.string.unit_mile);
}
public void setSpeedUnits(int unitLabelId, int unitLabelBottomId) {
TextView unitTextView = (TextView) activity.findViewById(unitLabelId);
unitTextView.setText(reportSpeed
? (metricUnits ? R.string.kilometer : R.string.mile)
: R.string.min);
? (metricUnits ? R.string.unit_kilometer : R.string.unit_mile)
: R.string.unit_minute);
unitTextView = (TextView) activity.findViewById(unitLabelBottomId);
unitTextView.setText(reportSpeed
? R.string.hr
: (metricUnits ? R.string.kilometer : R.string.mile));
? R.string.unit_hour
: (metricUnits ? R.string.unit_kilometer : R.string.unit_mile));
}
public void setTime(int id, long l) {
@@ -251,13 +251,13 @@ public class StatsUtilities {
public void setSpeedLabels() {
setSpeedLabel(R.id.average_speed_label,
R.string.average_speed_label,
R.string.average_pace_label);
R.string.stat_average_speed,
R.string.stat_average_pace);
setSpeedLabel(R.id.average_moving_speed_label,
R.string.average_moving_speed_label,
R.string.average_moving_pace_label);
R.string.stat_average_moving_speed,
R.string.stat_average_moving_pace);
setSpeedLabel(R.id.max_speed_label,
R.string.max_speed_label,
R.string.min_pace_label);
R.string.stat_max_speed,
R.string.stat_min_pace);
}
}
@@ -80,7 +80,7 @@ public class TrackDetails extends Activity implements OnClickListener {
cancel.setOnClickListener(this);
cancel.setVisibility(View.VISIBLE);
} else {
cancel.setVisibility(View.GONE);
cancel.setVisibility(View.INVISIBLE);
}
Button save = (Button) findViewById(R.id.trackdetails_save);
save.setOnClickListener(this);
@@ -75,44 +75,48 @@ public class TrackList extends ListActivity
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
menu.setHeaderTitle(R.string.tracklist_this_track);
menu.setHeaderTitle(R.string.track_list_context_menu_title);
AdapterView.AdapterContextMenuInfo info =
(AdapterView.AdapterContextMenuInfo) menuInfo;
contextPosition = info.position;
trackId = TrackList.this.listView.getAdapter().getItemId(
contextPosition);
menu.add(0, Constants.MENU_SHOW, 0,
R.string.tracklist_show_track);
R.string.track_list_show_on_map);
menu.add(0, Constants.MENU_EDIT, 0,
R.string.tracklist_edit_track);
R.string.track_list_edit_track);
if (!isRecording() || trackId != recordingTrackId) {
String saveFileFormat = getString(R.string.track_list_save_file);
String shareFileFormat = getString(R.string.track_list_share_file);
String fileTypes[] = getResources().getStringArray(R.array.file_types);
menu.add(0, Constants.MENU_SEND_TO_GOOGLE, 0,
R.string.tracklist_send_to_google);
R.string.track_list_send_google);
SubMenu share = menu.addSubMenu(0, Constants.MENU_SHARE, 0,
R.string.tracklist_share_track);
R.string.track_list_share_track);
share.add(0, Constants.MENU_SHARE_LINK, 0,
R.string.tracklist_share_link);
share.add(0, Constants.MENU_SHARE_GPX_FILE, 0,
R.string.tracklist_share_gpx_file);
share.add(0, Constants.MENU_SHARE_KML_FILE, 0,
R.string.tracklist_share_kml_file);
share.add(0, Constants.MENU_SHARE_CSV_FILE, 0,
R.string.tracklist_share_csv_file);
share.add(0, Constants.MENU_SHARE_TCX_FILE, 0,
R.string.tracklist_share_tcx_file);
R.string.track_list_share_url);
share.add(
0, Constants.MENU_SHARE_GPX_FILE, 0, String.format(shareFileFormat, fileTypes[0]));
share.add(
0, Constants.MENU_SHARE_KML_FILE, 0, String.format(shareFileFormat, fileTypes[1]));
share.add(
0, Constants.MENU_SHARE_CSV_FILE, 0, String.format(shareFileFormat, fileTypes[2]));
share.add(
0, Constants.MENU_SHARE_TCX_FILE, 0, String.format(shareFileFormat, fileTypes[3]));
SubMenu save = menu.addSubMenu(0,
Constants.MENU_WRITE_TO_SD_CARD, 0,
R.string.tracklist_write_to_sd);
save.add(0, Constants.MENU_SAVE_GPX_FILE, 0,
R.string.tracklist_save_as_gpx);
save.add(0, Constants.MENU_SAVE_KML_FILE, 0,
R.string.tracklist_save_as_kml);
save.add(0, Constants.MENU_SAVE_CSV_FILE, 0,
R.string.tracklist_save_as_csv);
save.add(0, Constants.MENU_SAVE_TCX_FILE, 0,
R.string.tracklist_save_as_tcx);
R.string.track_list_save_sd);
save.add(
0, Constants.MENU_SAVE_GPX_FILE, 0, String.format(saveFileFormat, fileTypes[0]));
save.add(
0, Constants.MENU_SAVE_KML_FILE, 0, String.format(saveFileFormat, fileTypes[1]));
save.add(
0, Constants.MENU_SAVE_CSV_FILE, 0, String.format(saveFileFormat, fileTypes[2]));
save.add(
0, Constants.MENU_SAVE_TCX_FILE, 0, String.format(saveFileFormat, fileTypes[3]));
menu.add(0, Constants.MENU_DELETE, 0,
R.string.tracklist_delete_track);
R.string.track_list_delete_track);
}
}
};
@@ -312,17 +316,17 @@ public class TrackList extends ListActivity
if (metricUnits) {
if (length > 1000) {
length /= 1000;
lengthUnit = getString(R.string.kilometer);
lengthUnit = getString(R.string.unit_kilometer);
} else {
lengthUnit = getString(R.string.meter);
lengthUnit = getString(R.string.unit_meter);
}
} else {
if (length > UnitConversions.MI_TO_M) {
length /= UnitConversions.MI_TO_M;
lengthUnit = getString(R.string.mile);
lengthUnit = getString(R.string.unit_mile);
} else {
length *= UnitConversions.M_TO_FT;
lengthUnit = getString(R.string.feet);
lengthUnit = getString(R.string.unit_feet);
}
}
textView.setText(String.format("%s %.2f %s",
@@ -110,7 +110,7 @@ public class WaypointDetails extends Activity
cancel.setOnClickListener(this);
cancel.setVisibility(View.VISIBLE);
} else {
cancel.setVisibility(View.GONE);
cancel.setVisibility(View.INVISIBLE);
}
Button save = (Button) findViewById(R.id.waypointdetails_save);
save.setOnClickListener(this);
@@ -73,7 +73,7 @@ public class WaypointsList extends ListActivity
new OnCreateContextMenuListener() {
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
menu.setHeaderTitle(R.string.waypointslist_this_waypoint);
menu.setHeaderTitle(R.string.marker_list_context_menu_title);
AdapterView.AdapterContextMenuInfo info =
(AdapterView.AdapterContextMenuInfo) menuInfo;
contextPosition = info.position;
@@ -83,11 +83,11 @@ public class WaypointsList extends ListActivity
if (waypoint != null) {
int type = waypoint.getType();
menu.add(0, Constants.MENU_SHOW, 0,
R.string.waypointslist_show_waypoint);
R.string.marker_list_show_on_map);
menu.add(0, Constants.MENU_EDIT, 0,
R.string.waypointslist_edit_waypoint);
R.string.marker_list_edit_marker);
menu.add(0, Constants.MENU_DELETE, 0,
R.string.waypointslist_delete_waypoint).setEnabled(
R.string.marker_list_delete_marker).setEnabled(
recordingTrackId < 0 || type == Waypoint.TYPE_WAYPOINT ||
info.id != providerUtils.getLastWaypointId(recordingTrackId));
}
@@ -211,8 +211,7 @@ public class WaypointsList extends ListActivity
}
long id = insertWaypoint(request);
if (id < 0) {
Toast.makeText(this, R.string.error_unable_to_insert_marker,
Toast.LENGTH_LONG).show();
Toast.makeText(this, R.string.marker_insert_error, Toast.LENGTH_LONG).show();
Log.e(Constants.TAG, "Failed to insert marker.");
return;
}
@@ -227,8 +226,7 @@ public class WaypointsList extends ListActivity
if (trackRecordingService != null) {
long waypointId = trackRecordingService.insertWaypoint(request);
if (waypointId >= 0) {
Toast.makeText(this, R.string.status_statistics_inserted,
Toast.LENGTH_LONG).show();
Toast.makeText(this, R.string.marker_insert_success, Toast.LENGTH_LONG).show();
return waypointId;
}
} else {
@@ -295,10 +293,10 @@ public class WaypointsList extends ListActivity
public void deleteWaypoint(final long waypointId) {
AlertDialog dialog = null;
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(getString(R.string.marker_will_be_permanently_deleted));
builder.setTitle(getString(R.string.are_you_sure_question));
builder.setMessage(getString(R.string.marker_list_delete_marker_confirm_message));
builder.setTitle(getString(R.string.generic_confirm_title));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setPositiveButton(getString(R.string.yes),
builder.setPositiveButton(getString(R.string.generic_yes),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
@@ -307,7 +305,7 @@ public class WaypointsList extends ListActivity
new StringUtils(WaypointsList.this));
}
});
builder.setNegativeButton(getString(R.string.no),
builder.setNegativeButton(getString(R.string.generic_no),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
@@ -47,7 +47,7 @@ public class WelcomeActivity extends Activity {
WebView web = (WebView) findViewById(R.id.welcome_web);
web.loadUrl("file:///android_asset/welcome.html");
findViewById(R.id.welcome_read_later).setOnClickListener(
findViewById(R.id.welcome_ok).setOnClickListener(
new OnClickListener() {
public void onClick(View v) {
finish();
@@ -71,8 +71,8 @@ public class WelcomeActivity extends Activity {
View view = li.inflate(R.layout.about, null);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setView(view);
builder.setPositiveButton(R.string.ok, null);
builder.setNeutralButton(R.string.license, new DialogInterface.OnClickListener() {
builder.setPositiveButton(R.string.generic_ok, null);
builder.setNegativeButton(R.string.about_license, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Eula.showEula(WelcomeActivity.this);
@@ -147,10 +147,10 @@ class DataSourcesWrapperImpl implements DataSourcesWrapper {
// We don't have a recent GPS fix, just use cell towers if available
loc = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
int toastResId = R.string.status_approximate_location;
int toastResId = R.string.my_location_approximate_location;
if (loc == null || loc.getTime() < now - MAX_NETWORK_AGE_MS) {
// We don't have a recent cell tower location, let the user know:
toastResId = R.string.status_no_location;
toastResId = R.string.my_location_no_location;
}
// Let the user know we have only an approximate location:
@@ -17,11 +17,13 @@ package com.google.android.apps.mytracks.content;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.maps.mytracks.R;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
@@ -30,6 +32,8 @@ import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Binder;
import android.os.Process;
import android.text.TextUtils;
import android.util.Log;
@@ -183,8 +187,22 @@ public class MyTracksProvider extends ContentProvider {
"waypoints/#", WAYPOINTS_ID);
}
private boolean canAccess() {
if (Binder.getCallingPid() == Process.myPid()) {
return true;
} else {
Context context = getContext();
SharedPreferences sharedPreferences = context.getSharedPreferences(
Constants.SETTINGS_NAME, 0);
return sharedPreferences.getBoolean(context.getString(R.string.allow_access_key), false);
}
}
@Override
public boolean onCreate() {
if (!canAccess()) {
return false;
}
DatabaseHelper dbHelper = new DatabaseHelper(getContext());
try {
db = dbHelper.getWritableDatabase();
@@ -196,6 +214,9 @@ public class MyTracksProvider extends ContentProvider {
@Override
public int delete(Uri url, String where, String[] selectionArgs) {
if (!canAccess()) {
return 0;
}
String table;
boolean shouldVacuum = false;
switch (urlMatcher.match(url)) {
@@ -228,6 +249,9 @@ public class MyTracksProvider extends ContentProvider {
@Override
public String getType(Uri url) {
if (!canAccess()) {
return null;
}
switch (urlMatcher.match(url)) {
case TRACKPOINTS:
return TrackPointsColumns.CONTENT_TYPE;
@@ -248,6 +272,9 @@ public class MyTracksProvider extends ContentProvider {
@Override
public Uri insert(Uri url, ContentValues initialValues) {
if (!canAccess()) {
return null;
}
Log.d(MyTracksProvider.TAG, "MyTracksProvider.insert");
ContentValues values;
if (initialValues != null) {
@@ -276,6 +303,9 @@ public class MyTracksProvider extends ContentProvider {
@Override
public int bulkInsert(Uri url, ContentValues[] valuesBulk) {
if (!canAccess()) {
return 0;
}
Log.d(MyTracksProvider.TAG, "MyTracksProvider.bulkInsert");
int numInserted = 0;
try {
@@ -348,6 +378,9 @@ public class MyTracksProvider extends ContentProvider {
public Cursor query(
Uri url, String[] projection, String selection, String[] selectionArgs,
String sort) {
if (!canAccess()) {
return null;
}
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
int match = urlMatcher.match(url);
String sortOrder = null;
@@ -399,6 +432,9 @@ public class MyTracksProvider extends ContentProvider {
@Override
public int update(Uri url, ContentValues values, String where,
String[] selectionArgs) {
if (!canAccess()) {
return 0;
}
int count;
int match = urlMatcher.match(url);
if (match == TRACKPOINTS) {
@@ -84,7 +84,7 @@ public class TrackDataHub {
/**
* Listen to sampled-out points.
* Listening to this without listening to {@link #SAMPLED_POINT_UPDATES}
* Listening to this without listening to {@link #POINT_UPDATES}
* makes no sense and may yield unexpected results.
*/
SAMPLED_OUT_POINT_UPDATES,
@@ -292,7 +292,8 @@ public class TrackDataHub {
@Override
protected void finalize() throws Throwable {
if (isStarted() || listenerHandlerThread.isAlive()) {
if (isStarted() ||
(listenerHandlerThread != null && listenerHandlerThread.isAlive())) {
Log.e(TAG, "Forgot to stop() TrackDataHub");
}
@@ -384,8 +385,8 @@ public class TrackDataHub {
/**
* Loads the given track and makes it the currently-selected one.
* It is ok to call this method before {@link start}, and in that case
* the data will only be passed to listeners when {@link start} is called.
* It is ok to call this method before {@link #start}, and in that case
* the data will only be passed to listeners when {@link #start} is called.
*
* @param trackId the ID of the track to load
*/
@@ -397,7 +398,7 @@ public class TrackDataHub {
// Save the selection to memory and flush.
selectedTrackId = trackId;
ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges(
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(
preferences.edit().putLong(SELECTED_TRACK_KEY, trackId));
// Force it to reload data from the beginning.
@@ -800,7 +801,7 @@ public class TrackDataHub {
/**
* Notifies that a new track has been selected..
*
* @param track the new selected track
* @param trackId the new selected track
* @param listeners the listeners to notify
*/
private void notifySelectedTrackChanged(long trackId,
@@ -97,8 +97,8 @@ class TrackDataListeners {
/**
* Registers a listener to send data to.
* It is ok to call this method before {@link start}, and in that case
* the data will only be passed to listeners when {@link start} is called.
* It is ok to call this method before {@link TrackDataHub#start}, and in that case
* the data will only be passed to listeners when {@link TrackDataHub#start} is called.
*
* @param listener the listener to register
* @param dataTypes the type of data that the listener is interested in
@@ -135,9 +135,6 @@ public class ModernAuthManager implements AuthManager {
* {@link Activity#onActivityResult} function
* @param results The data passed in to the {@link Activity}'s
* {@link Activity#onActivityResult} function
* @return True if the auth token was fetched or we aren't done fetching
* the auth token, or False if there was an error or the request was
* canceled
*/
public void authResult(int resultCode, Intent results) {
boolean retry = false;
@@ -169,12 +166,11 @@ public class ModernAuthManager implements AuthManager {
}
/**
* Invalidates the existing auth token and request a new one. The
* {@link Runnable} provided will be executed when the new auth token is
* successfully fetched.
* Invalidates the existing auth token and request a new one. The callback
* provided will be executed when the new auth token is successfully fetched.
*
* @param runnable A {@link Runnable} to execute when a new auth token
* is successfully fetched
* @param callback A callback to execute when a new auth token is successfully
* fetched
*/
public void invalidateAndRefresh(final AuthCallback callback) {
this.authCallback = callback;
@@ -22,6 +22,7 @@ import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.io.docs.DocsHelper;
import com.google.android.apps.mytracks.io.gdata.GDataClientFactory;
import com.google.android.apps.mytracks.io.gdata.GDataWrapper;
import com.google.android.apps.mytracks.io.sendtogoogle.SendType;
import com.google.android.common.gdata.AndroidXmlParserFactory;
import com.google.android.maps.mytracks.R;
import com.google.wireless.gdata.client.GDataClient;
@@ -92,7 +93,9 @@ public class SendToDocs {
private void doUpload(long trackId) {
// TODO
statusMessage = activity.getString(R.string.error_sending_to_fusiontables);
String errorFormat = activity.getString(R.string.send_google_error_service);
String serviceName = activity.getString(SendType.DOCS.getServiceName());
statusMessage = String.format(errorFormat, serviceName);
success = false;
try {
@@ -115,15 +118,11 @@ public class SendToDocs {
Log.d(Constants.TAG, "SendToDocs: Uploading to spreadsheet");
success = uploadToDocs(track);
if (success) {
if (createdNewSpreadSheet) {
statusMessage = activity.getString(
R.string.status_tracks_have_been_uploaded_to_new_doc);
} else {
statusMessage = activity.getString(
R.string.status_tracks_have_been_uploaded_to_docs);
}
} else {
statusMessage = activity.getString(R.string.error_sending_to_docs);
String successFormat = createdNewSpreadSheet
? activity.getString(R.string.send_google_success_new)
: activity.getString(R.string.send_google_success_existing);
String url = activity.getString(SendType.DOCS.getServiceUrl());
statusMessage = String.format(successFormat, serviceName, url);
}
Log.d(Constants.TAG, "SendToDocs: Done.");
} finally {
@@ -24,6 +24,7 @@ import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.gdata.GDataWrapper;
import com.google.android.apps.mytracks.io.gdata.GDataWrapper.QueryFunction;
import com.google.android.apps.mytracks.io.sendtogoogle.SendType;
import com.google.android.apps.mytracks.stats.DoubleBuffer;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.ApiFeatures;
@@ -41,8 +42,6 @@ import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.InputStreamContent;
import com.google.api.client.http.apache.ApacheHttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.util.Strings;
import android.app.Activity;
@@ -73,7 +72,7 @@ public class SendToFusionTables implements Runnable {
* Listener invoked when sending to fusion tables completes.
*/
public interface OnSendCompletedListener {
void onSendCompleted(String tableId, boolean success, int statusMessage);
void onSendCompleted(String tableId, boolean success, String statusMessage);
}
/** The GData service id for Fusion Tables. */
@@ -140,8 +139,7 @@ public class SendToFusionTables implements Runnable {
this.stringUtils = new StringUtils(context);
this.providerUtils = MyTracksProviderUtils.Factory.get(context);
HttpTransport transport = ApiFeatures.getInstance().useNetHttpTransport()
? new NetHttpTransport() : new ApacheHttpTransport();
HttpTransport transport = ApiFeatures.getInstance().getApiAdapter().getHttpTransport();
httpRequestFactory = transport.createRequestFactory(new MethodOverride());
}
@@ -171,11 +169,14 @@ public class SendToFusionTables implements Runnable {
}
private void doUpload() {
int statusMessageId = R.string.error_sending_to_fusiontables;
String errorFormat = context.getString(R.string.send_google_error_service);
String serviceName = context.getString(SendType.FUSION_TABLES.getServiceName());
String statusMessage = String.format(errorFormat, serviceName);
boolean success = true;
try {
progressIndicator.setProgressValue(PROGRESS_INITIALIZATION);
progressIndicator.setProgressMessage(R.string.progress_message_reading_track);
progressIndicator.setProgressMessage(
context.getString(R.string.send_google_progress_reading_track));
// Get the track meta-data
Track track = providerUtils.getTrack(trackId);
@@ -188,13 +189,15 @@ public class SendToFusionTables implements Runnable {
// Create a new table:
progressIndicator.setProgressValue(PROGRESS_FUSION_TABLE_CREATE);
progressIndicator.setProgressMessage(R.string.progress_message_creating_fusiontable);
String creatingFormat = context.getString(R.string.send_google_progress_creating);
progressIndicator.setProgressMessage(String.format(creatingFormat, serviceName));
if (!createNewTable(track) || !makeTableUnlisted()) {
return;
}
progressIndicator.setProgressValue(PROGRESS_UPLOAD_DATA_MIN);
progressIndicator.setProgressMessage(R.string.progress_message_sending_fusiontables);
String sendingFormat = context.getString(R.string.send_google_progress_sending);
progressIndicator.setProgressMessage(String.format(sendingFormat, serviceName));
// Upload all of the segments of the track plus start/end markers
if (!uploadAllTrackPoints(track, originalDescription)) {
@@ -208,17 +211,18 @@ public class SendToFusionTables implements Runnable {
return;
}
statusMessageId = R.string.status_new_fusiontable_has_been_created;
String successFormat = context.getString(R.string.send_google_success_new);
String url = context.getString(SendType.FUSION_TABLES.getServiceUrl());
statusMessage = String.format(successFormat, serviceName, url);
Log.d(Constants.TAG, "SendToFusionTables: Done: " + success);
progressIndicator.setProgressValue(PROGRESS_COMPLETE);
} finally {
final boolean finalSuccess = success;
final int finalStatusMessageId = statusMessageId;
final String finalStatusMessage = statusMessage;
context.runOnUiThread(new Runnable() {
public void run() {
if (onCompletion != null) {
onCompletion.onSendCompleted(
tableId, finalSuccess, finalStatusMessageId);
onCompletion.onSendCompleted(tableId, finalSuccess, finalStatusMessage);
}
}
});
@@ -342,7 +346,7 @@ public class SendToFusionTables implements Runnable {
if (totalLocationsRead == 0) {
// Put a marker at the first point of the first valid segment:
String name = track.getName() + " " + context.getString(R.string.start);
String name = track.getName() + " " + context.getString(R.string.marker_label_start);
createNewPoint(name, "", loc, MARKER_TYPE_START);
}
@@ -388,7 +392,7 @@ public class SendToFusionTables implements Runnable {
track.setDescription("<p>" + originalDescription + "</p><p>"
+ stringUtils.generateTrackDescription(track, distances, elevations)
+ "</p>");
String name = track.getName() + " " + context.getString(R.string.end);
String name = track.getName() + " " + context.getString(R.string.marker_label_end);
return createNewPoint(name, track.getDescription(), lastLocation, MARKER_TYPE_END);
}
@@ -465,7 +469,7 @@ public class SendToFusionTables implements Runnable {
if (totalSegmentsUploaded > 1) {
splitTrack.setName(splitTrack.getName() + " "
+ String.format(
context.getString(R.string.part), totalSegmentsUploaded));
context.getString(R.string.send_google_track_part_label), totalSegmentsUploaded));
}
totalSegmentsUploaded++;
Log.d(Constants.TAG,
@@ -16,12 +16,14 @@
package com.google.android.apps.mytracks.io;
import static com.google.android.apps.mytracks.Constants.TAG;
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.content.Waypoint;
import com.google.android.apps.mytracks.io.mymaps.MapsFacade;
import com.google.android.apps.mytracks.io.sendtogoogle.SendType;
import com.google.android.apps.mytracks.stats.DoubleBuffer;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.LocationUtils;
@@ -74,7 +76,7 @@ public class SendToMyMaps implements Runnable {
private int totalSegmentsUploaded;
public interface OnSendCompletedListener {
void onSendCompleted(String mapId, boolean success, int statusMessage);
void onSendCompleted(String mapId, boolean success, String statusMessage);
}
public SendToMyMaps(Activity context, String mapId, AuthManager auth,
@@ -97,11 +99,13 @@ public class SendToMyMaps implements Runnable {
}
private void doUpload() {
int statusMessageId = R.string.error_sending_to_mymap;
String errorFormat = context.getString(R.string.send_google_error_service);
String serviceName = context.getString(SendType.MYMAPS.getServiceName());
String statusMessage = String.format(errorFormat, serviceName);
boolean success = true;
try {
progressIndicator.setProgressMessage(
R.string.progress_message_reading_track);
context.getString(R.string.send_google_progress_reading_track));
// Get the track meta-data
Track track = providerUtils.getTrack(trackId);
@@ -127,8 +131,8 @@ public class SendToMyMaps implements Runnable {
context.getString(R.string.default_map_public_key), true);
}
progressIndicator.setProgressMessage(
R.string.progress_message_creating_map);
String creatingFormat = context.getString(R.string.send_google_progress_creating);
progressIndicator.setProgressMessage(String.format(creatingFormat, serviceName));
StringBuilder mapIdBuilder = new StringBuilder();
success = mapsClient.createNewMap(
@@ -172,9 +176,10 @@ public class SendToMyMaps implements Runnable {
}
if (success) {
statusMessageId = isNewMap
? R.string.status_new_mymap_has_been_created
: R.string.status_tracks_have_been_uploaded;
String successFormat = isNewMap ? context.getString(R.string.send_google_success_new)
: context.getString(R.string.send_google_success_existing);
String url = context.getString(SendType.MYMAPS.getServiceUrl());
statusMessage = String.format(successFormat, serviceName, url);
}
Log.d(TAG, "SendToMyMaps: Done: " + success);
progressIndicator.setProgressValue(100);
@@ -184,12 +189,11 @@ public class SendToMyMaps implements Runnable {
}
final boolean finalSuccess = success;
final int finalStatusMessageId = statusMessageId;
final String finalStatusMessage = statusMessage;
context.runOnUiThread(new Runnable() {
public void run() {
if (onCompletion != null) {
onCompletion.onSendCompleted(
mapId, finalSuccess, finalStatusMessageId);
onCompletion.onSendCompleted(mapId, finalSuccess, finalStatusMessage);
}
}
});
@@ -236,7 +240,7 @@ public class SendToMyMaps implements Runnable {
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
progressIndicator.setProgressMessage(
R.string.progress_message_reading_track);
context.getString(R.string.send_google_progress_reading_track));
Location lastLocation = null;
do {
if (totalLocationsRead % 100 == 0) {
@@ -310,7 +314,7 @@ public class SendToMyMaps implements Runnable {
private boolean prepareAndUploadPoints(Track track, List<Location> locations) {
progressIndicator.setProgressMessage(
R.string.progress_message_preparing_track);
context.getString(R.string.send_google_progress_preparing_track));
updateProgress();
int numLocations = locations.size();
@@ -324,13 +328,14 @@ public class SendToMyMaps implements Runnable {
ArrayList<Track> splitTracks = prepareLocations(track, locations);
// Start uploading them
progressIndicator.setProgressMessage(
R.string.progress_message_sending_mymaps);
String sendingFormat = context.getString(R.string.send_google_progress_sending);
String serviceName = context.getString(SendType.MYMAPS.getServiceName());
progressIndicator.setProgressMessage(String.format(sendingFormat, serviceName));
for (Track splitTrack : splitTracks) {
if (totalSegmentsUploaded > 1) {
splitTrack.setName(splitTrack.getName() + " "
+ String.format(
context.getString(R.string.part), totalSegmentsUploaded));
context.getString(R.string.send_google_track_part_label), totalSegmentsUploaded));
}
totalSegmentsUploaded++;
Log.d(TAG,
@@ -357,7 +362,7 @@ public class SendToMyMaps implements Runnable {
* Prepares a buffer of locations for transmission to google maps.
*
* @param track the original track with meta data
* @param buffer a buffer of locations on the track
* @param locations a buffer of locations on the track
* @return an array of tracks each with a sub section of the points in the
* original buffer
*/
@@ -15,8 +15,8 @@
*/
package com.google.android.apps.mytracks.io.backup;
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.util.FileUtils;
import com.google.android.maps.mytracks.R;
@@ -74,19 +74,19 @@ public class BackupActivityHelper {
*/
public void writeBackup() {
if (!fileUtils.isSdCardAvailable()) {
showToast(R.string.io_no_external_storage_found);
showToast(R.string.sd_card_error_no_storage);
return;
}
if (!backup.isBackupsDirectoryAvailable(true)) {
showToast(R.string.io_create_dir_failed);
showToast(R.string.sd_card_error_create_dir);
return;
}
final ProgressDialog progressDialog = ProgressDialog.show(
activity,
activity.getString(R.string.progress_title),
activity.getString(R.string.backup_write_progress_message),
activity.getString(R.string.generic_progress_title),
activity.getString(R.string.settings_backup_now_progress_message),
true);
// Do the writing in another thread
@@ -95,10 +95,10 @@ public class BackupActivityHelper {
public void run() {
try {
backup.writeToDefaultFile();
showToast(R.string.io_write_finished);
showToast(R.string.sd_card_success_write_file);
} catch (IOException e) {
Log.e(Constants.TAG, "Failed to write backup", e);
showToast(R.string.io_write_failed);
showToast(R.string.sd_card_error_write_file);
} finally {
dismissDialog(progressDialog);
}
@@ -114,25 +114,25 @@ public class BackupActivityHelper {
public void restoreBackup() {
// Get the list of existing backups
if (!fileUtils.isSdCardAvailable()) {
showToast(R.string.io_no_external_storage_found);
showToast(R.string.sd_card_error_no_storage);
return;
}
if (!backup.isBackupsDirectoryAvailable(false)) {
showToast(R.string.no_backups);
showToast(R.string.settings_backup_restore_no_backup);
return;
}
final Date[] backupDates = backup.getAvailableBackups();
if (backupDates == null || backupDates.length == 0) {
showToast(R.string.no_backups);
showToast(R.string.settings_backup_restore_no_backup);
return;
}
Arrays.sort(backupDates, REVERSE_DATE_ORDER);
// Show a confirmation dialog
Builder confirmationDialogBuilder = new AlertDialog.Builder(activity);
confirmationDialogBuilder.setMessage(R.string.restore_overwrites_warning);
confirmationDialogBuilder.setMessage(R.string.settings_backup_restore_confirm_message);
confirmationDialogBuilder.setCancelable(true);
confirmationDialogBuilder.setPositiveButton(android.R.string.yes,
new OnClickListener() {
@@ -148,7 +148,6 @@ public class BackupActivityHelper {
/**
* Shows a backup list for the user to pick, then restores it.
*
* @param backupDir the backup directory
* @param backupDates the list of available backup files
*/
private void pickBackupForRestore(final Date[] backupDates) {
@@ -167,7 +166,7 @@ public class BackupActivityHelper {
// Show a dialog for the user to pick which backup to restore
Builder dialogBuilder = new AlertDialog.Builder(activity);
dialogBuilder.setCancelable(true);
dialogBuilder.setTitle(R.string.select_backup_to_restore);
dialogBuilder.setTitle(R.string.settings_backup_restore_select_title);
dialogBuilder.setItems(backupDateStrs, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
@@ -179,16 +178,16 @@ public class BackupActivityHelper {
}
/**
* Shows a progress dialog, then starts restoring the backup osynchronously.
* Shows a progress dialog, then starts restoring the backup asynchronously.
*
* @param inputFile the file to restore from
* @param date the date
*/
private void restoreFromDateAsync(final Date date) {
// Show a progress dialog
ProgressDialog.show(
activity,
activity.getString(R.string.progress_title),
activity.getString(R.string.backup_import_progress_message),
activity.getString(R.string.generic_progress_title),
activity.getString(R.string.settings_backup_restore_progress_message),
true);
// Do the actual importing in another thread (don't block the UI)
@@ -197,10 +196,10 @@ public class BackupActivityHelper {
public void run() {
try {
backup.restoreFromDate(date);
showToast(R.string.io_read_finished);
showToast(R.string.sd_card_success_read_file);
} catch (IOException e) {
Log.e(Constants.TAG, "Failed to restore backup", e);
showToast(R.string.io_read_failed);
showToast(R.string.sd_card_error_read_file);
} finally {
// Data may have been restored, "reboot" the app to catch it
restartApplication();
@@ -15,63 +15,13 @@
*/
package com.google.android.apps.mytracks.io.backup;
import com.google.android.apps.mytracks.util.ApiFeatures;
import android.app.backup.BackupManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
/**
* Shared preferences listener which notifies the backup system about new data
* being available for backup.
* This class is API-version-safe and will provide a dummy implementation if
* the device doesn't support backup services.
*
* @author Rodrigo Damazio
*/
public abstract class BackupPreferencesListener
implements OnSharedPreferenceChangeListener {
/**
* Real implementation of the listener, which calls the {@link BackupManager}.
*/
private static class BackupPreferencesListenerImpl
extends BackupPreferencesListener {
private final BackupManager backupManager;
public BackupPreferencesListenerImpl(Context context) {
this.backupManager = new BackupManager(context);
}
@Override
public void onSharedPreferenceChanged(
SharedPreferences sharedPreferences, String key) {
backupManager.dataChanged();
}
}
/**
* Dummy implementation of the listener which does nothing.
*/
private static class DummyBackupPreferencesListener
extends BackupPreferencesListener {
@Override
public void onSharedPreferenceChanged(
SharedPreferences sharedPreferences, String key) {
// Do nothing
}
}
/**
* Creates and returns a proper instance of the listener for this device.
*/
public static BackupPreferencesListener create(
Context context, ApiFeatures apiFeatures) {
if (apiFeatures.hasBackup()) {
return new BackupPreferencesListenerImpl(context);
} else {
return new DummyBackupPreferencesListener();
}
}
public interface BackupPreferencesListener extends OnSharedPreferenceChangeListener {
}
@@ -0,0 +1,40 @@
/*
* 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.backup;
import android.app.backup.BackupManager;
import android.content.Context;
import android.content.SharedPreferences;
/**
* Implementation of {@link BackupPreferencesListener} that calls the
* {@link BackupManager}.
*
* @author Jimmy Shih
*/
public class BackupPreferencesListenerImpl implements BackupPreferencesListener {
private final BackupManager backupManager;
public BackupPreferencesListenerImpl(Context context) {
this.backupManager = new BackupManager(context);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
backupManager.dataChanged();
}
}
@@ -34,6 +34,7 @@ import com.google.wireless.gdata.parser.ParseException;
import com.google.wireless.gdata2.client.AuthenticationException;
import android.content.Context;
import android.text.format.DateFormat;
import android.util.Log;
import java.io.BufferedReader;
@@ -44,7 +45,6 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
import java.text.DateFormat;
import java.util.Date;
import java.util.concurrent.atomic.AtomicReference;
@@ -275,25 +275,22 @@ public class DocsHelper {
public void addTrackRow(Context context, AuthManager trixAuth,
String spreadsheetId, String worksheetId, Track track,
boolean metricUnits) throws IOException {
String worksheetUri = String.format(DOCS_SPREADSHEET_URL_FORMAT,
spreadsheetId, worksheetId);
TripStatistics stats = track.getStatistics();
String distanceUnit = context.getString(metricUnits ?
R.string.kilometer : R.string.mile);
R.string.unit_kilometer : R.string.unit_mile);
String speedUnit = context.getString(metricUnits ?
R.string.kilometer_per_hour : R.string.mile_per_hour);
R.string.unit_kilometer_per_hour : R.string.unit_mile_per_hour);
String elevationUnit = context.getString(metricUnits ?
R.string.meter : R.string.feet);
DateFormat trackDateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
R.string.unit_meter : R.string.unit_feet);
// Prepare the Post-Text we are going to send.
DocsTagBuilder tagBuilder = new DocsTagBuilder(metricUnits)
.append("name", track.getName())
.append("description", track.getDescription())
.append("date", trackDateFormat.format(new Date(stats.getStartTime())))
.append("date", getDisplayDate(context, stats.getStartTime()))
.append("totaltime", StringUtils.formatTimeAlwaysShowingHours(
stats.getTotalTime()))
.append("movingtime", StringUtils.formatTimeAlwaysShowingHours(
@@ -333,6 +330,21 @@ public class DocsHelper {
Log.i(Constants.TAG, "Post finished.");
}
/**
* Gets the display string for a time based on the phone's setting.
*
* @param context the context to obtain the phone's setting.
* @param time the time
* @return the display string of the time
*/
protected String getDisplayDate(Context context, long time) {
java.text.DateFormat dateFormat = DateFormat.getDateFormat(context);
java.text.DateFormat timeFormat = DateFormat.getTimeFormat(context);
Date startTime = new Date(time);
String dateString = dateFormat.format(startTime) + " " + timeFormat.format(startTime);
return dateString;
}
/**
* Writes spreadsheet row data to the indicated worksheet.
*
@@ -15,6 +15,7 @@
*/
package com.google.android.apps.mytracks.io.docs;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
@@ -45,8 +46,9 @@ import java.util.Locale;
* @author Matthew Simmons
*/
class DocsTagBuilder {
private static final DecimalFormatSymbols FORMAT_SYMBOLS =
DecimalFormatSymbols.getInstance(Locale.ENGLISH);
ApiFeatures.getInstance().getApiAdapter().getDecimalFormatSymbols(Locale.ENGLISH);
private static final NumberFormat LARGE_UNIT_FORMAT =
new DecimalFormat("#,###,###.00", FORMAT_SYMBOLS);
private static final NumberFormat SMALL_UNIT_FORMAT =
@@ -17,56 +17,21 @@ package com.google.android.apps.mytracks.io.file;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.ImportAllTracks;
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.Dialog;
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.
* An activity that imports a track from a file and displays the track in My Tracks.
*
* @author Rodrigo Damazio
*/
public class ImportActivity extends Activity {
private static final int PROGRESS_DIALOG = 1;
private static final int SUCCESS_DIALOG = 2;
private static final int FAILURE_DIALOG = 3;
private MyTracksProviderUtils providerUtils;
private ProgressDialog progressDialog;
private int resultMessage;
private long importedTrackIds[];
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
providerUtils = MyTracksProviderUtils.Factory.get(this);
}
@Override
public void onStart() {
super.onStart();
@@ -74,143 +39,20 @@ public class ImportActivity extends Activity {
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);
if (!(Intent.ACTION_VIEW.equals(action) || Intent.ACTION_ATTACH_DATA.equals(action))) {
Log.e(TAG, "Received an intent with unsupported action: " + intent);
finish();
return;
}
Log.i(TAG, "Importing GPX file at " + data);
startTrackImport(data.getPath());
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case PROGRESS_DIALOG:
progressDialog = new ProgressDialog(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));
return progressDialog;
case SUCCESS_DIALOG:
final Builder dialogBuilder = new AlertDialog.Builder(this);
dialogBuilder.setCancelable(true);
dialogBuilder.setPositiveButton(android.R.string.ok, new OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
finish();
}
});
dialogBuilder.setNeutralButton(R.string.import_show_track, new OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
showImportedTrack();
finish();
}
});
return dialogBuilder.create();
case FAILURE_DIALOG:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setNeutralButton(R.string.ok, new OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
finish();
}
});
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setTitle(R.string.error);
return builder.create();
if (!UriUtils.isFileUri(data)) {
Log.e(TAG, "Received an intent with unsupported data: " + intent);
finish();
return;
}
return null;
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
super.onPrepareDialog(id, dialog);
switch (id) {
case SUCCESS_DIALOG:
AlertDialog successDialog = (AlertDialog) dialog;
successDialog.setMessage(getString(R.string.import_success, importedTrackIds.length));
break;
case FAILURE_DIALOG:
AlertDialog failureDialog = (AlertDialog) dialog;
failureDialog.setMessage(getString(resultMessage));
break;
}
}
protected void showImportedTrack() {
long lastTrackId = importedTrackIds[importedTrackIds.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();
}
private void startTrackImport(final String fileName) {
showDialog(PROGRESS_DIALOG);
Thread t = new Thread() {
@Override
public void run() {
resultMessage = R.string.success;
importedTrackIds = null;
try {
try {
InputStream is = new FileInputStream(fileName);
importedTrackIds = GpxImporter.importGPXFile(is, providerUtils);
} catch (SAXException e) {
Log.e(TAG, "Caught an unexpected exception.", e);
resultMessage = R.string.error_generic;
} catch (ParserConfigurationException e) {
Log.e(TAG, "Caught an unexpected exception.", e);
resultMessage = R.string.error_generic;
} catch (IOException e) {
Log.e(TAG, "Caught an unexpected exception.", e);
resultMessage = R.string.error_unable_to_read_file;
} catch (NullPointerException e) {
Log.e(TAG, "Caught an unexpected exception.", e);
resultMessage = R.string.error_invalid_gpx_format;
} catch (OutOfMemoryError e) {
Log.e(TAG, "Caught an unexpected exception.", e);
resultMessage = R.string.error_out_of_memory;
}
boolean success = (importedTrackIds != null && importedTrackIds.length > 0);
showImportResult(success);
} finally {
runOnUiThread(new Runnable() {
public void run() {
dismissDialog(PROGRESS_DIALOG);
}
});
}
}
};
t.start();
}
private void showImportResult(boolean success) {
final int dialogToShow = success ? SUCCESS_DIALOG : FAILURE_DIALOG;
// Show a dialog telling the user about the import, and asking if he wishes
// to open the track right away.
runOnUiThread(new Runnable() {
@Override
public void run() {
showDialog(dialogToShow);
}
});
String path = data.getPath();
Log.i(TAG, "Importing GPX file at " + path);
new ImportAllTracks(this, path);
}
}
@@ -74,9 +74,9 @@ public class SaveActivity extends Activity {
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)) {
if (!getString(R.string.track_action_save).equals(action)
|| !TracksColumns.CONTENT_ITEMTYPE.equals(type)
|| !UriUtils.matchesContentUri(data, TracksColumns.CONTENT_URI)) {
Log.e(TAG, "Got bad save intent: " + intent);
finish();
return;
@@ -132,15 +132,16 @@ public class SaveActivity extends Activity {
// Share the file.
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_SUBJECT,
getResources().getText(R.string.send_track_subject).toString());
getResources().getText(R.string.share_track_subject).toString());
shareIntent.putExtra(Intent.EXTRA_TEXT,
getResources().getText(R.string.send_track_body_format)
getResources().getText(R.string.share_track_file_body_format)
.toString());
shareIntent.setType(format.getMimeType());
Uri u = Uri.fromFile(new File(writer.getAbsolutePath()));
shareIntent.putExtra(Intent.EXTRA_STREAM, u);
shareIntent.putExtra(getString(R.string.track_id_broadcast_extra), trackId);
startActivity(Intent.createChooser(shareIntent,
getResources().getText(R.string.share_track).toString()));
getResources().getText(R.string.share_track_picker_title).toString()));
}
private void showResultDialog() {
@@ -168,7 +169,7 @@ public class SaveActivity extends Activity {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(writer.getErrorMessage());
builder.setNeutralButton(R.string.ok, new OnClickListener() {
builder.setPositiveButton(R.string.generic_ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int arg1) {
dialog.dismiss();
@@ -184,7 +185,7 @@ public class SaveActivity extends Activity {
});
builder.setIcon(success ? android.R.drawable.ic_dialog_info :
android.R.drawable.ic_dialog_alert);
builder.setTitle(success ? R.string.success : R.string.error);
builder.setTitle(success ? R.string.generic_success_title : R.string.generic_error_title);
return builder.create();
}
@@ -227,7 +228,7 @@ public class SaveActivity extends Activity {
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.setAction(ctx.getString(R.string.track_action_save));
intent.setDataAndType(uri, TracksColumns.CONTENT_ITEMTYPE);
intent.putExtra(EXTRA_FILE_FORMAT, exportFormat.ordinal());
intent.putExtra(EXTRA_SHARE_FILE, shareFile);
@@ -17,6 +17,8 @@ package com.google.android.apps.mytracks.io.file;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.util.FileUtils;
import android.os.Environment;
import android.util.Log;
@@ -51,11 +53,9 @@ public class TempFileCleaner {
}
private void cleanTmpDirectory(String name) {
String sep = System.getProperty("file.separator");
cleanTmpDirectory(
new File(
Environment.getExternalStorageDirectory() + sep + name + sep
+ "tmp"));
FileUtils fileUtils = new FileUtils();
String dirName = fileUtils.buildExternalDirectoryPath(name, "tmp");
cleanTmpDirectory(new File(dirName));
}
// @VisibleForTesting
@@ -114,7 +114,7 @@ class TrackWriterImpl implements TrackWriter {
private void doWriteTrack() {
// Open the input and output
success = false;
errorMessage = R.string.error_track_does_not_exist;
errorMessage = R.string.export_error;
if (track != null) {
if (openFile()) {
try {
@@ -127,7 +127,7 @@ class TrackWriterImpl implements TrackWriter {
}
}
success = false;
errorMessage = R.string.error_operation_cancelled;
errorMessage = R.string.export_canceled;
}
}
}
@@ -208,7 +208,7 @@ class TrackWriterImpl implements TrackWriter {
writer.prepare(track, newOutputStream(fileName));
} catch (FileNotFoundException e) {
Log.e(Constants.TAG, "Failed to open output file.", e);
errorMessage = R.string.io_write_failed;
errorMessage = R.string.sd_card_error_write_file;
return false;
}
return true;
@@ -226,12 +226,12 @@ class TrackWriterImpl implements TrackWriter {
if (!fileUtils.isSdCardAvailable()) {
Log.i(Constants.TAG, "Could not find SD card.");
errorMessage = R.string.io_no_external_storage_found;
errorMessage = R.string.sd_card_error_no_storage;
return false;
}
if (!fileUtils.ensureDirectoryExists(directory)) {
Log.i(Constants.TAG, "Could not create export directory.");
errorMessage = R.string.io_create_dir_failed;
errorMessage = R.string.sd_card_error_create_dir;
return false;
}
@@ -297,7 +297,7 @@ class TrackWriterImpl implements TrackWriter {
writer.close();
success = true;
Log.d(Constants.TAG, "Done writing track.");
errorMessage = R.string.io_write_finished;
errorMessage = R.string.sd_card_success_write_file;
}
private void writeLocations() throws InterruptedException {
@@ -79,9 +79,9 @@ class WriteProgressController {
public ProgressDialog createProgressDialog() {
dialog = new ProgressDialog(activity);
dialog.setIcon(android.R.drawable.ic_dialog_info);
dialog.setTitle(activity.getString(R.string.progress_title));
dialog.setTitle(activity.getString(R.string.generic_progress_title));
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setMessage(activity.getString(R.string.write_progress_message));
dialog.setMessage(activity.getString(R.string.export_progress_message));
dialog.setIndeterminate(true);
dialog.setOnCancelListener(dialogCancelListener);
return dialog;
@@ -23,9 +23,17 @@ import com.google.wireless.gdata.parser.ParseException;
import com.google.wireless.gdata.serializer.GDataSerializer;
import android.text.TextUtils;
import android.util.Config;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.zip.GZIPInputStream;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
@@ -39,15 +47,6 @@ import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.zip.GZIPInputStream;
/**
* Implementation of a GDataClient using GoogleHttpClient to make HTTP requests.
* Always issues GETs and POSTs, using the X-HTTP-Method-Override header when a
@@ -58,8 +57,7 @@ public class AndroidGDataClient implements GDataClient {
private static final String TAG = "GDataClient";
private static final boolean DEBUG = false;
private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
private static final String X_HTTP_METHOD_OVERRIDE = "X-HTTP-Method-Override";
private static final int MAX_REDIRECTS = 10;
@@ -196,7 +194,7 @@ public class AndroidGDataClient implements GDataClient {
if (!TextUtils.isEmpty(authToken)) {
request.addHeader("Authorization", "GoogleLogin auth=" + authToken);
}
if (LOCAL_LOGV) {
if (DEBUG) {
for (Header h : request.getAllHeaders()) {
Log.v(TAG, h.getName() + ": " + h.getValue());
}
@@ -36,7 +36,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* handling. After a query is run, the wrapper can be queried about the error
* that occurred.
*
* @param C the GData service client
* @param <C> the GData service client
* @author Sandor Dornbush
*/
public class GDataWrapper<C> {
@@ -3,6 +3,7 @@ package com.google.android.apps.mytracks.io.mymaps;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.AuthManager;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.mytracks.R;
import com.google.wireless.gdata.client.HttpException;
@@ -119,7 +120,7 @@ public class MapsFacade {
MyMapsMapMetadata metaData = new MyMapsMapMetadata();
metaData.setTitle(title);
metaData.setDescription(description + " - "
+ category + " - " + context.getString(R.string.new_map_description));
+ category + " - " + StringUtils.getCreatedByMyTracks(context, false));
metaData.setSearchable(isPublic);
Entry entry = MyMapsGDataConverter.getMapEntryForMetadata(metaData);
Log.d(MyMapsConstants.TAG, "Title: " + entry.getTitle());
@@ -138,7 +139,7 @@ public class MapsFacade {
* @param mapId ID of the map to upload to
* @param trackName name of the track being started/ended
* @param trackDescription description of the track being started/ended
* @param location the location of the marker
* @param loc the location of the marker
* @param isStart true to add a start marker, false to add an end marker
* @return true on success, false otherwise
*/
@@ -185,7 +186,9 @@ public class MapsFacade {
/**
* Builds a placemark MyMapsFeature from a track.
*
* @param track the track
* @param trackName the track
* @param trackDescription the track description
* @param geoPoint the geo point
* @param isStart true if it's the start of the track, or false for end
* @return a MyMapsFeature
*/
@@ -199,16 +202,19 @@ public class MapsFacade {
iconUrl = END_ICON_URL;
}
String title = trackName + " "
+ (isStart ? context.getString(R.string.start)
: context.getString(R.string.end));
+ (isStart ? context.getString(R.string.marker_label_start)
: context.getString(R.string.marker_label_end));
String description = isStart ? "" : trackDescription;
return buildMyMapsPlacemarkFeature(title, description, iconUrl, geoPoint);
}
/**
* Builds a MyMapsFeature from a track.
*
* @param wpt the waypoint
* Builds a MyMapsFeature from a waypoint.
*
* @param title the title
* @param description the description
* @param iconUrl the icon url
* @param geoPoint the waypoint
* @return a MyMapsFeature
*/
private static MyMapsFeature buildMyMapsPlacemarkFeature(
@@ -336,7 +342,8 @@ public class MapsFacade {
/**
* Builds a MyMapsFeature from a track.
*
* @param track the track
* @param trackName the track name
* @param locations locations on the track
* @return a MyMapsFeature
*/
private static MyMapsFeature buildMyMapsLineFeature(String trackName,
@@ -12,12 +12,12 @@ import com.google.wireless.gdata.serializer.xml.XmlEntryGDataSerializer;
import android.util.Log;
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.xmlpull.v1.XmlPullParserException;
/**
* Factory of Xml parsers for gdata maps data.
*/
@@ -39,7 +39,7 @@ class XmlMapsGDataParserFactory implements GDataParserFactory {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@SuppressWarnings("unchecked")
@Override
public GDataParser createParser(Class cls, InputStream is)
throws ParseException {
@@ -60,7 +60,11 @@ class XmlMapsGDataSerializer extends XmlEntryGDataSerializer {
declareEntryNamespaces(serializer);
serializer.startTag(XmlGDataParser.NAMESPACE_ATOM_URI, "entry");
stream = MapsClient.LOG_COMMUNICATION ? printStream : out;
if (MapsClient.LOG_COMMUNICATION) {
stream = printStream;
} else {
stream = out;
}
serializeEntryContents(serializer, format);
serializer.endTag(XmlGDataParser.NAMESPACE_ATOM_URI, "entry");
@@ -72,16 +72,16 @@ public class ResultDialogFactory {
.setView(makeDialogContent(activity, results, success));
if (success) {
builder.setTitle(R.string.success);
builder.setTitle(R.string.generic_success_title);
builder.setIcon(android.R.drawable.ic_dialog_info);
} else {
builder.setTitle(R.string.error);
builder.setTitle(R.string.generic_error_title);
builder.setIcon(android.R.drawable.ic_dialog_alert);
}
builder.setPositiveButton(activity.getString(R.string.ok), onOkClickListener);
builder.setPositiveButton(activity.getString(R.string.generic_ok), onOkClickListener);
if (onShareClickListener != null) {
builder.setNeutralButton(activity.getString(R.string.share_track),
builder.setNegativeButton(activity.getString(R.string.send_google_result_share_url),
onShareClickListener);
}
@@ -163,7 +163,7 @@ public class SendActivity extends Activity implements ProgressIndicator {
tracker = GoogleAnalyticsTracker.getInstance();
// Start the tracker in manual dispatch mode...
tracker.start(getString(R.string.google_analytics_id),
tracker.start(getString(R.string.my_tracks_analytics_id),
getApplicationContext());
tracker.setProductVersion("android-mytracks",
SystemUtils.getMyTracksVersion(this));
@@ -392,7 +392,7 @@ public class SendActivity extends Activity implements ProgressIndicator {
progressDialog = new ProgressDialog(this);
progressDialog.setCancelable(false);
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
progressDialog.setTitle(R.string.progress_title);
progressDialog.setTitle(R.string.generic_progress_title);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setMax(100);
progressDialog.setProgress(0);
@@ -403,8 +403,7 @@ public class SendActivity extends Activity implements ProgressIndicator {
private SendState authenticateToGoogleMaps() {
Log.d(TAG, "SendActivity.authenticateToGoogleMaps");
progressDialog.setProgress(0);
progressDialog.setMessage(getString(
R.string.progress_message_authenticating_mymaps));
progressDialog.setMessage(getAuthenticatingProgressMessage(SendType.MYMAPS));
authenticate(Constants.AUTHENTICATE_TO_MY_MAPS, MyMapsConstants.SERVICE_NAME);
// AUTHENTICATE_TO_MY_MAPS callback calls sendToGoogleMaps
return SendState.NOT_READY;
@@ -429,9 +428,9 @@ public class SendActivity extends Activity implements ProgressIndicator {
SendToMyMaps.OnSendCompletedListener onCompletion = new SendToMyMaps.OnSendCompletedListener() {
@Override
public void onSendCompleted(String mapId, boolean success, int statusMessage) {
public void onSendCompleted(String mapId, boolean success, String statusMessage) {
// TODO: Use this message
sendToMyMapsMessage = getString(statusMessage);
sendToMyMapsMessage = statusMessage;
sendToMyMapsSuccess = success;
if (sendToMyMapsSuccess) {
sendToMyMapsMapId = mapId;
@@ -479,8 +478,7 @@ public class SendActivity extends Activity implements ProgressIndicator {
private SendState authenticateToFusionTables() {
progressDialog.setProgress(0);
progressDialog.setMessage(getString(
R.string.progress_message_authenticating_fusiontables));
progressDialog.setMessage(getAuthenticatingProgressMessage(SendType.FUSION_TABLES));
authenticate(Constants.AUTHENTICATE_TO_FUSION_TABLES, SendToFusionTables.SERVICE_ID);
// AUTHENTICATE_TO_FUSION_TABLES callback calls sendToFusionTables
return SendState.NOT_READY;
@@ -491,10 +489,9 @@ public class SendActivity extends Activity implements ProgressIndicator {
OnSendCompletedListener onCompletion = new OnSendCompletedListener() {
@Override
public void onSendCompleted(String tableId, boolean success,
int statusMessage) {
public void onSendCompleted(String tableId, boolean success, String statusMessage) {
// TODO: Use this message
sendToFusionTablesMessage = getString(statusMessage);
sendToFusionTablesMessage = statusMessage;
sendToFusionTablesSuccess = success;
if (sendToFusionTablesSuccess) {
sendToFusionTablesTableId = tableId;
@@ -536,8 +533,7 @@ public class SendActivity extends Activity implements ProgressIndicator {
private SendState authenticateToGoogleDocs() {
setProgressValue(0);
setProgressMessage(
R.string.progress_message_authenticating_docs);
setProgressMessage(getAuthenticatingProgressMessage(SendType.DOCS));
authenticate(Constants.AUTHENTICATE_TO_DOCLIST, SendToDocs.GDATA_SERVICE_NAME_DOCLIST);
// AUTHENTICATE_TO_DOCLIST callback calls authenticateToGoogleTrix
return SendState.NOT_READY;
@@ -545,8 +541,7 @@ public class SendActivity extends Activity implements ProgressIndicator {
private SendState authenticateToGoogleTrix() {
setProgressValue(30);
setProgressMessage(
R.string.progress_message_authenticating_docs);
setProgressMessage(getAuthenticatingProgressMessage(SendType.DOCS));
authenticate(Constants.AUTHENTICATE_TO_TRIX, SendToDocs.GDATA_SERVICE_NAME_TRIX);
// AUTHENTICATE_TO_TRIX callback calls sendToGoogleDocs
return SendState.NOT_READY;
@@ -557,7 +552,9 @@ public class SendActivity extends Activity implements ProgressIndicator {
tracker.trackPageView("/send/docs");
setProgressValue(50);
setProgressMessage(R.string.progress_message_sending_docs);
String format = getString(R.string.send_google_progress_sending);
String serviceName = getString(SendType.DOCS.getServiceName());
setProgressMessage(String.format(format, serviceName));
final SendToDocs sender = new SendToDocs(this,
authMap.get(SendToDocs.GDATA_SERVICE_NAME_TRIX),
authMap.get(SendToDocs.GDATA_SERVICE_NAME_DOCLIST),
@@ -692,15 +689,15 @@ public class SendActivity extends Activity implements ProgressIndicator {
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);
getResources().getText(R.string.share_track_url_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());
getResources().getText(R.string.share_track_subject).toString());
shareIntent.putExtra(Intent.EXTRA_TEXT, msg);
startActivity(Intent.createChooser(shareIntent,
getResources().getText(R.string.share_map).toString()));
getResources().getText(R.string.share_track_picker_title).toString()));
}
protected String getFusionTablesUrl(long trackId) {
@@ -883,12 +880,24 @@ public class SendActivity extends Activity implements ProgressIndicator {
sendToFusionTablesTableId = null;
}
/**
* Gets a progress message indicating My Tracks is authenticating to a
* service.
*
* @param type the type of service
*/
private String getAuthenticatingProgressMessage(SendType type) {
String format = getString(R.string.send_google_progress_authenticating);
String serviceName = getString(type.getServiceName());
return String.format(format, serviceName);
}
@Override
public void setProgressMessage(final int resId) {
public void setProgressMessage(final String message) {
runOnUiThread(new Runnable() {
public void run() {
if (progressDialog != null) {
progressDialog.setMessage(getString(resId));
progressDialog.setMessage(message);
}
}
});
@@ -45,7 +45,6 @@ public class SendDialog extends Dialog {
private CheckBox sendToMyMapsCheckBox;
private CheckBox sendToFusionTablesCheckBox;
private CheckBox sendToDocsCheckBox;
private RadioButton sendStatsAndPointsRadioButton;
private OnClickListener clickListener;
public SendDialog(Context context) {
@@ -69,9 +68,6 @@ public class SendDialog extends Dialog {
(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() {
@@ -124,14 +120,6 @@ public class SendDialog extends Dialog {
createNewMapRadioButton.setChecked(
!prefs.getBoolean(
getContext().getString(R.string.pick_existing_map_key), false));
sendStatsAndPointsRadioButton.setChecked(
prefs.getBoolean(
getContext().getString(R.string.send_stats_and_points_key),
false));
sendStatsRadioButton.setChecked(
!prefs.getBoolean(
getContext().getString(R.string.send_stats_and_points_key),
false));
}
}
@@ -154,9 +142,6 @@ public class SendDialog extends Dialog {
editor.putBoolean(
getContext().getString(R.string.pick_existing_map_key),
pickMapRadioButton.isChecked());
editor.putBoolean(
getContext().getString(R.string.send_stats_and_points_key),
sendStatsAndPointsRadioButton.isChecked());
editor.commit();
}
}
@@ -182,8 +167,4 @@ public class SendDialog extends Dialog {
public boolean getCreateNewMap() {
return createNewMapRadioButton.isChecked();
}
public boolean getSendStatsAndPoints() {
return sendStatsAndPointsRadioButton.isChecked();
}
}
@@ -23,12 +23,12 @@ import com.google.android.maps.mytracks.R;
* @author Matthew Simmons
*/
public enum SendType {
MYMAPS(R.string.sendtogoogle_google_mymaps,
R.string.sendtogoogle_google_mymaps_url),
FUSION_TABLES(R.string.sendtogoogle_google_fusiontables,
R.string.sendtogoogle_google_fusiontables_url),
DOCS(R.string.sendtogoogle_google_docs,
R.string.sendtogoogle_google_docs_url);
MYMAPS(R.string.send_google_my_maps,
R.string.send_google_my_maps_url),
FUSION_TABLES(R.string.send_google_fusion_tables,
R.string.send_google_fusion_tables_url),
DOCS(R.string.send_google_docs,
R.string.send_google_docs_url);
private int serviceName;
private int serviceUrl;
@@ -57,7 +57,7 @@ public class SingleColorTrackPathPainter implements TrackPathPainter {
final Point pt = new Point();
// Loop over track points.
int numPoints = points.size();
path = new Path();
path = newPath();
path.incReserve(numPoints);
for (int i = startLocationIdx; i < numPoints ; ++i) {
@@ -104,4 +104,9 @@ public class SingleColorTrackPathPainter implements TrackPathPainter {
public Path getLastPath() {
return path;
}
// Visible for testing
public Path newPath() {
return new Path();
}
}
@@ -49,12 +49,13 @@ public class TrackPathPainterFactory {
String colorMode = prefs.getString(context.getString(R.string.track_color_mode_key), null);
Log.i(TAG, "Creating track path painter of type: " + colorMode);
if (colorMode == null || colorMode.equals(context.getString(R.string.track_color_mode_none))) {
if (colorMode == null
|| colorMode.equals(context.getString(R.string.display_track_color_value_none))) {
return new SingleColorTrackPathPainter(context);
} else if (colorMode.equals(context.getString(R.string.track_color_mode_fixed))) {
} else if (colorMode.equals(context.getString(R.string.display_track_color_value_fixed))) {
return new DynamicSpeedTrackPathPainter(context,
new FixedSpeedTrackPathDescriptor(context));
} else if (colorMode.equals(context.getString(R.string.track_color_mode_dynamic))) {
} else if (colorMode.equals(context.getString(R.string.display_track_color_value_dynamic))) {
return new DynamicSpeedTrackPathPainter(context,
new DynamicSpeedTrackPathDescriptor(context));
} else {
@@ -0,0 +1,121 @@
/*
* 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.widgets.TrackWidgetProvider;
import com.google.android.maps.mytracks.R;
import android.app.IntentService;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
/**
* A service to control starting and stopping of a recording. This service,
* through the AndroidManifest.xml, is configured to only allow components of
* the same application to invoke it. Thus this service can be used my MyTracks
* app widget, {@link TrackWidgetProvider}, but not by other applications. This
* application delegates starting and stopping a recording to
* {@link TrackRecordingService} using RPC calls.
*
* @author jshih@google.com (Jimmy Shih)
*/
public class ControlRecordingService extends IntentService implements ServiceConnection {
private ITrackRecordingService trackRecordingService;
private boolean connected = false;
public ControlRecordingService() {
super(ControlRecordingService.class.getSimpleName());
}
@Override
public void onCreate() {
super.onCreate();
Intent newIntent = new Intent(this, TrackRecordingService.class);
startService(newIntent);
bindService(newIntent, this, 0);
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
trackRecordingService = ITrackRecordingService.Stub.asInterface(service);
notifyConnected();
}
@Override
public void onServiceDisconnected(ComponentName name) {
connected = false;
}
/**
* Notifies all threads that connection to {@link TrackRecordingService} is
* available.
*/
private synchronized void notifyConnected() {
connected = true;
notifyAll();
}
/**
* Waits until the connection to {@link TrackRecordingService} is available.
*/
private synchronized void waitConnected() {
while (!connected) {
try {
wait();
} catch (InterruptedException e) {
// can safely ignore
}
}
}
@Override
protected void onHandleIntent(Intent intent) {
waitConnected();
String action = intent.getAction();
if (action != null) {
try {
if (action.equals(getString(R.string.track_action_start))) {
trackRecordingService.startNewTrack();
} else if (action.equals(getString(R.string.track_action_end))) {
trackRecordingService.endCurrentTrack();
}
} catch (RemoteException e) {
Log.d(TAG, "ControlRecordingService onHandleIntent RemoteException", e);
}
}
unbindService(this);
connected = false;
}
@Override
public void onDestroy() {
super.onDestroy();
if (connected) {
unbindService(this);
connected = false;
}
}
}
@@ -51,7 +51,7 @@ class DefaultTrackNameFactory {
SimpleDateFormat formatter = new SimpleDateFormat(TIMESTAMP_DATE_FORMAT);
return formatter.format(new Date(startTime));
} else {
return String.format(context.getString(R.string.new_track), trackId);
return String.format(context.getString(R.string.track_name_format), trackId);
}
}
@@ -37,7 +37,7 @@ import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.apps.mytracks.util.ApiPlatformAdapter;
import com.google.android.apps.mytracks.util.ApiLevelAdapter;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
@@ -56,11 +56,13 @@ import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.Process;
import android.util.Log;
import java.util.Timer;
@@ -229,6 +231,13 @@ public class TrackRecordingService extends Service {
* Application lifetime events:
*/
/*
* Note that this service, through the AndroidManifest.xml, is configured to
* allow both MyTracks and third party apps to invoke it. For the onCreate
* callback, we cannot tell whether the caller is MyTracks or a third party
* app, thus it cannot start/stop a recording or write/update MyTracks
* database. However, it can resume a recording.
*/
@Override
public void onCreate() {
super.onCreate();
@@ -268,49 +277,41 @@ public class TrackRecordingService extends Service {
showNotification();
}
/*
* Note that this service, through the AndroidManifest.xml, is configured to
* allow both MyTracks and third party apps to invoke it. For the onStart
* callback, we cannot tell whether the caller is MyTracks or a third party
* app, thus it cannot start/stop a recording or write/update MyTracks
* database. However, it can resume a recording.
*/
@Override
public void onStart(Intent intent, int startId) {
handleStartCommand(intent, startId);
}
/*
* Note that this service, through the AndroidManifest.xml, is configured to
* allow both MyTracks and third party apps to invoke it. For the
* onStartCommand callback, we cannot tell whether the caller is MyTracks or a
* third party app, thus it cannot start/stop a recording or write/update
* MyTracks database. However, it can resume a recording.
*/
@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);
Log.d(TAG, "TrackRecordingService.handleStartCommand: " + startId);
if (intent == null)
if (intent == null) {
return;
}
// Check if called on phone reboot with resume intent.
if (intent.getBooleanExtra(RESUME_TRACK_EXTRA_NAME, false)) {
resumeTrack(startId);
} else {
// Process actions for controlling the service.
processStartStopIntent(intent);
}
}
private void processStartStopIntent(Intent intent) {
String action = intent.getAction();
if (isNewTrackAction(action)) {
if (!isTrackInProgress()) {
boolean selectNewTrack = intent.getBooleanExtra(getString(R.string.select_new_track_extra), false);
startNewTrack();
if (selectNewTrack) {
prefManager.setSelectedTrack(recordingTrackId);
}
}
} else if (isEndTrackAction(action)) {
if (isTrackInProgress()) {
endCurrentTrack();
}
}
}
@@ -318,14 +319,6 @@ public class TrackRecordingService extends Service {
return recordingTrackId != -1 || isRecording;
}
private boolean isNewTrackAction(String action) {
return getString(R.string.start_new_track_action).equals(action);
}
private boolean isEndTrackAction(String action) {
return getString(R.string.end_current_track_action).equals(action);
}
private void resumeTrack(int startId) {
Log.d(TAG, "TrackRecordingService: requested resume");
@@ -492,8 +485,8 @@ public class TrackRecordingService extends Service {
* Shows the notification message and icon in the notification bar.
*/
private void showNotification() {
final ApiPlatformAdapter apiPlatformAdapter =
ApiFeatures.getInstance().getApiPlatformAdapter();
final ApiLevelAdapter apiLevelAdapter =
ApiFeatures.getInstance().getApiAdapter();
if (isRecording) {
Notification notification = new Notification(
R.drawable.arrow_320, null /* tickerText */,
@@ -501,13 +494,13 @@ public class TrackRecordingService extends Service {
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.setLatestEventInfo(this, getString(R.string.my_tracks_app_name),
getString(R.string.track_record_notification), contentIntent);
notification.flags += Notification.FLAG_NO_CLEAR;
apiPlatformAdapter.startForeground(this, notificationManager, 1,
apiLevelAdapter.startForeground(this, notificationManager, 1,
notification);
} else {
apiPlatformAdapter.stopForeground(this, notificationManager, 1);
apiLevelAdapter.stopForeground(this, notificationManager, 1);
}
}
@@ -566,15 +559,20 @@ public class TrackRecordingService extends Service {
Log.d(TAG,
"Location listener now unregistered w/ TrackRecordingService.");
}
private String getDefaultActivityType(Context context) {
SharedPreferences prefs = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
return prefs.getString(context.getString(R.string.default_activity_key), "");
}
/*
* Recording lifecycle.
*/
public long startNewTrack() {
private long startNewTrack() {
Log.d(TAG, "TrackRecordingService.startNewTrack");
if (isTrackInProgress()) {
throw new IllegalStateException("A track is already in progress!");
return -1L;
}
long startTime = System.currentTimeMillis();
@@ -589,6 +587,7 @@ public class TrackRecordingService extends Service {
track.setId(recordingTrackId);
track.setName(new DefaultTrackNameFactory(this).newTrackName(
recordingTrackId, startTime));
track.setCategory(getDefaultActivityType(this));
isRecording = true;
isMoving = true;
@@ -964,12 +963,12 @@ public class TrackRecordingService extends Service {
private void buildMarker(Waypoint wpt, WaypointCreationRequest request) {
wpt.setType(Waypoint.TYPE_WAYPOINT);
if (request.getIconUrl() == null) {
wpt.setIcon(getString(R.string.waypoint_icon_url));
wpt.setIcon(getString(R.string.marker_waypoint_icon_url));
} else {
wpt.setIcon(request.getIconUrl());
}
if (request.getName() == null) {
wpt.setName(getString(R.string.waypoint));
wpt.setName(getString(R.string.marker_type_waypoint));
} else {
wpt.setName(request.getName());
}
@@ -982,8 +981,7 @@ public class TrackRecordingService extends Service {
* Build a statistics marker.
* A statistics marker holds the stats for the* last segment up to this marker.
*
* @param Waypoint The waypoint which will be populated with stats data.
* @return the unique id of the inserted marker
* @param waypoint The waypoint which will be populated with stats data.
*/
private void buildStatisticsMarker(Waypoint waypoint) {
StringUtils utils = new StringUtils(TrackRecordingService.this);
@@ -998,10 +996,10 @@ public class TrackRecordingService extends Service {
// Set the rest of the waypoint data
waypoint.setType(Waypoint.TYPE_STATISTICS);
waypoint.setName(getString(R.string.statistics));
waypoint.setName(getString(R.string.marker_type_statistics));
waypoint.setStatistics(waypointStatsBuilder.getStatistics());
waypoint.setDescription(utils.generateWaypointDescription(waypoint));
waypoint.setIcon(getString(R.string.stats_icon_url));
waypoint.setIcon(getString(R.string.marker_statistics_icon_url));
waypoint.setStartId(providerUtils.getLastLocationId(recordingTrackId));
@@ -1012,7 +1010,7 @@ public class TrackRecordingService extends Service {
private void endCurrentTrack() {
Log.d(TAG, "TrackRecordingService.endCurrentTrack");
if (!isTrackInProgress()) {
throw new IllegalStateException("No recording track in progress!");
return;
}
announcementExecutor.shutdown();
@@ -1053,12 +1051,15 @@ public class TrackRecordingService extends Service {
}
private void sendTrackBroadcast(int actionResId, long trackId) {
Intent broadcastIntent =
new Intent()
.setAction(getString(actionResId))
.putExtra(getString(R.string.track_id_broadcast_extra), trackId);
sendBroadcast(broadcastIntent,
getString(R.string.broadcast_notifications_permission));
Intent broadcastIntent = new Intent()
.setAction(getString(actionResId))
.putExtra(getString(R.string.track_id_broadcast_extra), trackId);
sendBroadcast(broadcastIntent, getString(R.string.permission_notification_value));
SharedPreferences sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
if (sharedPreferences.getBoolean(getString(R.string.allow_access_key), false)) {
sendBroadcast(broadcastIntent, getString(R.string.broadcast_notifications_permission));
}
}
/*
@@ -1190,29 +1191,58 @@ public class TrackRecordingService extends Service {
}
}
/**
* Checks if the service is available. If not, throws an
* {@link IllegalStateException}.
*/
private void checkService() {
if (service == null) {
throw new IllegalStateException("The service has been already detached!");
}
}
/**
* Returns true if the RPC caller is from the same application or if the
* "Allow access" setting indicates that another app can invoke this service's
* RPCs.
*/
private boolean canAccess() {
// As a precondition for access, must check if the service is available.
checkService();
if (Process.myPid() == Binder.getCallingPid()) {
return true;
} else {
SharedPreferences sharedPreferences = service.getSharedPreferences(
Constants.SETTINGS_NAME, 0);
return sharedPreferences.getBoolean(service.getString(R.string.allow_access_key), false);
}
}
// Service method delegates.
@Override
public boolean isRecording() {
checkService();
if (!canAccess()) {
return false;
}
return service.isRecording();
}
@Override
public long getRecordingTrackId() {
checkService();
if (!canAccess()) {
return -1L;
}
return service.recordingTrackId;
}
@Override
public long startNewTrack() {
checkService();
if (!canAccess()) {
return -1L;
}
return service.startNewTrack();
}
@@ -1223,25 +1253,33 @@ public class TrackRecordingService extends Service {
* @return the unique ID of the inserted marker
*/
public long insertWaypoint(WaypointCreationRequest request) {
checkService();
if (!canAccess()) {
return -1L;
}
return service.insertWaypoint(request);
}
@Override
public void endCurrentTrack() {
checkService();
if (!canAccess()) {
return;
}
service.endCurrentTrack();
}
@Override
public void recordLocation(Location loc) {
checkService();
if (!canAccess()) {
return;
}
service.locationListener.onLocationChanged(loc);
}
@Override
public byte[] getSensorData() {
checkService();
if (!canAccess()) {
return null;
}
if (service.sensorManager == null) {
Log.d(TAG, "No sensor manager for data.");
return null;
@@ -1255,7 +1293,9 @@ public class TrackRecordingService extends Service {
@Override
public int getSensorState() {
checkService();
if (!canAccess()) {
return Sensor.SensorState.NONE.getNumber();
}
if (service.sensorManager == null) {
Log.d(TAG, "No sensor manager for data.");
return Sensor.SensorState.NONE.getNumber();
@@ -18,6 +18,7 @@ package com.google.android.apps.mytracks.services.sensors;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.util.ApiFeatures;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@@ -68,8 +69,8 @@ public class BluetoothConnectionManager {
/**
* Constructor. Prepares a new BluetoothSensor session.
*
* @param context The UI Activity Context
* @param handler A Handler to send messages back to the UI Activity
* @param parser A message parser
*/
public BluetoothConnectionManager(Handler handler, MessageParser parser) {
this.adapter = BluetoothAdapter.getDefaultAdapter();
@@ -261,22 +262,29 @@ public class BluetoothConnectionManager {
}
private BluetoothSocket getSocket() throws IOException {
try {
Class<? extends BluetoothDevice> c = device.getClass();
Method insecure = c.getMethod("createInsecureRfcommSocket",
Integer.class);
insecure.setAccessible(true);
return (BluetoothSocket) insecure.invoke(device, 1);
} catch (SecurityException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
} catch (NoSuchMethodException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
} catch (IllegalArgumentException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
} catch (IllegalAccessException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
} catch (InvocationTargetException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
if (ApiFeatures.getInstance().hasBluetoothDeviceCreateInsecureRfcommSocketToServiceRecord()) {
try {
return device.createInsecureRfcommSocketToServiceRecord(SPP_UUID);
} catch (IOException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
}
} else {
try {
Class<? extends BluetoothDevice> c = device.getClass();
Method insecure = c.getMethod("createInsecureRfcommSocket", Integer.class);
insecure.setAccessible(true);
return (BluetoothSocket) insecure.invoke(device, 1);
} catch (SecurityException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
} catch (NoSuchMethodException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
} catch (IllegalArgumentException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
} catch (IllegalAccessException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
} catch (InvocationTargetException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
}
}
return device.createRfcommSocketToServiceRecord(SPP_UUID);
}
@@ -15,6 +15,8 @@
*/
package com.google.android.apps.mytracks.services.sensors;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.maps.mytracks.R;
@@ -24,10 +26,13 @@ import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;
import java.util.ArrayList;
/**
* Manage the connection to a bluetooth sensor.
*
@@ -36,8 +41,8 @@ import android.widget.Toast;
public class BluetoothSensorManager extends SensorManager {
// Local Bluetooth adapter
private BluetoothAdapter bluetoothAdapter = null;
private static final BluetoothAdapter bluetoothAdapter = getDefaultBluetoothAdapter();
// Member object for the sensor threads and connections.
private BluetoothConnectionManager connectionManager = null;
@@ -54,8 +59,6 @@ public class BluetoothSensorManager extends SensorManager {
Context context, MessageParser parser) {
this.context = context;
this.parser = parser;
// Get local Bluetooth adapter
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// If BT is not available or not enabled quit.
if (!isEnabled()) {
return;
@@ -71,6 +74,50 @@ public class BluetoothSensorManager extends SensorManager {
connectionManager = new BluetoothConnectionManager(messageHandler, parser);
}
/**
* Code for assigning the local bluetooth adapter
*
* @return The default bluetooth adapter, if one is available, NULL if it isn't.
*/
private static BluetoothAdapter getDefaultBluetoothAdapter() {
// Check if the calling thread is the main application thread,
// if it is, do it directly.
if (Thread.currentThread().equals(Looper.getMainLooper().getThread())) {
return BluetoothAdapter.getDefaultAdapter();
}
// If the calling thread, isn't the main application thread,
// then get the main application thread to return the default adapter.
final ArrayList<BluetoothAdapter> adapters = new ArrayList<BluetoothAdapter>(1);
final Object mutex = new Object();
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
adapters.add(BluetoothAdapter.getDefaultAdapter());
synchronized (mutex) {
mutex.notify();
}
}
});
while (adapters.isEmpty()) {
synchronized (mutex) {
try {
mutex.wait(1000L);
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while waiting for default bluetooth adapter", e);
}
}
}
if (adapters.get(0) == null) {
Log.w(TAG, "No bluetooth adapter found!");
}
return adapters.get(0);
}
public boolean isEnabled() {
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
}
@@ -125,7 +172,7 @@ public class BluetoothSensorManager extends SensorManager {
}
// The Handler that gets information back from the BluetoothSensorService
private final Handler messageHandler = new Handler() {
private final Handler messageHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -48,8 +48,8 @@ public class PolarMessageParser implements MessageParser {
* offset 2 = check byte, 255 - packet length.
* offset 3 = sequence byte, range from 0 to 15.
*
* @param an array of bytes to parse
* @param buffer offset to beginning of packet.
* @param buffer an array of bytes to parse
* @param i buffer offset to beginning of packet.
* @return whether buffer has a valid packet at offset i
*/
private boolean packetValid (byte[] buffer, int i) {
@@ -100,7 +100,7 @@ public class PolarMessageParser implements MessageParser {
/**
* Applies packet validation rules to buffer
*
* @param an array of bytes to parse
* @param buffer an array of bytes to parse
* @return whether buffer has a valid packet starting at index zero
*/
@Override
@@ -124,7 +124,7 @@ public class PolarMessageParser implements MessageParser {
/**
* Searches buffer for the beginning of a valid packet.
*
* @param an array of bytes to parse
* @param buffer an array of bytes to parse
* @return index to beginning of good packet, or -1 if none found.
*/
@Override
@@ -53,13 +53,13 @@ public class SensorManagerFactory {
if (sensor == null) {
return null;
} else if (sensor.equals(context.getString(R.string.ant_sensor_type))) {
} else if (sensor.equals(context.getString(R.string.sensor_type_value_ant))) {
return new AntDirectSensorManager(context);
} else if (sensor.equals(context.getString(R.string.srm_ant_bridge_sensor_type))) {
} else if (sensor.equals(context.getString(R.string.sensor_type_value_srm_ant_bridge))) {
return new AntSRMSensorManager(context);
} else if (sensor.equals(context.getString(R.string.zephyr_sensor_type))) {
} else if (sensor.equals(context.getString(R.string.sensor_type_value_zephyr))) {
return new ZephyrSensorManager(context);
} else if (sensor.equals(context.getString(R.string.polar_sensor_type))) {
} else if (sensor.equals(context.getString(R.string.sensor_type_value_polar))) {
return new PolarSensorManager(context);
} else {
Log.w(Constants.TAG, "Unable to find sensor type: " + sensor);
@@ -15,11 +15,11 @@
*/
package com.google.android.apps.mytracks.services.sensors;
import android.content.Context;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.maps.mytracks.R;
import android.content.Context;
/**
* A collection of methods for message parsers.
*
@@ -58,12 +58,12 @@ public class SensorUtils {
}
/**
* Returns CRC8 (polynomial 0x8C) from byte array buffer[start] until
* (including) buffer[end]
* Returns CRC8 (polynomial 0x8C) from byte array buffer[start] to
* (excluding) buffer[start + length]
*
* @param buffer the byte array of data (payload)
* @param start the position in the byte array where the payload begins
* @param end the position in the byte array where the payload ends
* @param length the length
* @return CRC8 value
*/
public static byte getCrc8(byte[] buffer, int start, int length) {
@@ -99,15 +99,15 @@ public class SensorUtils {
public static String getStateAsString(Sensor.SensorState state, Context c) {
switch (state) {
case NONE:
return c.getString(R.string.none);
return c.getString(R.string.sensor_type_value_none);
case CONNECTING:
return c.getString(R.string.connecting);
return c.getString(R.string.sensor_state_connecting);
case CONNECTED:
return c.getString(R.string.connected);
return c.getString(R.string.sensor_state_connected);
case DISCONNECTED:
return c.getString(R.string.disconnected);
return c.getString(R.string.sensor_state_disconnected);
case SENDING:
return c.getString(R.string.sending);
return c.getString(R.string.sensor_state_sending);
default:
return "";
}
@@ -16,6 +16,7 @@
package com.google.android.apps.mytracks.services.sensors;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.util.ApiFeatures;
import java.util.Arrays;
@@ -23,7 +24,7 @@ import java.util.Arrays;
* An implementation of a Sensor MessageParser for Zephyr.
*
* @author Sandor Dornbush
* @author Dominik Ršttsches
* @author Dominik Ršttsches
*/
public class ZephyrMessageParser implements MessageParser {
@@ -61,8 +62,8 @@ public class ZephyrMessageParser implements MessageParser {
// 0x1A00316550003162 produces erroneous values for Cadence and needs
// a workaround based on the stride counter.
// Firmware values range from field 3 to 10 (inclusive) of the byte buffer.
byte[] hardwareFirmwareId = Arrays.copyOfRange(buffer, 3, 11);
byte[] hardwareFirmwareId = ApiFeatures.getInstance().getApiAdapter()
.copyByteArray(buffer, 3, 11);
Sensor.SensorData.Builder cadence = Sensor.SensorData.newBuilder();
if (Arrays.equals(hardwareFirmwareId, CADENCE_BUG_FW_ID)) {
@@ -22,6 +22,7 @@ import com.dsi.ant.AntInterface;
import com.dsi.ant.AntInterfaceIntent;
import com.dsi.ant.AntMesg;
import com.dsi.ant.exception.AntInterfaceException;
import com.dsi.ant.exception.AntServiceNotConnectedException;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.services.sensors.SensorManager;
@@ -66,10 +67,6 @@ public abstract class AntSensorManager extends SensorManager {
private AntInterface antReceiver;
// Flag to know if the ANT App was interrupted
// TODO: This code path is not used but probably should be.
private boolean antInterrupted;
/**
* The data from the sensors.
*/
@@ -125,30 +122,19 @@ public abstract class AntSensorManager extends SensorManager {
public AntSensorManager(Context context) {
this.context = context;
// We register for ANT intents early because we want to have a record of
// the status intents in the log as we start up.
registerForAntIntents();
}
@Override
public void onDestroy() {
Log.i(TAG, "destroying AntSensorManager");
try {
context.unregisterReceiver(statusReceiver);
} catch (IllegalArgumentException e) {
Log.w(TAG, "Failed to unregister ANT status receiver", e);
}
try {
context.unregisterReceiver(dataReceiver);
} catch (IllegalArgumentException e) {
Log.w(TAG, "Failed to unregister ANT data receiver", e);
}
try {
antReceiver.releaseInterface();
} catch (AntInterfaceException e) {
Log.e(TAG, "failed to release ANT interface", e);
}
antReceiver.destroy();
cleanAntInterface();
unregisterForAntIntents();
}
@Override
@@ -173,16 +159,18 @@ public abstract class AntSensorManager extends SensorManager {
*/
@Override
protected final void setupChannel() {
setup();
}
private synchronized void setup() {
// We handle this unpleasantly because the UI should've checked for ANT
// support before it even instantiated this class.
if (!AntInterface.hasAntSupport(context)) {
throw new IllegalStateException("device does not have ANT support");
}
// We register for ANT intents early because we want to have a record of
// the status intents in the log as we start up.
registerForAntIntents();
cleanAntInterface();
antReceiver = AntInterface.getInstance(context, antServiceListener);
if (antReceiver == null) {
@@ -193,6 +181,29 @@ public abstract class AntSensorManager extends SensorManager {
setSensorState(Sensor.SensorState.CONNECTING);
}
/**
* Cleans up the ANT+ receiver interface, by releasing the interface
* and destroying it.
*/
private void cleanAntInterface()
{
Log.i(TAG, "Destroying AntSensorManager");
if (antReceiver != null) {
try {
antReceiver.releaseInterface();
} catch (AntServiceNotConnectedException e) {
Log.i(TAG, "ANT service not connected", e);
} catch (AntInterfaceException e) {
Log.e(TAG, "failed to release ANT interface", e);
}
antReceiver.destroy();
antReceiver = null;
}
}
/**
* This method is invoked via the ServiceListener when we're connected to
* the ANT service. If we're just starting up, this is our first opportunity
@@ -210,10 +221,8 @@ public abstract class AntSensorManager extends SensorManager {
if (!antReceiver.isEnabled()) {
// Make sure not to call AntInterface.enable() again, if it has been
// already called before
if (antInterrupted == false) {
Log.i(TAG, "Powering on Radio");
antReceiver.enable();
}
Log.i(TAG, "Powering on Radio");
antReceiver.enable();
} else {
Log.i(TAG, "Radio already enabled");
}
@@ -236,7 +245,7 @@ public abstract class AntSensorManager extends SensorManager {
/**
* Process a raw ANT message.
* @param antMessage the ANT message, including the size and message ID bytes
* @deprecated Use {@link #handleMessage(int, byte[])} instead.
* @deprecated Use {@link #handleMessage(byte, byte[])} instead.
*/
protected void handleMessage(byte[] antMessage) {
int len = antMessage[0];
@@ -333,6 +342,23 @@ public abstract class AntSensorManager extends SensorManager {
dataIntentFilter.addAction(AntInterfaceIntent.ANT_RX_MESSAGE_ACTION);
context.registerReceiver(dataReceiver, dataIntentFilter);
}
private void unregisterForAntIntents()
{
Log.i(TAG, "Unregistering ANT Intents.");
try {
context.unregisterReceiver(statusReceiver);
} catch (IllegalArgumentException e) {
Log.w(TAG, "Failed to unregister ANT status receiver", e);
}
try {
context.unregisterReceiver(dataReceiver);
} catch (IllegalArgumentException e) {
Log.w(TAG, "Failed to unregister ANT data receiver", e);
}
}
private String messageToString(byte[] message) {
StringBuilder out = new StringBuilder();
@@ -37,11 +37,7 @@ public class StatusAnnouncerFactory implements PeriodicTaskFactory {
@Override
public PeriodicTask create(Context context) {
if (hasTts) {
if (ApiFeatures.getInstance().isAudioFocusSupported()) {
return new FroyoStatusAnnouncerTask(context);
} else {
return new StatusAnnouncerTask(context);
}
return ApiFeatures.getInstance().getApiAdapter().getStatusAnnouncerTask(context);
} else {
return null;
}
@@ -24,6 +24,7 @@ import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import com.google.common.annotations.VisibleForTesting;
import android.content.Context;
import android.content.SharedPreferences;
@@ -53,11 +54,6 @@ public class StatusAnnouncerTask implements PeriodicTask {
*/
private final Context context;
/**
* String utilities.
*/
private final StringUtils stringUtils;
/**
* The interface to the text to speech engine.
*/
@@ -93,14 +89,8 @@ public class StatusAnnouncerTask implements PeriodicTask {
}
};
public StatusAnnouncerTask(Context context) {
this(context, new StringUtils(context));
}
public StatusAnnouncerTask(Context context, StringUtils stringUtils) {
this.context = context;
this.stringUtils = stringUtils;
}
/**
@@ -176,29 +166,29 @@ public class StatusAnnouncerTask implements PeriodicTask {
double d = stats.getTotalDistance() / 1000;
double s = stats.getAverageMovingSpeed() * 3.6;
if (d == 0) {
return context.getString(R.string.announce_no_distance);
return context.getString(R.string.voice_no_distance);
}
int speedLabel;
if (metricUnits) {
if (reportSpeed) {
speedLabel = R.string.kilometer_per_hour_long;
speedLabel = R.string.voice_kilometer_per_hour;
} else {
speedLabel = R.string.per_kilometer;
speedLabel = R.string.voice_per_kilometer;
}
} else {
s *= UnitConversions.KMH_TO_MPH;
d *= UnitConversions.KM_TO_MI;
if (reportSpeed) {
speedLabel = R.string.mile_per_hour_long;
speedLabel = R.string.voice_mile_per_hour;
} else {
speedLabel = R.string.per_mile;
speedLabel = R.string.voice_per_mile;
}
}
String speed = null;
if ((s == 0) || Double.isNaN(s)) {
speed = context.getString(R.string.unknown);
speed = context.getString(R.string.value_unknown);
} else {
if (reportSpeed) {
speed = String.format("%.1f", s);
@@ -206,17 +196,16 @@ public class StatusAnnouncerTask implements PeriodicTask {
double pace = 3600000.0 / s;
Log.w(Constants.TAG,
"Converted speed: " + s + " to pace: " + pace);
speed = stringUtils.formatTimeLong((long) pace);
speed = getAnnounceTime((long) pace);
}
}
return context.getString(R.string.announce_template,
context.getString(R.string.total_distance_label),
return context.getString(R.string.voice_template,
d,
context.getString(metricUnits
? R.string.kilometers_long
: R.string.miles_long),
stringUtils.formatTimeLong(stats.getMovingTime()),
? R.string.voice_kilometers
: R.string.voice_miles),
getAnnounceTime(stats.getMovingTime()),
speed,
context.getString(speedLabel));
}
@@ -333,4 +322,40 @@ public class StatusAnnouncerTask implements PeriodicTask {
public static int getVolumeStream() {
return TextToSpeech.Engine.DEFAULT_STREAM;
}
/**
* Gets a string to announce the time.
*
* @param time the time
*/
@VisibleForTesting
String getAnnounceTime(long time) {
int[] parts = StringUtils.getTimeParts(time);
String secLabel =
context.getString(parts[0] == 1 ? R.string.voice_second : R.string.voice_seconds);
String minLabel =
context.getString(parts[1] == 1 ? R.string.voice_minute : R.string.voice_minutes);
String hourLabel =
context.getString(parts[2] == 1 ? R.string.voice_hour : R.string.voice_hours);
StringBuilder sb = new StringBuilder();
if (parts[2] != 0) {
sb.append(parts[2]);
sb.append(" ");
sb.append(hourLabel);
sb.append(" ");
sb.append(parts[1]);
sb.append(" ");
sb.append(minLabel);
} else {
sb.append(parts[1]);
sb.append(" ");
sb.append(minLabel);
sb.append(" ");
sb.append(parts[0]);
sb.append(" ");
sb.append(secLabel);
}
return sb.toString();
}
}
@@ -16,7 +16,6 @@
package com.google.android.apps.mytracks.util;
import com.google.android.apps.mytracks.Constants;
import com.google.api.client.http.HttpTransport;
import android.os.Build;
import android.util.Log;
@@ -30,17 +29,17 @@ import android.util.Log;
public class ApiFeatures {
/**
* The API level of the Android version we're being run under.
* The API level of the Android version we are being run under.
*/
public static final int ANDROID_API_LEVEL = Integer.parseInt(
private static final int ANDROID_API_LEVEL = Integer.parseInt(
Build.VERSION.SDK);
private static ApiFeatures instance;
/**
* The API platform adapter supported by this system.
* The API level adapter for the Android version we are being run under.
*/
private ApiPlatformAdapter apiPlatformAdapter;
private ApiLevelAdapter apiLevelAdapter;
/**
* Returns the singleton instance of this class.
@@ -67,52 +66,33 @@ public class ApiFeatures {
// It is safe to import unsupported classes as long as we only actually
// load the class when supported.
if (getApiLevel() >= 9) {
apiPlatformAdapter = new GingerbreadPlatformAdapter();
apiLevelAdapter = new ApiLevel9Adapter();
} else if (getApiLevel() >= 8) {
apiLevelAdapter = new ApiLevel8Adapter();
} else if (getApiLevel() >= 5) {
apiPlatformAdapter = new EclairPlatformAdapter();
apiLevelAdapter = new ApiLevel5Adapter();
} else {
apiPlatformAdapter = new CupcakePlatformAdapter();
apiLevelAdapter = new ApiLevel3Adapter();
}
Log.i(Constants.TAG, "Using platform adapter " + apiPlatformAdapter.getClass());
Log.i(Constants.TAG, "Using API level adapter " + apiLevelAdapter.getClass());
}
public ApiPlatformAdapter getApiPlatformAdapter() {
return apiPlatformAdapter;
}
/**
* Returns whether cloud backup (a.k.a. Froyo backup) is available.
*/
public boolean hasBackup() {
return getApiLevel() >= 8;
public ApiLevelAdapter getApiAdapter() {
return apiLevelAdapter;
}
// API Level 4 Changes
/**
* Returns whether text-to-speech is available.
*/
public boolean hasTextToSpeech() {
if (getApiLevel() < 4) return false;
try {
Class.forName("android.speech.tts.TextToSpeech");
} catch (ClassNotFoundException ex) {
return false;
} catch (LinkageError er) {
return false;
}
return true;
}
public boolean hasStrictMode() {
return getApiLevel() >= 9;
}
public boolean isAudioFocusSupported() {
return getApiLevel() >= 8;
return getApiLevel() >= 4;
}
// API Level 5 Changes
/**
* There's a bug (#1587) in Cupcake and Donut which prevents you from
* using a SQLiteQueryBuilder twice. That is, if you call buildQuery
@@ -122,18 +102,19 @@ public class ApiFeatures {
* up. Specifically, it'll add extra parens which don't belong.
*/
public boolean canReuseSQLiteQueryBuilder() {
return getApiLevel() > 4;
return getApiLevel() >= 5;
}
// API Level 10 changes
/**
* Returns true if com.google.api.client.javanet.NetHttpTransport should be
* used rather than com.google.api.client.apache.ApacheHttpTransport. See
* {@link HttpTransport} for more info.
* Returns true if BluetoothDevice.createInsecureRfcommSocketToServiceRecord
* is available.
*/
public boolean useNetHttpTransport() {
return getApiLevel() >= 9;
public boolean hasBluetoothDeviceCreateInsecureRfcommSocketToServiceRecord() {
return getApiLevel() >= 10;
}
// Visible for testing.
protected int getApiLevel() {
return ANDROID_API_LEVEL;
@@ -17,22 +17,31 @@ package com.google.android.apps.mytracks.util;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener;
import com.google.android.apps.mytracks.services.tasks.PeriodicTask;
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerTask;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.apache.ApacheHttpTransport;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
/**
* The Cupcake (API level 3) specific implementation of the
* {@link ApiPlatformAdapter}.
*
* API level 3 specific implementation of the {@link ApiLevelAdapter}.
*
* @author Bartlomiej Niechwiej
*/
public class CupcakePlatformAdapter implements ApiPlatformAdapter {
public class ApiLevel3Adapter implements ApiLevelAdapter {
@Override
public void startForeground(Service service,
@@ -51,6 +60,21 @@ public class CupcakePlatformAdapter implements ApiPlatformAdapter {
notificationManager.cancel(id);
}
}
@Override
public PeriodicTask getStatusAnnouncerTask(Context context) {
return new StatusAnnouncerTask(context);
}
@Override
public BackupPreferencesListener getBackupPreferencesListener(Context context) {
return new BackupPreferencesListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
// Do nothing
}
};
}
private void setServiceForeground(Service service, boolean foreground) {
// setForeground has been completely removed in API level 11, so we use reflection.
@@ -79,4 +103,22 @@ public class CupcakePlatformAdapter implements ApiPlatformAdapter {
public void enableStrictMode() {
// Not supported
}
@Override
public byte[] copyByteArray(byte[] input, int start, int end) {
int length = end - start;
byte[] output = new byte[length];
System.arraycopy(input, start, output, 0, length);
return output;
}
@Override
public DecimalFormatSymbols getDecimalFormatSymbols(Locale locale) {
return new DecimalFormatSymbols(locale);
}
@Override
public HttpTransport getHttpTransport() {
return new ApacheHttpTransport();
}
}
@@ -18,15 +18,13 @@ package com.google.android.apps.mytracks.util;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.SharedPreferences.Editor;
/**
* The Eclair (API level 5) specific implementation of the
* {@link ApiPlatformAdapter}.
*
* API level 5 specific implementation of the {@link ApiLevelAdapter}.
*
* @author Bartlomiej Niechwiej
*/
public class EclairPlatformAdapter implements ApiPlatformAdapter {
public class ApiLevel5Adapter extends ApiLevel3Adapter {
@Override
public void startForeground(Service service,
@@ -40,14 +38,4 @@ public class EclairPlatformAdapter implements ApiPlatformAdapter {
NotificationManager notificationManager, int id) {
service.stopForeground(id != -1);
}
@Override
public void applyPreferenceChanges(Editor editor) {
editor.commit();
}
@Override
public void enableStrictMode() {
// Not supported
}
}
@@ -0,0 +1,26 @@
package com.google.android.apps.mytracks.util;
import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener;
import com.google.android.apps.mytracks.io.backup.BackupPreferencesListenerImpl;
import com.google.android.apps.mytracks.services.tasks.FroyoStatusAnnouncerTask;
import com.google.android.apps.mytracks.services.tasks.PeriodicTask;
import android.content.Context;
/**
* API level 8 specific implementation of the {@link ApiLevelAdapter}.
*
* @author Jimmy Shih
*/
public class ApiLevel8Adapter extends ApiLevel5Adapter {
@Override
public PeriodicTask getStatusAnnouncerTask(Context context) {
return new FroyoStatusAnnouncerTask(context);
}
@Override
public BackupPreferencesListener getBackupPreferencesListener(Context context) {
return new BackupPreferencesListenerImpl(context);
}
}
@@ -1,18 +1,24 @@
package com.google.android.apps.mytracks.util;
import com.google.android.apps.mytracks.Constants;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import android.content.SharedPreferences.Editor;
import android.os.StrictMode;
import android.util.Log;
import java.text.DecimalFormatSymbols;
import java.util.Arrays;
import java.util.Locale;
/**
* The Gingerbread (API level 9) specific implememntation of the
* {@link ApiPlatformAdapter}.
* API level 9 specific implementation of the {@link ApiLevelAdapter}.
*
* @author Rodrigo Damazio
*/
public class GingerbreadPlatformAdapter extends EclairPlatformAdapter {
public class ApiLevel9Adapter extends ApiLevel8Adapter {
@Override
public void applyPreferenceChanges(Editor editor) {
// Apply asynchronously
@@ -32,4 +38,19 @@ public class GingerbreadPlatformAdapter extends EclairPlatformAdapter {
.penaltyLog()
.build());
}
@Override
public byte[] copyByteArray(byte[] input, int start, int end) {
return Arrays.copyOfRange(input, start, end);
}
@Override
public DecimalFormatSymbols getDecimalFormatSymbols(Locale locale) {
return DecimalFormatSymbols.getInstance(locale);
}
@Override
public HttpTransport getHttpTransport() {
return new NetHttpTransport();
}
}
@@ -15,21 +15,31 @@
*/
package com.google.android.apps.mytracks.util;
import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener;
import com.google.android.apps.mytracks.services.tasks.PeriodicTask;
import com.google.api.client.http.HttpTransport;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.SharedPreferences;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
/**
* A set of methods that may be implemented in a platform specific way.
* A set of methods that may be implemented differently depending on the Android API level.
*
* @author Bartlomiej Niechwiej
*/
public interface ApiPlatformAdapter {
public interface ApiLevelAdapter {
/**
* Puts the specified service into foreground.
*
* Due to changes in API level 5.
*
* @param service the service to be put in foreground.
* @param notificationManager the notification manager used to post the given
* notification.
@@ -41,6 +51,9 @@ public interface ApiPlatformAdapter {
/**
* Puts the given service into background.
*
* Due to changes in API level 5.
*
* @param service the service to put into background.
* @param notificationManager the notification manager to user when removing
* notifications.
@@ -50,14 +63,64 @@ public interface ApiPlatformAdapter {
void stopForeground(Service service, NotificationManager notificationManager,
int id);
/**
* Gets a status announcer task.
*
* Due to changes in API level 8.
*/
PeriodicTask getStatusAnnouncerTask(Context context);
/**
* Gets a {@link BackupPreferencesListener}.
*
* Due to changes in API level 8.
*/
BackupPreferencesListener getBackupPreferencesListener(Context context);
/**
* Applies all changes done to the given preferences editor.
* Changes may or may not be applied immediately.
*
* Due to changes in API level 9.
*/
void applyPreferenceChanges(SharedPreferences.Editor editor);
/**
* Enables strict mode where supported, only if this is a development build.
*
* Due to changes in API level 9.
*/
void enableStrictMode();
/**
* Copies elements from the input byte array into a new byte array, from
* indexes start (inclusive) to end (exclusive). The end index must be less
* than or equal to input.length.
*
* Due to changes in API level 9.
*
* @param input the input byte array
* @param start the start index
* @param end the end index
* @return a new array containing elements from the input byte array
*/
byte[] copyByteArray(byte[] input, int start, int end);
/**
* Gets an instance of {@link DecimalFormatSymbols} for a locale.
*
* Due to changes in API level 9.
*
* @param locale the locale
* @return a new instance of {@link DecimalFormatSymbols}
*/
DecimalFormatSymbols getDecimalFormatSymbols(Locale locale);
/**
* Gets a {@link HttpTransport}.
*
* Due to changes in API level 9.
*/
HttpTransport getHttpTransport();
}
@@ -58,7 +58,7 @@ public class ChartURLGenerator {
}
return getChartUrl(distances, elevations, track,
context.getString(R.string.elevation_label), metricUnits);
context.getString(R.string.stat_elevation), metricUnits);
}
/**
@@ -221,34 +221,6 @@ public class StringUtils implements DescriptionGenerator {
this.context = context;
}
public String formatTimeLong(long time) {
int[] parts = getTimeParts(time);
String secLabel =
context.getString(parts[0] == 1 ? R.string.second : R.string.seconds);
String minLabel =
context.getString(parts[1] == 1 ? R.string.minute : R.string.minutes);
String hourLabel =
context.getString(parts[2] == 1 ? R.string.hour : R.string.hours);
StringBuilder sb = new StringBuilder();
if (parts[2] != 0) {
sb.append(parts[2]);
sb.append(" ");
sb.append(hourLabel);
sb.append(" ");
sb.append(parts[1]);
sb.append(minLabel);
} else {
sb.append(parts[1]);
sb.append(" ");
sb.append(minLabel);
sb.append(" ");
sb.append(parts[0]);
sb.append(secLabel);
}
return sb.toString();
}
/**
* Generates a description for a track (with information about the
* statistics).
@@ -292,7 +264,7 @@ public class StringUtils implements DescriptionGenerator {
minGrade = Math.round(trackMinGrade * 100);
}
String category = context.getString(R.string.unknown);
String category = context.getString(R.string.value_unknown);
String trackCategory = track.getCategory();
if (trackCategory != null && trackCategory.length() > 0) {
category = trackCategory;
@@ -300,20 +272,20 @@ public class StringUtils implements DescriptionGenerator {
String averageSpeed =
getSpeedString(trackStats.getAverageSpeed(),
R.string.average_speed_label,
R.string.average_pace_label,
R.string.stat_average_speed,
R.string.stat_average_pace,
displaySpeed);
String averageMovingSpeed =
getSpeedString(trackStats.getAverageMovingSpeed(),
R.string.average_moving_speed_label,
R.string.average_moving_pace_label,
R.string.stat_average_moving_speed,
R.string.stat_average_moving_pace,
displaySpeed);
String maxSpeed =
getSpeedString(trackStats.getMaxSpeed(),
R.string.max_speed_label,
R.string.min_pace_label,
R.string.stat_max_speed,
R.string.stat_min_pace,
displaySpeed);
return String.format("%s<p>"
@@ -331,56 +303,72 @@ public class StringUtils implements DescriptionGenerator {
+ "<img border=\"0\" src=\"%s\"/>",
// Line 1
context.getString(R.string.fusiontable_link),
getCreatedByMyTracks(context, true),
// Line 2
context.getString(R.string.total_distance_label),
distanceInKm, context.getString(R.string.kilometer),
distanceInMiles, context.getString(R.string.mile),
context.getString(R.string.stat_total_distance),
distanceInKm, context.getString(R.string.unit_kilometer),
distanceInMiles, context.getString(R.string.unit_mile),
// Line 3
context.getString(R.string.total_time_label),
context.getString(R.string.stat_total_time),
StringUtils.formatTime(trackStats.getTotalTime()),
// Line 4
context.getString(R.string.moving_time_label),
context.getString(R.string.stat_moving_time),
StringUtils.formatTime(trackStats.getMovingTime()),
// Line 5
averageSpeed, averageMovingSpeed, maxSpeed,
// Line 6
context.getString(R.string.min_elevation_label),
minElevationInMeters, context.getString(R.string.meter),
minElevationInFeet, context.getString(R.string.feet),
context.getString(R.string.stat_min_elevation),
minElevationInMeters, context.getString(R.string.unit_meter),
minElevationInFeet, context.getString(R.string.unit_feet),
// Line 7
context.getString(R.string.max_elevation_label),
maxElevationInMeters, context.getString(R.string.meter),
maxElevationInFeet, context.getString(R.string.feet),
context.getString(R.string.stat_max_elevation),
maxElevationInMeters, context.getString(R.string.unit_meter),
maxElevationInFeet, context.getString(R.string.unit_feet),
// Line 8
context.getString(R.string.elevation_gain_label),
elevationGainInMeters, context.getString(R.string.meter),
elevationGainInFeet, context.getString(R.string.feet),
context.getString(R.string.stat_elevation_gain),
elevationGainInMeters, context.getString(R.string.unit_meter),
elevationGainInFeet, context.getString(R.string.unit_feet),
// Line 9
context.getString(R.string.max_grade_label), maxGrade,
context.getString(R.string.stat_max_grade), maxGrade,
// Line 10
context.getString(R.string.min_grade_label), minGrade,
context.getString(R.string.stat_min_grade), minGrade,
// Line 11
context.getString(R.string.recorded_date),
context.getString(R.string.send_google_recorded),
new Date(trackStats.getStartTime()),
// Line 12
context.getString(R.string.category), category,
context.getString(R.string.track_detail_activity_type_hint), category,
// Line 13
ChartURLGenerator.getChartUrl(distances, elevations, track, context));
}
/**
* Returns the 'Created by My Tracks on Android' string.
*
* @param context the context
* @param addLink true to add a link to the My Tracks web site
*/
public static String getCreatedByMyTracks(Context context, boolean addLink) {
String format = context.getString(R.string.send_google_by_my_tracks);
if (addLink) {
String url = context.getString(R.string.my_tracks_web_url);
return String.format(format, "<a href='http://" + url + "'>", "</a>");
} else {
return String.format(format, "", "");
}
}
private String getSpeedString(double speed, int speedLabel, int paceLabel,
boolean displaySpeed) {
double speedInKph = speed * 3.6;
@@ -388,8 +376,8 @@ public class StringUtils implements DescriptionGenerator {
if (displaySpeed) {
return String.format("%s: %.2f %s (%.1f %s)<br>",
context.getString(speedLabel),
speedInKph, context.getString(R.string.kilometer_per_hour),
speedInMph, context.getString(R.string.mile_per_hour));
speedInKph, context.getString(R.string.unit_kilometer_per_hour),
speedInMph, context.getString(R.string.unit_mile_per_hour));
} else {
double paceInKm;
double paceInMi;
@@ -402,8 +390,8 @@ public class StringUtils implements DescriptionGenerator {
}
return String.format("%s: %.2f %s (%.1f %s)<br>",
context.getString(paceLabel),
paceInKm, context.getString(R.string.min_per_kilometer),
paceInMi, context.getString(R.string.min_per_mile));
paceInKm, context.getString(R.string.unit_minute_per_kilometer),
paceInMi, context.getString(R.string.unit_minute_per_mile));
}
}
@@ -462,34 +450,34 @@ public class StringUtils implements DescriptionGenerator {
+ "%s: %d %s (%d %s)\n"
+ "%s: %d %s\n"
+ "%s: %d %s\n",
context.getString(R.string.distance_label),
distanceInKm, context.getString(R.string.kilometer),
distanceInMiles, context.getString(R.string.mile),
context.getString(R.string.time_label),
context.getString(R.string.stat_total_distance),
distanceInKm, context.getString(R.string.unit_kilometer),
distanceInMiles, context.getString(R.string.unit_mile),
context.getString(R.string.stat_total_time),
StringUtils.formatTime(stats.getTotalTime()),
context.getString(R.string.moving_time_label),
context.getString(R.string.stat_moving_time),
StringUtils.formatTime(stats.getMovingTime()),
context.getString(R.string.average_speed_label),
averageSpeedInKmh, context.getString(R.string.kilometer_per_hour),
averageSpeedInMph, context.getString(R.string.mile_per_hour),
context.getString(R.string.average_moving_speed_label),
movingSpeedInKmh, context.getString(R.string.kilometer_per_hour),
movingSpeedInMph, context.getString(R.string.mile_per_hour),
context.getString(R.string.max_speed_label),
maxSpeedInKmh, context.getString(R.string.kilometer_per_hour),
maxSpeedInMph, context.getString(R.string.mile_per_hour),
context.getString(R.string.min_elevation_label),
minElevationInMeters, context.getString(R.string.meter),
minElevationInFeet, context.getString(R.string.feet),
context.getString(R.string.max_elevation_label),
maxElevationInMeters, context.getString(R.string.meter),
maxElevationInFeet, context.getString(R.string.feet),
context.getString(R.string.elevation_gain_label),
elevationGainInMeters, context.getString(R.string.meter),
elevationGainInFeet, context.getString(R.string.feet),
context.getString(R.string.max_grade_label),
context.getString(R.string.stat_average_speed),
averageSpeedInKmh, context.getString(R.string.unit_kilometer_per_hour),
averageSpeedInMph, context.getString(R.string.unit_mile_per_hour),
context.getString(R.string.stat_average_moving_speed),
movingSpeedInKmh, context.getString(R.string.unit_kilometer_per_hour),
movingSpeedInMph, context.getString(R.string.unit_mile_per_hour),
context.getString(R.string.stat_max_speed),
maxSpeedInKmh, context.getString(R.string.unit_kilometer_per_hour),
maxSpeedInMph, context.getString(R.string.unit_mile_per_hour),
context.getString(R.string.stat_min_elevation),
minElevationInMeters, context.getString(R.string.unit_meter),
minElevationInFeet, context.getString(R.string.unit_feet),
context.getString(R.string.stat_max_elevation),
maxElevationInMeters, context.getString(R.string.unit_meter),
maxElevationInFeet, context.getString(R.string.unit_feet),
context.getString(R.string.stat_elevation_gain),
elevationGainInMeters, context.getString(R.string.unit_meter),
elevationGainInFeet, context.getString(R.string.unit_feet),
context.getString(R.string.stat_max_grade),
theMaxGrade, percent,
context.getString(R.string.min_grade_label),
context.getString(R.string.stat_min_grade),
theMinGrade, percent);
}
}
@@ -19,6 +19,16 @@ package com.google.android.apps.mytracks.widgets;
import static com.google.android.apps.mytracks.Constants.SETTINGS_NAME;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.MyTracks;
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.services.ControlRecordingService;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
@@ -32,16 +42,6 @@ import android.os.Handler;
import android.util.Log;
import android.widget.RemoteViews;
import com.google.android.apps.mytracks.MyTracks;
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.services.TrackRecordingService;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
/**
* An AppWidgetProvider for displaying key track statistics (distance, time,
* speed) from the current or most recent track.
@@ -85,14 +85,14 @@ public class TrackWidgetProvider
selectedTrackId = -1;
}
private void initialize(Context context) {
private void initialize(Context aContext) {
if (this.context != null) {
return;
}
this.context = context;
this.context = aContext;
trackObserver = new TrackObserver();
providerUtils = MyTracksProviderUtils.Factory.get(context);
unknown = context.getString(R.string.unknown);
unknown = context.getString(R.string.value_unknown);
sharedPreferences = context.getSharedPreferences(SETTINGS_NAME, 0);
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
@@ -105,9 +105,9 @@ public class TrackWidgetProvider
}
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
initialize(context);
public void onReceive(Context aContext, Intent intent) {
super.onReceive(aContext, intent);
initialize(aContext);
selectedTrackId = intent.getLongExtra(
context.getString(R.string.track_id_broadcast_extra), selectedTrackId);
@@ -124,9 +124,9 @@ public class TrackWidgetProvider
}
@Override
public void onDisabled(Context context) {
public void onDisabled(Context aContext) {
if (trackObserver != null) {
context.getContentResolver().unregisterContentObserver(trackObserver);
aContext.getContentResolver().unregisterContentObserver(trackObserver);
}
if (sharedPreferences != null) {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
@@ -154,7 +154,7 @@ public class TrackWidgetProvider
views.setOnClickPendingIntent(R.id.appwidget_track_statistics, pendingIntent);
if (action != null) {
updateViewButton(views, context, action);
updateViewButton(views, action);
}
updateViewTrackStatistics(views, track);
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(widget);
@@ -167,22 +167,17 @@ public class TrackWidgetProvider
* Update the widget's button with the appropriate intent and icon.
*
* @param views The RemoteViews containing the button
* @param context The Context of the AppWidget
* @param action The action broadcast from the track service
*/
private void updateViewButton(RemoteViews views, Context context, String action) {
private void updateViewButton(RemoteViews views, String action) {
if (TRACK_STARTED_ACTION.equals(action)) {
// If a new track is started by this appwidget or elsewhere,
// toggle the button to active and have it disable the track if pressed.
setButtonIntent(
views, context, R.string.end_current_track_action, R.drawable.appwidget_button_enabled,
-1);
setButtonIntent(views, R.string.track_action_end, R.drawable.appwidget_button_enabled);
} else {
// If a track is stopped by this appwidget or elsewhere,
// toggle the button to inactive and have it start a new track if pressed.
setButtonIntent(
views, context, R.string.start_new_track_action, R.drawable.appwidget_button_disabled,
R.string.select_new_track_extra);
setButtonIntent(views, R.string.track_action_start, R.drawable.appwidget_button_disabled);
}
}
@@ -190,18 +185,12 @@ public class TrackWidgetProvider
* Set up the main widget button.
*
* @param views The widget views
* @param context The widget context
* @param action The resource id of the action to fire when the button is pressed
* @param icon The resource id of the icon to show for the button
* @param extra Optional resource id of a boolean extra on the intent
*/
private void setButtonIntent(
RemoteViews views, Context context, int action, int icon, int extra) {
Intent intent = new Intent(context, TrackRecordingService.class);
private void setButtonIntent(RemoteViews views, int action, int icon) {
Intent intent = new Intent(context, ControlRecordingService.class);
intent.setAction(context.getString(action));
if (extra != -1) {
intent.putExtra(context.getString(extra), true);
}
PendingIntent pendingIntent = PendingIntent.getService(context, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.appwidget_button, pendingIntent);
@@ -261,9 +250,11 @@ public class TrackWidgetProvider
String metricUnitsKey = context.getString(R.string.metric_units_key);
if (key == null || key.equals(metricUnitsKey)) {
isMetric = prefs.getBoolean(metricUnitsKey, true);
distanceLabel = context.getString(isMetric ? R.string.kilometer : R.string.mile);
speedLabel = context.getString(isMetric ? R.string.kilometer_per_hour : R.string.mile_per_hour);
paceLabel = context.getString(isMetric ? R.string.min_per_kilometer : R.string.min_per_mile);
distanceLabel = context.getString(isMetric ? R.string.unit_kilometer : R.string.unit_mile);
speedLabel = context.getString(
isMetric ? R.string.unit_kilometer_per_hour : R.string.unit_mile_per_hour);
paceLabel = context.getString(
isMetric ? R.string.unit_minute_per_kilometer : R.string.unit_minute_per_mile);
}
String reportSpeedKey = context.getString(R.string.report_speed_key);
@@ -153,7 +153,7 @@ public class SpreadsheetsClient extends GDataServiceClient {
* @return a parser for the indicated feed
* @throws ParseException if the response from the server could not be parsed
*/
@SuppressWarnings("rawtypes")
@SuppressWarnings("unchecked")
@Override
public GDataParser getParserForFeed(
Class feedEntryClass, String feedUri, String authToken)
@@ -227,7 +227,7 @@ public class SpreadsheetsClient extends GDataServiceClient {
* Converts an HTTP exception which happened while writing to the equivalent
* local exception.
*/
@SuppressWarnings("rawtypes")
@SuppressWarnings("unchecked")
private void convertHttpExceptionForWrites(
Class entryClass, String message, HttpException cause)
throws ParseException, IOException {
@@ -253,7 +253,7 @@ public class SpreadsheetsClient extends GDataServiceClient {
/**
* Parses one entry from the input stream.
*/
@SuppressWarnings("rawtypes")
@SuppressWarnings("unchecked")
private Entry parseEntry(Class entryClass, InputStream is)
throws ParseException, IOException {
GDataParser parser = null;
@@ -48,7 +48,7 @@ public class XmlDocsGDataParserFactory implements GDataParserFactory {
}
}
@SuppressWarnings("rawtypes")
@SuppressWarnings("unchecked")
@Override
public GDataParser createParser(Class cls, InputStream is)
throws ParseException {