merge heads

This commit is contained in:
Jimmy Shih
2012-04-06 10:54:22 -07:00
parent 79fde1c118
commit f5085b208d
319 changed files with 6180 additions and 5754 deletions
@@ -3,6 +3,7 @@ package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
@@ -12,7 +13,6 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Window;
import android.widget.ScrollView;
import android.widget.TextView;
@@ -60,8 +60,9 @@ public class AggregatedStatsActivity extends Activity implements
this.tracksProvider = MyTracksProviderUtils.Factory.get(this);
// We don't need a window title bar:
requestWindowFeature(Window.FEATURE_NO_TITLE);
// Show the action bar (or nothing at all).
ApiAdapterFactory.getApiAdapter().showActionBar(this);
setContentView(R.layout.stats);
ScrollView sv = ((ScrollView) findViewById(R.id.scrolly));
@@ -28,26 +28,26 @@ import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.DoubleBuffer;
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import com.google.common.annotations.VisibleForTesting;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.location.Location;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.RadioGroup;
import android.widget.ZoomControls;
import java.util.ArrayList;
@@ -61,7 +61,8 @@ import java.util.EnumSet;
*/
public class ChartActivity extends Activity implements TrackDataListener {
private static final int CHART_SETTINGS_DIALOG = 1;
private static final int DIALOG_CHART_SETTINGS_ID = 0;
private final DoubleBuffer elevationBuffer =
new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR);
private final DoubleBuffer speedBuffer =
@@ -84,7 +85,6 @@ public class ChartActivity extends Activity implements TrackDataListener {
* UI elements:
*/
private ChartView chartView;
private MenuItem chartSettingsMenuItem;
private LinearLayout busyPane;
private ZoomControls zoomControls;
@@ -118,7 +118,9 @@ public class ChartActivity extends Activity implements TrackDataListener {
// The volume we want to control is the Text-To-Speech volume
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
requestWindowFeature(Window.FEATURE_NO_TITLE);
// Show the action bar (or nothing at all).
ApiAdapterFactory.getApiAdapter().showActionBar(this);
setContentView(R.layout.mytracks_charts);
ViewGroup layout = (ViewGroup) findViewById(R.id.elevation_chart);
chartView = new ChartView(this);
@@ -183,64 +185,59 @@ public class ChartActivity extends Activity implements TrackDataListener {
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
chartSettingsMenuItem = menu.add(Menu.NONE, Constants.MENU_CHART_SETTINGS, Menu.NONE,
R.string.menu_chart_view_chart_settings);
chartSettingsMenuItem.setIcon(R.drawable.chart_settings);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case Constants.MENU_CHART_SETTINGS:
showDialog(CHART_SETTINGS_DIALOG);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected Dialog onCreateDialog(int id) {
if (id == CHART_SETTINGS_DIALOG) {
final ChartSettingsDialog settingsDialog = new ChartSettingsDialog(this);
settingsDialog.setOnClickListener(new OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int which) {
if (which != DialogInterface.BUTTON_POSITIVE) {
return;
}
for (int i = 0; i < ChartView.NUM_SERIES; i++) {
chartView.setChartValueSeriesEnabled(i, settingsDialog.isSeriesEnabled(i));
}
setMode(settingsDialog.getMode());
chartView.postInvalidate();
}
});
return settingsDialog;
if (id != DIALOG_CHART_SETTINGS_ID) {
return null;
}
return super.onCreateDialog(id);
}
View view = getLayoutInflater().inflate(R.layout.chart_settings, null);
final RadioGroup radioGroup = (RadioGroup) view.findViewById(R.id.chart_settings_x);
radioGroup.check(chartView.getMode() == Mode.BY_DISTANCE
? R.id.chart_settings_by_distance : R.id.chart_settings_by_time);
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
super.onPrepareDialog(id, dialog);
final CheckBox[] checkBoxes = new CheckBox[ChartView.NUM_SERIES];
checkBoxes[ChartView.ELEVATION_SERIES] = (CheckBox) view.findViewById(
R.id.chart_settings_elevation);
checkBoxes[ChartView.SPEED_SERIES] = (CheckBox) view.findViewById(R.id.chart_settings_speed);
checkBoxes[ChartView.POWER_SERIES] = (CheckBox) view.findViewById(R.id.chart_settings_power);
checkBoxes[ChartView.CADENCE_SERIES] = (CheckBox) view.findViewById(
R.id.chart_settings_cadence);
checkBoxes[ChartView.HEART_RATE_SERIES] = (CheckBox) view.findViewById(
R.id.chart_settings_heart_rate);
if (id == CHART_SETTINGS_DIALOG) {
prepareSettingsDialog((ChartSettingsDialog) dialog);
}
}
private void prepareSettingsDialog(ChartSettingsDialog settingsDialog) {
settingsDialog.setMode(chartView.getMode());
settingsDialog.setDisplaySpeed(reportSpeed);
// set checkboxes values
for (int i = 0; i < ChartView.NUM_SERIES; i++) {
settingsDialog.setSeriesEnabled(i, chartView.isChartValueSeriesEnabled(i));
checkBoxes[i].setChecked(chartView.isChartValueSeriesEnabled(i));
}
checkBoxes[ChartView.SPEED_SERIES].setText(reportSpeed
? R.string.stat_speed : R.string.stat_pace);
return new AlertDialog.Builder(this)
.setCancelable(true)
.setNegativeButton(R.string.generic_cancel, null)
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
setMode(radioGroup.getCheckedRadioButtonId() == R.id.chart_settings_by_distance
? Mode.BY_DISTANCE
: Mode.BY_TIME);
for (int i = 0; i < ChartView.NUM_SERIES; i++) {
chartView.setChartValueSeriesEnabled(i, checkBoxes[i].isChecked());
}
chartView.postInvalidate();
}
})
.setTitle(R.string.menu_chart_settings)
.setView(view)
.create();
}
/**
* Shows the chart settings dialog
*/
public void showChartSettingsDialog() {
showDialog(DIALOG_CHART_SETTINGS_ID);
}
/**
@@ -486,11 +483,6 @@ public class ChartActivity extends Activity implements TrackDataListener {
return chartView;
}
@VisibleForTesting
MenuItem getChartSettingsMenuItem() {
return chartSettingsMenuItem;
}
@VisibleForTesting
void setTrackMaxSpeed(double maxSpeed) {
trackMaxSpeed = maxSpeed;
@@ -1,127 +0,0 @@
/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.ChartView.Mode;
import com.google.android.maps.mytracks.R;
import com.google.common.annotations.VisibleForTesting;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.RadioButton;
import android.widget.RadioGroup;
/**
* An activity that allows the user to set the chart settings.
*
* @author Sandor Dornbush
*/
public class ChartSettingsDialog extends Dialog {
private RadioButton distance;
private CheckBox[] series;
private OnClickListener clickListener;
public ChartSettingsDialog(Context context) {
super(context);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.chart_settings);
Button cancel = (Button) findViewById(R.id.chart_settings_cancel);
cancel.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (clickListener != null) {
clickListener.onClick(ChartSettingsDialog.this, BUTTON_NEGATIVE);
}
dismiss();
}
});
Button okButton = (Button) findViewById(R.id.chart_settings_ok);
okButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (clickListener != null) {
clickListener.onClick(ChartSettingsDialog.this, BUTTON_POSITIVE);
}
dismiss();
}
});
distance = (RadioButton) findViewById(R.id.chart_settings_by_distance);
series = new CheckBox[ChartView.NUM_SERIES];
series[ChartView.ELEVATION_SERIES] =
(CheckBox) findViewById(R.id.chart_settings_elevation);
series[ChartView.SPEED_SERIES] =
(CheckBox) findViewById(R.id.chart_settings_speed);
series[ChartView.POWER_SERIES] =
(CheckBox) findViewById(R.id.chart_settings_power);
series[ChartView.CADENCE_SERIES] =
(CheckBox) findViewById(R.id.chart_settings_cadence);
series[ChartView.HEART_RATE_SERIES] =
(CheckBox) findViewById(R.id.chart_settings_heart_rate);
}
public void setMode(Mode mode) {
RadioGroup rd = (RadioGroup) findViewById(R.id.chart_settings_x);
rd.check(mode == Mode.BY_DISTANCE
? R.id.chart_settings_by_distance
: R.id.chart_settings_by_time);
}
/**
* Sets whether to display speed or pace.
*
* @param displaySpeed true to display speed
*/
public void setDisplaySpeed(boolean displaySpeed) {
series[ChartView.SPEED_SERIES].setText(displaySpeed ? R.string.stat_speed : R.string.stat_pace);
}
public void setSeriesEnabled(int seriesIdx, boolean enabled) {
series[seriesIdx].setChecked(enabled);
}
public Mode getMode() {
if (distance == null) return Mode.BY_DISTANCE;
return distance.isChecked() ? Mode.BY_DISTANCE : Mode.BY_TIME;
}
public boolean isSeriesEnabled(int seriesIdx) {
if (series == null) return true;
return series[seriesIdx].isChecked();
}
public void setOnClickListener(OnClickListener clickListener) {
this.clickListener = clickListener;
}
@VisibleForTesting
CheckBox[] getSeries() {
return series;
}
}
@@ -1,83 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.maps.mytracks.R;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
/**
* Checks with the user if he prefers the metric units or the imperial units.
*
* @author Sandor Dornbush
*/
class 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;
public static void check(final Context context) {
final SharedPreferences checkUnitsSharedPreferences = context.getSharedPreferences(
CHECK_UNITS_PREFERENCE_FILE, Context.MODE_PRIVATE);
if (checkUnitsSharedPreferences.getBoolean(CHECK_UNITS_PREFERENCE_KEY, false)) {
return;
}
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) {
if (which == 0) {
metric = true;
} else {
metric = false;
}
}
});
builder.setCancelable(true);
builder.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
recordCheckPerformed(checkUnitsSharedPreferences);
SharedPreferences useMetricPreferences = context.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = useMetricPreferences.edit();
String key = context.getString(R.string.metric_units_key);
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor.putBoolean(key, metric));
}
});
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
recordCheckPerformed(checkUnitsSharedPreferences);
}
});
builder.show();
}
private static void recordCheckPerformed(SharedPreferences preferences) {
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(
preferences.edit().putBoolean(CHECK_UNITS_PREFERENCE_KEY, true));
}
private CheckUnits() {}
}
@@ -36,47 +36,22 @@ public abstract class Constants {
/*
* onActivityResult request codes:
*/
public static final int SHOW_TRACK = 0;
public static final int SHARE_GPX_FILE = 1;
public static final int SHARE_KML_FILE = 2;
public static final int SHARE_CSV_FILE = 3;
public static final int SHARE_TCX_FILE = 4;
public static final int SAVE_GPX_FILE = 5;
public static final int SAVE_KML_FILE = 6;
public static final int SAVE_CSV_FILE = 7;
public static final int SAVE_TCX_FILE = 8;
public static final int SHOW_WAYPOINT = 9;
public static final int WELCOME = 10;
/*
* Menu ids:
*/
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 SHOW_WAYPOINT = 0;
/*
* Context menu ids. Sorted alphabetically.
*/
public static final int MENU_CLEAR_MAP = 100;
public static final int MENU_DELETE = 101;
public static final int MENU_EDIT = 102;
public static final int MENU_PLAY = 103;
public static final int MENU_SAVE_CSV_FILE = 104;
public static final int MENU_SAVE_GPX_FILE = 105;
public static final int MENU_SAVE_KML_FILE = 106;
public static final int MENU_SAVE_TCX_FILE = 107;
public static final int MENU_SEND_TO_GOOGLE = 108;
public static final int MENU_SHARE = 109;
public static final int MENU_SHARE_CSV_FILE = 110;
public static final int MENU_SHARE_FUSION_TABLE = 111;
public static final int MENU_SHARE_GPX_FILE = 112;
public static final int MENU_SHARE_KML_FILE = 113;
public static final int MENU_SHARE_MAP = 114;
public static final int MENU_SHARE_TCX_FILE = 115;
public static final int MENU_SHOW = 116;
public static final int MENU_WRITE_TO_SD_CARD = 117;
public static final int MENU_DELETE = 100;
public static final int MENU_EDIT = 101;
public static final int MENU_SHOW = 102;
/*
* Main screen tab tags:
*/
public static final String MAP_TAB_TAG = "map";
public static final String STATS_TAB_TAG = "stats";
public static final String CHART_TAB_TAG = "chart";
/**
* The number of distance readings to smooth to get a stable signal.
@@ -169,29 +144,6 @@ public abstract class Constants {
public static final String RESUME_TRACK_EXTRA_NAME =
"com.google.android.apps.mytracks.RESUME_TRACK";
public static int getActionFromMenuId(int menuId) {
switch (menuId) {
case Constants.MENU_SHARE_KML_FILE:
return Constants.SHARE_KML_FILE;
case Constants.MENU_SHARE_GPX_FILE:
return Constants.SHARE_GPX_FILE;
case Constants.MENU_SHARE_CSV_FILE:
return Constants.SHARE_CSV_FILE;
case Constants.MENU_SHARE_TCX_FILE:
return Constants.SHARE_TCX_FILE;
case Constants.MENU_SAVE_GPX_FILE:
return Constants.SAVE_GPX_FILE;
case Constants.MENU_SAVE_KML_FILE:
return Constants.SAVE_KML_FILE;
case Constants.MENU_SAVE_CSV_FILE:
return Constants.SAVE_CSV_FILE;
case Constants.MENU_SAVE_TCX_FILE:
return Constants.SAVE_TCX_FILE;
default:
return -1;
}
}
public static final String MAPSHOP_BASE_URL =
"https://maps.google.com/maps/ms";
@@ -1,85 +0,0 @@
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.maps.mytracks.R;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
/**
* A utility class that can be used to delete all tracks and track points
* from the provider, including asking for confirmation from the user via
* a dialog.
*
* @author Leif Hendrik Wilden
*/
public class DeleteAllTracks extends Handler {
private final Context context;
private final Runnable done;
public DeleteAllTracks(Context context, Runnable done) {
this.context = context;
this.done = done;
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
AlertDialog dialog = null;
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(
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.generic_yes),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
Log.w(Constants.TAG, "deleting all!");
MyTracksProviderUtils.Factory.get(context).deleteAllTracks();
SharedPreferences prefs = context.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
Editor editor = prefs.edit();
// TODO: Go through data manager
editor.putLong(context.getString(R.string.selected_track_key), -1);
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
if (done != null) {
Handler h = new Handler();
h.post(done);
}
}
});
builder.setNegativeButton(context.getString(R.string.generic_no),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
dialog = builder.create();
dialog.show();
}
}
@@ -1,116 +0,0 @@
/*
* 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;
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.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.UriUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
/**
* Activity used to delete a track.
*
* @author Rodrigo Damazio
*/
public class DeleteTrack extends Activity
implements DialogInterface.OnClickListener, OnCancelListener {
private static final int CONFIRM_DIALOG = 1;
private MyTracksProviderUtils providerUtils;
private long deleteTrackId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
providerUtils = MyTracksProviderUtils.Factory.get(this);
Intent intent = getIntent();
String action = intent.getAction();
Uri data = intent.getData();
if (!Intent.ACTION_DELETE.equals(action) ||
!UriUtils.matchesContentUri(data, TracksColumns.CONTENT_URI)) {
Log.e(TAG, "Got bad delete intent: " + intent);
finish();
}
deleteTrackId = ContentUris.parseId(data);
showDialog(CONFIRM_DIALOG);
}
@Override
protected Dialog onCreateDialog(int id) {
if (id != CONFIRM_DIALOG) {
Log.e(TAG, "Unknown dialog " + id);
return null;
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
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.generic_yes), this);
builder.setNegativeButton(getString(R.string.generic_no), this);
builder.setOnCancelListener(this);
return builder.create();
}
@Override
public void onClick(DialogInterface dialogInterface, int which) {
dialogInterface.dismiss();
if (which == DialogInterface.BUTTON_POSITIVE) {
deleteTrack();
}
finish();
}
@Override
public void onCancel(DialogInterface dialog) {
onClick(dialog, DialogInterface.BUTTON_NEGATIVE);
}
private void deleteTrack() {
providerUtils.deleteTrack(deleteTrackId);
// If the track we just deleted was selected, unselect it.
String selectedKey = getString(R.string.selected_track_key);
SharedPreferences preferences = getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
if (preferences.getLong(selectedKey, -1) == deleteTrackId) {
Editor editor = preferences.edit().putLong(selectedKey, -1);
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
}
}
}
@@ -0,0 +1,115 @@
/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
/**
* An activity to export all the tracks to the SD card.
*
* @author Sandor Dornbush
*/
public class ExportActivity extends Activity {
public static final String EXTRA_TRACK_FILE_FORMAT = "track_file_format";
private static final int DIALOG_PROGRESS_ID = 0;
private ExportAsyncTask exportAsyncTask;
private ProgressDialog progressDialog;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Object retained = getLastNonConfigurationInstance();
if (retained instanceof ExportAsyncTask) {
exportAsyncTask = (ExportAsyncTask) retained;
exportAsyncTask.setActivity(this);
} else {
Intent intent = getIntent();
TrackFileFormat trackFileFormat = intent.getParcelableExtra(EXTRA_TRACK_FILE_FORMAT);
exportAsyncTask = new ExportAsyncTask(this, trackFileFormat);
exportAsyncTask.execute();
}
}
@Override
public Object onRetainNonConfigurationInstance() {
exportAsyncTask.setActivity(null);
return exportAsyncTask;
}
@Override
protected Dialog onCreateDialog(int id) {
if (id != DIALOG_PROGRESS_ID) {
return null;
}
progressDialog = DialogUtils.createHorizontalProgressDialog(
this, R.string.export_progress_message, new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
exportAsyncTask.cancel(true);
finish();
}
});
return progressDialog;
}
/**
* Invokes when the associated AsyncTask completes.
*
* @param success true if the AsyncTask is successful
* @param messageId message id to display to user
*/
public void onAsyncTaskCompleted(boolean success, int messageId) {
removeDialog(DIALOG_PROGRESS_ID);
Toast.makeText(this, messageId, success ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG).show();
finish();
}
/**
* Shows the progress dialog.
*/
public void showProgressDialog() {
showDialog(DIALOG_PROGRESS_ID);
}
/**
* Sets the progress dialog value.
*
* @param number the number of tracks exported
* @param max the maximum number of tracks
*/
public void setProgressDialogValue(int number, int max) {
if (progressDialog != null) {
progressDialog.setIndeterminate(false);
progressDialog.setMax(max);
progressDialog.setProgress(Math.min(number, max));
}
}
}
@@ -1,229 +0,0 @@
/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.io.file.TrackWriter;
import com.google.android.apps.mytracks.io.file.TrackWriterFactory;
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
import com.google.android.apps.mytracks.util.SystemUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import android.widget.Toast;
/**
* A class that will export all tracks to the sd card.
*
* @author Sandor Dornbush
*/
public class ExportAllTracks {
// These must line up with the index in the array.
public static final int GPX_OPTION_INDEX = 0;
public static final int KML_OPTION_INDEX = 1;
public static final int CSV_OPTION_INDEX = 2;
public static final int TCX_OPTION_INDEX = 3;
private final Activity activity;
private WakeLock wakeLock;
private ProgressDialog progress;
private TrackFileFormat format = TrackFileFormat.GPX;
private final DialogInterface.OnClickListener itemClick =
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case GPX_OPTION_INDEX:
format = TrackFileFormat.GPX;
break;
case KML_OPTION_INDEX:
format = TrackFileFormat.KML;
break;
case CSV_OPTION_INDEX:
format = TrackFileFormat.CSV;
break;
case TCX_OPTION_INDEX:
format = TrackFileFormat.TCX;
break;
default:
Log.w(Constants.TAG, "Unknown export format: " + which);
}
}
};
public ExportAllTracks(Activity activity) {
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.setTitle(R.string.track_list_export_all);
builder.setSingleChoiceItems(choices, 0, itemClick);
builder.setPositiveButton(R.string.generic_ok, positiveClick);
builder.setNegativeButton(R.string.generic_cancel, null);
builder.show();
}
private final DialogInterface.OnClickListener positiveClick =
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new Thread(runner, "ExportAllTracks").start();
}
};
private final Runnable runner = new Runnable() {
public void run() {
aquireLocksAndExport();
}
};
/**
* Makes sure that we keep the phone from sleeping.
* See if there is a current track. Aquire a wake lock if there is no
* current track.
*/
private void aquireLocksAndExport() {
SharedPreferences prefs = activity.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
long recordingTrackId = -1;
if (prefs != null) {
recordingTrackId =
prefs.getLong(activity.getString(R.string.recording_track_key), -1);
}
if (recordingTrackId != -1) {
wakeLock = SystemUtils.acquireWakeLock(activity, wakeLock);
}
// Now we can safely export everything.
exportAll();
// Release the wake lock if we recorded one.
// TODO check what happens if we started recording after getting this lock.
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
Log.i(Constants.TAG, "ExportAllTracks: Releasing wake lock.");
}
Log.i(Constants.TAG, "ExportAllTracks: Done");
showToast(R.string.export_success, Toast.LENGTH_SHORT);
}
private void makeProgressDialog(final int trackCount) {
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);
progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progress.setMax(trackCount);
progress.setProgress(0);
progress.show();
}
/**
* Actually export the tracks.
* This should be called after the wake locks have been aquired.
*/
private void exportAll() {
// Get a cursor over all tracks.
Cursor cursor = null;
try {
MyTracksProviderUtils providerUtils =
MyTracksProviderUtils.Factory.get(activity);
cursor = providerUtils.getTracksCursor(null, null, TracksColumns._ID);
if (cursor == null) {
return;
}
final int trackCount = cursor.getCount();
Log.i(Constants.TAG,
"ExportAllTracks: Exporting: " + cursor.getCount() + " tracks.");
int idxTrackId = cursor.getColumnIndexOrThrow(TracksColumns._ID);
activity.runOnUiThread(new Runnable() {
public void run() {
makeProgressDialog(trackCount);
}
});
for (int i = 0; cursor.moveToNext(); i++) {
final int status = i;
activity.runOnUiThread(new Runnable() {
public void run() {
synchronized (this) {
if (progress == null) {
return;
}
progress.setProgress(status);
}
}
});
long id = cursor.getLong(idxTrackId);
Log.i(Constants.TAG, "ExportAllTracks: exporting: " + id);
TrackWriter writer =
TrackWriterFactory.newWriter(activity, providerUtils, id, format);
if (writer == null) {
showToast(R.string.export_error, Toast.LENGTH_LONG);
return;
}
writer.writeTrack();
if (!writer.wasSuccess()) {
// Abort the whole export on the first error.
showToast(writer.getErrorMessage(), Toast.LENGTH_LONG);
return;
}
}
} finally {
if (cursor != null) {
cursor.close();
}
if (progress != null) {
synchronized (this) {
progress.dismiss();
progress = null;
}
}
}
}
private void showToast(final int messageId, final int length) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(activity, messageId, length).show();
}
});
}
}
@@ -0,0 +1,166 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.io.file.TrackWriter;
import com.google.android.apps.mytracks.io.file.TrackWriterFactory;
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
import com.google.android.apps.mytracks.util.SystemUtils;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.PowerManager.WakeLock;
/**
* AsyncTask to export all the tracks to the SD card.
*
* @author Jimmy Shih
*/
public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private ExportActivity exportActivity;
private final TrackFileFormat trackFileFormat;
private final Context context;
private final MyTracksProviderUtils myTracksProviderUtils;
private WakeLock wakeLock;
private TrackWriter trackWriter;
// true if the AsyncTask result is success
private boolean success;
// true if the AsyncTask has completed
private boolean completed;
// message id to return to the activity
private int messageId;
/**
* Creates an AsyncTask.
*
* @param exportActivity the activity currently associated with this AsyncTask
* @param trackFileFormat the track file format
*/
public ExportAsyncTask(ExportActivity exportActivity, TrackFileFormat trackFileFormat) {
this.exportActivity = exportActivity;
this.trackFileFormat = trackFileFormat;
context = exportActivity.getApplicationContext();
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(exportActivity);
SharedPreferences sharedPreferences = exportActivity.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
long recordingTrackId = sharedPreferences.getLong(
exportActivity.getString(R.string.recording_track_key), -1L);
// Get the wake lock if not recording
if (recordingTrackId == -1L) {
wakeLock = SystemUtils.acquireWakeLock(exportActivity, wakeLock);
}
success = false;
completed = false;
messageId = R.string.export_error;
}
/**
* Sets the current {@link ExportActivity} associated with this AyncTask.
*
* @param exportActivity the current {@link ExportActivity}, can be null
*/
public void setActivity(ExportActivity exportActivity) {
this.exportActivity = exportActivity;
if (completed && exportActivity != null) {
exportActivity.onAsyncTaskCompleted(success, messageId);
}
}
@Override
protected void onPreExecute() {
if (exportActivity != null) {
exportActivity.showProgressDialog();
}
}
@Override
protected Boolean doInBackground(Void... params) {
Cursor cursor = null;
try {
cursor = myTracksProviderUtils.getTracksCursor(null, null, TracksColumns._ID);
if (cursor == null) {
messageId = R.string.export_success;
return true;
}
int count = cursor.getCount();
int idIndex = cursor.getColumnIndexOrThrow(TracksColumns._ID);
for (int i = 0; i < count; i++) {
if (isCancelled()) {
return false;
}
cursor.moveToPosition(i);
long id = cursor.getLong(idIndex);
trackWriter = TrackWriterFactory.newWriter(
context, myTracksProviderUtils, id, trackFileFormat);
if (trackWriter == null) {
return false;
}
trackWriter.writeTrack();
if (!trackWriter.wasSuccess()) {
messageId = trackWriter.getErrorMessage();
return false;
}
publishProgress(i + 1, count);
}
messageId = R.string.export_success;
return true;
} finally {
if (cursor != null) {
cursor.close();
}
// Release the wake lock if obtained
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
}
}
}
@Override
protected void onProgressUpdate(Integer... values) {
if (exportActivity != null) {
exportActivity.setProgressDialogValue(values[0], values[1]);
}
}
@Override
protected void onPostExecute(Boolean result) {
success = result;
completed = true;
if (exportActivity != null) {
exportActivity.onAsyncTaskCompleted(success, messageId);
}
}
@Override
protected void onCancelled() {
if (trackWriter != null) {
trackWriter.stopWriteTrack();
}
}
}
@@ -0,0 +1,199 @@
/*
* 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;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.UriUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
/**
* An activity to import GPX files from the SD card. Optionally to import one
* GPX file and display it in My Tracks.
*
* @author Rodrigo Damazio
*/
public class ImportActivity extends Activity {
public static final String EXTRA_IMPORT_ALL = "import_all";
private static final String TAG = ImportActivity.class.getSimpleName();
private static final int DIALOG_PROGRESS_ID = 0;
private static final int DIALOG_RESULT_ID = 1;
private ImportAsyncTask importAsyncTask;
private ProgressDialog progressDialog;
private boolean importAll;
// path on the SD card to import
private String path;
// number of succesfully imported files
private int successCount;
// number of files to import
private int totalCount;
// last successfully imported track id
private long trackId;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
importAll = intent.getBooleanExtra(EXTRA_IMPORT_ALL, false);
if (importAll) {
path = FileUtils.buildExternalDirectoryPath("gpx");
} else {
String action = intent.getAction();
if (!(Intent.ACTION_ATTACH_DATA.equals(action) || Intent.ACTION_VIEW.equals(action))) {
Log.d(TAG, "Invalid action: " + intent);
finish();
return;
}
Uri data = intent.getData();
if (!UriUtils.isFileUri(data)) {
Log.d(TAG, "Invalid data: " + intent);
finish();
return;
}
path = data.getPath();
}
Object retained = getLastNonConfigurationInstance();
if (retained instanceof ImportAsyncTask) {
importAsyncTask = (ImportAsyncTask) retained;
importAsyncTask.setActivity(this);
} else {
importAsyncTask = new ImportAsyncTask(this, importAll, path);
importAsyncTask.execute();
}
}
@Override
public Object onRetainNonConfigurationInstance() {
importAsyncTask.setActivity(null);
return importAsyncTask;
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_PROGRESS_ID:
progressDialog = DialogUtils.createHorizontalProgressDialog(
this, R.string.import_progress_message, new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
importAsyncTask.cancel(true);
finish();
}
});
return progressDialog;
case DIALOG_RESULT_ID:
String message;
if (successCount == 0) {
message = getString(R.string.import_no_file, path);
} else {
String totalFiles = getResources()
.getQuantityString(R.plurals.importGpxFiles, totalCount, totalCount);
message = getString(R.string.import_success, successCount, totalFiles, path);
}
return new AlertDialog.Builder(this)
.setCancelable(true)
.setMessage(message)
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
finish();
}
})
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (!importAll && trackId != -1L) {
Intent intent = new Intent(ImportActivity.this, TrackDetailActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(TrackDetailActivity.TRACK_ID, trackId);
startActivity(intent);
}
finish();
}
})
.create();
default:
return null;
}
}
/**
* Invokes when the associated AsyncTask completes.
*
* @param success true if the AsyncTask is successful
* @param imported the number of files successfully imported
* @param total the total number of files to import
* @param id the last successfully imported track id
*/
public void onAsyncTaskCompleted(boolean success, int imported, int total, long id) {
successCount = imported;
totalCount = total;
trackId = id;
removeDialog(DIALOG_PROGRESS_ID);
if (success) {
showDialog(DIALOG_RESULT_ID);
} else {
Toast.makeText(this, getString(R.string.import_error, path), Toast.LENGTH_LONG).show();
finish();
}
}
/**
* Shows the progress dialog.
*/
public void showProgressDialog() {
showDialog(DIALOG_PROGRESS_ID);
}
/**
* Sets the progress dialog value.
*
* @param number the number of files imported
* @param max the maximum number of files
*/
public void setProgressDialogValue(int number, int max) {
if (progressDialog != null) {
progressDialog.setIndeterminate(false);
progressDialog.setMax(max);
progressDialog.setProgress(Math.min(number, max));
}
}
}
@@ -1,269 +0,0 @@
/*
* 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;
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;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
/**
* A class that will import all GPX tracks in /sdcard/MyTracks/gpx/
*
* @author David Piggott
*/
public class ImportAllTracks {
private final Activity activity;
private FileUtils fileUtils;
private boolean singleTrackSelected;
private String gpxPath;
private WakeLock wakeLock;
private ProgressDialog progress;
private int gpxFileCount;
private int importSuccessCount;
private long importedTrackIds[];
public ImportAllTracks(Activity activity) {
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();
}
};
/**
* Makes sure that we keep the phone from sleeping. See if there is a current
* track. Acquire a wake lock if there is no current track.
*/
private void aquireLocksAndImport() {
SharedPreferences prefs = activity.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
long recordingTrackId = -1;
if (prefs != null) {
recordingTrackId = prefs.getLong(activity.getString(R.string.recording_track_key), -1);
}
if (recordingTrackId == -1) {
wakeLock = SystemUtils.acquireWakeLock(activity, wakeLock);
}
// Now we can safely import everything.
importAll();
// Release the wake lock if we acquired one.
// TODO check what happens if we started recording after getting this lock.
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
Log.i(Constants.TAG, "ImportAllTracks: Releasing wake lock.");
}
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
showDoneDialog();
}
});
}
private void showDoneDialog() {
Log.i(Constants.TAG, "ImportAllTracks: Done");
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
if (gpxFileCount == 0) {
builder.setMessage(activity.getString(R.string.import_no_file, gpxPath));
} else {
String totalFiles = activity.getResources().getQuantityString(
R.plurals.importGpxFiles, gpxFileCount, gpxFileCount);
builder.setMessage(
activity.getString(R.string.import_success, importSuccessCount, totalFiles, gpxPath));
}
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.track_list_import_all);
progress = new ProgressDialog(activity);
progress.setIcon(android.R.drawable.ic_dialog_info);
progress.setTitle(importMsg);
progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progress.setMax(trackCount);
progress.setProgress(0);
progress.show();
}
/**
* Actually import the tracks. This should be called after the wake locks have
* been acquired.
*/
private void importAll() {
MyTracksProviderUtils providerUtils = MyTracksProviderUtils.Factory.get(activity);
if (!fileUtils.isSdCardAvailable()) {
return;
}
List<File> gpxFiles = getGpxFiles();
gpxFileCount = gpxFiles.size();
if (gpxFileCount == 0) {
return;
}
Log.i(Constants.TAG, "ImportAllTracks: Importing: " + gpxFileCount + " tracks.");
activity.runOnUiThread(new Runnable() {
public void run() {
makeProgressDialog(gpxFileCount);
}
});
Iterator<File> gpxFilesIterator = gpxFiles.iterator();
for (int currentFileNumber = 0; gpxFilesIterator.hasNext(); currentFileNumber++) {
File currentFile = gpxFilesIterator.next();
final int status = currentFileNumber;
activity.runOnUiThread(new Runnable() {
public void run() {
synchronized (this) {
if (progress == null) {
return;
}
progress.setProgress(status);
}
}
});
if (importFile(currentFile, providerUtils)) {
importSuccessCount++;
}
}
if (progress != null) {
synchronized (this) {
progress.dismiss();
progress = null;
}
}
}
/**
* Attempts to import a GPX file. Returns true on success, issues error
* notifications and returns false on failure.
*/
private boolean importFile(final File gpxFile, MyTracksProviderUtils providerUtils) {
Log.i(Constants.TAG, "ImportAllTracks: importing: " + gpxFile.getName());
try {
importedTrackIds = GpxImporter.importGPXFile(new FileInputStream(gpxFile), providerUtils);
return true;
} catch (FileNotFoundException e) {
Log.w(Constants.TAG, "GPX file wasn't found/went missing: "
+ gpxFile.getAbsolutePath(), e);
} catch (ParserConfigurationException e) {
Log.w(Constants.TAG, "Error parsing file: " + gpxFile.getAbsolutePath(), e);
} catch (SAXException e) {
Log.w(Constants.TAG, "Error parsing file: " + gpxFile.getAbsolutePath(), e);
} catch (IOException e) {
Log.w(Constants.TAG, "Error reading file: " + gpxFile.getAbsolutePath(), e);
}
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(activity, activity.getString(R.string.import_error, gpxFile.getName()),
Toast.LENGTH_LONG).show();
}
});
return false;
}
/**
* 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 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);
}
}
}
}
return gpxFiles;
}
}
@@ -0,0 +1,220 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
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;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
/**
* AsyncTask to import GPX files from the SD card.
*
* @author Jimmy Shih
*/
public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private static final String TAG = ImportAsyncTask.class.getSimpleName();
private ImportActivity importActivity;
private final boolean importAll;
private final String path;
private final MyTracksProviderUtils myTracksProviderUtils;
private WakeLock wakeLock;
// true if the AsyncTask result is success
private boolean success;
// true if the AsyncTask has completed
private boolean completed;
// number of files successfully imported
private int successCount;
// number of files to import
private int totalCount;
// the last successfully imported track id
private long trackId;
/**
* Creates an AsyncTask.
*
* @param importActivity the activity currently associated with this AsyncTask
* @param importAll true to import all GPX files
* @param path path to import GPX files
*/
public ImportAsyncTask(ImportActivity importActivity, boolean importAll, String path) {
this.importActivity = importActivity;
this.importAll = importAll;
this.path = path;
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(importActivity);
SharedPreferences sharedPreferences = importActivity.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
long recordingTrackId = sharedPreferences.getLong(
importActivity.getString(R.string.recording_track_key), -1L);
// Get the wake lock if not recording
if (recordingTrackId == -1L) {
wakeLock = SystemUtils.acquireWakeLock(importActivity, wakeLock);
}
success = false;
completed = false;
successCount = 0;
totalCount = 0;
trackId = -1L;
}
/**
* Sets the current {@link ImportActivity} associated with this AyncTask.
*
* @param importActivity the current {@link ImportActivity}, can be null
*/
public void setActivity(ImportActivity importActivity) {
this.importActivity = importActivity;
if (completed && importActivity != null) {
importActivity.onAsyncTaskCompleted(success, successCount, totalCount, trackId);
}
}
@Override
protected void onPreExecute() {
if (importActivity != null) {
importActivity.showProgressDialog();
}
}
@Override
protected Boolean doInBackground(Void... params) {
try {
if (!FileUtils.isSdCardAvailable()) {
return false;
}
List<File> files = getFiles();
totalCount = files.size();
if (totalCount == 0) {
return true;
}
for (int i = 0; i < totalCount; i++) {
if (isCancelled()) {
// If cancelled, return true to show the number of files imported
return true;
}
File file = files.get(i);
if (importFile(file)) {
successCount++;
}
publishProgress(i + 1, totalCount);
}
return true;
} finally {
// Release the wake lock if obtained
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
}
}
}
@Override
protected void onProgressUpdate(Integer... values) {
if (importActivity != null) {
importActivity.setProgressDialogValue(values[0], values[1]);
}
}
@Override
protected void onPostExecute(Boolean result) {
success = result;
completed = true;
if (importActivity != null) {
importActivity.onAsyncTaskCompleted(success, successCount, totalCount, trackId);
}
}
/**
* Imports a GPX file.
*
* @param file the file
*/
private boolean importFile(final File file) {
try {
long trackIds[] = GpxImporter.importGPXFile(new FileInputStream(file), myTracksProviderUtils);
int length = trackIds.length;
if (length > 0) {
trackId = trackIds[length - 1];
}
return true;
} catch (FileNotFoundException e) {
Log.d(TAG, "file: " + file.getAbsolutePath(), e);
return false;
} catch (ParserConfigurationException e) {
Log.d(TAG, "file: " + file.getAbsolutePath(), e);
return false;
} catch (SAXException e) {
Log.d(TAG, "file: " + file.getAbsolutePath(), e);
return false;
} catch (IOException e) {
Log.d(TAG, "file: " + file.getAbsolutePath(), e);
return false;
}
}
/**
* Gets a list of GPX files. If importAll is true, returns a list of GPX files
* under the path directory. If importAll is false, returns a list containing
* just the path file.
*/
private List<File> getFiles() {
List<File> files = new ArrayList<File>();
File file = new File(path);
if (importAll) {
File[] candidates = file.listFiles();
if (candidates != null) {
for (File candidate : candidates) {
if (!candidate.isDirectory() && candidate.getName().endsWith(".gpx")) {
files.add(candidate);
}
}
}
} else {
files.add(file);
}
return files;
}
}
@@ -22,39 +22,24 @@ 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.io.file.SaveActivity;
import com.google.android.apps.mytracks.io.sendtogoogle.SendRequest;
import com.google.android.apps.mytracks.io.sendtogoogle.UploadServiceChooserActivity;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.GeoRect;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.apps.mytracks.util.PlayTrackUtils;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.mytracks.R;
import android.app.Dialog;
import android.content.ContentUris;
import android.content.Intent;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.speech.tts.TextToSpeech;
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.View.OnCreateContextMenuListener;
import android.view.Window;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
@@ -72,8 +57,6 @@ public class MapActivity extends com.google.android.maps.MapActivity
implements View.OnTouchListener, View.OnClickListener,
TrackDataListener {
private static final int DIALOG_INSTALL_EARTH = 0;
// Saved instance state keys:
// ---------------------------
@@ -88,6 +71,12 @@ public class MapActivity extends com.google.android.maps.MapActivity
*/
private boolean keepMyLocationVisible;
/**
* True if we've already zoomed into the current location.
* Only relevant when {@link #keepMyLocationVisible} is true.
*/
private boolean myLocationWasZoomedIn;
/**
* The ID of a track on which we want to show a waypoint.
* The waypoint will be shown as soon as the track is loaded.
@@ -122,10 +111,6 @@ public class MapActivity extends com.google.android.maps.MapActivity
private LinearLayout messagePane;
private TextView messageText;
private LinearLayout busyPane;
private ImageButton optionsBtn;
private MenuItem myLocation;
private MenuItem toggleLayers;
/**
* We are not displaying driving directions. Just an arbitrary track that is
@@ -157,8 +142,8 @@ public class MapActivity extends com.google.android.maps.MapActivity
// The volume we want to control is the Text-To-Speech volume
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
// We don't need a window title bar:
requestWindowFeature(Window.FEATURE_NO_TITLE);
// Show the action bar (or nothing at all).
ApiAdapterFactory.getApiAdapter().showActionBar(this);
// Inflate the layout:
setContentView(R.layout.mytracks_layout);
@@ -177,10 +162,6 @@ public class MapActivity extends com.google.android.maps.MapActivity
messagePane = (LinearLayout) findViewById(R.id.messagepane);
messageText = (TextView) findViewById(R.id.messagetext);
busyPane = (LinearLayout) findViewById(R.id.busypane);
optionsBtn = (ImageButton) findViewById(R.id.showOptions);
optionsBtn.setOnCreateContextMenuListener(contextMenuListener);
optionsBtn.setOnClickListener(this);
}
@Override
@@ -235,27 +216,6 @@ public class MapActivity extends com.google.android.maps.MapActivity
super.onPause();
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_INSTALL_EARTH:
return PlayTrackUtils.createInstallEarthDialog(this);
default:
return null;
}
}
// Utility functions:
// -------------------
/**
* Shows the options button if a track is selected, or hide it if not.
*/
private void updateOptionsButton(boolean trackSelected) {
optionsBtn.setVisibility(
trackSelected ? View.VISIBLE : View.INVISIBLE);
}
/**
* Tests if a location is visible.
*
@@ -286,6 +246,18 @@ public class MapActivity extends com.google.android.maps.MapActivity
return r.contains(geoPoint);
}
/**
* Centers (and keeps centered) the map on the current location.
*/
public void showMyLocation() {
dataHub.forceUpdateLocation();
keepMyLocationVisible = true;
myLocationWasZoomedIn = false;
if (currentLocation != null) {
showCurrentLocation();
}
}
/**
* Moves the location pointer to the current location and center the map if
* the current location is outside the visible area.
@@ -302,6 +274,11 @@ public class MapActivity extends com.google.android.maps.MapActivity
GeoPoint geoPoint = LocationUtils.getGeoPoint(currentLocation);
MapController controller = mapView.getController();
controller.animateTo(geoPoint);
if (!myLocationWasZoomedIn && mapView.getZoomLevel() < 18) {
// Only zoom in the first time we show the location.
myLocationWasZoomedIn = true;
controller.setZoom(18);
}
}
}
@@ -408,8 +385,6 @@ public class MapActivity extends com.google.android.maps.MapActivity
@Override
public void run() {
boolean trackSelected = track != null;
updateOptionsButton(trackSelected);
mapOverlay.setTrackDrawingEnabled(trackSelected);
if (trackSelected) {
@@ -430,153 +405,24 @@ public class MapActivity extends com.google.android.maps.MapActivity
});
}
private final OnCreateContextMenuListener contextMenuListener =
new OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
menu.setHeaderTitle(R.string.track_list_context_menu_title);
menu.add(Menu.NONE, Constants.MENU_EDIT, Menu.NONE, 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(Menu.NONE, Constants.MENU_PLAY, Menu.NONE, R.string.track_list_play);
menu.add(Menu.NONE, Constants.MENU_SEND_TO_GOOGLE, Menu.NONE,
R.string.track_list_send_google);
SubMenu share = menu.addSubMenu(
Menu.NONE, Constants.MENU_SHARE, Menu.NONE, R.string.track_list_share_track);
share.add(
Menu.NONE, Constants.MENU_SHARE_MAP, Menu.NONE, R.string.track_list_share_map);
share.add(Menu.NONE, Constants.MENU_SHARE_FUSION_TABLE, Menu.NONE,
R.string.track_list_share_fusion_table);
share.add(Menu.NONE, Constants.MENU_SHARE_GPX_FILE, Menu.NONE,
String.format(shareFileFormat, fileTypes[0]));
share.add(Menu.NONE, Constants.MENU_SHARE_KML_FILE, Menu.NONE,
String.format(shareFileFormat, fileTypes[1]));
share.add(Menu.NONE, Constants.MENU_SHARE_CSV_FILE, Menu.NONE,
String.format(shareFileFormat, fileTypes[2]));
share.add(Menu.NONE, Constants.MENU_SHARE_TCX_FILE, Menu.NONE,
String.format(shareFileFormat, fileTypes[3]));
SubMenu save = menu.addSubMenu(
Menu.NONE, Constants.MENU_WRITE_TO_SD_CARD, Menu.NONE, R.string.track_list_save_sd);
save.add(Menu.NONE, Constants.MENU_SAVE_GPX_FILE, Menu.NONE,
String.format(saveFileFormat, fileTypes[0]));
save.add(Menu.NONE, Constants.MENU_SAVE_KML_FILE, Menu.NONE,
String.format(saveFileFormat, fileTypes[1]));
save.add(Menu.NONE, Constants.MENU_SAVE_CSV_FILE, Menu.NONE,
String.format(saveFileFormat, fileTypes[2]));
save.add(Menu.NONE, Constants.MENU_SAVE_TCX_FILE, Menu.NONE,
String.format(saveFileFormat, fileTypes[3]));
menu.add(Menu.NONE, Constants.MENU_CLEAR_MAP, Menu.NONE, R.string.track_list_clear_map);
menu.add(Menu.NONE, Constants.MENU_DELETE, Menu.NONE, R.string.track_list_delete_track);
}
}
};
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
Intent intent;
long trackId = dataHub.getSelectedTrackId();
switch (item.getItemId()) {
case Constants.MENU_EDIT:
intent = new Intent(this, TrackDetail.class).putExtra(TrackDetail.TRACK_ID, trackId);
startActivity(intent);
return true;
case Constants.MENU_PLAY:
if (PlayTrackUtils.isEarthInstalled(this)) {
PlayTrackUtils.playTrack(this, trackId);
return true;
} else {
showDialog(DIALOG_INSTALL_EARTH);
return true;
}
case Constants.MENU_SEND_TO_GOOGLE:
intent = new Intent(this, UploadServiceChooserActivity.class)
.putExtra(SendRequest.SEND_REQUEST_KEY, new SendRequest(trackId, true, true, true));
startActivity(intent);
return true;
case Constants.MENU_SHARE_MAP:
intent = new Intent(this, UploadServiceChooserActivity.class)
.putExtra(SendRequest.SEND_REQUEST_KEY, new SendRequest(trackId, true, false, false));
startActivity(intent);
return true;
case Constants.MENU_SHARE_FUSION_TABLE:
intent = new Intent(this, UploadServiceChooserActivity.class)
.putExtra(SendRequest.SEND_REQUEST_KEY, new SendRequest(trackId, false, true, false));
startActivity(intent);
return true;
case Constants.MENU_SHARE_GPX_FILE:
case Constants.MENU_SHARE_KML_FILE:
case Constants.MENU_SHARE_CSV_FILE:
case Constants.MENU_SHARE_TCX_FILE:
case Constants.MENU_SAVE_GPX_FILE:
case Constants.MENU_SAVE_KML_FILE:
case Constants.MENU_SAVE_CSV_FILE:
case Constants.MENU_SAVE_TCX_FILE:
SaveActivity.handleExportTrackAction(
this, trackId, Constants.getActionFromMenuId(item.getItemId()));
return true;
case Constants.MENU_CLEAR_MAP:
dataHub.unloadCurrentTrack();
return true;
case Constants.MENU_DELETE:
Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId);
intent = new Intent(Intent.ACTION_DELETE, uri);
startActivity(intent);
return true;
default:
return super.onMenuItemSelected(featureId, item);
}
/**
* Returns whether the map is currently in satellite view mode.
*/
public boolean isSatelliteView() {
return mapView.isSatellite();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
myLocation = menu.add(
Menu.NONE, Constants.MENU_MY_LOCATION, Menu.NONE, R.string.menu_map_view_my_location);
myLocation.setIcon(android.R.drawable.ic_menu_mylocation);
toggleLayers = menu.add(
Menu.NONE, Constants.MENU_TOGGLE_LAYERS, Menu.NONE, R.string.menu_map_view_satellite_mode);
toggleLayers.setIcon(android.R.drawable.ic_menu_mapmode);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
toggleLayers.setTitle(mapView.isSatellite() ?
R.string.menu_map_view_map_mode : R.string.menu_map_view_satellite_mode);
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case Constants.MENU_MY_LOCATION: {
dataHub.forceUpdateLocation();
keepMyLocationVisible = true;
if (mapView.getZoomLevel() < 18) {
mapView.getController().setZoom(18);
}
if (currentLocation != null) {
showCurrentLocation();
}
return true;
}
case Constants.MENU_TOGGLE_LAYERS: {
mapView.setSatellite(!mapView.isSatellite());
return true;
}
}
return super.onOptionsItemSelected(item);
/**
* Changes whether the map should be in satellite view mode.
*/
public void setSatelliteView(boolean sat) {
mapView.setSatellite(sat);
}
@Override
public void onClick(View v) {
if (v == messagePane) {
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
} else if (v == optionsBtn) {
optionsBtn.performLongClick();
}
}
@@ -1,102 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.content.Intent;
import android.view.Menu;
import android.view.MenuItem;
/**
* Manage the application menus.
*
* @author Sandor Dornbush
*/
class MenuManager {
private final MyTracks activity;
public MenuManager(MyTracks activity) {
this.activity = activity;
}
public boolean onCreateOptionsMenu(Menu menu) {
activity.getMenuInflater().inflate(R.menu.main, menu);
// TODO: Replace search button with search widget if API level >= 11
return true;
}
public void onPrepareOptionsMenu(Menu menu, boolean hasRecorded,
boolean isRecording, boolean hasSelectedTrack) {
menu.findItem(R.id.menu_markers)
.setEnabled(hasRecorded && hasSelectedTrack);
menu.findItem(R.id.menu_record_track)
.setEnabled(!isRecording)
.setVisible(!isRecording);
menu.findItem(R.id.menu_stop_recording)
.setEnabled(isRecording)
.setVisible(isRecording);
}
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_record_track: {
activity.startRecording();
return true;
}
case R.id.menu_stop_recording: {
activity.stopRecording();
return true;
}
case R.id.menu_tracks: {
activity.startActivityForResult(new Intent(activity, TrackList.class),
Constants.SHOW_TRACK);
return true;
}
case R.id.menu_markers: {
Intent startIntent = new Intent(activity, WaypointsList.class);
startIntent.putExtra("trackid", activity.getSelectedTrackId());
activity.startActivityForResult(startIntent, Constants.SHOW_WAYPOINT);
return true;
}
case R.id.menu_sensor_state: {
return startActivity(SensorStateActivity.class);
}
case R.id.menu_settings: {
return startActivity(SettingsActivity.class);
}
case R.id.menu_aggregated_statistics: {
return startActivity(AggregatedStatsActivity.class);
}
case R.id.menu_help: {
return startActivity(WelcomeActivity.class);
}
case R.id.menu_search: {
// TODO: Pass the current track ID and current location to do some fancier ranking.
activity.onSearchRequested();
}
}
return false;
}
private boolean startActivity(Class<? extends Activity> activityClass) {
activity.startActivity(new Intent(activity, activityClass));
return true;
}
}
@@ -1,489 +0,0 @@
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
import com.google.android.apps.mytracks.content.WaypointsColumns;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
import com.google.android.apps.mytracks.services.ServiceUtils;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.util.AnalyticsUtils;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.EulaUtils;
import com.google.android.apps.mytracks.util.SystemUtils;
import com.google.android.apps.mytracks.util.UriUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.TabActivity;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.widget.RelativeLayout;
import android.widget.TabHost;
import android.widget.Toast;
/**
* The super activity that embeds our sub activities.
*
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
*/
@SuppressWarnings("deprecation")
public class MyTracks extends TabActivity implements OnTouchListener {
private static final int DIALOG_EULA_ID = 0;
private TrackDataHub dataHub;
/**
* Menu manager.
*/
private MenuManager menuManager;
/**
* Preferences.
*/
private SharedPreferences preferences;
/**
* True if a new track should be created after the track recording service
* binds.
*/
private boolean startNewTrackRequested = false;
/**
* Utilities to deal with the database.
*/
private MyTracksProviderUtils providerUtils;
/*
* Tabs/View navigation:
*/
private NavControls navControls;
private final Runnable changeTab = new Runnable() {
public void run() {
getTabHost().setCurrentTab(navControls.getCurrentIcons());
}
};
/*
* Recording service interaction:
*/
private final Runnable serviceBindCallback = new Runnable() {
@Override
public void run() {
synchronized (serviceConnection) {
ITrackRecordingService service = serviceConnection.getServiceIfBound();
if (startNewTrackRequested && service != null) {
Log.i(TAG, "Starting recording");
startNewTrackRequested = false;
startRecordingNewTrack(service);
} else if (startNewTrackRequested) {
Log.w(TAG, "Not yet starting recording");
}
}
}
};
private TrackRecordingServiceConnection serviceConnection;
/*
* Application lifetime events:
* ============================
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "MyTracks.onCreate");
super.onCreate(savedInstanceState);
if (!SystemUtils.isRelease(this)) {
ApiAdapterFactory.getApiAdapter().enableStrictMode();
}
AnalyticsUtils.sendPageViews(this, "/appstart");
providerUtils = MyTracksProviderUtils.Factory.get(this);
preferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
dataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
menuManager = new MenuManager(this);
serviceConnection = new TrackRecordingServiceConnection(this, serviceBindCallback);
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
// We don't need a window title bar:
requestWindowFeature(Window.FEATURE_NO_TITLE);
// If the user just starts typing (on a device with a keyboard), we start a search.
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
final Resources res = getResources();
final TabHost tabHost = getTabHost();
tabHost.addTab(tabHost.newTabSpec("tab1")
.setIndicator("Map", res.getDrawable(
android.R.drawable.ic_menu_mapmode))
.setContent(new Intent(this, MapActivity.class)));
tabHost.addTab(tabHost.newTabSpec("tab2")
.setIndicator("Stats", res.getDrawable(R.drawable.menu_stats))
.setContent(new Intent(this, StatsActivity.class)));
tabHost.addTab(tabHost.newTabSpec("tab3")
.setIndicator("Chart", res.getDrawable(R.drawable.menu_elevation))
.setContent(new Intent(this, ChartActivity.class)));
// Hide the tab widget itself. We'll use overlayed prev/next buttons to
// switch between the tabs:
tabHost.getTabWidget().setVisibility(View.GONE);
RelativeLayout layout = new RelativeLayout(this);
LayoutParams params =
new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
layout.setLayoutParams(params);
navControls =
new NavControls(this, layout,
getResources().obtainTypedArray(R.array.left_icons),
getResources().obtainTypedArray(R.array.right_icons),
changeTab);
navControls.show();
tabHost.addView(layout);
layout.setOnTouchListener(this);
if (!EulaUtils.getEulaValue(this)) {
showDialog(DIALOG_EULA_ID);
}
}
@Override
protected void onStart() {
Log.d(TAG, "MyTracks.onStart");
super.onStart();
dataHub.start();
// Ensure that service is running and bound if we're supposed to be recording
if (ServiceUtils.isRecording(this, null, preferences)) {
serviceConnection.startAndBind();
}
Intent intent = getIntent();
String action = intent.getAction();
Uri data = intent.getData();
if ((Intent.ACTION_VIEW.equals(action) || Intent.ACTION_EDIT.equals(action))
&& TracksColumns.CONTENT_ITEMTYPE.equals(intent.getType())
&& UriUtils.matchesContentUri(data, TracksColumns.CONTENT_URI)) {
long trackId = ContentUris.parseId(data);
dataHub.loadTrack(trackId);
} else if (Intent.ACTION_VIEW.equals(action)
&& WaypointsColumns.CONTENT_ITEMTYPE.equals(intent.getType())
&& UriUtils.matchesContentUri(data, WaypointsColumns.CONTENT_URI)) {
// TODO(rdamazio): Waypoint URIs should be base/trackid/waypointid
long waypointId = ContentUris.parseId(data);
Waypoint waypoint = providerUtils.getWaypoint(waypointId);
long trackId = waypoint.getTrackId();
// Request that the waypoint is shown (now or when the right track is loaded).
showWaypoint(trackId, waypointId);
// Load the right track, if not loaded already.
dataHub.loadTrack(trackId);
}
}
@Override
protected void onResume() {
// Called when the current activity is being displayed or re-displayed
// to the user.
Log.d(TAG, "MyTracks.onResume");
serviceConnection.bindIfRunning();
super.onResume();
}
@Override
protected void onPause() {
// Called when activity is going into the background, but has not (yet) been
// killed. Shouldn't block longer than approx. 2 seconds.
Log.d(TAG, "MyTracks.onPause");
super.onPause();
}
@Override
protected void onStop() {
Log.d(TAG, "MyTracks.onStop");
dataHub.stop();
super.onStop();
}
@Override
protected void onDestroy() {
Log.d(TAG, "MyTracks.onDestroy");
serviceConnection.unbind();
super.onDestroy();
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_EULA_ID:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.eula_title);
builder.setMessage(EulaUtils.getEulaMessage(this));
builder.setPositiveButton(R.string.eula_accept, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
EulaUtils.setEulaValue(MyTracks.this);
Intent startIntent = new Intent(MyTracks.this, WelcomeActivity.class);
startActivityForResult(startIntent, Constants.WELCOME);
}
});
builder.setNegativeButton(R.string.eula_decline, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
builder.setCancelable(true);
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
finish();
}
});
return builder.create();
default:
return null;
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
return menuManager.onCreateOptionsMenu(menu);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menuManager.onPrepareOptionsMenu(menu, providerUtils.getLastTrack() != null,
ServiceUtils.isRecording(this, serviceConnection.getServiceIfBound(), preferences),
dataHub.isATrackSelected());
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return menuManager.onOptionsItemSelected(item)
? true
: super.onOptionsItemSelected(item);
}
/*
* Key events:
* ===========
*/
@Override
public boolean onTrackballEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (ServiceUtils.isRecording(this, serviceConnection.getServiceIfBound(), preferences)) {
try {
insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
} catch (RemoteException e) {
Log.e(TAG, "Cannot insert statistics marker.", e);
} catch (IllegalStateException e) {
Log.e(TAG, "Cannot insert statistics marker.", e);
}
return true;
}
}
return super.onTrackballEvent(event);
}
@Override
public void onActivityResult(int requestCode, int resultCode,
final Intent results) {
Log.d(TAG, "MyTracks.onActivityResult");
long trackId = dataHub.getSelectedTrackId();
if (results != null) {
trackId = results.getLongExtra("trackid", trackId);
}
switch (requestCode) {
case Constants.SHOW_TRACK: {
if (results != null) {
if (trackId >= 0) {
dataHub.loadTrack(trackId);
// The track list passed the requested action as result code. Hand
// it off to the onActivityResult for further processing:
if (resultCode != Constants.SHOW_TRACK) {
onActivityResult(resultCode, Activity.RESULT_OK, results);
}
}
}
break;
}
case Constants.SHOW_WAYPOINT: {
if (results != null) {
final long waypointId = results.getLongExtra(WaypointDetails.WAYPOINT_ID_EXTRA, -1);
if (waypointId >= 0) {
showWaypoint(trackId, waypointId);
}
}
break;
}
case Constants.WELCOME: {
CheckUnits.check(this);
break;
}
default: {
Log.w(TAG, "Warning unhandled request code: " + requestCode);
}
}
}
private void showWaypoint(long trackId, long waypointId) {
MapActivity map =
(MapActivity) getLocalActivityManager().getActivity("tab1");
if (map != null) {
getTabHost().setCurrentTab(0);
map.showWaypoint(trackId, waypointId);
} else {
Log.e(TAG, "Couldnt' get map tab");
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
navControls.show();
}
return false;
}
/**
* Inserts a waypoint marker.
*
* TODO: Merge with WaypointsList#insertWaypoint.
*
* @return Id of the inserted statistics marker.
* @throws RemoteException If the call on the service failed.
*/
private long insertWaypoint(WaypointCreationRequest request) throws RemoteException {
ITrackRecordingService trackRecordingService = serviceConnection.getServiceIfBound();
if (trackRecordingService == null) {
throw new IllegalStateException("The recording service is not bound.");
}
try {
long waypointId = trackRecordingService.insertWaypoint(request);
if (waypointId >= 0) {
Toast.makeText(this, R.string.marker_insert_success, Toast.LENGTH_LONG).show();
}
return waypointId;
} catch (RemoteException e) {
Toast.makeText(this, R.string.marker_insert_error, Toast.LENGTH_LONG).show();
throw e;
}
}
private void startRecordingNewTrack(
ITrackRecordingService trackRecordingService) {
try {
long recordingTrackId = trackRecordingService.startNewTrack();
// Select the recording track.
dataHub.loadTrack(recordingTrackId);
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.track_record_error), Toast.LENGTH_SHORT).show();
Log.w(TAG, "Unable to start recording.", e);
}
}
/**
* Starts the track recording service (if not already running) and binds to
* it. Starts recording a new track.
*/
void startRecording() {
synchronized (serviceConnection) {
startNewTrackRequested = true;
serviceConnection.startAndBind();
// Binding was already requested before, it either already happened
// (in which case running the callback manually triggers the actual recording start)
// or it will happen in the future
// (in which case running the callback now will have no effect).
serviceBindCallback.run();
}
}
/**
* Stops the track recording service and unbinds from it. Will display a toast
* "Stopped recording" and pop up the Track Details activity.
*/
void stopRecording() {
// Save the track id as the shared preference will overwrite the recording track id.
SharedPreferences sharedPreferences = getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
long currentTrackId = sharedPreferences.getLong(getString(R.string.recording_track_key), -1);
ITrackRecordingService trackRecordingService = serviceConnection.getServiceIfBound();
if (trackRecordingService != null) {
try {
trackRecordingService.endCurrentTrack();
} catch (Exception e) {
Log.e(TAG, "Unable to stop recording.", e);
}
}
serviceConnection.stop();
if (currentTrackId > 0) {
Intent intent = new Intent(MyTracks.this, TrackDetail.class);
intent.putExtra(TrackDetail.TRACK_ID, currentTrackId);
intent.putExtra(TrackDetail.SHOW_CANCEL, false);
startActivity(intent);
}
}
long getSelectedTrackId() {
return dataHub.getSelectedTrackId();
}
}
@@ -17,6 +17,9 @@ package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.services.RemoveTempFilesService;
import com.google.android.apps.mytracks.util.AnalyticsUtils;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.maps.mytracks.BuildConfig;
import android.app.Application;
import android.content.Intent;
@@ -32,6 +35,10 @@ public class MyTracksApplication extends Application {
@Override
public void onCreate() {
if (BuildConfig.DEBUG) {
ApiAdapterFactory.getApiAdapter().enableStrictMode();
}
AnalyticsUtils.sendPageViews(this, "/appstart");
startService(new Intent(this, RemoveTempFilesService.class));
}
@@ -23,22 +23,18 @@ import com.google.android.apps.mytracks.content.SearchEngine.ScoredResult;
import com.google.android.apps.mytracks.content.SearchEngine.SearchQuery;
import com.google.android.apps.mytracks.content.SearchEngineProvider;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.WaypointsColumns;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
import android.app.ListActivity;
import android.app.SearchManager;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.SearchRecentSuggestions;
import android.util.Log;
@@ -193,7 +189,7 @@ public class SearchActivity extends ListActivity {
resultMap.put(DESCRIPTION_FIELD, waypoint.getDescription());
resultMap.put(CATEGORY_FIELD, waypoint.getCategory());
// In the same place as we show time for tracks, show the track name for waypoints.
resultMap.put(TIME_FIELD, getString(R.string.track_list_track_name, trackName));
resultMap.put(TIME_FIELD, getString(R.string.search_track_location, trackName));
resultMap.put(STATS_FIELD, StringUtils.formatDateTime(this, waypoint.getLocation().getTime()));
resultMap.put(TRACK_ID_FIELD, waypoint.getTrackId());
resultMap.put(WAYPOINT_ID_FIELD, waypoint.getId());
@@ -251,15 +247,12 @@ public class SearchActivity extends ListActivity {
}
private Intent createViewDataIntent(Map<String, Object> clickedData) {
Intent intent = new Intent(Intent.ACTION_VIEW);
Intent intent = new Intent(this, TrackDetailActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
if (clickedData.containsKey(WAYPOINT_ID_FIELD)) {
long waypointId = (Long) clickedData.get(WAYPOINT_ID_FIELD);
Uri uri = ContentUris.withAppendedId(WaypointsColumns.CONTENT_URI, waypointId);
intent.setDataAndType(uri, WaypointsColumns.CONTENT_ITEMTYPE);
intent.putExtra(TrackDetailActivity.WAYPOINT_ID, (Long) clickedData.get(WAYPOINT_ID_FIELD));
} else {
long trackId = (Long) clickedData.get(TRACK_ID_FIELD);
Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId);
intent.setDataAndType(uri, TracksColumns.CONTENT_ITEMTYPE);
intent.putExtra(TrackDetailActivity.TRACK_ID, (Long) clickedData.get(TRACK_ID_FIELD));
}
return intent;
}
@@ -17,8 +17,9 @@ package com.google.android.apps.mytracks;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.io.backup.BackupActivityHelper;
import com.google.android.apps.mytracks.io.backup.BackupActivity;
import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener;
import com.google.android.apps.mytracks.io.backup.RestoreChooserActivity;
import com.google.android.apps.mytracks.services.sensors.ant.AntUtils;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.BluetoothDeviceUtils;
@@ -29,7 +30,6 @@ import com.google.android.maps.mytracks.R;
import android.app.Dialog;
import android.bluetooth.BluetoothAdapter;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
@@ -62,8 +62,9 @@ import java.util.Set;
*/
public class SettingsActivity extends PreferenceActivity {
private static final int DIALOG_CONFIRM_RESET = 0;
private static final int DIALOG_CONFIRM_ACCESS = 1;
private static final int DIALOG_CONFIRM_RESET_ID = 0;
private static final int DIALOG_CONFIRM_ACCESS_ID = 1;
private static final int DIALOG_CONFIRM_RESTORE_ID = 2;
// Value when the task frequency is off.
private static final String TASK_FREQUENCY_OFF = "0";
@@ -150,7 +151,7 @@ public class SettingsActivity extends PreferenceActivity {
resetPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference arg0) {
showDialog(DIALOG_CONFIRM_RESET);
showDialog(DIALOG_CONFIRM_RESET_ID);
return true;
}
});
@@ -162,7 +163,7 @@ public class SettingsActivity extends PreferenceActivity {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if ((Boolean) newValue) {
showDialog(DIALOG_CONFIRM_ACCESS);
showDialog(DIALOG_CONFIRM_ACCESS_ID);
return false;
} else {
return true;
@@ -174,17 +175,18 @@ public class SettingsActivity extends PreferenceActivity {
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_CONFIRM_RESET:
case DIALOG_CONFIRM_RESET_ID:
return DialogUtils.createConfirmationDialog(
this, R.string.settings_reset_confirm_message, new OnClickListener() {
this, R.string.settings_reset_confirm_message, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int button) {
onResetPreferencesConfirmed();
}
});
case DIALOG_CONFIRM_ACCESS:
return DialogUtils.createConfirmationDialog(
this, R.string.settings_sharing_allow_access_confirm_message, new OnClickListener() {
case DIALOG_CONFIRM_ACCESS_ID:
return DialogUtils.createConfirmationDialog(this,
R.string.settings_sharing_allow_access_confirm_message,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int button) {
CheckBoxPreference pref = (CheckBoxPreference) findPreference(
@@ -192,6 +194,15 @@ public class SettingsActivity extends PreferenceActivity {
pref.setChecked(true);
}
});
case DIALOG_CONFIRM_RESTORE_ID:
return DialogUtils.createConfirmationDialog(this,
R.string.settings_backup_restore_confirm_message,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startActivity(new Intent(SettingsActivity.this, RestoreChooserActivity.class));
}
});
default:
return null;
}
@@ -354,9 +365,7 @@ public class SettingsActivity extends PreferenceActivity {
new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
BackupActivityHelper backupHelper =
new BackupActivityHelper(SettingsActivity.this);
backupHelper.writeBackup();
startActivity(new Intent(SettingsActivity.this, BackupActivity.class));
return true;
}
});
@@ -364,9 +373,7 @@ public class SettingsActivity extends PreferenceActivity {
new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
BackupActivityHelper backupHelper =
new BackupActivityHelper(SettingsActivity.this);
backupHelper.restoreBackup();
showDialog(DIALOG_CONFIRM_RESTORE_ID);
return true;
}
});
@@ -23,6 +23,7 @@ import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.services.ServiceUtils;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
@@ -34,7 +35,6 @@ import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Window;
import android.widget.ScrollView;
import android.widget.TextView;
@@ -107,8 +107,8 @@ public class StatsActivity extends Activity implements TrackDataListener {
// The volume we want to control is the Text-To-Speech volume
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
// We don't need a window title bar:
requestWindowFeature(Window.FEATURE_NO_TITLE);
// Show the action bar (or nothing at all).
ApiAdapterFactory.getApiAdapter().showActionBar(this);
setContentView(R.layout.stats);
@@ -117,7 +117,7 @@ public class StatsActivity extends Activity implements TrackDataListener {
showUnknownLocation();
updateLabels();
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
if (metrics.heightPixels > 600) {
@@ -0,0 +1,550 @@
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import static com.google.android.apps.mytracks.Constants.CHART_TAB_TAG;
import static com.google.android.apps.mytracks.Constants.MAP_TAB_TAG;
import static com.google.android.apps.mytracks.Constants.STATS_TAB_TAG;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
import com.google.android.apps.mytracks.io.file.SaveActivity;
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
import com.google.android.apps.mytracks.io.sendtogoogle.SendRequest;
import com.google.android.apps.mytracks.io.sendtogoogle.UploadServiceChooserActivity;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
import com.google.android.apps.mytracks.services.ServiceUtils;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.apps.mytracks.util.PlayTrackUtils;
import com.google.android.maps.mytracks.R;
import android.app.Dialog;
import android.app.TabActivity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.RemoteException;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.RelativeLayout;
import android.widget.TabHost;
import android.widget.Toast;
/**
* An activity to show the track detail.
*
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
*/
@SuppressWarnings("deprecation")
public class TrackDetailActivity extends TabActivity implements OnTouchListener {
public static final String TRACK_ID = "track_id";
public static final String WAYPOINT_ID = "waypoint_id";
private static final String TAG = TrackDetailActivity.class.getSimpleName();
private static final int DIALOG_INSTALL_EARTH_ID = 0;
private static final int DIALOG_DELETE_CURRENT_ID = 1;
private SharedPreferences sharedPreferences;
private TrackDataHub trackDataHub;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private NavControls navControls;
private MenuItem stopRecordingMenuItem;
private MenuItem insertMarkerMenuItem;
private MenuItem playMenuItem;
private MenuItem shareMenuItem;
private MenuItem sendGoogleMenuItem;
private MenuItem saveMenuItem;
private MenuItem editMenuItem;
private MenuItem deleteMenuItem;
private final Runnable changeTab = new Runnable() {
public void run() {
getTabHost().setCurrentTab(navControls.getCurrentIcons());
}
};
/*
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
* class. Anonymous inner class will get garbage collected.
*/
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener =
new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
// Note that key can be null
if (getString(R.string.recording_track_key).equals(key)) {
if (isRecording()) {
trackRecordingServiceConnection.startAndBind();
}
updateMenu();
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackDataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
// Show the action bar (or nothing at all).
ApiAdapterFactory.getApiAdapter().showActionBar(this);
final Resources res = getResources();
final TabHost tabHost = getTabHost();
tabHost.addTab(tabHost.newTabSpec(MAP_TAB_TAG)
.setIndicator("Map", res.getDrawable(android.R.drawable.ic_menu_mapmode))
.setContent(new Intent(this, MapActivity.class)));
tabHost.addTab(tabHost.newTabSpec(STATS_TAB_TAG)
.setIndicator("Stats", res.getDrawable(R.drawable.ic_menu_statistics))
.setContent(new Intent(this, StatsActivity.class)));
tabHost.addTab(tabHost.newTabSpec(CHART_TAB_TAG)
.setIndicator("Chart", res.getDrawable(R.drawable.menu_elevation))
.setContent(new Intent(this, ChartActivity.class)));
// Hide the tab widget itself. We'll use overlayed prev/next buttons to
// switch between the tabs:
tabHost.getTabWidget().setVisibility(View.GONE);
RelativeLayout layout = new RelativeLayout(this);
LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
layout.setLayoutParams(params);
navControls = new NavControls(this, layout, getResources().obtainTypedArray(R.array.left_icons),
getResources().obtainTypedArray(R.array.right_icons), changeTab);
navControls.show();
tabHost.addView(layout);
layout.setOnTouchListener(this);
}
@Override
protected void onStart() {
super.onStart();
trackDataHub.start();
if (isRecording()) {
trackRecordingServiceConnection.startAndBind();
}
Intent intent = getIntent();
long trackId = intent.getLongExtra(TRACK_ID, -1L);
if (trackId != -1L) {
trackDataHub.loadTrack(trackId);
return;
}
long waypointId = intent.getLongExtra(WAYPOINT_ID, -1L);
if (waypointId != -1L) {
Waypoint waypoint = MyTracksProviderUtils.Factory.get(this).getWaypoint(waypointId);
if (waypoint != null) {
trackId = waypoint.getTrackId();
trackDataHub.loadTrack(trackId);
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
getTabHost().setCurrentTab(0);
mapActivity.showWaypoint(trackId, waypointId);
} else {
Log.e(TAG, "MapActivity is null");
}
return;
}
}
// No track id or waypoint id, return to the track list activity.
startTrackListActivity();
}
@Override
protected void onResume() {
super.onResume();
trackRecordingServiceConnection.bindIfRunning();
}
@Override
protected void onStop() {
super.onStop();
trackDataHub.stop();
}
@Override
protected void onDestroy() {
super.onDestroy();
trackRecordingServiceConnection.unbind();
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_INSTALL_EARTH_ID:
return PlayTrackUtils.createInstallEarthDialog(this);
case DIALOG_DELETE_CURRENT_ID:
return DialogUtils.createConfirmationDialog(this,
R.string.track_detail_delete_confirm_message, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
deleteCurrentTrack();
startTrackListActivity();
}
});
default:
return null;
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.track_detail, menu);
String fileTypes[] = getResources().getStringArray(R.array.file_types);
menu.findItem(R.id.menu_save_gpx).setTitle(getString(R.string.menu_save_format, fileTypes[0]));
menu.findItem(R.id.menu_save_kml).setTitle(getString(R.string.menu_save_format, fileTypes[1]));
menu.findItem(R.id.menu_save_csv).setTitle(getString(R.string.menu_save_format, fileTypes[2]));
menu.findItem(R.id.menu_save_tcx).setTitle(getString(R.string.menu_save_format, fileTypes[3]));
menu.findItem(R.id.menu_share_gpx).setTitle(getString(R.string.menu_share_file, fileTypes[0]));
menu.findItem(R.id.menu_share_kml).setTitle(getString(R.string.menu_share_file, fileTypes[1]));
menu.findItem(R.id.menu_share_csv).setTitle(getString(R.string.menu_share_file, fileTypes[2]));
menu.findItem(R.id.menu_share_tcx).setTitle(getString(R.string.menu_share_file, fileTypes[3]));
stopRecordingMenuItem = menu.findItem(R.id.menu_stop_recording);
insertMarkerMenuItem = menu.findItem(R.id.menu_insert_marker);
playMenuItem = menu.findItem(R.id.menu_play);
shareMenuItem = menu.findItem(R.id.menu_share);
sendGoogleMenuItem = menu.findItem(R.id.menu_send_google);
saveMenuItem = menu.findItem(R.id.menu_save);
editMenuItem = menu.findItem(R.id.menu_edit);
deleteMenuItem = menu.findItem(R.id.menu_delete);
updateMenu();
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
String currentTabTag = getTabHost().getCurrentTabTag();
menu.findItem(R.id.menu_chart_settings).setVisible(CHART_TAB_TAG.equals(currentTabTag));
menu.findItem(R.id.menu_my_location).setVisible(MAP_TAB_TAG.equals(currentTabTag));
// Set map or satellite mode
MapActivity mapActivity = getMapActivity();
boolean isSatelliteMode = mapActivity != null ? mapActivity.isSatelliteView() : false;
menu.findItem(R.id.menu_satellite_mode).setVisible(MAP_TAB_TAG.equals(currentTabTag))
.setTitle(isSatelliteMode ? R.string.menu_map_mode : R.string.menu_satellite_mode);
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
MapActivity mapActivity;
Intent intent;
long trackId = trackDataHub.getSelectedTrackId();
switch (item.getItemId()) {
case R.id.menu_stop_recording:
updateMenuItems(false);
stopRecording();
return true;
case R.id.menu_insert_marker:
// TODO: Add insert marker when updating WaypointList to ICS
return true;
case R.id.menu_play:
if (PlayTrackUtils.isEarthInstalled(this)) {
PlayTrackUtils.playTrack(this, trackId);
} else {
showDialog(DIALOG_INSTALL_EARTH_ID);
}
return true;
case R.id.menu_share_map:
intent = new Intent(this, UploadServiceChooserActivity.class)
.putExtra(SendRequest.SEND_REQUEST_KEY, new SendRequest(trackId, true, false, false));
startActivity(intent);
return true;
case R.id.menu_share_fusion_table:
intent = new Intent(this, UploadServiceChooserActivity.class)
.putExtra(SendRequest.SEND_REQUEST_KEY, new SendRequest(trackId, false, true, false));
startActivity(intent);
return true;
case R.id.menu_share_gpx:
startSaveActivity(trackId, TrackFileFormat.GPX, true);
return true;
case R.id.menu_share_kml:
startSaveActivity(trackId, TrackFileFormat.KML, true);
return true;
case R.id.menu_share_csv:
startSaveActivity(trackId, TrackFileFormat.CSV, true);
return true;
case R.id.menu_share_tcx:
startSaveActivity(trackId, TrackFileFormat.TCX, true);
return true;
case R.id.menu_markers:
intent = new Intent(this, WaypointsList.class)
.putExtra("trackid", trackDataHub.getSelectedTrackId());
startActivityForResult(intent, Constants.SHOW_WAYPOINT);
return true;
case R.id.menu_send_google:
intent = new Intent(this, UploadServiceChooserActivity.class)
.putExtra(SendRequest.SEND_REQUEST_KEY, new SendRequest(trackId, true, true, true));
startActivity(intent);
return true;
case R.id.menu_save_gpx:
startSaveActivity(trackId, TrackFileFormat.GPX, false);
return true;
case R.id.menu_save_kml:
startSaveActivity(trackId, TrackFileFormat.KML, false);
return true;
case R.id.menu_save_csv:
startSaveActivity(trackId, TrackFileFormat.CSV, false);
return true;
case R.id.menu_save_tcx:
startSaveActivity(trackId, TrackFileFormat.TCX, false);
return true;
case R.id.menu_edit:
startActivity(new Intent(this, TrackEditActivity.class)
.putExtra(TrackEditActivity.TRACK_ID, trackId));
return true;
case R.id.menu_delete:
showDialog(DIALOG_DELETE_CURRENT_ID);
return true;
case R.id.menu_my_location:
mapActivity = getMapActivity();
if (mapActivity != null) {
mapActivity.showMyLocation();
}
return true;
case R.id.menu_satellite_mode:
mapActivity = getMapActivity();
if (mapActivity != null) {
mapActivity.setSatelliteView(!mapActivity.isSatelliteView());
}
return true;
case R.id.menu_chart_settings:
ChartActivity chartActivity = getChartActivity();
if (chartActivity != null) {
chartActivity.showChartSettingsDialog();
}
return true;
case R.id.menu_sensor_state:
startActivity(new Intent(this, SensorStateActivity.class));
return true;
case R.id.menu_settings:
startActivity(new Intent(this, SettingsActivity.class));
return true;
case R.id.menu_help:
startActivity(new Intent(this, WelcomeActivity.class));
return true;
default:
return false;
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, final Intent results) {
if (requestCode != Constants.SHOW_WAYPOINT) {
Log.d(TAG, "Invalid request code: " + requestCode);
return;
}
if (results != null) {
long waypointId = results.getLongExtra(WaypointDetails.WAYPOINT_ID_EXTRA, -1L);
if (waypointId != -1L) {
MapActivity mapActivity = (MapActivity) getLocalActivityManager().getActivity(MAP_TAB_TAG);
if (mapActivity != null) {
getTabHost().setCurrentTab(0);
mapActivity.showWaypoint(waypointId);
}
}
}
}
@Override
public boolean onTrackballEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (isRecording()) {
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection
.getServiceIfBound();
if (trackRecordingService == null) {
Log.e(TAG, "The track recording service is null");
return true;
}
boolean success = false;
try {
long waypointId = trackRecordingService.insertWaypoint(
WaypointCreationRequest.DEFAULT_STATISTICS);
if (waypointId != -1L) {
success = true;
}
} catch (RemoteException e) {
Log.e(TAG, "Unable to insert waypoint", e);
}
Toast.makeText(this,
success ? R.string.marker_insert_success : R.string.marker_insert_error,
success ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG)
.show();
return true;
}
}
return super.onTrackballEvent(event);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
navControls.show();
}
return false;
}
/**
* Updates the menu.
*/
private void updateMenu() {
updateMenuItems(isRecording());
}
/**
* Updates the menu items.
*
* @param isRecording true if recording
*/
private void updateMenuItems(boolean isRecording) {
if (stopRecordingMenuItem != null) {
stopRecordingMenuItem.setVisible(isRecording);
}
if (insertMarkerMenuItem != null) {
insertMarkerMenuItem.setVisible(isRecording);
}
if (playMenuItem != null) {
playMenuItem.setVisible(!isRecording);
}
if (shareMenuItem != null) {
shareMenuItem.setVisible(!isRecording);
}
if (sendGoogleMenuItem != null) {
sendGoogleMenuItem.setVisible(!isRecording);
}
if (saveMenuItem != null) {
saveMenuItem.setVisible(!isRecording);
}
if (editMenuItem != null) {
editMenuItem.setVisible(!isRecording);
}
if (deleteMenuItem != null) {
deleteMenuItem.setVisible(!isRecording);
}
}
/**
* Deletes the current track.
*/
private void deleteCurrentTrack() {
long trackId = trackDataHub.getSelectedTrackId();
MyTracksProviderUtils.Factory.get(TrackDetailActivity.this).deleteTrack(trackId);
}
/**
* Starts the {@link TrackListActivity} and ends this activity.
*/
private void startTrackListActivity() {
Intent intent = new Intent(this, TrackListActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish();
}
/**
* Starts the {@link SaveActivity} to save a track.
*
* @param trackId the track id
* @param trackFileFormat the track file format
* @param shareTrack true to share the track after saving
*/
private void startSaveActivity(
long trackId, TrackFileFormat trackFileFormat, boolean shareTrack) {
Intent intent = new Intent(this, SaveActivity.class)
.putExtra(SaveActivity.EXTRA_TRACK_ID, trackId)
.putExtra(SaveActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) trackFileFormat)
.putExtra(SaveActivity.EXTRA_SHARE_TRACK, shareTrack);
startActivity(intent);
}
/**
* Returns true if recording.
*/
private boolean isRecording() {
return ServiceUtils.isRecording(
this, trackRecordingServiceConnection.getServiceIfBound(), sharedPreferences);
}
/**
* Stops the recording and the track recording service connection and shows
* {@link TrackEditActivity}.
*/
private void stopRecording() {
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection
.getServiceIfBound();
if (trackRecordingService != null) {
try {
trackRecordingService.endCurrentTrack();
} catch (Exception e) {
Log.e(TAG, "Unable to stop recording.", e);
}
}
trackRecordingServiceConnection.stop();
long recordingTrackId = sharedPreferences.getLong(getString(R.string.recording_track_key), -1L);
if (recordingTrackId != -1L) {
Intent intent = new Intent(this, TrackEditActivity.class)
.putExtra(TrackEditActivity.SHOW_CANCEL, false)
.putExtra(TrackEditActivity.TRACK_ID, recordingTrackId);
startActivity(intent);
}
}
/**
* Gets the map activity, can be null.
*/
private MapActivity getMapActivity() {
return (MapActivity) getLocalActivityManager().getActivity(MAP_TAB_TAG);
}
/**
* Gets the chart activity, can be null.
*/
private ChartActivity getChartActivity() {
return (ChartActivity) getLocalActivityManager().getActivity(CHART_TAB_TAG);
}
}
@@ -35,12 +35,12 @@ import android.widget.EditText;
*
* @author Leif Hendrik Wilden
*/
public class TrackDetail extends Activity implements OnClickListener {
public class TrackEditActivity extends Activity implements OnClickListener {
public static final String TRACK_ID = "trackId";
public static final String SHOW_CANCEL = "showCancel";
private static final String TAG = TrackDetail.class.getSimpleName();
private static final String TAG = TrackEditActivity.class.getSimpleName();
private Long trackId;
private MyTracksProviderUtils myTracksProviderUtils;
@@ -53,7 +53,7 @@ public class TrackDetail extends Activity implements OnClickListener {
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.track_detail);
setContentView(R.layout.track_edit);
trackId = getIntent().getLongExtra(TRACK_ID, -1);
if (trackId < 0) {
@@ -71,22 +71,22 @@ public class TrackDetail extends Activity implements OnClickListener {
return;
}
trackName = (EditText) findViewById(R.id.track_detail_track_name);
trackName = (EditText) findViewById(R.id.track_edit_track_name);
trackName.setText(track.getName());
activityType = (AutoCompleteTextView) findViewById(R.id.track_detail_activity_type);
activityType = (AutoCompleteTextView) findViewById(R.id.track_edit_activity_type);
activityType.setText(track.getCategory());
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
activityType.setAdapter(adapter);
trackDescription = (EditText) findViewById(R.id.track_detail_track_description);
trackDescription = (EditText) findViewById(R.id.track_edit_track_description);
trackDescription.setText(track.getDescription());
Button save = (Button) findViewById(R.id.track_detail_save);
Button save = (Button) findViewById(R.id.track_edit_save);
save.setOnClickListener(this);
Button cancel = (Button) findViewById(R.id.track_detail_cancel);
Button cancel = (Button) findViewById(R.id.track_edit_cancel);
if (getIntent().getBooleanExtra(SHOW_CANCEL, true)) {
cancel.setOnClickListener(this);
cancel.setVisibility(View.VISIBLE);
@@ -98,11 +98,11 @@ public class TrackDetail extends Activity implements OnClickListener {
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.track_detail_save:
case R.id.track_edit_save:
save();
finish();
break;
case R.id.track_detail_cancel:
case R.id.track_edit_cancel:
finish();
break;
default:
@@ -1,373 +0,0 @@
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.io.file.SaveActivity;
import com.google.android.apps.mytracks.io.sendtogoogle.SendRequest;
import com.google.android.apps.mytracks.io.sendtogoogle.UploadServiceChooserActivity;
import com.google.android.apps.mytracks.services.ServiceUtils;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.util.PlayTrackUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
import android.app.Dialog;
import android.app.ListActivity;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.View.OnCreateContextMenuListener;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
/**
* A list activity displaying all the recorded tracks. There's a context
* menu (via long press) displaying various options such as showing, editing,
* deleting, sending to Google, or writing to SD card.
*
* @author Leif Hendrik Wilden
*/
public class TrackList extends ListActivity
implements SharedPreferences.OnSharedPreferenceChangeListener,
View.OnClickListener {
private static final int DIALOG_INSTALL_EARTH = 0;
private int contextPosition = -1;
private long trackId = -1;
private ListView listView = null;
private boolean metricUnits = true;
private Cursor tracksCursor = null;
/**
* The id of the currently recording track.
*/
private long recordingTrackId = -1;
private final OnCreateContextMenuListener contextMenuListener =
new OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
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(Menu.NONE, Constants.MENU_SHOW, Menu.NONE, R.string.track_list_show_on_map);
menu.add(Menu.NONE, Constants.MENU_EDIT, Menu.NONE, 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(Menu.NONE, Constants.MENU_PLAY, Menu.NONE, R.string.track_list_play);
menu.add(Menu.NONE, Constants.MENU_SEND_TO_GOOGLE, Menu.NONE,
R.string.track_list_send_google);
SubMenu share = menu.addSubMenu(
Menu.NONE, Constants.MENU_SHARE, Menu.NONE, R.string.track_list_share_track);
share.add(
Menu.NONE, Constants.MENU_SHARE_MAP, Menu.NONE, R.string.track_list_share_map);
share.add(Menu.NONE, Constants.MENU_SHARE_FUSION_TABLE, Menu.NONE,
R.string.track_list_share_fusion_table);
share.add(Menu.NONE, Constants.MENU_SHARE_GPX_FILE, Menu.NONE,
String.format(shareFileFormat, fileTypes[0]));
share.add(Menu.NONE, Constants.MENU_SHARE_KML_FILE, Menu.NONE,
String.format(shareFileFormat, fileTypes[1]));
share.add(Menu.NONE, Constants.MENU_SHARE_CSV_FILE, Menu.NONE,
String.format(shareFileFormat, fileTypes[2]));
share.add(Menu.NONE, Constants.MENU_SHARE_TCX_FILE, Menu.NONE,
String.format(shareFileFormat, fileTypes[3]));
SubMenu save = menu.addSubMenu(
Menu.NONE, Constants.MENU_WRITE_TO_SD_CARD, Menu.NONE, R.string.track_list_save_sd);
save.add(Menu.NONE, Constants.MENU_SAVE_GPX_FILE, Menu.NONE,
String.format(saveFileFormat, fileTypes[0]));
save.add(Menu.NONE, Constants.MENU_SAVE_KML_FILE, Menu.NONE,
String.format(saveFileFormat, fileTypes[1]));
save.add(Menu.NONE, Constants.MENU_SAVE_CSV_FILE, Menu.NONE,
String.format(saveFileFormat, fileTypes[2]));
save.add(Menu.NONE, Constants.MENU_SAVE_TCX_FILE, Menu.NONE,
String.format(saveFileFormat, fileTypes[3]));
menu.add(Menu.NONE, Constants.MENU_DELETE, Menu.NONE, R.string.track_list_delete_track);
}
}
};
private final Runnable serviceBindingChanged = new Runnable() {
@Override
public void run() {
updateButtonsEnabled();
}
};
private TrackRecordingServiceConnection serviceConnection;
private SharedPreferences preferences;
@Override
public void onSharedPreferenceChanged(
SharedPreferences sharedPreferences, String key) {
if (key == null) {
return;
}
if (key.equals(getString(R.string.metric_units_key))) {
metricUnits = sharedPreferences.getBoolean(
getString(R.string.metric_units_key), true);
if (tracksCursor != null && !tracksCursor.isClosed()) {
tracksCursor.requery();
}
}
if (key.equals(getString(R.string.recording_track_key))) {
recordingTrackId = sharedPreferences.getLong(
getString(R.string.recording_track_key), -1);
}
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Intent result = new Intent();
result.putExtra("trackid", id);
setResult(Constants.SHOW_TRACK, result);
finish();
}
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
Intent intent;
switch (item.getItemId()) {
case Constants.MENU_SHOW: {
onListItemClick(null, null, 0, trackId);
return true;
}
case Constants.MENU_EDIT: {
intent = new Intent(this, TrackDetail.class).putExtra(TrackDetail.TRACK_ID, trackId);
startActivity(intent);
return true;
}
case Constants.MENU_PLAY:
if (PlayTrackUtils.isEarthInstalled(this)) {
PlayTrackUtils.playTrack(this, trackId);
return true;
} else {
showDialog(DIALOG_INSTALL_EARTH);
return true;
}
case Constants.MENU_SEND_TO_GOOGLE:
intent = new Intent(this, UploadServiceChooserActivity.class)
.putExtra(SendRequest.SEND_REQUEST_KEY, new SendRequest(trackId, true, true, true));
startActivity(intent);
return true;
case Constants.MENU_SHARE_MAP:
intent = new Intent(this, UploadServiceChooserActivity.class)
.putExtra(SendRequest.SEND_REQUEST_KEY, new SendRequest(trackId, true, false, false));
startActivity(intent);
return true;
case Constants.MENU_SHARE_FUSION_TABLE:
intent = new Intent(this, UploadServiceChooserActivity.class)
.putExtra(SendRequest.SEND_REQUEST_KEY, new SendRequest(trackId, false, true, false));
startActivity(intent);
return true;
case Constants.MENU_SHARE_GPX_FILE:
case Constants.MENU_SHARE_KML_FILE:
case Constants.MENU_SHARE_CSV_FILE:
case Constants.MENU_SHARE_TCX_FILE:
case Constants.MENU_SAVE_GPX_FILE:
case Constants.MENU_SAVE_KML_FILE:
case Constants.MENU_SAVE_CSV_FILE:
case Constants.MENU_SAVE_TCX_FILE:
SaveActivity.handleExportTrackAction(
this, trackId, Constants.getActionFromMenuId(item.getItemId()));
return true;
case Constants.MENU_DELETE:
Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId);
intent = new Intent(Intent.ACTION_DELETE)
.setDataAndType(uri, TracksColumns.CONTENT_ITEMTYPE);
startActivity(intent);
return true;
default:
Log.w(TAG, "Unknown menu item: " + item.getItemId() + "(" + item.getTitle() + ")");
return super.onMenuItemSelected(featureId, item);
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.tracklist_btn_delete_all: {
Handler h = new DeleteAllTracks(this, null);
h.handleMessage(null);
break;
}
case R.id.tracklist_btn_export_all: {
new ExportAllTracks(this);
break;
}
case R.id.tracklist_btn_import_all: {
new ImportAllTracks(this);
break;
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// We don't need a window title bar:
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.mytracks_list);
listView = getListView();
listView.setOnCreateContextMenuListener(contextMenuListener);
preferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
serviceConnection = new TrackRecordingServiceConnection(this, serviceBindingChanged);
View deleteAll = findViewById(R.id.tracklist_btn_delete_all);
deleteAll.setOnClickListener(this);
View exportAll = findViewById(R.id.tracklist_btn_export_all);
exportAll.setOnClickListener(this);
updateButtonsEnabled();
findViewById(R.id.tracklist_btn_import_all).setOnClickListener(this);
preferences.registerOnSharedPreferenceChangeListener(this);
metricUnits =
preferences.getBoolean(getString(R.string.metric_units_key), true);
recordingTrackId =
preferences.getLong(getString(R.string.recording_track_key), -1);
tracksCursor = getContentResolver().query(
TracksColumns.CONTENT_URI, null, null, null, "_id DESC");
startManagingCursor(tracksCursor);
setListAdapter();
}
@Override
protected void onStart() {
super.onStart();
serviceConnection.bindIfRunning();
}
@Override
protected void onDestroy() {
serviceConnection.unbind();
super.onDestroy();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.search_only, menu);
return true;
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_INSTALL_EARTH:
return PlayTrackUtils.createInstallEarthDialog(this);
default:
return null;
}
}
/* Callback from menu/search_only.xml */
public void onSearch(@SuppressWarnings("unused") MenuItem i) {
onSearchRequested();
}
private void updateButtonsEnabled() {
View deleteAll = findViewById(R.id.tracklist_btn_delete_all);
View exportAll = findViewById(R.id.tracklist_btn_export_all);
boolean notRecording = !isRecording();
deleteAll.setEnabled(notRecording);
exportAll.setEnabled(notRecording);
}
private void setListAdapter() {
// Get a cursor with all tracks
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
this,
R.layout.mytracks_list_item,
tracksCursor,
new String[] { TracksColumns.NAME, TracksColumns.STARTTIME,
TracksColumns.TOTALDISTANCE, TracksColumns.DESCRIPTION,
TracksColumns.CATEGORY },
new int[] { R.id.track_list_item_name, R.id.track_list_item_time,
R.id.track_list_item_stats, R.id.track_list_item_description,
R.id.track_list_item_category });
final int startTimeIdx =
tracksCursor.getColumnIndexOrThrow(TracksColumns.STARTTIME);
final int totalTimeIdx =
tracksCursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME);
final int totalDistanceIdx =
tracksCursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE);
adapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
@Override
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
TextView textView = (TextView) view;
if (columnIndex == startTimeIdx) {
long time = cursor.getLong(startTimeIdx);
textView.setText(StringUtils.formatDateTime(TrackList.this, time));
} else if (columnIndex == totalDistanceIdx) {
double totalDistance = cursor.getDouble(totalDistanceIdx);
long totalTime = cursor.getLong(totalTimeIdx);
String totalDistanceStr = StringUtils.formatTimeDistance(
TrackList.this, totalTime, totalDistance, metricUnits);
textView.setText(totalDistanceStr);
} else {
textView.setText(cursor.getString(columnIndex));
if (textView.getText().length() < 1) {
textView.setVisibility(View.GONE);
} else {
textView.setVisibility(View.VISIBLE);
}
}
return true;
}
});
setListAdapter(adapter);
}
private boolean isRecording() {
return ServiceUtils.isRecording(TrackList.this, serviceConnection.getServiceIfBound(), preferences);
}
}
@@ -0,0 +1,535 @@
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
import com.google.android.apps.mytracks.services.ServiceUtils;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.CheckUnitsUtils;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.apps.mytracks.util.EulaUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Parcelable;
import android.speech.tts.TextToSpeech;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.ResourceCursorAdapter;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
/**
* An activity displaying a list of tracks.
*
* @author Leif Hendrik Wilden
*/
public class TrackListActivity extends FragmentActivity {
private static final String TAG = TrackListActivity.class.getSimpleName();
private static final String CHECK_UNITS_DIALOG_TAG = "checkUnitsDialog";
private static final String DELETE_ALL_DIALOG_TAG = "deleteAllDialog";
private static final String EULA_DIALOG_TAG = "eulaDialog";
private static final String EXPORT_ALL_DIALOG_TAG = "exportAllDialog";
private static final int WELCOME_ACTIVITY_REQUEST_CODE = 0;
private static final String[] PROJECTION = new String[] {
TracksColumns._ID,
TracksColumns.NAME,
TracksColumns.CATEGORY,
TracksColumns.TOTALTIME,
TracksColumns.TOTALDISTANCE,
TracksColumns.STARTTIME,
TracksColumns.DESCRIPTION };
// Callback when the trackRecordingServiceConnection binding changes.
private final Runnable bindChangedCallback = new Runnable() {
@Override
public void run() {
if (!startNewRecording) {
return;
}
ITrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service == null) {
Log.d(TAG, "service not available to start a new recording");
return;
}
try {
recordingTrackId = service.startNewTrack();
startNewRecording = false;
Toast.makeText(
TrackListActivity.this, R.string.track_list_record_success, Toast.LENGTH_SHORT).show();
Log.d(TAG, "Started a new recording");
} catch (Exception e) {
Toast.makeText(TrackListActivity.this, R.string.track_list_record_error, Toast.LENGTH_LONG)
.show();
Log.e(TAG, "Unable to start a new recording.", e);
}
}
};
/*
* Note that sharedPreferenceChangeListenr cannot be an anonymous inner class.
* Anonymous inner class will get garbage collected.
*/
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener =
new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
// Note that key can be null
if (getString(R.string.metric_units_key).equals(key)) {
metricUnits = preferences.getBoolean(getString(R.string.metric_units_key), true);
}
if (getString(R.string.recording_track_key).equals(key)) {
recordingTrackId = sharedPreferences.getLong(getString(R.string.recording_track_key), -1L);
if (isRecording()) {
trackRecordingServiceConnection.startAndBind();
}
updateMenu();
}
adapter.notifyDataSetChanged();
}
};
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private SharedPreferences sharedPreferences;
private boolean metricUnits;
private long recordingTrackId;
private ListView listView;
private ResourceCursorAdapter adapter;
// True to start a new recording.
private boolean startNewRecording = false;
private MenuItem recordTrack;
private MenuItem stopRecording;
private MenuItem search;
private MenuItem importAll;
private MenuItem exportAll;
private MenuItem deleteAll;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
setContentView(R.layout.track_list);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(
this, bindChangedCallback);
sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
metricUnits = sharedPreferences.getBoolean(getString(R.string.metric_units_key), true);
recordingTrackId = sharedPreferences.getLong(getString(R.string.recording_track_key), -1L);
listView = (ListView) findViewById(R.id.track_list);
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent = new Intent(TrackListActivity.this, TrackDetailActivity.class)
.putExtra(TrackDetailActivity.TRACK_ID, id);
startActivity(intent);
}
});
adapter = new ResourceCursorAdapter(this, R.layout.track_list_item, null, 0) {
@Override
public void bindView(View view, Context context, Cursor cursor) {
int idIndex = cursor.getColumnIndex(TracksColumns._ID);
int nameIndex = cursor.getColumnIndex(TracksColumns.NAME);
int categoryIndex = cursor.getColumnIndex(TracksColumns.CATEGORY);
int timeIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME);
int distanceIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE);
int startIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME);
int descriptionIndex = cursor.getColumnIndex(TracksColumns.DESCRIPTION);
boolean isRecording = cursor.getLong(idIndex) == recordingTrackId;
TextView name = (TextView) view.findViewById(R.id.track_list_item_name);
name.setText(cursor.getString(nameIndex));
name.setCompoundDrawablesWithIntrinsicBounds(
isRecording ? R.drawable.menu_record_track : R.drawable.track, 0, 0, 0);
TextView category = (TextView) view.findViewById(R.id.track_list_item_category);
category.setText(cursor.getString(categoryIndex));
TextView time = (TextView) view.findViewById(R.id.track_list_item_time);
time.setText(StringUtils.formatElapsedTime(cursor.getLong(timeIndex)));
time.setVisibility(isRecording ? View.GONE : View.VISIBLE);
TextView distance = (TextView) view.findViewById(R.id.track_list_item_distance);
distance.setText(StringUtils.formatDistance(
TrackListActivity.this, cursor.getDouble(distanceIndex), metricUnits));
distance.setVisibility(isRecording ? View.GONE : View.VISIBLE);
TextView start = (TextView) view.findViewById(R.id.track_list_item_start);
start.setText(
StringUtils.formatDateTime(TrackListActivity.this, cursor.getLong(startIndex)));
start.setVisibility(start.getText().equals(name.getText()) ? View.GONE : View.VISIBLE);
TextView description = (TextView) view.findViewById(R.id.track_list_item_description);
description.setText(cursor.getString(descriptionIndex));
description.setVisibility(description.getText().length() == 0 ? View.GONE : View.VISIBLE);
}
};
listView.setAdapter(adapter);
getSupportLoaderManager().initLoader(0, null, new LoaderCallbacks<Cursor>() {
@Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
return new CursorLoader(TrackListActivity.this,
TracksColumns.CONTENT_URI,
PROJECTION,
null,
null,
TracksColumns._ID + " DESC");
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
adapter.swapCursor(cursor);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
adapter.swapCursor(null);
}
});
if (!EulaUtils.getEulaValue(this)) {
new EulaDialogFragment().show(getSupportFragmentManager(), EULA_DIALOG_TAG);
}
}
@Override
protected void onStart() {
super.onStart();
if (isRecording()) {
trackRecordingServiceConnection.startAndBind();
}
}
@Override
protected void onResume() {
super.onResume();
trackRecordingServiceConnection.bindIfRunning();
}
@Override
protected void onDestroy() {
super.onDestroy();
trackRecordingServiceConnection.unbind();
}
@Override
public void onActivityResult(int requestCode, int resultCode, final Intent results) {
if (requestCode == WELCOME_ACTIVITY_REQUEST_CODE) {
if (!CheckUnitsUtils.getCheckUnitsValue(this)) {
/*
* See bug http://code.google.com/p/android/issues/detail?id=23761.
* Cannot use
* CheckUnitsDialogFragment().show(getSupportFragmentManager(),
* CHECK_UNITS_DIALOG_TAG). Need to use commitAllowingStateLoss with the
* support package.
*/
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.add(new CheckUnitsDialogFragment(), CHECK_UNITS_DIALOG_TAG);
fragmentTransaction.commitAllowingStateLoss();
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.track_list, menu);
recordTrack = menu.findItem(R.id.menu_record_track);
stopRecording = menu.findItem(R.id.menu_stop_recording);
search = menu.findItem(R.id.menu_search);
importAll = menu.findItem(R.id.menu_import_all);
exportAll = menu.findItem(R.id.menu_export_all);
deleteAll = menu.findItem(R.id.menu_delete_all);
ApiAdapterFactory.getApiAdapter().configureSearchWidget(this, search);
updateMenu();
return true;
}
/**
* Updates the menu based on whether My Tracks is recording or not.
*/
private void updateMenu() {
boolean isRecording = isRecording();
updateMenuItems(isRecording);
}
/**
* Updates the menu items.
*
* @param isRecording true if recording
*/
private void updateMenuItems(boolean isRecording) {
if (recordTrack != null) {
recordTrack.setVisible(!isRecording);
}
if (stopRecording != null) {
stopRecording.setVisible(isRecording);
}
if (importAll != null) {
importAll.setVisible(!isRecording);
}
if (exportAll != null) {
exportAll.setVisible(!isRecording);
}
if (deleteAll != null) {
deleteAll.setVisible(!isRecording);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_record_track:
updateMenuItems(true);
startRecording();
return true;
case R.id.menu_stop_recording:
updateMenuItems(false);
stopRecording();
return true;
case R.id.menu_search:
return ApiAdapterFactory.getApiAdapter().handleSearchMenuSelection(this);
case R.id.menu_import_all:
startActivity(
new Intent(this, ImportActivity.class).putExtra(ImportActivity.EXTRA_IMPORT_ALL, true));
return true;
case R.id.menu_export_all:
new ExportAllDialogFragment().show(getSupportFragmentManager(), EXPORT_ALL_DIALOG_TAG);
return true;
case R.id.menu_delete_all:
new DeleteAllDialogFragment().show(getSupportFragmentManager(), DELETE_ALL_DIALOG_TAG);
return true;
case R.id.menu_aggregated_statistics:
startActivity(new Intent(this, AggregatedStatsActivity.class));
return true;
case R.id.menu_settings:
startActivity(new Intent(this, SettingsActivity.class));
return true;
case R.id.menu_help:
startActivity(new Intent(this, WelcomeActivity.class));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
/**
* Returns true if recording.
*/
private boolean isRecording() {
return ServiceUtils.isRecording(
this, trackRecordingServiceConnection.getServiceIfBound(), sharedPreferences);
}
/**
* Starts a new recording.
*/
private void startRecording() {
startNewRecording = true;
trackRecordingServiceConnection.startAndBind();
/*
* If the binding has happened, then invoke the callback to start a new
* recording. If the binding hasn't happened, then invoking the callback
* will have no effect. But when the binding occurs, the callback will get
* invoked.
*/
bindChangedCallback.run();
}
/**
* Stops the current recording.
*/
private void stopRecording() {
ITrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service != null) {
try {
service.endCurrentTrack();
if (recordingTrackId != -1L) {
Intent intent = new Intent(this, TrackEditActivity.class)
.putExtra(TrackEditActivity.SHOW_CANCEL, false)
.putExtra(TrackEditActivity.TRACK_ID, recordingTrackId);
startActivity(intent);
}
recordingTrackId = -1L;
} catch (Exception e) {
Log.d(TAG, "Unable to stop recording.", e);
}
}
trackRecordingServiceConnection.stop();
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_SEARCH) {
if (ApiAdapterFactory.getApiAdapter().handleSearchKey(search)) {
return true;
}
}
return super.onKeyUp(keyCode, event);
}
/**
* Check Units DialogFragment.
*/
public static class CheckUnitsDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity()).setCancelable(true)
.setOnCancelListener(new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
CheckUnitsUtils.setCheckUnitsValue(getActivity());
}
})
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
CheckUnitsUtils.setCheckUnitsValue(getActivity());
int position = ((AlertDialog) dialog).getListView().getSelectedItemPosition();
SharedPreferences sharedPreferences = getActivity()
.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(sharedPreferences.edit()
.putBoolean(getString(R.string.metric_units_key), position == 0));
}
})
.setSingleChoiceItems(new CharSequence[] { getString(R.string.preferred_units_metric),
getString(R.string.preferred_units_imperial) }, 0, null)
.setTitle(R.string.preferred_units_title)
.create();
}
}
/**
* Delete All DialogFragment.
*/
public static class DeleteAllDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return DialogUtils.createConfirmationDialog(getActivity(),
R.string.track_list_delete_all_confirm_message, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
MyTracksProviderUtils.Factory.get(getActivity()).deleteAllTracks();
}
});
}
};
/**
* Eula DialogFragment.
*/
public static class EulaDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setCancelable(true)
.setMessage(EulaUtils.getEulaMessage(getActivity()))
.setNegativeButton(R.string.eula_decline, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
getActivity().finish();
}
})
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
getActivity().finish();
}
})
.setPositiveButton(R.string.eula_accept, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
EulaUtils.setEulaValue(getActivity());
getActivity().startActivityForResult(
new Intent(getActivity(), WelcomeActivity.class), WELCOME_ACTIVITY_REQUEST_CODE);
}
})
.setTitle(R.string.eula_title)
.create();
}
}
/**
* Export All DialogFragment.
*/
public static class ExportAllDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
String fileTypes[] = getResources().getStringArray(R.array.file_types);
String[] choices = new String[fileTypes.length];
for (int i = 0; i < fileTypes.length; i++) {
choices[i] = getString(R.string.menu_export_all_format, fileTypes[i]);
}
return new AlertDialog.Builder(getActivity()).setNegativeButton(R.string.generic_cancel, null)
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int index = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
Intent intent = new Intent(getActivity(), ExportActivity.class).putExtra(
ExportActivity.EXTRA_TRACK_FILE_FORMAT,
(Parcelable) TrackFileFormat.values()[index]);
getActivity().startActivity(intent);
}
})
.setSingleChoiceItems(choices, 0, null)
.setTitle(R.string.menu_export_all)
.create();
}
}
}
@@ -24,10 +24,11 @@ import com.google.android.apps.mytracks.content.WaypointCreationRequest;
import com.google.android.apps.mytracks.content.WaypointsColumns;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ListActivity;
import android.content.Context;
import android.content.DialogInterface;
@@ -57,9 +58,10 @@ import android.widget.Toast;
*
* @author Leif Hendrik Wilden
*/
public class WaypointsList extends ListActivity
implements View.OnClickListener {
public class WaypointsList extends ListActivity implements View.OnClickListener {
private static final int DIALOG_DELETE_CURRENT_ID = 0;
private int contextPosition = -1;
private long trackId = -1;
private long selectedWaypointId = -1;
@@ -119,7 +121,7 @@ public class WaypointsList extends ListActivity
return true;
}
case Constants.MENU_DELETE: {
deleteWaypoint(selectedWaypointId);
showDialog(DIALOG_DELETE_CURRENT_ID);
}
}
}
@@ -199,15 +201,36 @@ public class WaypointsList extends ListActivity
super.onDestroy();
}
@Override
protected Dialog onCreateDialog(int id) {
if (id != DIALOG_DELETE_CURRENT_ID) {
return null;
}
return DialogUtils.createConfirmationDialog(this,
R.string.marker_list_delete_marker_confirm_message, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
providerUtils.deleteWaypoint(
selectedWaypointId, new DescriptionGeneratorImpl(WaypointsList.this));
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.search_only, menu);
return true;
}
/* Callback from menu/search_only.xml */
public void onSearch(@SuppressWarnings("unused") MenuItem i) {
onSearchRequested();
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_search:
onSearchRequested();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
@@ -303,34 +326,4 @@ public class WaypointsList extends ListActivity
});
setListAdapter(adapter);
}
/**
* Deletes the way point with the given id.
* Prompts the user if he want to really delete it.
*/
public void deleteWaypoint(final long waypointId) {
AlertDialog dialog = null;
AlertDialog.Builder builder = new AlertDialog.Builder(this);
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.generic_yes),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
providerUtils.deleteWaypoint(waypointId,
new DescriptionGeneratorImpl(WaypointsList.this));
}
});
builder.setNegativeButton(getString(R.string.generic_no),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
dialog = builder.create();
dialog.show();
}
}
@@ -24,7 +24,6 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
@@ -57,31 +56,30 @@ public class WelcomeActivity extends Activity {
@Override
protected Dialog onCreateDialog(int id) {
AlertDialog.Builder builder;
switch (id) {
case DIALOG_ABOUT_ID:
LayoutInflater layoutInflator = LayoutInflater.from(this);
View view = layoutInflator.inflate(R.layout.about, null);
TextView aboutVersionTextView = (TextView) view.findViewById(R.id.about_version);
aboutVersionTextView.setText(SystemUtils.getMyTracksVersion(this));
builder = new AlertDialog.Builder(this);
builder.setView(view);
builder.setPositiveButton(R.string.generic_ok, null);
builder.setNegativeButton(R.string.about_license, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
showDialog(DIALOG_EULA_ID);
}
});
return builder.create();
View view = getLayoutInflater().inflate(R.layout.about, null);
TextView aboutVersion = (TextView) view.findViewById(R.id.about_version);
aboutVersion.setText(SystemUtils.getMyTracksVersion(this));
return new AlertDialog.Builder(this)
.setCancelable(true)
.setNegativeButton(R.string.about_license, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
showDialog(DIALOG_EULA_ID);
}
})
.setPositiveButton(R.string.generic_ok, null)
.setTitle(R.string.my_tracks_app_name)
.setView(view)
.create();
case DIALOG_EULA_ID:
builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.eula_title);
builder.setMessage(EulaUtils.getEulaMessage(this));
builder.setPositiveButton(R.string.generic_ok, null);
builder.setCancelable(true);
return builder.create();
return new AlertDialog.Builder(this)
.setCancelable(true)
.setMessage(EulaUtils.getEulaMessage(this))
.setPositiveButton(R.string.generic_ok, null)
.setTitle(R.string.eula_title)
.create();
default:
return null;
}
@@ -15,21 +15,24 @@
*/
package com.google.android.apps.mytracks.io.backup;
import android.annotation.TargetApi;
import android.app.backup.BackupManager;
import android.content.Context;
import android.content.SharedPreferences;
/**
* Implementation of {@link BackupPreferencesListener} that calls the
* {@link BackupManager}.
* {@link BackupManager}. <br>
* For API Level 8 or higher.
*
* @author Jimmy Shih
*/
public class BackupPreferencesListenerImpl implements BackupPreferencesListener {
@TargetApi(8)
public class Api8BackupPreferencesListener implements BackupPreferencesListener {
private final BackupManager backupManager;
public BackupPreferencesListenerImpl(Context context) {
public Api8BackupPreferencesListener(Context context) {
this.backupManager = new BackupManager(context);
}
@@ -0,0 +1,91 @@
/*
* Copyright 2012 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 com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.widget.Toast;
/**
* Activity to backup data to the SD card.
*
* @author Jimmy Shih
*/
public class BackupActivity extends Activity {
private static final int DIALOG_PROGRESS_ID = 0;
private BackupAsyncTask backupAsyncTask;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Object retained = getLastNonConfigurationInstance();
if (retained instanceof BackupAsyncTask) {
backupAsyncTask = (BackupAsyncTask) retained;
backupAsyncTask.setActivity(this);
} else {
backupAsyncTask = new BackupAsyncTask(this);
backupAsyncTask.execute();
}
}
@Override
public Object onRetainNonConfigurationInstance() {
backupAsyncTask.setActivity(null);
return backupAsyncTask;
}
@Override
protected Dialog onCreateDialog(int id) {
if (id != DIALOG_PROGRESS_ID) {
return null;
}
return DialogUtils.createSpinnerProgressDialog(
this, R.string.settings_backup_now_progress_message, new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
finish();
}
});
}
/**
* Invokes when the associated AsyncTask completes.
*
* @param success true if the AsyncTask is successful
* @param messageId message id to display to user
*/
public void onAsyncTaskCompleted(boolean success, int messageId) {
removeDialog(DIALOG_PROGRESS_ID);
Toast.makeText(this, messageId, success ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG).show();
finish();
}
/**
* Shows the progress dialog.
*/
public void showProgressDialog() {
showDialog(DIALOG_PROGRESS_ID);
}
}
@@ -1,240 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.backup;
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.apps.mytracks.util.StringUtils;
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.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
/**
* Helper which shows a UI for writing or restoring a backup,
* and calls the appropriate handler for actually executing those
* operations.
*
* @author Rodrigo Damazio
*/
public class BackupActivityHelper {
private static final Comparator<Date> REVERSE_DATE_ORDER =
new Comparator<Date>() {
@Override
public int compare(Date s1, Date s2) {
return s2.compareTo(s1);
}
};
private final FileUtils fileUtils;
private final ExternalFileBackup backup;
private final Activity activity;
public BackupActivityHelper(Activity activity) {
this.activity = activity;
this.fileUtils = new FileUtils();
this.backup = new ExternalFileBackup(activity, fileUtils);
}
/**
* Writes a full backup to the default file.
* This shows the results to the user.
*/
public void writeBackup() {
if (!fileUtils.isSdCardAvailable()) {
showToast(R.string.sd_card_error_no_storage);
return;
}
if (!backup.isBackupsDirectoryAvailable(true)) {
showToast(R.string.sd_card_error_create_dir);
return;
}
final ProgressDialog progressDialog = ProgressDialog.show(
activity,
activity.getString(R.string.generic_progress_title),
activity.getString(R.string.settings_backup_now_progress_message),
true);
// Do the writing in another thread
new Thread() {
@Override
public void run() {
try {
backup.writeToDefaultFile();
showToast(R.string.sd_card_success_write_file);
} catch (IOException e) {
Log.e(Constants.TAG, "Failed to write backup", e);
showToast(R.string.sd_card_error_write_file);
} finally {
dismissDialog(progressDialog);
}
}
}.start();
}
/**
* Restores a full backup from the SD card.
* The user will be given a choice of which backup to restore as well as a
* confirmation dialog.
*/
public void restoreBackup() {
// Get the list of existing backups
if (!fileUtils.isSdCardAvailable()) {
showToast(R.string.sd_card_error_no_storage);
return;
}
if (!backup.isBackupsDirectoryAvailable(false)) {
showToast(R.string.settings_backup_restore_no_backup);
return;
}
final Date[] backupDates = backup.getAvailableBackups();
if (backupDates == null || backupDates.length == 0) {
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.settings_backup_restore_confirm_message);
confirmationDialogBuilder.setCancelable(true);
confirmationDialogBuilder.setPositiveButton(android.R.string.yes,
new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
pickBackupForRestore(backupDates);
}
});
confirmationDialogBuilder.setNegativeButton(android.R.string.no, null);
confirmationDialogBuilder.create().show();
}
/**
* Shows a backup list for the user to pick, then restores it.
*
* @param backupDates the list of available backup files
*/
private void pickBackupForRestore(final Date[] backupDates) {
if (backupDates.length == 1) {
// Only one choice, don't bother showing the list
restoreFromDateAsync(backupDates[0]);
return;
}
// Make a user-visible version of the backup filenames
final String backupDateStrs[] = new String[backupDates.length];
for (int i = 0; i < backupDates.length; i++) {
backupDateStrs[i] = StringUtils.formatDateTime(activity, backupDates[i].getTime());
}
// 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.settings_backup_restore_select_title);
dialogBuilder.setItems(backupDateStrs, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// User picked to restore this one
restoreFromDateAsync(backupDates[which]);
}
});
dialogBuilder.create().show();
}
/**
* Shows a progress dialog, then starts restoring the backup asynchronously.
*
* @param date the date
*/
private void restoreFromDateAsync(final Date date) {
// Show a progress dialog
ProgressDialog.show(
activity,
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)
new Thread() {
@Override
public void run() {
try {
backup.restoreFromDate(date);
showToast(R.string.sd_card_success_read_file);
} catch (IOException e) {
Log.e(Constants.TAG, "Failed to restore backup", e);
showToast(R.string.sd_card_error_read_file);
} finally {
// Data may have been restored, "reboot" the app to catch it
restartApplication();
}
}
}.start();
}
/**
* Restarts My Tracks completely.
* This forces any modified data to be re-read.
*/
private void restartApplication() {
Intent intent = new Intent(activity, MyTracks.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
activity.startActivity(intent);
}
/**
* Shows a toast with the given contents.
*/
private void showToast(final int resId) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(activity, resId, Toast.LENGTH_LONG).show();
}
});
}
/**
* Safely dismisses the given dialog.
*/
private void dismissDialog(final Dialog dialog) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
dialog.dismiss();
}
});
}
}
@@ -0,0 +1,111 @@
/*
* Copyright 2012 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 com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.maps.mytracks.R;
import android.os.AsyncTask;
import android.util.Log;
import java.io.IOException;
/**
* AsyncTask to backup data to the SD card.
*
* @author Jimmy Shih
*/
public class BackupAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private static final String TAG = BackupAsyncTask.class.getSimpleName();
private BackupActivity backupActivity;
private final ExternalFileBackup externalFileBackup;
// true if the AsyncTask result is success
private boolean success;
// true if the AsyncTask has completed
private boolean completed;
// message id to return to the activity
private int messageId;
/**
* Creates an AsyncTask.
*
* @param backupActivity the activity currently associated with this
* AsyncTask
*/
public BackupAsyncTask(BackupActivity backupActivity) {
this.backupActivity = backupActivity;
this.externalFileBackup = new ExternalFileBackup(backupActivity);
success = false;
completed = false;
messageId = R.string.sd_card_error_write_file;
}
/**
* Sets the current {@link BackupActivity} associated with this AyncTask.
*
* @param activity the current {@link BackupActivity}, can be null
*/
public void setActivity(BackupActivity activity) {
this.backupActivity = activity;
if (completed && backupActivity != null) {
backupActivity.onAsyncTaskCompleted(success, messageId);
}
}
@Override
protected void onPreExecute() {
if (backupActivity != null) {
backupActivity.showProgressDialog();
}
}
@Override
protected Boolean doInBackground(Void... params) {
if (!FileUtils.isSdCardAvailable()) {
messageId = R.string.sd_card_error_no_storage;
return false;
}
if (!externalFileBackup.isBackupsDirectoryAvailable(true)) {
messageId = R.string.sd_card_error_create_dir;
return false;
}
try {
externalFileBackup.writeToDefaultFile();
messageId = R.string.sd_card_success_write_file;
return true;
} catch (IOException e) {
Log.d(TAG, "IO exception", e);
return false;
}
}
@Override
protected void onPostExecute(Boolean result) {
success = result;
completed = true;
if (backupActivity != null) {
backupActivity.onAsyncTaskCompleted(success, messageId);
}
}
}
@@ -65,11 +65,9 @@ class ExternalFileBackup {
private static final int COMPRESSION_LEVEL = 8;
private final Context context;
private final FileUtils fileUtils;
public ExternalFileBackup(Context context, FileUtils fileUtils) {
public ExternalFileBackup(Context context) {
this.context = context;
this.fileUtils = fileUtils;
}
/**
@@ -87,12 +85,12 @@ class ExternalFileBackup {
* @param create whether to try creating the directory if it doesn't exist
*/
private File getBackupsDirectory(boolean create) {
String dirName = fileUtils.buildExternalDirectoryPath(BACKUPS_SUBDIR);
String dirName = FileUtils.buildExternalDirectoryPath(BACKUPS_SUBDIR);
final File dir = new File(dirName);
Log.d(Constants.TAG, "Dir: " + dir.getAbsolutePath());
if (create) {
// Try to create - if that fails, return null
return fileUtils.ensureDirectoryExists(dir) ? dir : null;
return FileUtils.ensureDirectoryExists(dir) ? dir : null;
} else {
// Return it if it already exists, otherwise return null
return dir.isDirectory() ? dir : null;
@@ -17,6 +17,7 @@ package com.google.android.apps.mytracks.io.backup;
import com.google.android.apps.mytracks.Constants;
import android.annotation.TargetApi;
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
@@ -34,6 +35,7 @@ import java.io.IOException;
*
* @author Rodrigo Damazio
*/
@TargetApi(8)
public class MyTracksBackupAgent extends BackupAgent {
private static final String PREFERENCES_ENTITY = "prefs";
@@ -0,0 +1,106 @@
/*
* Copyright 2012 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 com.google.android.apps.mytracks.TrackListActivity;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import java.util.Date;
/**
* An activity to restore data from the SD card.
*
* @author Jimmy Shih
*/
public class RestoreActivity extends Activity {
public static final String EXTRA_DATE = "date";
private static final String TAG = RestoreActivity.class.getSimpleName();
private static final int DIALOG_PROGRESS_ID = 0;
private RestoreAsyncTask restoreAsyncTask;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Object retained = getLastNonConfigurationInstance();
if (retained instanceof RestoreAsyncTask) {
restoreAsyncTask = (RestoreAsyncTask) retained;
restoreAsyncTask.setActivity(this);
} else {
long date = getIntent().getLongExtra(EXTRA_DATE, -1L);
if (date == -1L) {
Log.d(TAG, "Invalid date");
finish();
}
restoreAsyncTask = new RestoreAsyncTask(this, new Date(date));
restoreAsyncTask.execute();
}
}
@Override
public Object onRetainNonConfigurationInstance() {
restoreAsyncTask.setActivity(null);
return restoreAsyncTask;
}
@Override
protected Dialog onCreateDialog(int id) {
if (id != DIALOG_PROGRESS_ID) {
return null;
}
return DialogUtils.createSpinnerProgressDialog(this,
R.string.settings_backup_restore_progress_message, new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
finish();
}
});
}
/**
* Invokes when the associated AsyncTask completes.
*
* @param success true if the AsyncTask is successful
* @param messageId message id to display to user
*/
public void onAsyncTaskCompleted(boolean success, int messageId) {
removeDialog(DIALOG_PROGRESS_ID);
Toast.makeText(this, messageId, success ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG).show();
startActivity(
new Intent(this, TrackListActivity.class).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
finish();
}
/**
* Shows the progress dialog.
*/
public void showProgressDialog() {
showDialog(DIALOG_PROGRESS_ID);
}
}
@@ -0,0 +1,104 @@
/*
* Copyright 2012 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 com.google.android.maps.mytracks.R;
import android.os.AsyncTask;
import android.util.Log;
import java.io.IOException;
import java.util.Date;
/**
* AsyncTask to restore data from the SD card.
*
* @author Jimmy Shih
*/
public class RestoreAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private static final String TAG = RestoreAsyncTask.class.getSimpleName();
private RestoreActivity restoreActivity;
private final Date date;
private final ExternalFileBackup externalFileBackup;
// true if the AsyncTask result is success
private boolean success;
// true if the AsyncTask has completed
private boolean completed;
// message id to return to the activity
private int messageId;
/**
* Creates an AsyncTask.
*
* @param restoreActivity the activity currently associated with this
* AsyncTask
* @param date the date to retore from
*/
public RestoreAsyncTask(RestoreActivity restoreActivity, Date date) {
this.restoreActivity = restoreActivity;
this.date = date;
this.externalFileBackup = new ExternalFileBackup(restoreActivity);
success = false;
completed = false;
messageId = R.string.sd_card_error_read_file;
}
/**
* Sets the current {@link RestoreActivity} associated with this AyncTask.
*
* @param activity the current {@link RestoreActivity}, can be null
*/
public void setActivity(RestoreActivity activity) {
this.restoreActivity = activity;
if (completed && restoreActivity != null) {
restoreActivity.onAsyncTaskCompleted(success, messageId);
}
}
@Override
protected void onPreExecute() {
if (restoreActivity != null) {
restoreActivity.showProgressDialog();
}
}
@Override
protected Boolean doInBackground(Void... params) {
try {
externalFileBackup.restoreFromDate(date);
messageId = R.string.sd_card_success_read_file;
return true;
} catch (IOException e) {
Log.d(TAG, "IO exception", e);
return false;
}
}
@Override
protected void onPostExecute(Boolean result) {
success = result;
completed = true;
if (restoreActivity != null) {
restoreActivity.onAsyncTaskCompleted(success, messageId);
}
}
}
@@ -0,0 +1,117 @@
/*
* Copyright 2012 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 com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
/**
* An activity to choose a date to restore from.
*
* @author Jimmy Shih
*/
public class RestoreChooserActivity extends Activity {
private static final Comparator<Date> REVERSE_DATE_ORDER = new Comparator<Date>() {
@Override
public int compare(Date s1, Date s2) {
return s2.compareTo(s1);
}
};
private static final int DIALOG_CHOOSER_ID = 0;
private Date[] backupDates;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ExternalFileBackup externalFileBackup = new ExternalFileBackup(this);
// Get the list of existing backups
if (!FileUtils.isSdCardAvailable()) {
Toast.makeText(this, R.string.sd_card_error_no_storage, Toast.LENGTH_LONG).show();
finish();
return;
}
if (!externalFileBackup.isBackupsDirectoryAvailable(false)) {
Toast.makeText(this, R.string.settings_backup_restore_no_backup, Toast.LENGTH_LONG).show();
finish();
return;
}
backupDates = externalFileBackup.getAvailableBackups();
if (backupDates == null || backupDates.length == 0) {
Toast.makeText(this, R.string.settings_backup_restore_no_backup, Toast.LENGTH_LONG).show();
finish();
return;
}
if (backupDates.length == 1) {
startActivity(new Intent(this, RestoreActivity.class)
.putExtra(RestoreActivity.EXTRA_DATE, backupDates[0].getTime()));
finish();
return;
}
Arrays.sort(backupDates, REVERSE_DATE_ORDER);
showDialog(DIALOG_CHOOSER_ID);
}
@Override
protected Dialog onCreateDialog(int id) {
if (id != DIALOG_CHOOSER_ID) {
return null;
}
String items[] = new String[backupDates.length];
for (int i = 0; i < backupDates.length; i++) {
items[i] = StringUtils.formatDateTime(this, backupDates[i].getTime());
}
return new AlertDialog.Builder(this)
.setCancelable(true)
.setItems(items, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startActivity(new Intent(RestoreChooserActivity.this, RestoreActivity.class).putExtra(
RestoreActivity.EXTRA_DATE, backupDates[which].getTime()));
finish();
}
})
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
finish();
}
})
.setTitle(R.string.settings_backup_restore_select_title)
.create();
}
}
@@ -1,58 +0,0 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.ImportAllTracks;
import com.google.android.apps.mytracks.util.UriUtils;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
/**
* An activity that imports a track from a file and displays the track in My Tracks.
*
* @author Rodrigo Damazio
*/
public class ImportActivity extends Activity {
@Override
public void onStart() {
super.onStart();
Intent intent = getIntent();
String action = intent.getAction();
Uri data = intent.getData();
if (!(Intent.ACTION_VIEW.equals(action) || Intent.ACTION_ATTACH_DATA.equals(action))) {
Log.e(TAG, "Received an intent with unsupported action: " + intent);
finish();
return;
}
if (!UriUtils.isFileUri(data)) {
Log.e(TAG, "Received an intent with unsupported data: " + intent);
finish();
return;
}
String path = data.getPath();
Log.i(TAG, "Importing GPX file at " + path);
new ImportAllTracks(this, path);
}
}
@@ -13,27 +13,21 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.PlayTrackUtils;
import com.google.android.apps.mytracks.util.UriUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentUris;
import android.content.Context;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@@ -42,219 +36,184 @@ import android.util.Log;
import java.io.File;
/**
* Activity for saving a track to a file (and optionally sending that file).
* Activity for saving a track to the SD card, and optionally share or play the
* track.
*
* @author Rodrigo Damazio
*/
public class SaveActivity extends Activity {
public static final String EXTRA_FILE_FORMAT = "file_format";
public static final String EXTRA_SHARE_FILE = "share_file";
public static final String EXTRA_PLAY_FILE = "play_file";
private static final int RESULT_DIALOG = 1;
/* VisibleForTesting */
static final int PROGRESS_DIALOG = 2;
public static final String EXTRA_TRACK_ID = "track_id";
public static final String EXTRA_TRACK_FILE_FORMAT = "track_file_format";
public static final String EXTRA_SHARE_TRACK = "share_track";
public static final String EXTRA_PLAY_TRACK = "play_track";
private static final String TAG = SaveActivity.class.getSimpleName();
private static final int DIALOG_PROGRESS_ID = 0;
private static final int DIALOG_RESULT_ID = 1;
private MyTracksProviderUtils providerUtils;
private long trackId;
private TrackWriter writer;
private boolean shareFile;
private boolean playFile;
private TrackFileFormat format;
private WriteProgressController controller;
private TrackFileFormat trackFileFormat;
private boolean shareTrack;
private boolean playTrack;
private SaveAsyncTask saveAsyncTask;
private ProgressDialog progressDialog;
// result from the AsyncTask
private boolean success;
// message id from the AsyncTask
private int messageId;
// path of the saved file
private String filePath;
@Override
protected void onCreate(Bundle savedInstanceState) {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
providerUtils = MyTracksProviderUtils.Factory.get(this);
Intent intent = getIntent();
trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L);
if (trackId < 0) {
Log.d(TAG, "Invalid track id");
finish();
return;
}
trackFileFormat = intent.getParcelableExtra(EXTRA_TRACK_FILE_FORMAT);
shareTrack = intent.getBooleanExtra(EXTRA_SHARE_TRACK, false);
playTrack = intent.getBooleanExtra(EXTRA_PLAY_TRACK, false);
Object retained = getLastNonConfigurationInstance();
if (retained instanceof SaveAsyncTask) {
saveAsyncTask = (SaveAsyncTask) retained;
saveAsyncTask.setActivity(this);
} else {
TrackWriter trackWriter = TrackWriterFactory.newWriter(
this, MyTracksProviderUtils.Factory.get(this), trackId, trackFileFormat);
if (trackWriter == null) {
Log.e(TAG, "Track writer is null");
finish();
return;
}
if (shareTrack || playTrack) {
// Save to the temp directory
String dirName = FileUtils.buildExternalDirectoryPath(
trackFileFormat.getExtension(), "tmp");
trackWriter.setDirectory(new File(dirName));
}
saveAsyncTask = new SaveAsyncTask(this, trackWriter);
saveAsyncTask.execute();
}
}
@Override
protected void onStart() {
super.onStart();
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
Uri data = intent.getData();
if (!getString(R.string.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;
}
trackId = ContentUris.parseId(data);
int formatIdx = intent.getIntExtra(EXTRA_FILE_FORMAT, -1);
format = TrackFileFormat.values()[formatIdx];
shareFile = intent.getBooleanExtra(EXTRA_SHARE_FILE, false);
playFile = intent.getBooleanExtra(EXTRA_PLAY_FILE, false);
writer = TrackWriterFactory.newWriter(this, providerUtils, trackId, format);
if (writer == null) {
Log.e(TAG, "Unable to build writer");
finish();
return;
}
if (shareFile || playFile) {
// If the file is for sending, save it to a temporary location instead.
FileUtils fileUtils = new FileUtils();
String extension = format.getExtension();
String dirName = fileUtils.buildExternalDirectoryPath(extension, "tmp");
File dir = new File(dirName);
writer.setDirectory(dir);
}
controller = new WriteProgressController(this, writer, PROGRESS_DIALOG);
controller.setOnCompletionListener(new WriteProgressController.OnCompletionListener() {
@Override
public void onComplete() {
onWriteComplete();
}
});
controller.startWrite();
}
private void onWriteComplete() {
if (shareFile) {
shareWrittenFile();
} if (playFile) {
playWrittenFile();
} else {
showResultDialog();
}
}
private void shareWrittenFile() {
if (!writer.wasSuccess()) {
showResultDialog();
return;
}
// Share the file.
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_SUBJECT,
getResources().getText(R.string.share_track_subject).toString());
shareIntent.putExtra(Intent.EXTRA_TEXT,
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_picker_title).toString()));
}
private void playWrittenFile() {
if (!writer.wasSuccess()) {
showResultDialog();
return;
}
Uri uri = Uri.fromFile(new File(writer.getAbsolutePath()));
Intent intent = new Intent()
.setClassName(PlayTrackUtils.GOOGLE_EARTH_PACKAGE, PlayTrackUtils.GOOGLE_EARTH_CLASS)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setDataAndType(uri, PlayTrackUtils.KML_MIME_TYPE)
.putExtra(PlayTrackUtils.TOUR_FEATURE_ID, KmlTrackWriter.TOUR_FEATURE_ID);
startActivity(intent);
}
private void showResultDialog() {
removeDialog(RESULT_DIALOG);
showDialog(RESULT_DIALOG);
public Object onRetainNonConfigurationInstance() {
saveAsyncTask.setActivity(null);
return saveAsyncTask;
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case RESULT_DIALOG:
return createResultDialog();
case PROGRESS_DIALOG:
if (controller != null) {
return controller.createProgressDialog();
}
//$FALL-THROUGH$
case DIALOG_PROGRESS_ID:
progressDialog = DialogUtils.createHorizontalProgressDialog(
this, R.string.sd_card_progress_message, new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
saveAsyncTask.cancel(true);
finish();
}
});
return progressDialog;
case DIALOG_RESULT_ID:
return new AlertDialog.Builder(this)
.setCancelable(true)
.setIcon(success
? android.R.drawable.ic_dialog_info : android.R.drawable.ic_dialog_alert)
.setMessage(messageId)
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
dialog.dismiss();
onPostResultDialog();
}
})
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int arg1) {
dialog.dismiss();
onPostResultDialog();
}
})
.setTitle(success ? R.string.generic_success_title : R.string.generic_error_title)
.create();
default:
return super.onCreateDialog(id);
return null;
}
}
private Dialog createResultDialog() {
boolean success = writer.wasSuccess();
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(writer.getErrorMessage());
builder.setPositiveButton(R.string.generic_ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int arg1) {
dialog.dismiss();
finish();
}
});
builder.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
dialog.dismiss();
finish();
}
});
builder.setIcon(success ? android.R.drawable.ic_dialog_info :
android.R.drawable.ic_dialog_alert);
builder.setTitle(success ? R.string.generic_success_title : R.string.generic_error_title);
return builder.create();
/**
* Invokes when the associated AsyncTask completes.
*
* @param isSuccess true if the AsyncTask is successful
* @param aMessageId the id of the AsyncTask message
* @param aPath the path of the saved file
*/
public void onAsyncTaskCompleted(boolean isSuccess, int aMessageId, String aPath) {
this.success = isSuccess;
this.messageId = aMessageId;
this.filePath = aPath;
removeDialog(DIALOG_PROGRESS_ID);
showDialog(DIALOG_RESULT_ID);
}
public static void handleExportTrackAction(Context ctx, long trackId, int actionCode) {
if (trackId < 0) {
return;
/**
* Shows the progress dialog.
*/
public void showProgressDialog() {
showDialog(DIALOG_PROGRESS_ID);
}
/**
* Sets the progress dialog value.
*
* @param number the number of points saved
* @param max the maximum number of points
*/
public void setProgressDialogValue(int number, int max) {
if (progressDialog != null) {
progressDialog.setIndeterminate(false);
progressDialog.setMax(max);
progressDialog.setProgress(Math.min(number, max));
}
}
TrackFileFormat exportFormat = null;
switch (actionCode) {
case Constants.SAVE_GPX_FILE:
case Constants.SHARE_GPX_FILE:
exportFormat = TrackFileFormat.GPX;
break;
case Constants.SAVE_KML_FILE:
case Constants.SHARE_KML_FILE:
exportFormat = TrackFileFormat.KML;
break;
case Constants.SAVE_CSV_FILE:
case Constants.SHARE_CSV_FILE:
exportFormat = TrackFileFormat.CSV;
break;
case Constants.SAVE_TCX_FILE:
case Constants.SHARE_TCX_FILE:
exportFormat = TrackFileFormat.TCX;
break;
default:
throw new IllegalArgumentException("Warning unhandled action code: " + actionCode);
/**
* To be invoked after showing the result dialog.
*/
private void onPostResultDialog() {
if (success) {
if (shareTrack) {
Intent intent = new Intent(Intent.ACTION_SEND)
.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(filePath)))
.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_track_subject))
.putExtra(Intent.EXTRA_TEXT, getString(R.string.share_track_file_body_format))
.putExtra(getString(R.string.track_id_broadcast_extra), trackId)
.setType(trackFileFormat.getMimeType());
startActivity(Intent.createChooser(intent, getString(R.string.share_track_picker_title)));
} else if (playTrack) {
Intent intent = new Intent()
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(PlayTrackUtils.TOUR_FEATURE_ID, KmlTrackWriter.TOUR_FEATURE_ID)
.setClassName(PlayTrackUtils.GOOGLE_EARTH_PACKAGE, PlayTrackUtils.GOOGLE_EARTH_CLASS)
.setDataAndType(Uri.fromFile(new File(filePath)), PlayTrackUtils.KML_MIME_TYPE);
startActivity(intent);
}
}
boolean shareFile = false;
switch (actionCode) {
case Constants.SHARE_GPX_FILE:
case Constants.SHARE_KML_FILE:
case Constants.SHARE_CSV_FILE:
case Constants.SHARE_TCX_FILE:
shareFile = true;
}
Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId);
Intent intent = new Intent(ctx, SaveActivity.class);
intent.setAction(ctx.getString(R.string.track_action_save));
intent.setDataAndType(uri, TracksColumns.CONTENT_ITEMTYPE);
intent.putExtra(EXTRA_FILE_FORMAT, exportFormat.ordinal());
intent.putExtra(EXTRA_SHARE_FILE, shareFile);
ctx.startActivity(intent);
finish();
}
}
@@ -0,0 +1,107 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file;
import android.os.AsyncTask;
/**
* Async Task to save a track to the SD card.
*
* @author Jimmy Shih
*/
public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private SaveActivity saveActivity;
private final TrackWriter trackWriter;
// true if the AsyncTask result is success
private boolean success;
// true if the AsyncTask has completed
private boolean completed;
/**
* Creates an AsyncTask.
*
* @param saveActivity the {@link SaveActivity} currently associated with this
* AsyncTask
* @param trackWriter the track writer
*/
public SaveAsyncTask(SaveActivity saveActivity, TrackWriter trackWriter) {
this.saveActivity = saveActivity;
this.trackWriter = trackWriter;
success = false;
completed = false;
}
/**
* Sets the current {@link SaveActivity} associated with this AyncTask.
*
* @param saveActivity the current {@link SaveActivity}, can be null
*/
public void setActivity(SaveActivity saveActivity) {
this.saveActivity = saveActivity;
if (completed && saveActivity != null) {
saveActivity.onAsyncTaskCompleted(
success, trackWriter.getErrorMessage(), trackWriter.getAbsolutePath());
}
}
@Override
protected void onPreExecute() {
if (saveActivity != null) {
saveActivity.showProgressDialog();
}
}
@Override
protected Boolean doInBackground(Void... params) {
trackWriter.setOnWriteListener(new TrackWriter.OnWriteListener() {
@Override
public void onWrite(int number, int max) {
// Update the progress dialog once every 500 points
if (number % 500 == 0) {
publishProgress(number, max);
}
}
});
trackWriter.writeTrack();
return trackWriter.wasSuccess();
}
@Override
protected void onProgressUpdate(Integer... values) {
if (saveActivity != null) {
saveActivity.setProgressDialogValue(values[0], values[1]);
}
}
@Override
protected void onPostExecute(Boolean result) {
success = result;
completed = true;
if (saveActivity != null) {
saveActivity.onAsyncTaskCompleted(
success, trackWriter.getErrorMessage(), trackWriter.getAbsolutePath());
}
}
@Override
protected void onCancelled() {
trackWriter.stopWriteTrack();
}
}
@@ -27,10 +27,6 @@ import java.io.File;
* @author Rodrigo Damazio
*/
public interface TrackWriter {
/** This listener is used to signal completion of track write */
public interface OnCompletionListener {
public void onComplete();
}
/** This listener is used to signal track writes. */
public interface OnWriteListener {
@@ -43,11 +39,6 @@ public interface TrackWriter {
public void onWrite(int number, int max);
}
/**
* Sets listener to be invoked when the writer has finished.
*/
void setOnCompletionListener(OnCompletionListener onCompletionListener);
/**
* Sets a listener to be invoked for each location writer.
*/
@@ -63,12 +54,6 @@ public interface TrackWriter {
*/
String getAbsolutePath();
/**
* Writes the given track id to the SD card.
* This is non-blocking.
*/
void writeTrackAsync();
/**
* Writes the given track id to the SD card.
* This is blocking.
@@ -22,6 +22,8 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
/**
@@ -34,7 +36,7 @@ public class TrackWriterFactory {
/**
* Definition of all possible track formats.
*/
public enum TrackFileFormat {
public enum TrackFileFormat implements Parcelable {
GPX {
@Override
TrackFormatWriter newFormatWriter(Context context) {
@@ -60,6 +62,28 @@ public class TrackWriterFactory {
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel dest, final int flags) {
dest.writeInt(ordinal());
}
public static final Creator<TrackFileFormat> CREATOR = new Creator<TrackFileFormat>() {
@Override
public TrackFileFormat createFromParcel(final Parcel source) {
return TrackFileFormat.values()[source.readInt()];
}
@Override
public TrackFileFormat[] newArray(final int size) {
return new TrackFileFormat[size];
}
};
/**
* Creates and returns a new format writer for each format.
*/
@@ -52,12 +52,10 @@ class TrackWriterImpl implements TrackWriter {
private final MyTracksProviderUtils providerUtils;
private final Track track;
private final TrackFormatWriter writer;
private final FileUtils fileUtils;
private boolean success = false;
private int errorMessage = -1;
private File directory = null;
private File file = null;
private OnCompletionListener onCompletionListener;
private OnWriteListener onWriteListener;
private Thread writeThread;
@@ -67,12 +65,6 @@ class TrackWriterImpl implements TrackWriter {
this.providerUtils = providerUtils;
this.track = track;
this.writer = writer;
this.fileUtils = new FileUtils();
}
@Override
public void setOnCompletionListener(OnCompletionListener onCompletionListener) {
this.onCompletionListener = onCompletionListener;
}
@Override
@@ -90,8 +82,7 @@ class TrackWriterImpl implements TrackWriter {
return file.getAbsolutePath();
}
@Override
public void writeTrackAsync() {
private void writeTrackAsync() {
writeThread = new Thread() {
@Override
public void run() {
@@ -131,7 +122,6 @@ class TrackWriterImpl implements TrackWriter {
}
}
}
finished();
}
public void stopWriteTrack() {
@@ -163,18 +153,6 @@ class TrackWriterImpl implements TrackWriter {
* ===============
*/
private void finished() {
if (onCompletionListener != null) {
runOnUiThread(new Runnable() {
@Override
public void run() {
onCompletionListener.onComplete();
}
});
return;
}
}
/**
* Runs the given runnable in the UI thread.
*/
@@ -195,7 +173,7 @@ class TrackWriterImpl implements TrackWriter {
}
// Make sure the file doesn't exist yet (possibly by changing the filename)
String fileName = fileUtils.buildUniqueFileName(
String fileName = FileUtils.buildUniqueFileName(
directory, track.getName(), writer.getExtension());
if (fileName == null) {
Log.e(Constants.TAG,
@@ -220,16 +198,16 @@ class TrackWriterImpl implements TrackWriter {
protected boolean canWriteFile() {
if (directory == null) {
String dirName =
fileUtils.buildExternalDirectoryPath(writer.getExtension());
FileUtils.buildExternalDirectoryPath(writer.getExtension());
directory = newFile(dirName);
}
if (!fileUtils.isSdCardAvailable()) {
if (!FileUtils.isSdCardAvailable()) {
Log.i(Constants.TAG, "Could not find SD card.");
errorMessage = R.string.sd_card_error_no_storage;
return false;
}
if (!fileUtils.ensureDirectoryExists(directory)) {
if (!FileUtils.ensureDirectoryExists(directory)) {
Log.i(Constants.TAG, "Could not create export directory.");
errorMessage = R.string.sd_card_error_create_dir;
return false;
@@ -1,132 +0,0 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
/**
* Given a {@link TrackWriter}, this class manages the process of writing the
* data in the track represented by the writer. This includes the display of
* a progress dialog, updating the progress bar in said dialog, and notifying
* interested parties when the write completes.
*
* @author Matthew Simmons
*/
class WriteProgressController {
/**
* This listener is used to notify interested parties when the write has
* completed.
*/
public interface OnCompletionListener {
/**
* When this method is invoked, the write has completed, and the progress
* dialog has been dismissed. Whether the write succeeded can be
* determined by examining the {@link TrackWriter}.
*/
public void onComplete();
}
private final Activity activity;
private final TrackWriter writer;
private ProgressDialog dialog;
private OnCompletionListener onCompletionListener;
private final int progressDialogId;
/**
* @param activity the activity associated with this write
* @param writer the writer which writes the track to disk. Note that this
* class will use the writer's completion listener. If callers are
* interested in notification upon completion of the write, they should
* use {@link #setOnCompletionListener}.
*/
public WriteProgressController(Activity activity, TrackWriter writer, int progressDialogId) {
this.activity = activity;
this.writer = writer;
this.progressDialogId = progressDialogId;
writer.setOnCompletionListener(writerCompleteListener);
writer.setOnWriteListener(writerWriteListener);
}
/** Set a listener to be invoked when the write completes. */
public void setOnCompletionListener(OnCompletionListener onCompletionListener) {
this.onCompletionListener = onCompletionListener;
}
// For testing purpose
OnCompletionListener getOnCompletionListener() {
return onCompletionListener;
}
public ProgressDialog createProgressDialog() {
dialog = new ProgressDialog(activity);
dialog.setIcon(android.R.drawable.ic_dialog_info);
dialog.setTitle(activity.getString(R.string.generic_progress_title));
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setMessage(activity.getString(R.string.sd_card_progress_write_file));
dialog.setIndeterminate(true);
dialog.setOnCancelListener(dialogCancelListener);
return dialog;
}
/** Initiate an asynchronous write. */
public void startWrite() {
activity.showDialog(progressDialogId);
writer.writeTrackAsync();
}
/** VisibleForTesting */
ProgressDialog getDialog() {
return dialog;
}
private final DialogInterface.OnCancelListener dialogCancelListener =
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialogInterface) {
writer.stopWriteTrack();
}
};
private final TrackWriter.OnCompletionListener writerCompleteListener =
new TrackWriter.OnCompletionListener() {
@Override
public void onComplete() {
activity.dismissDialog(progressDialogId);
if (onCompletionListener != null) {
onCompletionListener.onComplete();
}
}
};
private final TrackWriter.OnWriteListener writerWriteListener =
new TrackWriter.OnWriteListener() {
@Override
public void onWrite(int number, int max) {
if (number % 500 == 0) {
dialog.setIndeterminate(false);
dialog.setMax(max);
dialog.setProgress(Math.min(number, max));
}
}
};
}
@@ -425,18 +425,26 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
}
boolean isSuccess = response.isSuccessStatusCode();
if (isSuccess) {
InputStream content;
InputStream content = null;
try {
content = response.getContent();
if (setTableId) {
tableId = SendFusionTablesUtils.getTableId(content);
if (tableId == null) {
Log.d(TAG, "tableId is null");
return false;
}
}
} catch (IOException e) {
Log.d(TAG, "Unable to get response", e);
return false;
}
if (setTableId) {
tableId = SendFusionTablesUtils.getTableId(content);
if (tableId == null) {
Log.d(TAG, "tableId is null");
return false;
} finally {
if (content != null) {
try {
content.close();
} catch (IOException e) {
Log.d(TAG, "Unable to close content", e);
}
}
}
} else {
@@ -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("unchecked")
@SuppressWarnings("rawtypes")
@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("unchecked")
@SuppressWarnings("rawtypes")
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("unchecked")
@SuppressWarnings("rawtypes")
private Entry parseEntry(Class entryClass, InputStream is)
throws ParseException, IOException {
GDataParser parser = null;
@@ -48,7 +48,7 @@ public class XmlDocsGDataParserFactory implements GDataParserFactory {
}
}
@SuppressWarnings("unchecked")
@SuppressWarnings("rawtypes")
@Override
public GDataParser createParser(Class cls, InputStream is)
throws ParseException {
@@ -14,8 +14,6 @@ import java.util.Vector;
*/
public class MapsFeature {
private static final long serialVersionUID = 8439035544430497236L;
/** A marker feature displays an icon at a single point on the map. */
public static final int MARKER = 0;
@@ -169,7 +169,6 @@ public class MapsGDataConverter {
xmlSerializer.endTag(null, "Placemark");
xmlSerializer.flush();
entry.setContent(writer.toString());
Log.d("My Google Maps", "Generated kml:\n" + entry.getContent());
Log.d("My Google Maps", "Edit URI: " + entry.getEditUri());
} catch (IOException e) {
e.printStackTrace();
@@ -39,7 +39,7 @@ public class XmlMapsGDataParserFactory implements GDataParserFactory {
}
}
@SuppressWarnings("unchecked")
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public GDataParser createParser(Class cls, InputStream is)
throws ParseException {
@@ -17,16 +17,14 @@ package com.google.android.apps.mytracks.io.maps;
import com.google.android.apps.mytracks.io.gdata.maps.MapsMapMetadata;
import com.google.android.apps.mytracks.io.sendtogoogle.SendRequest;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.maps.mytracks.R;
import com.google.common.annotations.VisibleForTesting;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
@@ -46,13 +44,11 @@ import java.util.ArrayList;
*/
public class ChooseMapActivity extends Activity {
private static final int PROGRESS_DIALOG = 1;
@VisibleForTesting
static final int ERROR_DIALOG = 2;
private static final int DIALOG_PROGRESS_ID = 0;
private static final int DIALOG_ERROR_ID = 1;
private SendRequest sendRequest;
private ChooseMapAsyncTask asyncTask;
private ProgressDialog progressDialog;
@VisibleForTesting
ArrayAdapter<ListItem> arrayAdapter;
@@ -122,39 +118,34 @@ public class ChooseMapActivity extends Activity {
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case PROGRESS_DIALOG:
progressDialog = new ProgressDialog(this);
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progressDialog.setMessage(getString(R.string.maps_list_loading));
progressDialog.setCancelable(true);
progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
asyncTask.cancel(true);
finish();
}
});
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
progressDialog.setTitle(R.string.generic_progress_title);
return progressDialog;
case ERROR_DIALOG:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setTitle(R.string.generic_error_title);
builder.setMessage(R.string.maps_list_error);
builder.setPositiveButton(R.string.generic_ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int arg1) {
finish();
}
});
builder.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
finish();
}
});
return builder.create();
case DIALOG_PROGRESS_ID:
return DialogUtils.createSpinnerProgressDialog(
this, R.string.maps_list_progress_message, new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
asyncTask.cancel(true);
finish();
}
});
case DIALOG_ERROR_ID:
return new AlertDialog.Builder(this)
.setCancelable(true)
.setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(R.string.maps_list_error)
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
finish();
}
})
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int arg1) {
finish();
}
})
.setTitle(R.string.generic_error_title)
.create();
default:
return null;
}
@@ -192,7 +183,7 @@ public class ChooseMapActivity extends Activity {
* Shows the progress dialog.
*/
public void showProgressDialog() {
showDialog(PROGRESS_DIALOG);
showDialog(DIALOG_PROGRESS_ID);
}
/**
@@ -200,7 +191,7 @@ public class ChooseMapActivity extends Activity {
*/
@VisibleForTesting
void showErrorDialog() {
showDialog(ERROR_DIALOG);
showDialog(DIALOG_ERROR_ID);
}
/**
@@ -208,7 +199,7 @@ public class ChooseMapActivity extends Activity {
*/
@VisibleForTesting
void removeProgressDialog() {
removeDialog(PROGRESS_DIALOG);
removeDialog(DIALOG_PROGRESS_ID);
}
/**
@@ -24,6 +24,7 @@ import com.google.android.apps.mytracks.io.gdata.maps.MapsGDataConverter;
import com.google.android.apps.mytracks.io.gdata.maps.MapsMapMetadata;
import com.google.android.apps.mytracks.io.gdata.maps.XmlMapsGDataParserFactory;
import com.google.android.common.gdata.AndroidXmlParserFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.wireless.gdata.client.GDataClient;
import com.google.wireless.gdata.client.HttpException;
import com.google.wireless.gdata.parser.GDataParser;
@@ -75,19 +76,29 @@ public class ChooseMapAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private ArrayList<MapsMapMetadata> mapData;
public ChooseMapAsyncTask(ChooseMapActivity activity, Account account) {
this.activity = activity;
this.account = account;
context = activity.getApplicationContext();
gDataClient = GDataClientFactory.getGDataClient(context);
mapsClient = new MapsClient(
gDataClient, new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
canRetry = true;
completed = false;
success = false;
this(activity, account, activity.getApplicationContext(), GDataClientFactory
.getGDataClient(activity.getApplicationContext()), new MapsClient(
GDataClientFactory.getGDataClient(activity.getApplicationContext()),
new XmlMapsGDataParserFactory(new AndroidXmlParserFactory())));
}
/**
* Creates this constructor for test.
*/
public ChooseMapAsyncTask(ChooseMapActivity activity, Account account,
Context context, GDataClient gDataClient, MapsClient mapsClient) {
this.activity = activity;
this.account = account;
this.context = context;
this.gDataClient = gDataClient;
this.mapsClient = mapsClient;
canRetry = true;
completed = false;
success = false;
}
/**
* Sets the activity associated with this AyncTask.
*
@@ -107,17 +118,17 @@ public class ChooseMapAsyncTask extends AsyncTask<Void, Integer, Boolean> {
@Override
protected Boolean doInBackground(Void... params) {
return getMaps();
}
@Override
protected void onCancelled() {
closeClient();
try {
return getMaps();
} finally {
if (gDataClient != null) {
gDataClient.close();
}
}
}
@Override
protected void onPostExecute(Boolean result) {
closeClient();
success = result;
completed = true;
if (activity != null) {
@@ -125,21 +136,13 @@ public class ChooseMapAsyncTask extends AsyncTask<Void, Integer, Boolean> {
}
}
/**
* Closes the gdata client.
*/
private void closeClient() {
if (gDataClient != null) {
gDataClient.close();
}
}
/**
* Gets all the maps from Google Maps.
*
* @return true if success.
*/
private boolean getMaps() {
@VisibleForTesting
boolean getMaps() {
// Reset the per request states
authToken = null;
mapIds = new ArrayList<String>();
@@ -194,7 +197,8 @@ public class ChooseMapAsyncTask extends AsyncTask<Void, Integer, Boolean> {
* Retries upload. Invalidates the authToken. If can retry, invokes
* {@link ChooseMapAsyncTask#getMaps()}. Returns false if cannot retry.
*/
private boolean retryUpload() {
@VisibleForTesting
boolean retryUpload() {
if (isCancelled()) {
return false;
}
@@ -206,4 +210,38 @@ public class ChooseMapAsyncTask extends AsyncTask<Void, Integer, Boolean> {
}
return false;
}
}
/**
* Gets the complete status of task.
*/
@VisibleForTesting
boolean getCompleted() {
return completed;
}
/**
* Sets the complete status of task.
* @param completed
*/
@VisibleForTesting
void setCompleted(boolean completed) {
this.completed = completed;
}
/**
* Sets the status of canRetry.
* @param canRetry status of canRetry
*/
@VisibleForTesting
void setCanRetry(boolean canRetry) {
this.canRetry = canRetry;
}
/**
* Gets the status of canRetry.
*/
@VisibleForTesting
boolean getCanRetry() {
return canRetry;
}
}
@@ -34,6 +34,7 @@ import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.common.gdata.AndroidXmlParserFactory;
import com.google.android.maps.mytracks.R;
import com.google.common.annotations.VisibleForTesting;
import com.google.wireless.gdata.client.GDataClient;
import com.google.wireless.gdata.client.HttpException;
import com.google.wireless.gdata.parser.ParseException;
@@ -73,8 +74,10 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
private static final int MAX_POINTS_PER_UPLOAD = 500;
private static final int PROGRESS_FETCH_MAP_ID = 5;
private static final int PROGRESS_UPLOAD_DATA_MIN = 10;
private static final int PROGRESS_UPLOAD_DATA_MAX = 90;
@VisibleForTesting
static final int PROGRESS_UPLOAD_DATA_MIN = 10;
@VisibleForTesting
static final int PROGRESS_UPLOAD_DATA_MAX = 90;
private static final int PROGRESS_UPLOAD_WAYPOINTS = 95;
private static final int PROGRESS_COMPLETE = 100;
@@ -83,8 +86,8 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
private final long trackId;
private final Account account;
private final String chooseMapId;
private final Context context;
private final MyTracksProviderUtils myTracksProviderUtils;
private final Context context;
private final GDataClient gDataClient;
private final MapsClient mapsClient;
@@ -94,15 +97,25 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
private String mapId;
int currentSegment;
public SendMapsAsyncTask(SendMapsActivity activity, long trackId, Account account,
String chooseMapId) {
this(activity, trackId, account, chooseMapId, MyTracksProviderUtils.Factory.get(activity
.getApplicationContext()));
}
/**
* This constructor is created for test.
*/
@VisibleForTesting
public SendMapsAsyncTask (
SendMapsActivity activity, long trackId, Account account, String chooseMapId) {
SendMapsActivity activity, long trackId, Account account, String chooseMapId, MyTracksProviderUtils myTracksProviderUtils) {
super(activity);
this.trackId = trackId;
this.account = account;
this.chooseMapId = chooseMapId;
this.myTracksProviderUtils = myTracksProviderUtils;
context = activity.getApplicationContext();
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
gDataClient = GDataClientFactory.getGDataClient(context);
mapsClient = new MapsClient(
gDataClient, new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
@@ -201,7 +214,8 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
* @param track the Track
* @return true if able to fetch the mapId variable.
*/
private boolean fetchSendMapId(Track track) {
@VisibleForTesting
boolean fetchSendMapId(Track track) {
if (isCancelled()) {
return false;
}
@@ -239,7 +253,8 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
* @param track the track
* @return true if success.
*/
private boolean uploadAllTrackPoints(Track track) {
@VisibleForTesting
boolean uploadAllTrackPoints(Track track) {
Cursor locationsCursor = null;
try {
SharedPreferences prefs = context.getSharedPreferences(
@@ -313,9 +328,8 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
if (lastLocation != null) {
distances.add(tripStatisticsBuilder.getStatistics().getTotalDistance());
elevations.add(elevationBuffer.getAverage());
DescriptionGenerator descriptionGenerator = new DescriptionGeneratorImpl(context);
track.setDescription("<p>" + track.getDescription() + "</p><p>"
+ descriptionGenerator.generateTrackDescription(track, distances, elevations) + "</p>");
track.setDescription(getTrackDescription(track, distances, elevations));
if (!uploadMarker(context.getString(R.string.marker_label_end, track.getName()),
track.getDescription(), END_ICON_URL, lastLocation)) {
Log.d(TAG, "Unable to create an end marker");
@@ -330,6 +344,21 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
}
}
/**
* Gets the description of a track.
*
* @param track the track
* @param distances distance vectors
* @param elevations elevation vectors
* @return the description of a track.
*/
@VisibleForTesting
String getTrackDescription(Track track, Vector<Double> distances, Vector<Double> elevations) {
DescriptionGenerator descriptionGenerator = new DescriptionGeneratorImpl(context);
return "<p>" + track.getDescription() + "</p><p>"
+ descriptionGenerator.generateTrackDescription(track, distances, elevations) + "</p>";
}
/**
* Prepares and uploads a list of locations from a track.
*
@@ -338,7 +367,8 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
* @param lastBatch true if it is the last batch of locations
* @return true if success.
*/
private boolean prepareAndUploadPoints(Track track, List<Location> locations, boolean lastBatch) {
@VisibleForTesting
boolean prepareAndUploadPoints(Track track, List<Location> locations, boolean lastBatch) {
// Prepare locations
ArrayList<Track> splitTracks = SendToGoogleUtils.prepareLocations(track, locations);
@@ -367,7 +397,8 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
* @param location marker location
* @return true if success.
*/
private boolean uploadMarker(
@VisibleForTesting
boolean uploadMarker(
String title, String description, String iconUrl, Location location) {
if (isCancelled()) {
return false;
@@ -426,7 +457,8 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
*
* @return true if success.
*/
private boolean uploadWaypoints() {
@VisibleForTesting
boolean uploadWaypoints() {
Cursor cursor = null;
try {
cursor = myTracksProviderUtils.getWaypointsCursor(
@@ -471,10 +503,43 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
* @param uploaded the number of uploaded locations
* @param total the number of total locations
*/
private void updateProgress(int uploaded, int total) {
double totalPercentage = (double) uploaded / total;
double scaledPercentage = totalPercentage
* (PROGRESS_UPLOAD_DATA_MAX - PROGRESS_UPLOAD_DATA_MIN) + PROGRESS_UPLOAD_DATA_MIN;
publishProgress((int) scaledPercentage);
@VisibleForTesting
void updateProgress(int uploaded, int total) {
publishProgress(getPercentage(uploaded, total));
}
}
/**
* Count the percentage of the number of locations uploaded.
*
* @param uploaded the number of uploaded locations
* @param total the number of total locations
*/
@VisibleForTesting
static int getPercentage(int uploaded, int total) {
double totalPercentage = (double) uploaded / total;
double scaledPercentage = totalPercentage
* (PROGRESS_UPLOAD_DATA_MAX - PROGRESS_UPLOAD_DATA_MIN) + PROGRESS_UPLOAD_DATA_MIN;
return (int) scaledPercentage;
}
/**
* Gets the mapID.
*
* @return mapId
*/
@VisibleForTesting
String getMapId() {
return mapId;
}
/**
* Sets the value of mapsGDataConverter.
*
* @param mapsGDataConverter new value of mapsGDataConverter
*/
@VisibleForTesting
void setMapsGDataConverter(MapsGDataConverter mapsGDataConverter) {
this.mapsGDataConverter = mapsGDataConverter;
}
}
@@ -16,6 +16,7 @@
package com.google.android.apps.mytracks.io.sendtogoogle;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
@@ -41,7 +42,7 @@ import android.os.Bundle;
*/
public abstract class AbstractSendActivity extends Activity {
private static final int PROGRESS_DIALOG = 1;
private static final int DIALOG_PROGRESS_ID = 0;
protected SendRequest sendRequest;
private AbstractSendAsyncTask asyncTask;
@@ -70,26 +71,18 @@ public abstract class AbstractSendActivity extends Activity {
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case PROGRESS_DIALOG:
progressDialog = new ProgressDialog(this);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
progressDialog.setTitle(getString(R.string.send_google_progress_title, getServiceName()));
progressDialog.setMax(100);
progressDialog.setProgress(0);
progressDialog.setCancelable(true);
progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
if (id != DIALOG_PROGRESS_ID) {
return null;
}
progressDialog = DialogUtils.createHorizontalProgressDialog(
this, R.string.send_google_progress_message, new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
asyncTask.cancel(true);
startNextActivity(false, true);
}
});
return progressDialog;
default:
return null;
}
}, getServiceName());
return progressDialog;
}
/**
@@ -105,7 +98,7 @@ public abstract class AbstractSendActivity extends Activity {
* Shows the progress dialog.
*/
public void showProgressDialog() {
showDialog(PROGRESS_DIALOG);
showDialog(DIALOG_PROGRESS_ID);
}
/**
@@ -115,7 +108,9 @@ public abstract class AbstractSendActivity extends Activity {
*/
public void setProgressDialogValue(int value) {
if (progressDialog != null) {
progressDialog.setIndeterminate(false);
progressDialog.setProgress(value);
progressDialog.setMax(100);
}
}
@@ -76,7 +76,14 @@ public abstract class AbstractSendAsyncTask extends AsyncTask<Void, Integer, Boo
@Override
protected Boolean doInBackground(Void... params) {
return performTask();
try {
return performTask();
} finally {
closeConnection();
if (success) {
saveResult();
}
}
}
@Override
@@ -89,21 +96,12 @@ public abstract class AbstractSendAsyncTask extends AsyncTask<Void, Integer, Boo
@Override
protected void onPostExecute(Boolean result) {
success = result;
if (success) {
saveResult();
}
completed = true;
closeConnection();
if (activity != null) {
activity.onAsyncTaskCompleted(success);
}
}
@Override
protected void onCancelled() {
closeConnection();
}
/**
* Retries the task. First, invalidates the auth token. If can retry, invokes
* {@link #performTask()}. Returns false if cannot retry.
@@ -55,8 +55,8 @@ public class AccountChooserActivity extends Activity {
private static final String TAG = AccountChooserActivity.class.getSimpleName();
private static final int NO_ACCOUNT_DIALOG = 1;
private static final int CHOOSE_ACCOUNT_DIALOG = 2;
private static final int DIALOG_NO_ACCOUNT_ID = 0;
private static final int DIALOG_CHOOSER_ID = 1;
/**
* A callback after getting the permission to access a Google service.
@@ -108,77 +108,82 @@ public class AccountChooserActivity extends Activity {
protected void onResume() {
super.onResume();
if (accounts.length == 0) {
showDialog(NO_ACCOUNT_DIALOG);
showDialog(DIALOG_NO_ACCOUNT_ID);
} else if (accounts.length > 1 ) {
showDialog(CHOOSE_ACCOUNT_DIALOG);
showDialog(DIALOG_CHOOSER_ID);
}
}
@Override
protected Dialog onCreateDialog(int id) {
AlertDialog.Builder builder;
switch (id) {
case NO_ACCOUNT_DIALOG:
builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.send_google_no_account_title);
builder.setMessage(R.string.send_google_no_account_message);
builder.setCancelable(true);
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
case DIALOG_NO_ACCOUNT_ID:
return new AlertDialog.Builder(this)
.setCancelable(true)
.setMessage(R.string.send_google_no_account_message)
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
finish();
}
})
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finish();
}
})
.setTitle(R.string.send_google_no_account_title)
.create();
case DIALOG_CHOOSER_ID:
return createChooserDialog();
default:
return null;
}
}
/**
* Creates a chooser dialog.
*/
private Dialog createChooserDialog() {
String[] choices = new String[accounts.length];
for (int i = 0; i < accounts.length; i++) {
choices[i] = accounts[i].name;
}
return new AlertDialog.Builder(this)
.setCancelable(true)
.setNegativeButton(R.string.generic_cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finish();
}
})
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
finish();
}
});
builder.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
return builder.create();
case CHOOSE_ACCOUNT_DIALOG:
builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.send_google_choose_account_title);
String[] choices = new String[accounts.length];
for (int i = 0; i < accounts.length; i++) {
choices[i] = accounts[i].name;
}
builder.setSingleChoiceItems(choices, selectedAccountIndex, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
selectedAccountIndex = which;
}
});
builder.setCancelable(true);
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
finish();
}
});
builder.setNegativeButton(R.string.generic_cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
builder.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
})
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Account account = accounts[selectedAccountIndex];
SharedPreferences prefs = getSharedPreferences(
SharedPreferences sharedPreferences = getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
Editor editor = prefs.edit();
Editor editor = sharedPreferences.edit();
editor.putString(getString(R.string.preferred_account_key), account.name);
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
sendRequest.setAccount(account);
getPermission(MapsConstants.SERVICE_NAME, sendRequest.isSendMaps(), mapsCallback);
}
});
return builder.create();
default:
return null;
}
})
.setSingleChoiceItems(
choices, selectedAccountIndex, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
selectedAccountIndex = which;
}
})
.setTitle(R.string.send_google_choose_account_title)
.create();
}
private PermissionCallback spreadsheetsCallback = new PermissionCallback() {
@@ -19,6 +19,7 @@ package com.google.android.apps.mytracks.io.sendtogoogle;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.common.annotations.VisibleForTesting;
import android.location.Location;
import android.util.Log;
@@ -109,11 +110,12 @@ public class SendToGoogleUtils {
* @param segment the track segment
* @param splitTracks an array of track segments
*/
private static void prepareTrackSegment(Track segment, ArrayList<Track> splitTracks) {
@VisibleForTesting
static boolean prepareTrackSegment(Track segment, ArrayList<Track> splitTracks) {
// Make sure the segment has at least 2 points
if (segment.getLocations().size() < 2) {
Log.d(TAG, "segment has less than 2 points");
return;
return false;
}
// For a new segment, sets it stop time
@@ -128,5 +130,6 @@ public class SendToGoogleUtils {
LocationUtils.decimate(segment, 2.0);
splitTracks.add(segment);
return true;
}
}
@@ -43,7 +43,7 @@ import android.widget.TextView;
public class UploadResultActivity extends Activity {
private static final String TEXT_PLAIN_TYPE = "text/plain";
private static final int RESULT_DIALOG = 1;
private static final int DIALOG_RESULT_ID = 0;
private SendRequest sendRequest;
private Track track;
@@ -75,104 +75,99 @@ public class UploadResultActivity extends Activity {
@Override
protected void onResume() {
super.onResume();
showDialog(RESULT_DIALOG);
showDialog(DIALOG_RESULT_ID);
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case RESULT_DIALOG:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
View view = getLayoutInflater().inflate(R.layout.upload_result, null);
builder.setView(view);
if (id != DIALOG_RESULT_ID) {
return null;
}
View view = getLayoutInflater().inflate(R.layout.upload_result, null);
LinearLayout mapsResult = (LinearLayout) view.findViewById(R.id.upload_result_maps_result);
LinearLayout fusionTablesResult = (LinearLayout) view.findViewById(
R.id.upload_result_fusion_tables_result);
LinearLayout docsResult = (LinearLayout) view.findViewById(R.id.upload_result_docs_result);
LinearLayout mapsResult = (LinearLayout) view.findViewById(R.id.upload_result_maps_result);
LinearLayout fusionTablesResult = (LinearLayout) view.findViewById(
R.id.upload_result_fusion_tables_result);
LinearLayout docsResult = (LinearLayout) view.findViewById(R.id.upload_result_docs_result);
ImageView mapsResultIcon = (ImageView) view.findViewById(
R.id.upload_result_maps_result_icon);
ImageView fusionTablesResultIcon = (ImageView) view.findViewById(
R.id.upload_result_fusion_tables_result_icon);
ImageView docsResultIcon = (ImageView) view.findViewById(
R.id.upload_result_docs_result_icon);
ImageView mapsResultIcon = (ImageView) view.findViewById(R.id.upload_result_maps_result_icon);
ImageView fusionTablesResultIcon = (ImageView) view.findViewById(
R.id.upload_result_fusion_tables_result_icon);
ImageView docsResultIcon = (ImageView) view.findViewById(R.id.upload_result_docs_result_icon);
TextView successFooter = (TextView) view.findViewById(R.id.upload_result_success_footer);
TextView errorFooter = (TextView) view.findViewById(R.id.upload_result_error_footer);
TextView successFooter = (TextView) view.findViewById(R.id.upload_result_success_footer);
TextView errorFooter = (TextView) view.findViewById(R.id.upload_result_error_footer);
boolean hasError = false;
if (!sendRequest.isSendMaps()) {
mapsResult.setVisibility(View.GONE);
} else {
if (!sendRequest.isMapsSuccess()) {
mapsResultIcon.setImageResource(R.drawable.failure);
hasError = true;
}
}
boolean hasError = false;
if (!sendRequest.isSendMaps()) {
mapsResult.setVisibility(View.GONE);
} else {
if (!sendRequest.isMapsSuccess()) {
mapsResultIcon.setImageResource(R.drawable.failure);
mapsResultIcon.setContentDescription(getString(R.string.generic_error_title));
hasError = true;
}
}
if (!sendRequest.isSendFusionTables()) {
fusionTablesResult.setVisibility(View.GONE);
} else {
if (!sendRequest.isFusionTablesSuccess()) {
fusionTablesResultIcon.setImageResource(R.drawable.failure);
hasError = true;
}
}
if (!sendRequest.isSendFusionTables()) {
fusionTablesResult.setVisibility(View.GONE);
} else {
if (!sendRequest.isFusionTablesSuccess()) {
fusionTablesResultIcon.setImageResource(R.drawable.failure);
fusionTablesResultIcon.setContentDescription(getString(R.string.generic_error_title));
hasError = true;
}
}
if (!sendRequest.isSendDocs()) {
docsResult.setVisibility(View.GONE);
} else {
if (!sendRequest.isDocsSuccess()) {
docsResultIcon.setImageResource(R.drawable.failure);
hasError = true;
}
}
if (!sendRequest.isSendDocs()) {
docsResult.setVisibility(View.GONE);
} else {
if (!sendRequest.isDocsSuccess()) {
docsResultIcon.setImageResource(R.drawable.failure);
docsResultIcon.setContentDescription(getString(R.string.generic_error_title));
hasError = true;
}
}
if (hasError) {
builder.setTitle(R.string.generic_error_title);
builder.setIcon(android.R.drawable.ic_dialog_alert);
successFooter.setVisibility(View.GONE);
} else {
builder.setTitle(R.string.generic_success_title);
builder.setIcon(android.R.drawable.ic_dialog_info);
errorFooter.setVisibility(View.GONE);
}
if (hasError) {
successFooter.setVisibility(View.GONE);
} else {
errorFooter.setVisibility(View.GONE);
}
builder.setCancelable(true);
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setCancelable(true)
.setIcon(hasError ? android.R.drawable.ic_dialog_alert : android.R.drawable.ic_dialog_info)
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
finish();
}
});
builder.setPositiveButton(
getString(R.string.generic_ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (!sendRequest.isShowAll() && shareUrl != null) {
startShareUrlActivity(shareUrl);
}
finish();
}
});
})
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (!sendRequest.isShowAll() && shareUrl != null) {
startShareUrlActivity(shareUrl);
}
finish();
}
})
.setTitle(hasError ? R.string.generic_error_title : R.string.generic_success_title)
.setView(view);
// Add a Share URL button if showing all the options and a shareUrl
// exists
if (sendRequest.isShowAll() && shareUrl != null) {
builder.setNegativeButton(getString(R.string.send_google_result_share_url),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startShareUrlActivity(shareUrl);
finish();
}
});
}
return builder.create();
default:
return null;
// Add a Share URL button if showing all the options and a shareUrl exists
if (sendRequest.isShowAll() && shareUrl != null) {
builder.setNegativeButton(
R.string.send_google_result_share_url, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startShareUrlActivity(shareUrl);
finish();
}
});
}
return builder.create();
}
/**
@@ -22,6 +22,7 @@ import com.google.android.maps.mytracks.R;
import com.google.common.annotations.VisibleForTesting;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -30,27 +31,26 @@ import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.RadioButton;
import android.widget.TableRow;
import android.widget.Toast;
import java.util.ArrayList;
/**
* A chooser to select the Google services to upload a track to.
*
*
* @author Jimmy Shih
*/
public class UploadServiceChooserActivity extends Activity {
private static final int SERVICE_PICKER_DIALOG = 1;
private static final int DIALOG_CHOOSER_ID = 0;
private SendRequest sendRequest;
private Dialog dialog;
private AlertDialog alertDialog;
private TableRow mapsTableRow;
private TableRow fusionTablesTableRow;
@@ -64,9 +64,6 @@ public class UploadServiceChooserActivity extends Activity {
private RadioButton newMapRadioButton;
private RadioButton existingMapRadioButton;
private Button cancel;
private Button send;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -76,74 +73,78 @@ public class UploadServiceChooserActivity extends Activity {
@Override
protected void onResume() {
super.onResume();
showDialog(SERVICE_PICKER_DIALOG);
showDialog(DIALOG_CHOOSER_ID);
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case SERVICE_PICKER_DIALOG:
dialog = new Dialog(this);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(R.layout.upload_service_chooser);
dialog.setCancelable(true);
dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
if (id != DIALOG_CHOOSER_ID) {
return null;
}
View view = getLayoutInflater().inflate(R.layout.upload_service_chooser, null);
mapsTableRow = (TableRow) view.findViewById(R.id.send_google_maps_row);
fusionTablesTableRow = (TableRow) view.findViewById(R.id.send_google_fusion_tables_row);
docsTableRow = (TableRow) view.findViewById(R.id.send_google_docs_row);
mapsCheckBox = (CheckBox) view.findViewById(R.id.send_google_maps);
fusionTablesCheckBox = (CheckBox) view.findViewById(R.id.send_google_fusion_tables);
docsCheckBox = (CheckBox) view.findViewById(R.id.send_google_docs);
mapsOptionTableRow = (TableRow) view.findViewById(R.id.send_google_maps_option_row);
newMapRadioButton = (RadioButton) view.findViewById(R.id.send_google_new_map);
existingMapRadioButton = (RadioButton) view.findViewById(R.id.send_google_existing_map);
// Setup checkboxes
OnCheckedChangeListener checkBoxListener = new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton button, boolean checked) {
updateStateBySelection();
}
};
mapsCheckBox.setOnCheckedChangeListener(checkBoxListener);
fusionTablesCheckBox.setOnCheckedChangeListener(checkBoxListener);
docsCheckBox.setOnCheckedChangeListener(checkBoxListener);
// Setup initial state
initState();
// Update state based on sendRequest
updateStateBySendRequest();
// Update state based on current selection
updateStateBySelection();
alertDialog = new AlertDialog.Builder(this)
.setCancelable(true)
.setNegativeButton(R.string.generic_cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
})
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface d) {
finish();
}
});
mapsTableRow = (TableRow) dialog.findViewById(R.id.send_google_maps_row);
fusionTablesTableRow = (TableRow) dialog.findViewById(R.id.send_google_fusion_tables_row);
docsTableRow = (TableRow) dialog.findViewById(R.id.send_google_docs_row);
mapsCheckBox = (CheckBox) dialog.findViewById(R.id.send_google_maps);
fusionTablesCheckBox = (CheckBox) dialog.findViewById(R.id.send_google_fusion_tables);
docsCheckBox = (CheckBox) dialog.findViewById(R.id.send_google_docs);
mapsOptionTableRow = (TableRow) dialog.findViewById(R.id.send_google_maps_option_row);
newMapRadioButton = (RadioButton) dialog.findViewById(R.id.send_google_new_map);
existingMapRadioButton = (RadioButton) dialog.findViewById(R.id.send_google_existing_map);
// Setup checkboxes
OnCheckedChangeListener checkBoxListener = new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton button, boolean checked) {
updateStateBySelection();
}
};
mapsCheckBox.setOnCheckedChangeListener(checkBoxListener);
fusionTablesCheckBox.setOnCheckedChangeListener(checkBoxListener);
docsCheckBox.setOnCheckedChangeListener(checkBoxListener);
// Setup buttons
cancel = (Button) dialog.findViewById(R.id.send_google_cancel);
cancel.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
finish();
}
});
send = (Button) dialog.findViewById(R.id.send_google_send_now);
send.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
})
.setPositiveButton(R.string.send_google_send_now, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
saveState();
startNextActivity();
if (sendMaps() || sendFusionTables() || sendDocs()) {
startNextActivity();
} else {
Toast.makeText(UploadServiceChooserActivity.this,
R.string.send_google_no_service_selected, Toast.LENGTH_LONG).show();
finish();
}
}
});
// Setup initial state
initState();
// Update state based on sendRequest
updateStateBySendRequest();
// Update state based on current user selection
updateStateBySelection();
return dialog;
default:
return null;
}
})
.setTitle(R.string.send_google_title)
.setView(view)
.create();
return alertDialog;
}
/**
@@ -186,7 +187,6 @@ public class UploadServiceChooserActivity extends Activity {
*/
private void updateStateBySelection() {
mapsOptionTableRow.setVisibility(sendMaps() ? View.VISIBLE : View.GONE);
send.setEnabled(sendMaps() || sendFusionTables() || sendDocs());
}
/**
@@ -242,7 +242,7 @@ public class UploadServiceChooserActivity extends Activity {
startActivity(intent);
finish();
}
/**
* Sends stats to Google Analytics.
*/
@@ -259,12 +259,12 @@ public class UploadServiceChooserActivity extends Activity {
}
AnalyticsUtils.sendPageViews(this, pages.toArray(new String[pages.size()]));
}
@VisibleForTesting
Dialog getDialog() {
return dialog;
AlertDialog getAlertDialog() {
return alertDialog;
}
@VisibleForTesting
SendRequest getSendRequest() {
return sendRequest;
@@ -91,10 +91,7 @@ public class RemoveTempFilesService extends Service {
}
private void cleanTempDirectory(String name) {
FileUtils fileUtils = new FileUtils();
String dirName = fileUtils.buildExternalDirectoryPath(name, "tmp");
File dir = new File(dirName);
cleanTempDirectory(dir);
cleanTempDirectory(new File(FileUtils.buildExternalDirectoryPath(name, "tmp")));
}
/**
@@ -19,7 +19,7 @@ import static com.google.android.apps.mytracks.Constants.RESUME_TRACK_EXTRA_NAME
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.MyTracks;
import com.google.android.apps.mytracks.TrackDetailActivity;
import com.google.android.apps.mytracks.content.DescriptionGenerator;
import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl;
import com.google.android.apps.mytracks.content.MyTracksLocation;
@@ -484,9 +484,10 @@ public class TrackRecordingService extends Service {
Notification notification = new Notification(
R.drawable.arrow_320, null /* tickerText */,
System.currentTimeMillis());
Intent intent = new Intent(this, TrackDetailActivity.class)
.putExtra(TrackDetailActivity.TRACK_ID, recordingTrackId);
PendingIntent contentIntent = PendingIntent.getActivity(
this, 0 /* requestCode */, new Intent(this, MyTracks.class),
0 /* flags */);
this, 0 /* requestCode */, intent, 0 /* flags */);
notification.setLatestEventInfo(this, getString(R.string.my_tracks_app_name),
getString(R.string.track_record_notification), contentIntent);
notification.flags += Notification.FLAG_NO_CLEAR;
@@ -18,6 +18,7 @@ package com.google.android.apps.mytracks.services;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.util.SystemUtils;
import com.google.android.maps.mytracks.BuildConfig;
import android.content.ComponentName;
import android.content.Context;
@@ -157,7 +158,7 @@ public class TrackRecordingServiceConnection {
Log.i(TAG, "Binding to the service");
Intent intent = new Intent(context, TrackRecordingService.class);
int flags = SystemUtils.isRelease(context) ? 0 : Context.BIND_DEBUG_UNBIND;
int flags = BuildConfig.DEBUG ? Context.BIND_DEBUG_UNBIND : 0;
context.bindService(intent, serviceConnection, flags);
}
@@ -49,7 +49,7 @@ import android.util.Log;
* Initialization begins in {@link #setupChannel}, which is invoked by the
* {@link SensorManager} when track recording begins. {@link #setupChannel}
* asks the ANT Radio Service (via {@link AntInterface}) to start, using a
* {@link AntInterface.ServiceListener} to indicate when the service has
* AntInterface.ServiceListener to indicate when the service has
* connected. {@link #serviceConnected} claims and enables the Radio Service,
* and then resets it to a known state for our use. Completion of reset is
* indicated by receipt of a startup message (see {@link AntStartupMessage}).
@@ -23,7 +23,7 @@ import com.dsi.ant.exception.AntInterfaceException;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.SystemUtils;
import com.google.android.maps.mytracks.BuildConfig;
import com.google.android.maps.mytracks.R;
import android.content.Context;
@@ -32,7 +32,7 @@ import android.util.Log;
/**
* A sensor manager to the PC7 SRM ANT+ bridge.
*
*
* @author Sandor Dornbush
* @author Umran Abdulla
*/
@@ -54,15 +54,15 @@ public class AntSrmBridgeSensorManager extends AntSensorManager {
private static final int INDEX_MESSAGE_SPEED = 5;
private static final int INDEX_MESSAGE_CADENCE = 7;
private static final int INDEX_MESSAGE_BPM = 8;
private static final int MSG_INITIAL = 5;
private static final int MSG_DATA = 6;
private short deviceNumber;
public AntSrmBridgeSensorManager(Context context) {
super(context);
Log.i(TAG, "new ANT SRM Bridge Sensor Manager created");
deviceNumber = WILDCARD;
@@ -77,15 +77,15 @@ public class AntSrmBridgeSensorManager extends AntSensorManager {
}
Log.i(TAG, "Will pair with device: " + deviceNumber);
}
@Override
protected boolean handleMessage(byte messageId, byte[] messageData) {
if (super.handleMessage(messageId, messageData)) {
return true;
}
if (!SystemUtils.isRelease(context)) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Received ANT msg: " + AntUtils.antMessageToString(messageId) + "(" + messageId + ")");
}
@@ -100,7 +100,7 @@ public class AntSrmBridgeSensorManager extends AntSensorManager {
return true;
}
/**
* Decode an ant device message.
* @param messageData The byte array received from the device.
@@ -132,10 +132,10 @@ public class AntSrmBridgeSensorManager extends AntSensorManager {
}
setSensorState(Sensor.SensorState.CONNECTED);
int messageType = antMessage[INDEX_MESSAGE_TYPE] & 0xFF;
Log.d(TAG, "Received message-type=" + messageType);
switch (messageType) {
case MSG_INITIAL:
break;
@@ -149,7 +149,7 @@ public class AntSrmBridgeSensorManager extends AntSensorManager {
{
int messageId = msg[INDEX_MESSAGE_ID] & 0xFF;
Log.d(TAG, "Received message-id=" + messageId);
int powerVal = (((msg[INDEX_MESSAGE_POWER] & 0xFF) << 8) |
(msg[INDEX_MESSAGE_POWER+1] & 0xFF));
@SuppressWarnings("unused")
@@ -158,12 +158,12 @@ public class AntSrmBridgeSensorManager extends AntSensorManager {
int cadenceVal = (msg[INDEX_MESSAGE_CADENCE] & 0xFF);
int bpmVal = (msg[INDEX_MESSAGE_BPM] & 0xFF);
long time = System.currentTimeMillis();
Sensor.SensorData.Builder power =
Sensor.SensorData.Builder power =
Sensor.SensorData.newBuilder()
.setValue(powerVal)
.setState(Sensor.SensorState.SENDING);
/*
* Although speed is available from the SRM Bridge, MyTracks doesn't use the value, and
* computes speed from the GPS location data.
@@ -171,12 +171,12 @@ public class AntSrmBridgeSensorManager extends AntSensorManager {
// Sensor.SensorData.Builder speed = Sensor.SensorData.newBuilder().setValue(speedVal).setState(
// Sensor.SensorState.SENDING);
Sensor.SensorData.Builder cadence =
Sensor.SensorData.Builder cadence =
Sensor.SensorData.newBuilder()
.setValue(cadenceVal)
.setState(Sensor.SensorState.SENDING);
Sensor.SensorData.Builder bpm =
Sensor.SensorData.Builder bpm =
Sensor.SensorData.newBuilder()
.setValue(bpmVal)
.setState(Sensor.SensorState.SENDING);
@@ -189,7 +189,7 @@ public class AntSrmBridgeSensorManager extends AntSensorManager {
.setHeartRate(bpm)
.build();
}
void handleChannelId(byte[] rawMessage) {
AntChannelIdMessage message = new AntChannelIdMessage(rawMessage);
deviceNumber = message.getDeviceNumber();
@@ -203,12 +203,12 @@ public class AntSrmBridgeSensorManager extends AntSensorManager {
}
private void handleMessageResponse(byte[] rawMessage) {
AntChannelResponseMessage message =
AntChannelResponseMessage message =
new AntChannelResponseMessage(rawMessage);
if (!SystemUtils.isRelease(context)) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Received ANT Response: " + AntUtils.antMessageToString(message.getMessageId()) +
"(" + message.getMessageId() + ")" +
", Code: " + AntUtils.antEventToStr(message.getMessageCode()) +
", Code: " + AntUtils.antEventToStr(message.getMessageCode()) +
"(" + message.getMessageCode() + ")");
}
switch (message.getMessageId()) {
@@ -18,6 +18,7 @@ package com.google.android.apps.mytracks.services.tasks;
import static com.google.android.apps.mytracks.Constants.TAG;
import android.annotation.TargetApi;
import android.content.Context;
import android.media.AudioManager;
import android.speech.tts.TextToSpeech;
@@ -28,12 +29,14 @@ import java.util.HashMap;
/**
* This class will periodically announce the user's trip statistics for Froyo and future handsets.
* This class will request and release audio focus.
* This class will periodically announce the user's trip statistics. This class
* will request and release audio focus. <br>
* For API Level 8 or higher.
*
* @author Sandor Dornbush
*/
public class FroyoStatusAnnouncerTask extends StatusAnnouncerTask {
@TargetApi(8)
public class Api8StatusAnnouncerTask extends StatusAnnouncerTask {
private final static HashMap<String, String> SPEECH_PARAMS = new HashMap<String, String>();
static {
SPEECH_PARAMS.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "not_used");
@@ -51,7 +54,7 @@ public class FroyoStatusAnnouncerTask extends StatusAnnouncerTask {
}
};
public FroyoStatusAnnouncerTask(Context context) {
public Api8StatusAnnouncerTask(Context context) {
super(context);
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
@@ -18,6 +18,7 @@ package com.google.android.apps.mytracks.util;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.services.sensors.BluetoothConnectionManager;
import android.annotation.TargetApi;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.util.Log;
@@ -29,6 +30,7 @@ import java.io.IOException;
*
* @author Jimmy Shih
*/
@TargetApi(10)
public class Api10Adapter extends Api9Adapter {
@Override
@@ -0,0 +1,52 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.util;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.SearchManager;
import android.content.Context;
import android.view.MenuItem;
import android.view.Window;
import android.widget.SearchView;
/**
* API level 11 specific implementation of the {@link ApiAdapter}.
*
* @author Jimmy Shih
*/
@TargetApi(11)
public class Api11Adapter extends Api10Adapter {
@Override
public void showActionBar(Activity activity) {
activity.requestWindowFeature(Window.FEATURE_ACTION_BAR);
}
@Override
public void configureSearchWidget(Activity activity, MenuItem menuItem) {
SearchManager searchManager = (SearchManager) activity.getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) menuItem.getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(activity.getComponentName()));
}
@Override
public boolean handleSearchMenuSelection(Activity activity) {
// Returns false to allow the platform to expand the search widget.
return false;
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.util;
import android.annotation.TargetApi;
import android.view.MenuItem;
/**
* API level 14 specific implementation of the {@link ApiAdapter}.
*
* @author Jimmy Shih
*/
@TargetApi(14)
public class Api14Adapter extends Api11Adapter {
@Override
public boolean handleSearchKey(MenuItem menuItem) {
menuItem.expandActionView();
return true;
}
}
@@ -23,12 +23,15 @@ 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.Activity;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.util.Log;
import android.view.MenuItem;
import android.view.Window;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
@@ -50,7 +53,7 @@ public class Api7Adapter implements ApiAdapter {
public BackupPreferencesListener getBackupPreferencesListener(Context context) {
return new BackupPreferencesListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
// Do nothing
}
};
@@ -65,7 +68,7 @@ public class Api7Adapter implements ApiAdapter {
public void enableStrictMode() {
// Not supported
}
@Override
public byte[] copyByteArray(byte[] input, int start, int end) {
int length = end - start;
@@ -73,7 +76,7 @@ public class Api7Adapter implements ApiAdapter {
System.arraycopy(input, start, output, 0, length);
return output;
}
@Override
public HttpTransport getHttpTransport() {
return new ApacheHttpTransport();
@@ -99,4 +102,28 @@ public class Api7Adapter implements ApiAdapter {
}
return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.SPP_UUID);
}
@Override
public void showActionBar(Activity activity) {
// Action bar not available, just hide the title and let actions be shown
// via the regular menu.
activity.requestWindowFeature(Window.FEATURE_NO_TITLE);
}
@Override
public void configureSearchWidget(Activity activity, MenuItem menuItem) {
// Do nothing
}
@Override
public boolean handleSearchMenuSelection(Activity activity) {
activity.onSearchRequested();
return true;
}
@Override
public boolean handleSearchKey(MenuItem menuItem) {
// Return false and allow the framework to handle the search key.
return false;
}
}
@@ -16,8 +16,8 @@
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.io.backup.Api8BackupPreferencesListener;
import com.google.android.apps.mytracks.services.tasks.Api8StatusAnnouncerTask;
import com.google.android.apps.mytracks.services.tasks.PeriodicTask;
import android.content.Context;
@@ -31,11 +31,11 @@ public class Api8Adapter extends Api7Adapter {
@Override
public PeriodicTask getStatusAnnouncerTask(Context context) {
return new FroyoStatusAnnouncerTask(context);
return new Api8StatusAnnouncerTask(context);
}
@Override
public BackupPreferencesListener getBackupPreferencesListener(Context context) {
return new BackupPreferencesListenerImpl(context);
return new Api8BackupPreferencesListener(context);
}
}
@@ -19,6 +19,7 @@ import com.google.android.apps.mytracks.Constants;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import android.annotation.TargetApi;
import android.content.SharedPreferences.Editor;
import android.os.StrictMode;
import android.util.Log;
@@ -30,6 +31,7 @@ import java.util.Arrays;
*
* @author Rodrigo Damazio
*/
@TargetApi(9)
public class Api9Adapter extends Api8Adapter {
@Override
@@ -19,10 +19,12 @@ 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.Activity;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.content.SharedPreferences;
import android.view.MenuItem;
import java.io.IOException;
@@ -98,4 +100,42 @@ public interface ApiAdapter {
* @param bluetoothDevice
*/
public BluetoothSocket getBluetoothSocket(BluetoothDevice bluetoothDevice) throws IOException;
/**
* Shows the action bar, if the current system supports it.
* If it's not supported, the title bar will be kept hidden.
*
* Due to changes in API level 11.
*
* @param activity the current activity
*/
public void showActionBar(Activity activity);
/**
* Configures the search widget.
*
* Due to changes in API level 11.
*
* @param activity the activity
* @param menuItem the search menu item
*/
public void configureSearchWidget(Activity activity, MenuItem menuItem);
/**
* Handles the search menu selection. Returns true if handled.
*
* Due to changes in API level 11.
*
* @param activity the activity
*/
public boolean handleSearchMenuSelection(Activity activity);
/**
* Handles the search key press. Returns true if handled.
*
* Due to changes in API level 14.
*
* @param menu the search menu
*/
public boolean handleSearchKey(MenuItem menu);
}
@@ -31,7 +31,13 @@ public class ApiAdapterFactory {
*/
public static ApiAdapter getApiAdapter() {
if (apiAdapter == null) {
if (Build.VERSION.SDK_INT >= 10) {
if (Build.VERSION.SDK_INT >= 14) {
apiAdapter = new Api14Adapter();
return apiAdapter;
} else if (Build.VERSION.SDK_INT >= 11) {
apiAdapter = new Api11Adapter();
return apiAdapter;
} else if (Build.VERSION.SDK_INT >= 10) {
apiAdapter = new Api10Adapter();
return apiAdapter;
} else if (Build.VERSION.SDK_INT >= 9) {
@@ -0,0 +1,47 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.util;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
/**
* Utilities for checking units.
*
* @author Jimmy Shih
*/
public class CheckUnitsUtils {
private static final String CHECK_UNITS_PREFERENCE_FILE = "checkunits";
private static final String CHECK_UNITS_PREFERENCE_KEY = "checkunits.checked";
private CheckUnitsUtils() {}
public static boolean getCheckUnitsValue(Context context) {
SharedPreferences sharedPreferences = context.getSharedPreferences(
CHECK_UNITS_PREFERENCE_FILE, Context.MODE_PRIVATE);
return sharedPreferences.getBoolean(CHECK_UNITS_PREFERENCE_KEY, false);
}
public static void setCheckUnitsValue(Context context) {
SharedPreferences sharedPreferences = context.getSharedPreferences(
CHECK_UNITS_PREFERENCE_FILE, Context.MODE_PRIVATE);
Editor editor = sharedPreferences.edit().putBoolean(CHECK_UNITS_PREFERENCE_KEY, true);
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
}
}
@@ -20,34 +20,83 @@ import com.google.android.maps.mytracks.R;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface;
/**
* Utilities for creating dialogs.
*
*
* @author Jimmy Shih
*/
public class DialogUtils {
private DialogUtils() {}
/**
* Creates a confirmation dialog.
*
*
* @param context the context
* @param messageId the id of the confirmation message
* @param onClickListener the listener to invoke when the users clicks OK
* @param messageId the confirmation message id
* @param onClickListener the listener to invoke when the user clicks OK
*/
public static Dialog createConfirmationDialog(
Context context, int messageId, OnClickListener onClickListener) {
Context context, int messageId, DialogInterface.OnClickListener onClickListener) {
return new AlertDialog.Builder(context)
.setCancelable(true)
.setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(messageId)
.setMessage(context.getString(messageId))
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, onClickListener)
.setTitle(R.string.generic_confirm_title)
.create();
}
/**
* Creates a spinner progress dialog.
*
* @param context the context
* @param messageId the progress message id
* @param onCancelListener the cancel listener
*/
public static ProgressDialog createSpinnerProgressDialog(
Context context, int messageId, DialogInterface.OnCancelListener onCancelListener) {
return createProgressDialog(true, context, messageId, onCancelListener);
}
/**
* Creates a horizontal progress dialog.
*
* @param context the context
* @param messageId the progress message id
* @param onCancelListener the cancel listener
* @param formatArgs the format arguments for the messageId
*/
public static ProgressDialog createHorizontalProgressDialog(Context context, int messageId,
DialogInterface.OnCancelListener onCancelListener, Object... formatArgs) {
return createProgressDialog(false, context, messageId, onCancelListener, formatArgs);
}
/**
* Creates a progress dialog.
*
* @param spinner true to use the spinner style
* @param context the context
* @param messageId the progress message id
* @param onCancelListener the cancel listener
* @param formatArgs the format arguments for the message id
*/
private static ProgressDialog createProgressDialog(boolean spinner, Context context,
int messageId, DialogInterface.OnCancelListener onCancelListener, Object... formatArgs) {
ProgressDialog progressDialog = new ProgressDialog(context);
progressDialog.setCancelable(true);
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
progressDialog.setIndeterminate(true);
progressDialog.setMessage(context.getString(messageId, formatArgs));
progressDialog.setOnCancelListener(onCancelListener);
progressDialog.setProgressStyle(spinner ? ProgressDialog.STYLE_SPINNER
: ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setTitle(R.string.generic_progress_title);
return progressDialog;
}
}
@@ -25,53 +25,42 @@ import java.util.Calendar;
/**
* Utilities for EULA.
*
* @author Jimmy Shih
*/
public class EulaUtils {
private static final String EULA_PREFERENCE_FILE = "eula";
// Accepting Google mobile terms of service
private static final String EULA_PREFERENCE_KEY = "eula.google_mobile_tos_accepted";
// Google's mobile page
private static final String HOST_NAME = "m.google.com";
private EulaUtils() {}
public static boolean getEulaValue(Context context) {
SharedPreferences preferences = context.getSharedPreferences(
SharedPreferences sharedPreferences = context.getSharedPreferences(
EULA_PREFERENCE_FILE, Context.MODE_PRIVATE);
return preferences.getBoolean(EULA_PREFERENCE_KEY, false);
return sharedPreferences.getBoolean(EULA_PREFERENCE_KEY, false);
}
public static void setEulaValue(Context context) {
SharedPreferences preferences = context.getSharedPreferences(
SharedPreferences sharedPreferences = context.getSharedPreferences(
EULA_PREFERENCE_FILE, Context.MODE_PRIVATE);
Editor editor = preferences.edit();
Editor editor = sharedPreferences.edit();
editor.putBoolean(EULA_PREFERENCE_KEY, true);
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
}
public static String getEulaMessage(Context context) {
String item1 = String.format(context.getString(R.string.eula_message_item1), HOST_NAME,
HOST_NAME, HOST_NAME, HOST_NAME, HOST_NAME);
String item3 = String.format(context.getString(R.string.eula_message_item3), HOST_NAME);
String footer = String.format(context.getString(R.string.eula_message_footer), HOST_NAME);
String copyright = "©" + Calendar.getInstance().get(Calendar.YEAR);
return context.getString(R.string.eula_message_date)
return context.getString(R.string.eula_date)
+ "\n\n"
+ context.getString(R.string.eula_message_header)
+ "\n\n"
+ context.getString(R.string.eula_message_body)
+ "\n\n"
+ "1. " + item1
+ "\n\n"
+ "2. " + context.getString(R.string.eula_message_item2)
+ "\n\n"
+ "3. " + item3
+ "\n\n"
+ "4. " + context.getString(R.string.eula_message_item4)
+ "\n\n"
+ footer
+ "\n\n"
+ copyright;
+ context.getString(R.string.eula_body, HOST_NAME)
+ "\n\n"
+ context.getString(R.string.eula_footer, HOST_NAME)
+ "\n\n"
+ "©" + Calendar.getInstance().get(Calendar.YEAR);
}
}
@@ -29,20 +29,46 @@ import java.io.File;
*/
public class FileUtils {
private FileUtils() {}
/**
* The maximum FAT32 path length. See the FAT32 spec at
* http://msdn.microsoft.com/en-us/windows/hardware/gg463080
*/
@VisibleForTesting
static final int MAX_FAT32_PATH_LENGTH = 260;
/**
* Returns whether the SD card is available.
*/
public static boolean isSdCardAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
/**
* Ensures the given directory exists by creating it and its parents if
* necessary.
*
* @return whether the directory exists (either already existed or was
* successfully created)
*/
public static boolean ensureDirectoryExists(File dir) {
if (dir.exists() && dir.isDirectory()) {
return true;
}
if (dir.mkdirs()) {
return true;
}
return false;
}
/**
* Builds a path inside the My Tracks directory in the SD card.
*
* @param components the path components inside the mytracks directory
* @return the full path to the destination
*/
public String buildExternalDirectoryPath(String... components) {
public static String buildExternalDirectoryPath(String... components) {
StringBuilder dirNameBuilder = new StringBuilder();
dirNameBuilder.append(Environment.getExternalStorageDirectory());
dirNameBuilder.append(File.separatorChar);
@@ -54,33 +80,6 @@ public class FileUtils {
return dirNameBuilder.toString();
}
/**
* Returns whether the SD card is available.
*/
public boolean isSdCardAvailable() {
return Environment.MEDIA_MOUNTED.equals(
Environment.getExternalStorageState());
}
/**
* Ensures the given directory exists by creating it and its parents if
* necessary.
*
* @return whether the directory exists (either already existed or was
* successfully created)
*/
public boolean ensureDirectoryExists(File dir) {
if (dir.exists() && dir.isDirectory()) {
return true;
}
if (dir.mkdirs()) {
return true;
}
return false;
}
/**
* Builds a filename with the given base name (prefix) and the given
* extension, possibly adding a suffix to ensure the file doesn't exist.
@@ -90,8 +89,8 @@ public class FileUtils {
* @param extension the file's extension
* @return the complete file name, without the directory
*/
public synchronized String buildUniqueFileName(File directory,
String fileBaseName, String extension) {
public static synchronized String buildUniqueFileName(
File directory, String fileBaseName, String extension) {
return buildUniqueFileName(directory, fileBaseName, extension, 0);
}
@@ -105,7 +104,8 @@ public class FileUtils {
* @param suffix the first numeric suffix to try to use, or 0 for none
* @return the complete filename, without the directory
*/
private String buildUniqueFileName(File directory, String base, String extension, int suffix) {
private static String buildUniqueFileName(
File directory, String base, String extension, int suffix) {
String suffixName = "";
if (suffix > 0) {
suffixName += "(" + Integer.toString(suffix) + ")";
@@ -115,8 +115,8 @@ public class FileUtils {
String baseName = sanitizeFileName(base);
baseName = truncateFileName(directory, baseName, suffixName);
String fullName = baseName + suffixName;
if (!fileExists(directory, fullName)) {
if (!new File(directory, fullName).exists()) {
return fullName;
}
return buildUniqueFileName(directory, base, extension, suffix + 1);
@@ -131,7 +131,7 @@ public class FileUtils {
* @param name name
*/
@VisibleForTesting
String sanitizeFileName(String name) {
static String sanitizeFileName(String name) {
StringBuffer buffer = new StringBuffer(name.length());
for (int i = 0; i < name.length(); i++) {
int codePoint = name.codePointAt(i);
@@ -145,13 +145,13 @@ public class FileUtils {
String result = buffer.toString();
return result.replaceAll("_+", "_");
}
/**
* Returns true if it is a special FAT32 character.
*
*
* @param character the character
*/
private boolean isSpecialFat32(char character) {
private static boolean isSpecialFat32(char character) {
switch (character) {
case '$':
case '%':
@@ -191,8 +191,8 @@ public class FileUtils {
* @param suffix suffix
*/
@VisibleForTesting
String truncateFileName(File directory, String name, String suffix) {
// 1 at the end accounts for the FAT32 filename trailing NUL character
static String truncateFileName(File directory, String name, String suffix) {
// 1 at the end accounts for the FAT32 filename trailing NUL character
int requiredLength = directory.getPath().length() + suffix.length() + 1;
if (name.length() + requiredLength > MAX_FAT32_PATH_LENGTH) {
int limit = MAX_FAT32_PATH_LENGTH - requiredLength;
@@ -201,13 +201,4 @@ public class FileUtils {
return name;
}
}
/**
* Checks whether a file with the given name exists in the given directory.
* This is isolated so it can be overridden in tests.
*/
protected boolean fileExists(File directory, String fullName) {
File file = new File(directory, fullName);
return file.exists();
}
}
@@ -16,21 +16,19 @@
package com.google.android.apps.mytracks.util;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.io.file.SaveActivity;
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
import com.google.android.maps.mytracks.R;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Parcelable;
import java.util.List;
@@ -80,12 +78,10 @@ public class PlayTrackUtils {
public static void playTrack(Context context, long trackId) {
AnalyticsUtils.sendPageViews(context, "/action/play");
Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId);
Intent intent = new Intent(context, SaveActivity.class)
.putExtra(SaveActivity.EXTRA_FILE_FORMAT, TrackFileFormat.KML.ordinal())
.putExtra(SaveActivity.EXTRA_PLAY_FILE, true)
.setAction(context.getString(R.string.track_action_save))
.setDataAndType(uri, TracksColumns.CONTENT_ITEMTYPE);
.putExtra(SaveActivity.EXTRA_TRACK_ID, trackId)
.putExtra(SaveActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) TrackFileFormat.KML)
.putExtra(SaveActivity.EXTRA_PLAY_TRACK, true);
context.startActivity(intent);
}
@@ -97,9 +93,9 @@ public class PlayTrackUtils {
public static Dialog createInstallEarthDialog(final Context context) {
return new AlertDialog.Builder(context)
.setCancelable(true)
.setMessage(R.string.track_list_play_install_earth_message)
.setMessage(R.string.track_detail_install_earth_message)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, new OnClickListener() {
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent();
@@ -1,12 +1,12 @@
/*
* Copyright 2011 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -22,7 +22,6 @@ import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
@@ -34,26 +33,6 @@ import android.util.Log;
*/
public class SystemUtils {
private static final int RELEASE_SIGNATURE_HASHCODE = -1855564782;
/**
* Returns whether or not this is a release build.
*/
public static boolean isRelease(Context context) {
try {
Signature [] sigs = context.getPackageManager().getPackageInfo(
context.getPackageName(), PackageManager.GET_SIGNATURES).signatures;
for (Signature sig : sigs) {
if (sig.hashCode() == RELEASE_SIGNATURE_HASHCODE) {
return true;
}
}
} catch (NameNotFoundException e) {
Log.e(Constants.TAG, "Unable to get signatures", e);
}
return false;
}
/**
* Get the My Tracks version from the manifest.
*
@@ -19,7 +19,7 @@ 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.TrackDetailActivity;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TracksColumns;
@@ -145,7 +145,10 @@ public class TrackWidgetProvider
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.track_widget);
// Make all of the stats open the mytracks activity.
Intent intent = new Intent(context, MyTracks.class);
Intent intent = new Intent(context, TrackDetailActivity.class);
if (track != null) {
intent.putExtra(TrackDetailActivity.TRACK_ID, track.getId());
}
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.appwidget_track_statistics, pendingIntent);