mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-04 02:13:07 +02:00
Merge with head.
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
@@ -15,20 +15,22 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.io.sendtogoogle.SendDialog;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager.BadTokenException;
|
||||
|
||||
/**
|
||||
* A class to handle all dialog related events for My Tracks.
|
||||
*
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class DialogManager {
|
||||
@@ -37,14 +39,12 @@ public class DialogManager {
|
||||
public static final int DIALOG_IMPORT_PROGRESS = 2;
|
||||
public static final int DIALOG_PROGRESS = 3;
|
||||
public static final int DIALOG_SEND_TO_GOOGLE = 4;
|
||||
public static final int DIALOG_SEND_TO_GOOGLE_RESULT = 5;
|
||||
public static final int DIALOG_WRITE_PROGRESS = 6;
|
||||
public static final int DIALOG_WRITE_PROGRESS = 5;
|
||||
|
||||
private ProgressDialog progressDialog;
|
||||
private ProgressDialog importProgressDialog;
|
||||
private ProgressDialog writeProgressDialog;
|
||||
private SendToGoogleDialog sendToGoogleDialog;
|
||||
private AlertDialog sendToGoogleResultDialog;
|
||||
private SendDialog sendToGoogleDialog;
|
||||
private ChartSettingsDialog chartSettingsDialog;
|
||||
|
||||
private MyTracks activity;
|
||||
@@ -52,7 +52,7 @@ public class DialogManager {
|
||||
public DialogManager(MyTracks activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
|
||||
protected Dialog onCreateDialog(int id, Bundle args) {
|
||||
switch (id) {
|
||||
case DIALOG_CHART_SETTINGS:
|
||||
@@ -77,26 +77,8 @@ public class DialogManager {
|
||||
progressDialog.setProgress(10);
|
||||
return progressDialog;
|
||||
case DIALOG_SEND_TO_GOOGLE:
|
||||
sendToGoogleDialog = new SendToGoogleDialog(activity);
|
||||
sendToGoogleDialog = new SendDialog(activity);
|
||||
return sendToGoogleDialog;
|
||||
case DIALOG_SEND_TO_GOOGLE_RESULT:
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setIcon(android.R.drawable.ic_dialog_info);
|
||||
builder.setTitle("Title");
|
||||
builder.setMessage("Message");
|
||||
builder.setPositiveButton(activity.getString(R.string.ok), null);
|
||||
builder.setNeutralButton(activity.getString(R.string.share_track),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
SendToGoogleDialog sendToGoogleDialog = getSendToGoogleDialog();
|
||||
final boolean sentToMyMaps = sendToGoogleDialog.getSendToMyMaps();
|
||||
final boolean sentToFusionTables = sendToGoogleDialog.getSendToFusionTables();
|
||||
activity.shareLinkToMap(sentToMyMaps, sentToFusionTables);
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
sendToGoogleResultDialog = builder.create();
|
||||
return sendToGoogleResultDialog;
|
||||
case DIALOG_WRITE_PROGRESS:
|
||||
writeProgressDialog = new ProgressDialog(activity);
|
||||
writeProgressDialog.setIcon(android.R.drawable.ic_dialog_info);
|
||||
@@ -115,26 +97,8 @@ public class DialogManager {
|
||||
case DIALOG_SEND_TO_GOOGLE:
|
||||
activity.resetSendToGoogleStatus();
|
||||
break;
|
||||
case DIALOG_SEND_TO_GOOGLE_RESULT:
|
||||
boolean success = activity.getSendToGoogleSuccess();
|
||||
sendToGoogleResultDialog.setTitle(
|
||||
success ? R.string.success : R.string.error);
|
||||
sendToGoogleResultDialog.setIcon(success
|
||||
? android.R.drawable.ic_dialog_info
|
||||
: android.R.drawable.ic_dialog_alert);
|
||||
sendToGoogleResultDialog.setMessage(activity.getSendToGoogleResultMessage());
|
||||
|
||||
boolean canShare =
|
||||
activity.getSendToFusionTablesTableId() != null &&
|
||||
activity.getSendToMyMapsMapId() != null;
|
||||
View share =
|
||||
sendToGoogleResultDialog.findViewById(android.R.id.button3);
|
||||
if (share != null) {
|
||||
share.setVisibility(canShare ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
break;
|
||||
case DIALOG_CHART_SETTINGS:
|
||||
Log.d(Constants.TAG, "MyTracks.onPrepare chart dialog");
|
||||
Log.d(TAG, "MyTracks.onPrepare chart dialog");
|
||||
chartSettingsDialog.setup(activity.getChartActivity());
|
||||
break;
|
||||
}
|
||||
@@ -151,7 +115,7 @@ public class DialogManager {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void setProgressValue(final int percent) {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
@@ -167,7 +131,7 @@ public class DialogManager {
|
||||
/**
|
||||
* @return the sendToGoogleDialog
|
||||
*/
|
||||
public SendToGoogleDialog getSendToGoogleDialog() {
|
||||
public SendDialog getSendToGoogleDialog() {
|
||||
return sendToGoogleDialog;
|
||||
}
|
||||
|
||||
@@ -196,9 +160,9 @@ public class DialogManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Just like showDialog, but will catch a BadTokenException that sometimes
|
||||
* (very rarely) gets thrown. This might happen if the user hits the "back"
|
||||
* button immediately after sending tracks to google.
|
||||
* Just like showDialog, but will catch a {@link BadTokenException} that
|
||||
* sometimes (very rarely) gets thrown. This might happen if the user hits
|
||||
* the "back" button immediately after sending tracks to google.
|
||||
*
|
||||
* @param id the dialog id
|
||||
*/
|
||||
@@ -208,11 +172,27 @@ public class DialogManager {
|
||||
try {
|
||||
activity.showDialog(id);
|
||||
} catch (BadTokenException e) {
|
||||
Log.w(Constants.TAG,
|
||||
"Could not display dialog with id " + id, e);
|
||||
Log.w(TAG, "Could not display dialog with id " + id, e);
|
||||
} catch (IllegalStateException e) {
|
||||
Log.w(Constants.TAG,
|
||||
"Could not display dialog with id " + id, e);
|
||||
Log.w(TAG, "Could not display dialog with id " + id, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The equivalent of {@link #showDialogSafely(int)}, but for a specific
|
||||
* dialog instance.
|
||||
*/
|
||||
public static void showDialogSafely(Activity activity, final Dialog dialog) {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
dialog.show();
|
||||
} catch (BadTokenException e) {
|
||||
Log.w(TAG, "Could not display dialog", e);
|
||||
} catch (IllegalStateException e) {
|
||||
Log.w(TAG, "Could not display dialog", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -18,9 +18,8 @@ package com.google.android.apps.mytracks;
|
||||
import com.google.android.accounts.Account;
|
||||
import com.google.android.apps.mytracks.io.AuthManager;
|
||||
import com.google.android.apps.mytracks.io.AuthManagerFactory;
|
||||
import com.google.android.apps.mytracks.io.MyMapsFactory;
|
||||
import com.google.android.apps.mytracks.io.mymaps.MapsFacade;
|
||||
import com.google.android.apps.mytracks.io.mymaps.MapsService;
|
||||
import com.google.android.apps.mytracks.io.mymaps.MyMapsConstants;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Activity;
|
||||
@@ -84,7 +83,7 @@ public class MyMapsList extends Activity implements MapsFacade.MapsListCallback
|
||||
super.onCreate(icicle);
|
||||
|
||||
auth = AuthManagerFactory.getAuthManager(this, GET_LOGIN, null, true,
|
||||
MapsService.getServiceName());
|
||||
MyMapsConstants.SERVICE_NAME);
|
||||
|
||||
setContentView(R.layout.list);
|
||||
|
||||
@@ -125,7 +124,7 @@ public class MyMapsList extends Activity implements MapsFacade.MapsListCallback
|
||||
auth.doLogin(new Runnable() {
|
||||
public void run() {
|
||||
// Runs in UI thread.
|
||||
mapsClient = MyMapsFactory.newMapsClient(MyMapsList.this, auth);
|
||||
mapsClient = new MapsFacade(MyMapsList.this, auth);
|
||||
|
||||
startLookup();
|
||||
}
|
||||
@@ -200,7 +199,7 @@ public class MyMapsList extends Activity implements MapsFacade.MapsListCallback
|
||||
shareIntent.putExtra(Intent.EXTRA_TEXT, String.format(
|
||||
getText(R.string.share_map_body_format).toString(),
|
||||
listItem[1],
|
||||
MapsService.buildMapUrl(listItem[0])));
|
||||
MapsFacade.buildMapUrl(listItem[0])));
|
||||
startActivity(Intent.createChooser(shareIntent,
|
||||
getText(R.string.share_map).toString()));
|
||||
return true;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
import static com.google.android.apps.mytracks.DialogManager.DIALOG_IMPORT_PROGRESS;
|
||||
import static com.google.android.apps.mytracks.DialogManager.DIALOG_PROGRESS;
|
||||
import static com.google.android.apps.mytracks.DialogManager.DIALOG_SEND_TO_GOOGLE;
|
||||
@@ -35,7 +36,12 @@ import com.google.android.apps.mytracks.io.TempFileCleaner;
|
||||
import com.google.android.apps.mytracks.io.TrackWriter;
|
||||
import com.google.android.apps.mytracks.io.TrackWriterFactory;
|
||||
import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
|
||||
import com.google.android.apps.mytracks.io.mymaps.MapsService;
|
||||
import com.google.android.apps.mytracks.io.mymaps.MapsFacade;
|
||||
import com.google.android.apps.mytracks.io.mymaps.MyMapsConstants;
|
||||
import com.google.android.apps.mytracks.io.sendtogoogle.ResultDialogFactory;
|
||||
import com.google.android.apps.mytracks.io.sendtogoogle.SendDialog;
|
||||
import com.google.android.apps.mytracks.io.sendtogoogle.SendResult;
|
||||
import com.google.android.apps.mytracks.io.sendtogoogle.SendType;
|
||||
import com.google.android.apps.mytracks.services.ITrackRecordingService;
|
||||
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
|
||||
import com.google.android.apps.mytracks.services.TrackRecordingService;
|
||||
@@ -81,7 +87,9 @@ import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
@@ -188,7 +196,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName className) {
|
||||
Log.d(Constants.TAG, "MyTracks: Service now disconnected.");
|
||||
Log.d(TAG, "MyTracks: Service now disconnected.");
|
||||
trackRecordingService = null;
|
||||
}
|
||||
};
|
||||
@@ -233,7 +241,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
// TODO: We catch Exception, because after eliminating the service process
|
||||
// all exceptions it may throw are no longer wrapped in a RemoteException.
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "MyTracks: Remote exception.", e);
|
||||
Log.e(TAG, "MyTracks: Remote exception.", e);
|
||||
|
||||
// Fall back to alternative check method.
|
||||
return isRecordingBasedOnSharedPreferences();
|
||||
@@ -253,7 +261,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
Log.d(Constants.TAG, "MyTracks.onCreate");
|
||||
Log.d(TAG, "MyTracks.onCreate");
|
||||
super.onCreate(savedInstanceState);
|
||||
instance = this;
|
||||
ApiFeatures apiFeatures = ApiFeatures.getInstance();
|
||||
@@ -310,7 +318,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
recordingTrackId = sharedPreferences.getLong(
|
||||
getString(R.string.recording_track_key), -1);
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
|
||||
Log.d(Constants.TAG, "recordingTrackId: " + recordingTrackId
|
||||
Log.d(TAG, "recordingTrackId: " + recordingTrackId
|
||||
+ ", selectedTrackId: " + selectedTrackId);
|
||||
if (recordingTrackId > 0) {
|
||||
Intent startIntent = new Intent(this, TrackRecordingService.class);
|
||||
@@ -329,26 +337,22 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
// Do nothing.
|
||||
} else if (action.equals(Intent.ACTION_VIEW)) {
|
||||
if (intent.getScheme() != null && intent.getScheme().equals("file")) {
|
||||
Log.w(Constants.TAG,
|
||||
"Received a VIEW intent with file scheme. Importing.");
|
||||
Log.w(TAG, "Received a VIEW intent with file scheme. Importing.");
|
||||
importGpxFile(intent.getData().getPath());
|
||||
} else {
|
||||
Log.w(Constants.TAG,
|
||||
"Received a VIEW intent with unsupported scheme "
|
||||
+ intent.getScheme());
|
||||
Log.w(TAG, "Received a VIEW intent with unsupported scheme " + intent.getScheme());
|
||||
}
|
||||
} else {
|
||||
Log.w(Constants.TAG,
|
||||
"Received an intent with unsupported action " + action);
|
||||
Log.w(TAG, "Received an intent with unsupported action " + action);
|
||||
}
|
||||
} else {
|
||||
Log.d(Constants.TAG, "Received an intent with no action.");
|
||||
Log.d(TAG, "Received an intent with no action.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
Log.d(Constants.TAG, "MyTracks.onDestroy");
|
||||
Log.d(TAG, "MyTracks.onDestroy");
|
||||
tryUnbindTrackRecordingService();
|
||||
super.onDestroy();
|
||||
}
|
||||
@@ -357,7 +361,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
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(Constants.TAG, "MyTracks.onPause");
|
||||
Log.d(TAG, "MyTracks.onPause");
|
||||
tryUnbindTrackRecordingService();
|
||||
super.onPause();
|
||||
}
|
||||
@@ -366,14 +370,14 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
protected void onResume() {
|
||||
// Called when the current activity is being displayed or re-displayed
|
||||
// to the user.
|
||||
Log.d(Constants.TAG, "MyTracks.onResume");
|
||||
Log.d(TAG, "MyTracks.onResume");
|
||||
tryBindTrackRecordingService();
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
Log.d(Constants.TAG, "MyTracks.onStop");
|
||||
Log.d(TAG, "MyTracks.onStop");
|
||||
// Clean up any temporary track files.
|
||||
TempFileCleaner.clean();
|
||||
super.onStop();
|
||||
@@ -437,9 +441,9 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
try {
|
||||
insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "Cannot insert statistics marker.", e);
|
||||
Log.e(TAG, "Cannot insert statistics marker.", e);
|
||||
} catch (IllegalStateException e) {
|
||||
Log.e(Constants.TAG, "Cannot insert statistics marker.", e);
|
||||
Log.e(TAG, "Cannot insert statistics marker.", e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -600,7 +604,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
Track selectedTrack = providerUtils.getTrack(selectedTrackId);
|
||||
if (selectedTrack != null) {
|
||||
if (!TextUtils.isEmpty(selectedTrack.getMapId())) {
|
||||
shareLinkToMap(MapsService.buildMapUrl(selectedTrack.getMapId()));
|
||||
shareLinkToMap(MapsFacade.buildMapUrl(selectedTrack.getMapId()));
|
||||
} else if (!TextUtils.isEmpty(selectedTrack.getTableId())) {
|
||||
shareLinkToMap(getFusionTablesUrl(selectedTrackId));
|
||||
} else {
|
||||
@@ -640,8 +644,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
}
|
||||
|
||||
default: {
|
||||
Log.w(Constants.TAG,
|
||||
"Warning unhandled request code: " + requestCode);
|
||||
Log.w(TAG, "Warning unhandled request code: " + requestCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -695,19 +698,19 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
InputStream is = new FileInputStream(fileName);
|
||||
trackIdsImported = GpxImporter.importGPXFile(is, providerUtils);
|
||||
} catch (SAXException e) {
|
||||
Log.e(Constants.TAG, "Caught an unexpected exception.", e);
|
||||
Log.e(TAG, "Caught an unexpected exception.", e);
|
||||
message = R.string.error_generic;
|
||||
} catch (ParserConfigurationException e) {
|
||||
Log.e(Constants.TAG, "Caught an unexpected exception.", e);
|
||||
Log.e(TAG, "Caught an unexpected exception.", e);
|
||||
message = R.string.error_generic;
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Caught an unexpected exception.", e);
|
||||
Log.e(TAG, "Caught an unexpected exception.", e);
|
||||
message = R.string.error_unable_to_read_file;
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(Constants.TAG, "Caught an unexpected exception.", e);
|
||||
Log.e(TAG, "Caught an unexpected exception.", e);
|
||||
message = R.string.error_invalid_gpx_format;
|
||||
} catch (OutOfMemoryError e) {
|
||||
Log.e(Constants.TAG, "Caught an unexpected exception.", e);
|
||||
Log.e(TAG, "Caught an unexpected exception.", e);
|
||||
message = R.string.error_out_of_memory;
|
||||
}
|
||||
if (trackIdsImported != null && trackIdsImported.length > 0) {
|
||||
@@ -870,8 +873,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
final String service) {
|
||||
auth = authMap.get(service);
|
||||
if (auth == null) {
|
||||
Log.i(Constants.TAG,
|
||||
"Creating a new authentication for service: " + service);
|
||||
Log.i(TAG, "Creating a new authentication for service: " + service);
|
||||
auth = AuthManagerFactory.getAuthManager(this,
|
||||
Constants.GET_LOGIN,
|
||||
null,
|
||||
@@ -879,7 +881,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
service);
|
||||
authMap.put(service, auth);
|
||||
}
|
||||
Log.d(Constants.TAG, "Logging in to " + service + "...");
|
||||
Log.d(TAG, "Logging in to " + service + "...");
|
||||
if (AuthManagerFactory.useModernAuthManager()) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
@@ -906,7 +908,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
final String service, final Account account) {
|
||||
auth.doLogin(new Runnable() {
|
||||
public void run() {
|
||||
Log.d(Constants.TAG, "Loggin success for " + service + "!");
|
||||
Log.d(TAG, "Loggin success for " + service + "!");
|
||||
onActivityResult(requestCode, RESULT_OK, results);
|
||||
}
|
||||
}, account);
|
||||
@@ -926,7 +928,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
Toast.makeText(this,
|
||||
getString(R.string.error_unable_to_start_recording),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
Log.w(Constants.TAG, "Unable to start recording.", e);
|
||||
Log.w(TAG, "Unable to start recording.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -958,7 +960,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
// TODO: We catch Exception, because after eliminating the service process
|
||||
// all exceptions it may throw are no longer wrapped in a RemoteException.
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Unable to stop recording.", e);
|
||||
Log.e(TAG, "Unable to stop recording.", e);
|
||||
}
|
||||
Intent intent = new Intent(MyTracks.this, TrackDetails.class);
|
||||
intent.putExtra("trackid", currentTrackId);
|
||||
@@ -969,8 +971,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
try {
|
||||
stopService(new Intent(MyTracks.this, TrackRecordingService.class));
|
||||
} catch (SecurityException e) {
|
||||
Log.e(Constants.TAG,
|
||||
"Encountered a security exception when trying to stop service.", e);
|
||||
Log.e(TAG, "Encountered a security exception when trying to stop service.", e);
|
||||
}
|
||||
trackRecordingService = null;
|
||||
}
|
||||
@@ -983,7 +984,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
* TODO: Change this whole flow to an actual state machine.
|
||||
*/
|
||||
public void sendToGoogle() {
|
||||
SendToGoogleDialog sendToGoogleDialog =
|
||||
SendDialog sendToGoogleDialog =
|
||||
dialogManager.getSendToGoogleDialog();
|
||||
if (sendToGoogleDialog == null) {
|
||||
return;
|
||||
@@ -999,12 +1000,12 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
} else if (sendToGoogleDialog.getSendToDocs()) {
|
||||
authenticateToGoogleDocs();
|
||||
} else {
|
||||
Log.w(Constants.TAG, "Nowhere to upload to");
|
||||
Log.w(TAG, "Nowhere to upload to");
|
||||
onSendToGoogleDone();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendToGoogleMapsOrPickMap(SendToGoogleDialog sendToGoogleDialog) {
|
||||
private void sendToGoogleMapsOrPickMap(SendDialog sendToGoogleDialog) {
|
||||
if (!sendToGoogleDialog.getCreateNewMap()) {
|
||||
// Ask the user to choose a map to upload into
|
||||
Intent listIntent = new Intent(this, MyMapsList.class);
|
||||
@@ -1022,7 +1023,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
setProgressMessage(
|
||||
R.string.progress_message_authenticating_mymaps);
|
||||
authenticate(results, Constants.AUTHENTICATE_TO_MY_MAPS,
|
||||
MapsService.getServiceName());
|
||||
MyMapsConstants.SERVICE_NAME);
|
||||
// AUTHENTICATE_TO_MY_MAPS callback calls sendToGoogleMaps
|
||||
}
|
||||
|
||||
@@ -1042,7 +1043,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
} catch (RuntimeException e) {
|
||||
// If that fails whatever reasons we'll just log an error, but
|
||||
// continue.
|
||||
Log.w(Constants.TAG, "Updating map id failed.", e);
|
||||
Log.w(TAG, "Updating map id failed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1059,7 +1060,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
}
|
||||
|
||||
private void onSendToGoogleMapsDone() {
|
||||
SendToGoogleDialog sendToGoogleDialog = dialogManager.getSendToGoogleDialog();
|
||||
SendDialog sendToGoogleDialog = dialogManager.getSendToGoogleDialog();
|
||||
if (sendToGoogleDialog.getSendToFusionTables()) {
|
||||
authenticateToFusionTables(null);
|
||||
} else if (sendToGoogleDialog.getSendToDocs()) {
|
||||
@@ -1096,7 +1097,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
} catch (RuntimeException e) {
|
||||
// If that fails whatever reasons we'll just log an error, but
|
||||
// continue.
|
||||
Log.w(Constants.TAG, "Updating table id failed.", e);
|
||||
Log.w(TAG, "Updating table id failed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1114,7 +1115,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
}
|
||||
|
||||
private void onSendToFusionTablesDone() {
|
||||
SendToGoogleDialog sendToGoogleDialog = dialogManager.getSendToGoogleDialog();
|
||||
SendDialog sendToGoogleDialog = dialogManager.getSendToGoogleDialog();
|
||||
if (sendToGoogleDialog.getSendToDocs()) {
|
||||
authenticateToGoogleDocs();
|
||||
} else {
|
||||
@@ -1143,7 +1144,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
}
|
||||
|
||||
private void sendToGoogleDocs(final long trackId) {
|
||||
Log.d(Constants.TAG, "Sending to Docs....");
|
||||
Log.d(TAG, "Sending to Docs....");
|
||||
setProgressValue(50);
|
||||
setProgressMessage(R.string.progress_message_sending_docs);
|
||||
final SendToDocs sender = new SendToDocs(this,
|
||||
@@ -1169,34 +1170,57 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
}
|
||||
|
||||
private void onSendToGoogleDone() {
|
||||
SendToGoogleDialog sendToGoogleDialog = dialogManager.getSendToGoogleDialog();
|
||||
SendDialog sendToGoogleDialog = dialogManager.getSendToGoogleDialog();
|
||||
final boolean sentToMyMaps = sendToGoogleDialog.getSendToMyMaps();
|
||||
final boolean sentToFusionTables = sendToGoogleDialog.getSendToFusionTables();
|
||||
|
||||
dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (shareRequested) {
|
||||
Toast.makeText(MyTracks.this, getSendToGoogleResultMessage(), Toast.LENGTH_LONG)
|
||||
.show();
|
||||
|
||||
if (shareLinkToMap(sentToMyMaps, sentToFusionTables)) {
|
||||
return;
|
||||
}
|
||||
// We've finished sending the track to the user-selected services. Now
|
||||
// we tell them the results of the upload, and optionally share the track.
|
||||
// There are a few different paths through this code:
|
||||
//
|
||||
// 1. The user pre-requested a share (shareRequested == true). We're going
|
||||
// to display the result dialog *without* the share button (the share
|
||||
// listener will be null). The OK button listener will initiate the
|
||||
// share.
|
||||
//
|
||||
// 2. The user did not pre-request a share, and the set of services to
|
||||
// which we succeeded in uploading the track are compatible with
|
||||
// sharing. We'll display a share button (the share listener will be
|
||||
// non-null), and will share the link if the user clicks it.
|
||||
//
|
||||
// 3. The user did not pre-request a share, and the set of services to
|
||||
// which we succeeded in uploading the track are incompatible with
|
||||
// sharing. We won't display a share button.
|
||||
|
||||
final boolean canShare = getSendToFusionTablesTableId() != null
|
||||
|| getSendToMyMapsMapId() != null;
|
||||
|
||||
DialogInterface.OnClickListener doShareListener = null;
|
||||
if (canShare) {
|
||||
doShareListener = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
shareLinkToMap(sentToMyMaps, sentToFusionTables);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// If anything failed or sharing was not requested, show the dialog
|
||||
dialogManager.showDialogSafely(
|
||||
DialogManager.DIALOG_SEND_TO_GOOGLE_RESULT);
|
||||
}
|
||||
});
|
||||
DialogInterface.OnClickListener onOkListener = (canShare && shareRequested)
|
||||
? doShareListener : null;
|
||||
DialogInterface.OnClickListener onShareListener = (canShare && !shareRequested)
|
||||
? doShareListener : null;
|
||||
|
||||
AlertDialog sendToGoogleResultDialog = ResultDialogFactory.makeDialog(this,
|
||||
makeSendToGoogleResults(sendToGoogleDialog), onOkListener, onShareListener);
|
||||
DialogManager.showDialogSafely(this, sendToGoogleResultDialog);
|
||||
}
|
||||
|
||||
boolean shareLinkToMap(boolean sentToMyMaps, boolean sentToFusionTables) {
|
||||
String url = null;
|
||||
if (sentToMyMaps && sendToMyMapsSuccess) {
|
||||
// Prefer a link to My Maps
|
||||
url = MapsService.buildMapUrl(sendToMyMapsMapId);
|
||||
url = MapsFacade.buildMapUrl(sendToMyMapsMapId);
|
||||
} else if (sentToFusionTables && sendToFusionTablesSuccess) {
|
||||
// Otherwise try using the link to fusion tables
|
||||
url = getFusionTablesUrl(sendToTrackId);
|
||||
@@ -1215,9 +1239,29 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
return SendToFusionTables.getMapVisualizationUrl(track);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list of {@link SendResult} instances based on the set of
|
||||
* services selected in {@link SendDialog} and the results as known to
|
||||
* this class.
|
||||
*/
|
||||
private List<SendResult> makeSendToGoogleResults(SendDialog dialog) {
|
||||
List<SendResult> results = new ArrayList<SendResult>();
|
||||
if (dialog.getSendToMyMaps()) {
|
||||
results.add(new SendResult(SendType.MYMAPS, sendToMyMapsSuccess));
|
||||
}
|
||||
if (dialog.getSendToFusionTables()) {
|
||||
results.add(new SendResult(SendType.FUSION_TABLES, sendToFusionTablesSuccess));
|
||||
}
|
||||
if (dialog.getSendToDocs()) {
|
||||
results.add(new SendResult(SendType.DOCS, sendToDocsSuccess));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
String getSendToGoogleResultMessage() {
|
||||
StringBuilder message = new StringBuilder();
|
||||
SendToGoogleDialog sendToGoogleDialog =
|
||||
SendDialog sendToGoogleDialog =
|
||||
dialogManager.getSendToGoogleDialog();
|
||||
if (sendToGoogleDialog.getSendToMyMaps()) {
|
||||
message.append(sendToMyMapsMessage);
|
||||
@@ -1259,11 +1303,11 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
* Binds to track recording service if it is running.
|
||||
*/
|
||||
private void tryBindTrackRecordingService() {
|
||||
Log.d(Constants.TAG,
|
||||
Log.d(TAG,
|
||||
"MyTracks: Trying to bind to track recording service...");
|
||||
bindService(new Intent(this, TrackRecordingService.class),
|
||||
serviceConnection, 0);
|
||||
Log.d(Constants.TAG, "MyTracks: ...bind finished!");
|
||||
Log.d(TAG, "MyTracks: ...bind finished!");
|
||||
isBound = true;
|
||||
}
|
||||
|
||||
@@ -1273,14 +1317,12 @@ public class MyTracks extends TabActivity implements OnTouchListener,
|
||||
*/
|
||||
private void tryUnbindTrackRecordingService() {
|
||||
if (isBound) {
|
||||
Log.d(Constants.TAG,
|
||||
"MyTracks: Trying to unbind from track recording service...");
|
||||
Log.d(TAG, "MyTracks: Trying to unbind from track recording service...");
|
||||
try {
|
||||
unbindService(serviceConnection);
|
||||
Log.d(Constants.TAG, "MyTracks: ...unbind finished!");
|
||||
Log.d(TAG, "MyTracks: ...unbind finished!");
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.d(Constants.TAG,
|
||||
"MyTracks: Tried unbinding, but service was not registered.", e);
|
||||
Log.d(TAG, "MyTracks: Tried unbinding, but service was not registered.", e);
|
||||
}
|
||||
isBound = false;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
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.Track;
|
||||
import com.google.android.apps.mytracks.content.TrackPointsColumns;
|
||||
@@ -208,7 +210,7 @@ public class MyTracksMap extends MapActivity
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
Log.w(Constants.TAG, "Caught an unexpected exception.", e);
|
||||
Log.w(TAG, "Caught an unexpected exception.", e);
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
@@ -285,7 +287,7 @@ public class MyTracksMap extends MapActivity
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
Log.d(Constants.TAG, "MyTracksMap.onCreate");
|
||||
Log.d(TAG, "MyTracksMap.onCreate");
|
||||
super.onCreate(bundle);
|
||||
|
||||
// The volume we want to control is the Text-To-Speech volume
|
||||
@@ -335,7 +337,7 @@ public class MyTracksMap extends MapActivity
|
||||
observer = new ContentObserver(contentHandler) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
Log.d(Constants.TAG, "MyTracksMap: ContentObserver.onChange");
|
||||
Log.d(TAG, "MyTracksMap: ContentObserver.onChange");
|
||||
if (!isRecordingSelected()) {
|
||||
// No track, or one other than the recording track is selected,
|
||||
// don't bother.
|
||||
@@ -350,7 +352,7 @@ public class MyTracksMap extends MapActivity
|
||||
waypointObserver = new ContentObserver(contentHandler) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
Log.d(Constants.TAG,
|
||||
Log.d(TAG,
|
||||
"MyTracksMap: ContentObserver.onChange waypoints");
|
||||
if (!isATrackSelected()) {
|
||||
return;
|
||||
@@ -371,7 +373,7 @@ public class MyTracksMap extends MapActivity
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
Log.d(Constants.TAG, "MyTracksMap.onDestroy");
|
||||
Log.d(TAG, "MyTracksMap.onDestroy");
|
||||
|
||||
if (updateTrackThread != null) {
|
||||
ApiFeatures.getInstance().getApiPlatformAdapter().stopHandlerThread(
|
||||
@@ -407,7 +409,7 @@ public class MyTracksMap extends MapActivity
|
||||
protected void onStart() {
|
||||
// Called after onCreate or onStop.
|
||||
// Will be followed by onRestart.
|
||||
Log.d(Constants.TAG, "MyTracksMap.onStart");
|
||||
Log.d(TAG, "MyTracksMap.onStart");
|
||||
super.onStart();
|
||||
}
|
||||
|
||||
@@ -416,7 +418,7 @@ public class MyTracksMap extends MapActivity
|
||||
// Called when activity is no longer visible to user.
|
||||
// Next either onStart, onDestroy or nothing will be called.
|
||||
// This method may never be called in low memory situations.
|
||||
Log.d(Constants.TAG, "MyTracksMap.onStop");
|
||||
Log.d(TAG, "MyTracksMap.onStop");
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@@ -424,7 +426,7 @@ public class MyTracksMap extends MapActivity
|
||||
protected void onRestart() {
|
||||
// Called when the current activity is being re-displayed.
|
||||
// Will be followed by onResume.
|
||||
Log.d(Constants.TAG, "MyTracksMap.onRestart");
|
||||
Log.d(TAG, "MyTracksMap.onRestart");
|
||||
super.onRestart();
|
||||
}
|
||||
|
||||
@@ -432,7 +434,7 @@ public class MyTracksMap extends MapActivity
|
||||
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(Constants.TAG, "MyTracksMap.onPause");
|
||||
Log.d(TAG, "MyTracksMap.onPause");
|
||||
unregisterLocationAndSensorListeners();
|
||||
unregisterContentObservers();
|
||||
super.onPause();
|
||||
@@ -442,7 +444,7 @@ public class MyTracksMap extends MapActivity
|
||||
protected void onResume() {
|
||||
// Called when the current activity is being displayed or re-displayed
|
||||
// to the user.
|
||||
Log.d(Constants.TAG, "MyTracksMap.onResume");
|
||||
Log.d(TAG, "MyTracksMap.onResume");
|
||||
super.onResume();
|
||||
|
||||
// Reload all preferences as they might have changed since last run.
|
||||
@@ -477,7 +479,7 @@ public class MyTracksMap extends MapActivity
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
Log.d(Constants.TAG, "MyTracksMap.onSaveInstanceState");
|
||||
Log.d(TAG, "MyTracksMap.onSaveInstanceState");
|
||||
outState.putBoolean(KEY_HAVE_GOOD_FIX, haveGoodFix);
|
||||
outState.putBoolean(KEY_KEEP_MY_LOCATION_VISIBLE, keepMyLocationVisible);
|
||||
if (currentLocation != null) {
|
||||
@@ -488,7 +490,7 @@ public class MyTracksMap extends MapActivity
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle bundle) {
|
||||
Log.d(Constants.TAG, "MyTracksMap.onRestoreInstanceState");
|
||||
Log.d(TAG, "MyTracksMap.onRestoreInstanceState");
|
||||
if (bundle != null) {
|
||||
super.onRestoreInstanceState(bundle);
|
||||
haveGoodFix = bundle.getBoolean(KEY_HAVE_GOOD_FIX, false);
|
||||
@@ -528,7 +530,7 @@ public class MyTracksMap extends MapActivity
|
||||
alert(getString(R.string.error_no_gps_location_provider));
|
||||
return;
|
||||
} else {
|
||||
Log.d(Constants.TAG, "MyTracksMap: Using location provider "
|
||||
Log.d(TAG, "MyTracksMap: Using location provider "
|
||||
+ gpsProvider.getName());
|
||||
}
|
||||
locationManager.requestLocationUpdates(gpsProvider.getName(),
|
||||
@@ -539,7 +541,7 @@ public class MyTracksMap extends MapActivity
|
||||
} catch (RuntimeException e) {
|
||||
// If anything at all goes wrong with getting a cell location do not
|
||||
// abort. Cell location is not essential to this app.
|
||||
Log.w(Constants.TAG,
|
||||
Log.w(TAG,
|
||||
"Could not register network location listener.");
|
||||
}
|
||||
}
|
||||
@@ -550,7 +552,7 @@ public class MyTracksMap extends MapActivity
|
||||
if (compass == null) {
|
||||
return;
|
||||
}
|
||||
Log.d(Constants.TAG,
|
||||
Log.d(TAG,
|
||||
"MyTracksMap: Now registering sensor listeners.");
|
||||
sensorManager.registerListener(
|
||||
sensorListener, compass, SensorManager.SENSOR_DELAY_UI);
|
||||
@@ -561,12 +563,12 @@ public class MyTracksMap extends MapActivity
|
||||
*/
|
||||
void unregisterLocationAndSensorListeners() {
|
||||
if (locationManager != null) {
|
||||
Log.d(Constants.TAG,
|
||||
Log.d(TAG,
|
||||
"MyTracksMap: Now unregistering location listeners.");
|
||||
locationManager.removeUpdates(locationListener);
|
||||
}
|
||||
if (sensorManager != null) {
|
||||
Log.d(Constants.TAG,
|
||||
Log.d(TAG,
|
||||
"MyTracksMap: Now unregistering sensor listeners.");
|
||||
sensorManager.unregisterListener(sensorListener);
|
||||
}
|
||||
@@ -704,7 +706,7 @@ public class MyTracksMap extends MapActivity
|
||||
* @param trackId a given track id
|
||||
*/
|
||||
public void setSelectedTrack(final long trackId) {
|
||||
Log.d(Constants.TAG, "MyTracksMap.setSelectedTrack: "
|
||||
Log.d(TAG, "MyTracksMap.setSelectedTrack: "
|
||||
+ "selectedTrackId = " + selectedTrackId + ", trackId = " + trackId);
|
||||
|
||||
if (selectedTrackId == trackId) {
|
||||
@@ -763,7 +765,7 @@ public class MyTracksMap extends MapActivity
|
||||
timestamp);
|
||||
variation = field.getDeclination();
|
||||
|
||||
Log.d(Constants.TAG,
|
||||
Log.d(TAG,
|
||||
"MyTracksMap: Variation reset to " + variation + " degrees.");
|
||||
}
|
||||
|
||||
@@ -906,7 +908,7 @@ public class MyTracksMap extends MapActivity
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(
|
||||
final SharedPreferences sharedPreferences, final String key) {
|
||||
Log.d(Constants.TAG,
|
||||
Log.d(TAG,
|
||||
"MyTracksMap.onSharedPreferenceChanged: " + key);
|
||||
if (key != null) {
|
||||
uiHandler.post(new Runnable() {
|
||||
@@ -954,7 +956,7 @@ public class MyTracksMap extends MapActivity
|
||||
}
|
||||
showCurrentLocation();
|
||||
} else {
|
||||
Log.d(Constants.TAG,
|
||||
Log.d(TAG,
|
||||
"MyTracksMap: Network location update received.");
|
||||
}
|
||||
}
|
||||
@@ -1060,7 +1062,7 @@ public class MyTracksMap extends MapActivity
|
||||
// TODO: This shouldn't happen after adding currentSelectedTrackId,
|
||||
// but just to be safe until we have 100% confidence.
|
||||
if (samplingFrequency <= 0) {
|
||||
Log.w(Constants.TAG,
|
||||
Log.w(TAG,
|
||||
"readAllNewTrackPoints: samplingFreq <= 0, numTotalPoints = "
|
||||
+ numTotalPoints + ", trackId = " + currentSelectedTrackId);
|
||||
samplingFrequency = 1;
|
||||
|
||||
@@ -15,10 +15,7 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
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.BackupPreferencesListener;
|
||||
@@ -28,18 +25,28 @@ import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.BluetoothDeviceUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.preference.Preference.OnPreferenceChangeListener;
|
||||
import android.preference.Preference.OnPreferenceClickListener;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An activity that let's the user see and edit the settings.
|
||||
@@ -119,6 +126,16 @@ public class MyTracksSettings extends PreferenceActivity {
|
||||
announcementFrequency.setSummary(
|
||||
R.string.settings_not_available_summary);
|
||||
}
|
||||
|
||||
// Hook up action for resetting all settings
|
||||
Preference resetPreference = findPreference(getString(R.string.reset_key));
|
||||
resetPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference arg0) {
|
||||
onResetPreferencesClick();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void customizeSensorOptionsPreferences() {
|
||||
@@ -177,19 +194,24 @@ public class MyTracksSettings extends PreferenceActivity {
|
||||
findPreference(getString(R.string.backup_to_sd_key));
|
||||
Preference restoreNowPreference =
|
||||
findPreference(getString(R.string.restore_from_sd_key));
|
||||
Preference resetPreference = findPreference(getString(R.string.reset_key));
|
||||
|
||||
// If recording, disable backup/restore
|
||||
// If recording, disable backup/restore/reset
|
||||
// (we don't want to get to inconsistent states)
|
||||
boolean recording =
|
||||
preferences.getLong(getString(R.string.recording_track_key), -1) != -1;
|
||||
backupNowPreference.setEnabled(!recording);
|
||||
restoreNowPreference.setEnabled(!recording);
|
||||
resetPreference.setEnabled(!recording);
|
||||
backupNowPreference.setSummary(
|
||||
recording ? R.string.settings_no_backup_while_recording
|
||||
recording ? R.string.settings_not_while_recording
|
||||
: R.string.settings_backup_to_sd_summary);
|
||||
restoreNowPreference.setSummary(
|
||||
recording ? R.string.settings_no_backup_while_recording
|
||||
recording ? R.string.settings_not_while_recording
|
||||
: R.string.settings_restore_from_sd_summary);
|
||||
resetPreference.setSummary(
|
||||
recording ? R.string.settings_not_while_recording
|
||||
: R.string.settings_reset_summary);
|
||||
|
||||
// Add actions to the backup preferences
|
||||
backupNowPreference.setOnPreferenceClickListener(
|
||||
@@ -315,4 +337,53 @@ public class MyTracksSettings extends PreferenceActivity {
|
||||
devicesPreference.setEntryValues(entryValuesArray);
|
||||
devicesPreference.setEntries(entriesArray);
|
||||
}
|
||||
|
||||
/** Callback for when user asks to reset all settings. */
|
||||
private void onResetPreferencesClick() {
|
||||
AlertDialog dialog = new AlertDialog.Builder(this)
|
||||
.setCancelable(true)
|
||||
.setTitle(R.string.settings_reset)
|
||||
.setMessage(R.string.settings_reset_dialog_message)
|
||||
.setPositiveButton(android.R.string.ok,
|
||||
new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int button) {
|
||||
onResetPreferencesConfirmed();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create();
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
/** Callback for when user confirms resetting all settings. */
|
||||
private void onResetPreferencesConfirmed() {
|
||||
// Change preferences in a separate thread.
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.i(TAG, "Resetting all settings");
|
||||
|
||||
// Actually wipe preferences (and save synchronously).
|
||||
preferences.edit().clear().commit();
|
||||
|
||||
// Give UI feedback in the UI thread.
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Give feedback to the user.
|
||||
Toast.makeText(
|
||||
MyTracksSettings.this,
|
||||
R.string.settings_reset_done,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
|
||||
// Restart the settings activity so all changes are loaded.
|
||||
Intent intent = getIntent();
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,12 +193,14 @@ public class MyTracksProvider extends ContentProvider {
|
||||
@Override
|
||||
public int delete(Uri url, String where, String[] selectionArgs) {
|
||||
String table;
|
||||
boolean shouldVacuum = false;
|
||||
switch (urlMatcher.match(url)) {
|
||||
case TRACKPOINTS:
|
||||
table = TRACKPOINTS_TABLE;
|
||||
break;
|
||||
case TRACKS:
|
||||
table = TRACKS_TABLE;
|
||||
shouldVacuum = true;
|
||||
break;
|
||||
case WAYPOINTS:
|
||||
table = WAYPOINTS_TABLE;
|
||||
@@ -210,6 +212,13 @@ public class MyTracksProvider extends ContentProvider {
|
||||
Log.w(MyTracksProvider.TAG, "provider delete in " + table + "!");
|
||||
int count = db.delete(table, where, selectionArgs);
|
||||
getContext().getContentResolver().notifyChange(url, null, true);
|
||||
|
||||
if (shouldVacuum) {
|
||||
// If a potentially large amount of data was deleted, we want to reclaim its space.
|
||||
Log.i(TAG, "Vacuuming the database");
|
||||
db.execSQL("VACUUM");
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/*
|
||||
* Copyright 2008 Google Inc.
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
@@ -25,9 +25,10 @@ import android.location.Location;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
@@ -36,22 +37,33 @@ import java.util.TimeZone;
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class GpxTrackWriter implements TrackFormatWriter {
|
||||
private static final String TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
|
||||
|
||||
private static final int LATLONG_FORMAT = Location.FORMAT_DEGREES;
|
||||
private static final DecimalFormat ELEVATION_FORMATTER =
|
||||
new DecimalFormat("#.#");
|
||||
private static final SimpleDateFormat TIMESTAMP_FORMATTER =
|
||||
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||
static {
|
||||
TIMESTAMP_FORMATTER.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
private final NumberFormat elevationFormatter;
|
||||
private final NumberFormat coordinateFormatter;
|
||||
private final SimpleDateFormat timestampFormatter;
|
||||
private PrintWriter pw = null;
|
||||
private Track track;
|
||||
|
||||
public GpxTrackWriter() {
|
||||
// GPX readers expect to see fractional numbers with US-style punctuation.
|
||||
// That is, they want periods for decimal points, rather than commas.
|
||||
elevationFormatter = NumberFormat.getInstance(Locale.US);
|
||||
elevationFormatter.setMaximumFractionDigits(1);
|
||||
elevationFormatter.setGroupingUsed(false);
|
||||
|
||||
coordinateFormatter = NumberFormat.getInstance(Locale.US);
|
||||
coordinateFormatter.setMaximumFractionDigits(5);
|
||||
coordinateFormatter.setMaximumIntegerDigits(3);
|
||||
coordinateFormatter.setGroupingUsed(false);
|
||||
|
||||
timestampFormatter = new SimpleDateFormat(TIMESTAMP_FORMAT);
|
||||
timestampFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
private String formatLocation(Location l) {
|
||||
return "lat=\"" + Location.convert(l.getLatitude(), LATLONG_FORMAT)
|
||||
+ "\" lon=\"" + Location.convert(l.getLongitude(), LATLONG_FORMAT) + "\"";
|
||||
return "lat=\"" + coordinateFormatter.format(l.getLatitude())
|
||||
+ "\" lon=\"" + coordinateFormatter.format(l.getLongitude()) + "\"";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -129,8 +141,8 @@ public class GpxTrackWriter implements TrackFormatWriter {
|
||||
if (pw != null) {
|
||||
pw.println("<trkpt " + formatLocation(l) + ">");
|
||||
Date d = new Date(l.getTime());
|
||||
pw.println("<ele>" + ELEVATION_FORMATTER.format(l.getAltitude()) + "</ele>");
|
||||
pw.println("<time>" + TIMESTAMP_FORMATTER.format(d) + "</time>");
|
||||
pw.println("<ele>" + elevationFormatter.format(l.getAltitude()) + "</ele>");
|
||||
pw.println("<time>" + timestampFormatter.format(d) + "</time>");
|
||||
pw.println("</trkpt>");
|
||||
}
|
||||
}
|
||||
@@ -149,8 +161,8 @@ public class GpxTrackWriter implements TrackFormatWriter {
|
||||
Location l = waypoint.getLocation();
|
||||
if (l != null) {
|
||||
pw.println("<wpt " + formatLocation(l) + ">");
|
||||
pw.println("<ele>" + ELEVATION_FORMATTER.format(l.getAltitude()) + "</ele>");
|
||||
pw.println("<time>" + TIMESTAMP_FORMATTER.format(l.getTime()) + "</time>");
|
||||
pw.println("<ele>" + elevationFormatter.format(l.getAltitude()) + "</ele>");
|
||||
pw.println("<time>" + timestampFormatter.format(l.getTime()) + "</time>");
|
||||
pw.println("<name>" + StringUtils.stringAsCData(waypoint.getName())
|
||||
+ "</name>");
|
||||
pw.println("<desc>"
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
package com.google.android.apps.mytracks.io;
|
||||
|
||||
import com.google.android.apps.mytracks.io.gdata.GDataClientFactory;
|
||||
import com.google.android.apps.mytracks.io.mymaps.MapsFacade;
|
||||
import com.google.android.apps.mytracks.io.mymaps.MapsService;
|
||||
import com.google.android.apps.mytracks.io.mymaps.MapsStringsProvider;
|
||||
import com.google.android.apps.mytracks.io.mymaps.MapsFacade.AuthenticationRefresher;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* Factory to easily instantiate a {@link MapsFacade}.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class MyMapsFactory {
|
||||
|
||||
/**
|
||||
* Simple provider for reading strings from resources.
|
||||
*/
|
||||
private static class MyMapsStringProvider implements MapsStringsProvider {
|
||||
private final Context context;
|
||||
|
||||
public MyMapsStringProvider(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNewMapDescription() {
|
||||
return context.getString(R.string.new_map_description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStart() {
|
||||
return context.getString(R.string.start);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEnd() {
|
||||
return context.getString(R.string.end);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Maps fa�ade interface instance.
|
||||
*
|
||||
* @param context current context
|
||||
* @param auth a valid authentication manager
|
||||
* @return the fa�ade
|
||||
*/
|
||||
public static MapsFacade newMapsClient(Activity context, final AuthManager auth) {
|
||||
MapsFacade client = MapsService.newClient(context,
|
||||
GDataClientFactory.getGDataClient(context),
|
||||
new MyMapsStringProvider(context),
|
||||
auth.getAuthToken());
|
||||
client.setAuthenticationRefresher(new AuthenticationRefresher() {
|
||||
@Override
|
||||
public void invalidateAndRefresh(Runnable done) {
|
||||
auth.invalidateAndRefresh(done);
|
||||
}
|
||||
});
|
||||
return client;
|
||||
}
|
||||
|
||||
private MyMapsFactory() {}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.io.mymaps.MapsFacade;
|
||||
import com.google.android.apps.mytracks.io.mymaps.MapsFacade.WaypointData;
|
||||
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||
@@ -112,7 +111,7 @@ public class SendToMyMaps implements Runnable {
|
||||
track.setDescription("<p>" + track.getDescription() + "</p><p>"
|
||||
+ stringUtils.generateTrackDescription(track, null, null) + "</p>");
|
||||
|
||||
mapsClient = MyMapsFactory.newMapsClient(context, auth);
|
||||
mapsClient = new MapsFacade(context, auth);
|
||||
|
||||
// Create a new map if necessary:
|
||||
boolean isNewMap = mapId.equals(NEW_MAP_ID);
|
||||
@@ -149,11 +148,10 @@ public class SendToMyMaps implements Runnable {
|
||||
if (c.getCount() > 1 && c.moveToFirst()) {
|
||||
// This will skip the 1st waypoint (it carries the stats for the
|
||||
// last segment).
|
||||
ArrayList<WaypointData> waypoints = new ArrayList<WaypointData>(c.getCount() - 1);
|
||||
ArrayList<Waypoint> waypoints = new ArrayList<Waypoint>(c.getCount() - 1);
|
||||
while (c.moveToNext()) {
|
||||
Waypoint wpt = providerUtils.createWaypoint(c);
|
||||
waypoints.add(new WaypointData(
|
||||
wpt.getName(), wpt.getDescription(), wpt.getIcon(), wpt.getLocation()));
|
||||
waypoints.add(wpt);
|
||||
}
|
||||
|
||||
success = mapsClient.uploadWaypoints(mapId, waypoints);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
@@ -17,11 +17,13 @@ package com.google.android.apps.mytracks.io;
|
||||
|
||||
import com.google.android.apps.mytracks.content.MyTracksLocation;
|
||||
import com.google.android.apps.mytracks.content.Sensor;
|
||||
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
|
||||
import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
|
||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.os.Build;
|
||||
|
||||
@@ -30,6 +32,7 @@ import java.io.PrintWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
@@ -37,20 +40,35 @@ import java.util.TimeZone;
|
||||
* As defined by:
|
||||
* http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2
|
||||
*
|
||||
* The TCX file written by this class has been verified as compatible with
|
||||
* Garmin Training Center 3.5.3.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class TcxTrackWriter implements TrackFormatWriter {
|
||||
protected static final String TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
|
||||
|
||||
// These are the only sports allowed by the TCX v2 specification for fields
|
||||
// of type Sport_t.
|
||||
private static final String TCX_SPORT_BIKING = "Biking";
|
||||
private static final String TCX_SPORT_RUNNING = "Running";
|
||||
private static final String TCX_SPORT_OTHER = "Other";
|
||||
|
||||
// Values for fields of type Build_t/Type.
|
||||
private static final String TCX_TYPE_RELEASE = "Release";
|
||||
private static final String TCX_TYPE_INTERNAL = "Internal";
|
||||
|
||||
private final SimpleDateFormat timestampFormatter;
|
||||
private final Context context;
|
||||
|
||||
private PrintWriter pw = null;
|
||||
private Track track;
|
||||
|
||||
static final SimpleDateFormat TIMESTAMP_FORMAT =
|
||||
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||
static {
|
||||
TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
public TcxTrackWriter(Context context) {
|
||||
this.context = context;
|
||||
|
||||
public TcxTrackWriter() {
|
||||
timestampFormatter = new SimpleDateFormat(TIMESTAMP_FORMAT);
|
||||
timestampFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,16 +111,13 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
||||
if (pw == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String startTime = timestampFormatter.format(track.getStatistics().getStartTime());
|
||||
|
||||
pw.println(" <Activities>");
|
||||
pw.print(" <Activity Sport=\"");
|
||||
if (track.getCategory() != null) {
|
||||
pw.print(track.getCategory());
|
||||
}
|
||||
pw.println("\">");
|
||||
pw.print(" <Id>");
|
||||
pw.print(TIMESTAMP_FORMAT.format(track.getStatistics().getStartTime()));
|
||||
pw.println("</Id>");
|
||||
pw.println(" <Lap>");
|
||||
pw.format(" <Activity Sport=\"%s\">\n", categoryToTcxSport(track.getCategory()));
|
||||
pw.format(" <Id>%s</Id>\n", startTime);
|
||||
pw.format(" <Lap StartTime=\"%s\">\n", startTime);
|
||||
pw.print(" <TotalTimeSeconds>");
|
||||
pw.print(track.getStatistics().getTotalTime() / 1000);
|
||||
pw.println("</TotalTimeSeconds>");
|
||||
@@ -113,7 +128,7 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
||||
// Calories are a required element just put in 0.
|
||||
pw.print("<Calories>0</Calories>");
|
||||
pw.println("<Intensity>Active</Intensity>");
|
||||
pw.println("<TriggerMethod>Manual</TriggerMethod>)");
|
||||
pw.println("<TriggerMethod>Manual</TriggerMethod>");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -130,7 +145,7 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
||||
}
|
||||
pw.println(" <Trackpoint>");
|
||||
Date d = new Date(location.getTime());
|
||||
pw.println(" <Time>" + TIMESTAMP_FORMAT.format(d) + "</Time>");
|
||||
pw.println(" <Time>" + timestampFormatter.format(d) + "</Time>");
|
||||
pw.println(" <Position>");
|
||||
|
||||
pw.print(" <LatitudeDegrees>");
|
||||
@@ -194,10 +209,18 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
||||
}
|
||||
pw.println(" </Lap>");
|
||||
pw.print(" <Creator xsi:type=\"Device_t\">");
|
||||
pw.print("<Name>");
|
||||
pw.print(Build.MODEL);
|
||||
pw.print("</Name>");
|
||||
pw.println("</Creator>)");
|
||||
pw.format("<Name>%s</Name>\n", Build.MODEL);
|
||||
|
||||
// The following code is correct. ID is inconsistently capitalized in the
|
||||
// TCX schema.
|
||||
pw.println("<UnitId>0</UnitId>");
|
||||
pw.println("<ProductID>0</ProductID>");
|
||||
|
||||
pw.println("<Version>");
|
||||
pw.println("<VersionMajor>0</VersionMajor>");
|
||||
pw.println("<VersionMinor>0</VersionMinor>");
|
||||
pw.println("</Version>");
|
||||
pw.println("</Creator>");
|
||||
pw.println(" </Activity>");
|
||||
pw.println(" </Activities>");
|
||||
}
|
||||
@@ -207,8 +230,31 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
||||
if (pw == null) {
|
||||
return;
|
||||
}
|
||||
pw.print(" <Author xsi:type=\"Application_t\">");
|
||||
pw.print("<Name>My Tracks by Google</Name>");
|
||||
pw.println(" <Author xsi:type=\"Application_t\">");
|
||||
|
||||
// We put the version in the name because there isn't a better place for
|
||||
// it. The TCX schema tightly defined the Version tag, so we can't put it
|
||||
// there. They've similarly constrained the PartNumber tag, so it can't go
|
||||
// there either.
|
||||
pw.format("<Name>My Tracks %s by Google</Name>\n", MyTracksUtils.getMyTracksVersion(context));
|
||||
|
||||
pw.println("<Build>");
|
||||
|
||||
// The TCX spec for the Version tag is too strict to allow us to use our
|
||||
// user-visible version number, so we use our version code instead. We
|
||||
// also set the minor version to 1 if this is a development build to
|
||||
// signify that this build is newer than the one associated with the
|
||||
// version code given in VersionMajor.
|
||||
pw.println("<Version>");
|
||||
pw.format("<VersionMajor>%d</VersionMajor>\n", MyTracksUtils.getMyTracksVersion(context));
|
||||
pw.format("<VersionMinor>%d</VersionMinor>\n", MyTracksUtils.isRelease(context) ? 0 : 1);
|
||||
pw.println("</Version>");
|
||||
|
||||
pw.format("<Type>%s</Type>\n", MyTracksUtils.isRelease(context) ? TCX_TYPE_RELEASE
|
||||
: TCX_TYPE_INTERNAL);
|
||||
pw.println("</Build>");
|
||||
pw.format("<LangID>%s</LangID>\n", Locale.getDefault().getLanguage());
|
||||
pw.println("<PartNumber>000-00000-00</PartNumber>");
|
||||
pw.println("</Author>");
|
||||
pw.println("</TrainingCenterDatabase>");
|
||||
}
|
||||
@@ -217,4 +263,15 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
||||
public void writeWaypoint(Waypoint waypoint) {
|
||||
// TODO Write out the waypoints somewhere.
|
||||
}
|
||||
|
||||
private String categoryToTcxSport(String category) {
|
||||
category = category.trim();
|
||||
if (category.equalsIgnoreCase(TCX_SPORT_RUNNING)) {
|
||||
return TCX_SPORT_RUNNING;
|
||||
} else if (category.equalsIgnoreCase(TCX_SPORT_BIKING)) {
|
||||
return TCX_SPORT_BIKING;
|
||||
} else {
|
||||
return TCX_SPORT_OTHER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
@@ -53,7 +53,7 @@ public class TrackWriterFactory {
|
||||
TCX {
|
||||
@Override
|
||||
public TrackFormatWriter newFormatWriter(Context context) {
|
||||
return new TcxTrackWriter();
|
||||
return new TcxTrackWriter(context);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2009 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
|
||||
import com.google.android.maps.GeoPoint;
|
||||
|
||||
/**
|
||||
* A rectangle in geographical space.
|
||||
*/
|
||||
class GeoRect {
|
||||
public int top;
|
||||
public int left;
|
||||
public int bottom;
|
||||
public int right;
|
||||
|
||||
public GeoRect() {
|
||||
top = 0;
|
||||
left = 0;
|
||||
bottom = 0;
|
||||
right = 0;
|
||||
}
|
||||
|
||||
public GeoRect(GeoPoint center, int latSpan, int longSpan) {
|
||||
top = center.getLatitudeE6() - latSpan / 2;
|
||||
left = center.getLongitudeE6() - longSpan / 2;
|
||||
bottom = center.getLatitudeE6() + latSpan / 2;
|
||||
right = center.getLongitudeE6() + longSpan / 2;
|
||||
}
|
||||
|
||||
public GeoPoint getCenter() {
|
||||
return new GeoPoint(top / 2 + bottom / 2, left / 2 + right / 2);
|
||||
}
|
||||
|
||||
public int getLatSpan() {
|
||||
return bottom - top;
|
||||
}
|
||||
|
||||
public int getLongSpan() {
|
||||
return right - left;
|
||||
}
|
||||
|
||||
public boolean contains(GeoPoint geoPoint) {
|
||||
if (geoPoint.getLatitudeE6() >= top
|
||||
&& geoPoint.getLatitudeE6() <= bottom
|
||||
&& geoPoint.getLongitudeE6() >= left
|
||||
&& geoPoint.getLongitudeE6() <= right) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2010 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
|
||||
import com.google.wireless.gdata.data.Entry;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* GData entry for a map feature.
|
||||
*/
|
||||
class MapFeatureEntry extends Entry {
|
||||
|
||||
private String mPrivacy = null;
|
||||
private Map<String, String> mAttributes = new HashMap<String, String>();
|
||||
|
||||
public void setPrivacy(String privacy) {
|
||||
mPrivacy = privacy;
|
||||
}
|
||||
|
||||
public String getPrivacy() {
|
||||
return mPrivacy;
|
||||
}
|
||||
|
||||
public void setAttribute(String name, String value) {
|
||||
mAttributes.put(name, value);
|
||||
}
|
||||
|
||||
public void removeAttribute(String name) {
|
||||
mAttributes.remove(name);
|
||||
}
|
||||
|
||||
public Map<String, String> getAllAttributes() {
|
||||
return mAttributes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright 2010 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
|
||||
import com.google.wireless.gdata.client.GDataClient;
|
||||
import com.google.wireless.gdata.client.GDataParserFactory;
|
||||
import com.google.wireless.gdata.client.GDataServiceClient;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Client to talk to Google Maps via GData.
|
||||
*/
|
||||
class MapsClient extends GDataServiceClient {
|
||||
private static final boolean DEBUG = false;
|
||||
public static final boolean LOG_COMMUNICATION = false;
|
||||
|
||||
private static final String MAPS_BASE_FEED_URL =
|
||||
"http://maps.google.com/maps/feeds/";
|
||||
private static final String MAPS_MAP_FEED_PATH = "maps/default/full";
|
||||
private static final String MAPS_FEATURE_FEED_PATH_BEFORE_MAPID = "features/";
|
||||
private static final String MAPS_FEATURE_FEED_PATH_AFTER_MAPID = "/full";
|
||||
private static final String MAPS_VERSION_FEED_PATH_FORMAT =
|
||||
"%smaps/%s/versions/%s/full/%s";
|
||||
|
||||
private static final String MAP_ENTRY_ID_BEFORE_USER_ID = "maps/feeds/maps/";
|
||||
private static final String MAP_ENTRY_ID_BETWEEN_USER_ID_AND_MAP_ID = "/";
|
||||
private static final String V2_ONLY_PARAM = "?v=2.0";
|
||||
|
||||
public MapsClient(GDataClient dataClient,
|
||||
GDataParserFactory dataParserFactory) {
|
||||
super(dataClient, dataParserFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceName() {
|
||||
return MyMapsConstants.SERVICE_NAME;
|
||||
}
|
||||
|
||||
public static String buildMapUrl(String mapId) {
|
||||
return MyMapsConstants.MAPSHOP_BASE_URL + "?msa=0&msid=" + mapId;
|
||||
}
|
||||
|
||||
public static String getMapsFeed() {
|
||||
if (DEBUG) {
|
||||
Log.d("Maps Client", "Requesting map feed:");
|
||||
}
|
||||
return MAPS_BASE_FEED_URL + MAPS_MAP_FEED_PATH + V2_ONLY_PARAM;
|
||||
}
|
||||
|
||||
public static String getFeaturesFeed(String mapid) {
|
||||
StringBuilder feed = new StringBuilder();
|
||||
feed.append(MAPS_BASE_FEED_URL);
|
||||
feed.append(MAPS_FEATURE_FEED_PATH_BEFORE_MAPID);
|
||||
feed.append(mapid);
|
||||
feed.append(MAPS_FEATURE_FEED_PATH_AFTER_MAPID);
|
||||
feed.append(V2_ONLY_PARAM);
|
||||
return feed.toString();
|
||||
}
|
||||
|
||||
public static String getMapIdFromMapEntryId(String entryId) {
|
||||
String userId = null;
|
||||
String mapId = null;
|
||||
if (DEBUG) {
|
||||
Log.d("Maps GData Client", "Getting mapid from entry id: " + entryId);
|
||||
}
|
||||
int userIdStart =
|
||||
entryId.indexOf(MAP_ENTRY_ID_BEFORE_USER_ID)
|
||||
+ MAP_ENTRY_ID_BEFORE_USER_ID.length();
|
||||
int userIdEnd =
|
||||
entryId.indexOf(MAP_ENTRY_ID_BETWEEN_USER_ID_AND_MAP_ID, userIdStart);
|
||||
if (userIdStart >= 0 && userIdEnd < entryId.length()
|
||||
&& userIdStart <= userIdEnd) {
|
||||
userId = entryId.substring(userIdStart, userIdEnd);
|
||||
}
|
||||
int mapIdStart =
|
||||
entryId.indexOf(MAP_ENTRY_ID_BETWEEN_USER_ID_AND_MAP_ID, userIdEnd)
|
||||
+ MAP_ENTRY_ID_BETWEEN_USER_ID_AND_MAP_ID.length();
|
||||
if (mapIdStart >= 0 && mapIdStart < entryId.length()) {
|
||||
mapId = entryId.substring(mapIdStart);
|
||||
}
|
||||
if (userId == null) {
|
||||
userId = "";
|
||||
}
|
||||
if (mapId == null) {
|
||||
mapId = "";
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d("Maps GData Client", "Got user id: " + userId);
|
||||
Log.d("Maps GData Client", "Got map id: " + mapId);
|
||||
}
|
||||
return userId + "." + mapId;
|
||||
}
|
||||
|
||||
public static String getVersionFeed(String versionUserId,
|
||||
String versionClient, String currentVersion) {
|
||||
return String.format(MAPS_VERSION_FEED_PATH_FORMAT,
|
||||
MAPS_BASE_FEED_URL, versionUserId,
|
||||
versionClient, currentVersion);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
// Copyright 2011 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.io.AuthManager;
|
||||
import com.google.android.maps.GeoPoint;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.wireless.gdata.client.HttpException;
|
||||
import com.google.wireless.gdata.data.Entry;
|
||||
import com.google.wireless.gdata.parser.GDataParser;
|
||||
import com.google.wireless.gdata.parser.ParseException;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
/**
|
||||
* Single interface which abstracts all access to the Google Maps service.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class MapsFacade {
|
||||
|
||||
/**
|
||||
* Interface for receiving data back from getMapsList.
|
||||
* All calls to the interface will happen before getMapsList returns.
|
||||
*/
|
||||
public interface MapsListCallback {
|
||||
void onReceivedMapListing(String mapId, String title, String description,
|
||||
boolean isPublic);
|
||||
}
|
||||
|
||||
private static final String END_ICON_URL =
|
||||
"http://maps.google.com/mapfiles/ms/micons/red-dot.png";
|
||||
private static final String START_ICON_URL =
|
||||
"http://maps.google.com/mapfiles/ms/micons/green-dot.png";
|
||||
|
||||
private final Context context;
|
||||
private final MyMapsGDataWrapper wrapper;
|
||||
private final MyMapsGDataConverter gdataConverter;
|
||||
private final String authToken;
|
||||
|
||||
public MapsFacade(Context context, AuthManager auth) {
|
||||
this.context = context;
|
||||
this.authToken = auth.getAuthToken();
|
||||
|
||||
wrapper = new MyMapsGDataWrapper(context, auth);
|
||||
wrapper.setRetryOnAuthFailure(true);
|
||||
|
||||
try {
|
||||
gdataConverter = new MyMapsGDataConverter();
|
||||
} catch (XmlPullParserException e) {
|
||||
throw new IllegalStateException("Unable to create maps data converter", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String buildMapUrl(String mapId) {
|
||||
return MapsClient.buildMapUrl(mapId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all maps for the current user.
|
||||
*
|
||||
* @param callback callback to call for each map returned
|
||||
* @return true on success, false otherwise
|
||||
*/
|
||||
public boolean getMapsList(final MapsListCallback callback) {
|
||||
return wrapper.runQuery(new MyMapsGDataWrapper.QueryFunction() {
|
||||
@Override
|
||||
public void query(MapsClient client) throws IOException, Exception {
|
||||
GDataParser listParser = client.getParserForFeed(
|
||||
MapFeatureEntry.class, MapsClient.getMapsFeed(), authToken);
|
||||
listParser.init();
|
||||
while (listParser.hasMoreData()) {
|
||||
MapFeatureEntry entry =
|
||||
(MapFeatureEntry) listParser.readNextEntry(null);
|
||||
MyMapsMapMetadata metadata =
|
||||
MyMapsGDataConverter.getMapMetadataForEntry(entry);
|
||||
String mapId = MyMapsGDataConverter.getMapidForEntry(entry);
|
||||
|
||||
callback.onReceivedMapListing(
|
||||
mapId, metadata.getTitle(), metadata.getDescription(), metadata.getSearchable());
|
||||
}
|
||||
listParser.close();
|
||||
listParser = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new map for the current user.
|
||||
*
|
||||
* @param title title of the map
|
||||
* @param category category of the map
|
||||
* @param description description for the map
|
||||
* @param isPublic whether the map should be public
|
||||
* @param mapIdBuilder builder to append the map ID to
|
||||
* @return true on success, false otherwise
|
||||
*/
|
||||
public boolean createNewMap(
|
||||
final String title, final String category, final String description,
|
||||
final boolean isPublic, final StringBuilder mapIdBuilder) {
|
||||
if (mapIdBuilder.length() > 0) {
|
||||
throw new IllegalArgumentException("mapIdBuilder should be empty");
|
||||
}
|
||||
|
||||
return wrapper.runQuery(new MyMapsGDataWrapper.QueryFunction() {
|
||||
@Override
|
||||
public void query(MapsClient client) throws IOException, Exception {
|
||||
Log.d(MyMapsConstants.TAG, "Creating a new map.");
|
||||
String mapFeed = MapsClient.getMapsFeed();
|
||||
Log.d(MyMapsConstants.TAG, "Map feed is " + mapFeed);
|
||||
MyMapsMapMetadata metaData = new MyMapsMapMetadata();
|
||||
metaData.setTitle(title);
|
||||
metaData.setDescription(description + " - "
|
||||
+ category + " - " + context.getString(R.string.new_map_description));
|
||||
metaData.setSearchable(isPublic);
|
||||
Entry entry = MyMapsGDataConverter.getMapEntryForMetadata(metaData);
|
||||
Log.d(MyMapsConstants.TAG, "Title: " + entry.getTitle());
|
||||
Entry map = client.createEntry(mapFeed, authToken, entry);
|
||||
|
||||
String mapId = MapsClient.getMapIdFromMapEntryId(map.getId());
|
||||
mapIdBuilder.append(mapId);
|
||||
Log.d(MyMapsConstants.TAG, "New map id is: " + mapId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a single start or end marker to the given map.
|
||||
*
|
||||
* @param mapId ID of the map to upload to
|
||||
* @param trackName name of the track being started/ended
|
||||
* @param trackDescription description of the track being started/ended
|
||||
* @param location the location of the marker
|
||||
* @param isStart true to add a start marker, false to add an end marker
|
||||
* @return true on success, false otherwise
|
||||
*/
|
||||
public boolean uploadMarker(final String mapId, final String trackName,
|
||||
final String trackDescription, final Location loc, final boolean isStart) {
|
||||
return wrapper.runQuery(new MyMapsGDataWrapper.QueryFunction() {
|
||||
@Override
|
||||
public void query(MapsClient client)
|
||||
throws IOException, Exception {
|
||||
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
||||
GeoPoint geoPoint = getGeoPoint(loc);
|
||||
insertMarker(client, featureFeed, trackName, trackDescription, geoPoint, isStart);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a place mark. Second try if 1st try fails. Will throw exception on
|
||||
* 2nd failure.
|
||||
*/
|
||||
private void insertMarker(MapsClient client,
|
||||
String featureFeed,
|
||||
String trackName, String trackDescription,
|
||||
GeoPoint geoPoint,
|
||||
boolean isStart) throws IOException, Exception {
|
||||
MyMapsFeature feature =
|
||||
buildMyMapsPlacemarkFeature(trackName, trackDescription, geoPoint, isStart);
|
||||
Entry entry = gdataConverter.getEntryForFeature(feature);
|
||||
Log.d(MyMapsConstants.TAG, "SendToMyMaps: Creating placemark "
|
||||
+ entry.getTitle());
|
||||
try {
|
||||
client.createEntry(featureFeed, authToken, entry);
|
||||
Log.d(MyMapsConstants.TAG, "SendToMyMaps: createEntry success!");
|
||||
} catch (IOException e) {
|
||||
Log.w(MyMapsConstants.TAG,
|
||||
"SendToMyMaps: createEntry 1st try failed. Trying again.");
|
||||
// Retry once (often IOException is thrown on a timeout):
|
||||
client.createEntry(featureFeed, authToken, entry);
|
||||
Log.d(MyMapsConstants.TAG,
|
||||
"SendToMyMaps: createEntry success on 2nd try!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a placemark MyMapsFeature from a track.
|
||||
*
|
||||
* @param track the track
|
||||
* @param isStart true if it's the start of the track, or false for end
|
||||
* @return a MyMapsFeature
|
||||
*/
|
||||
private MyMapsFeature buildMyMapsPlacemarkFeature(
|
||||
String trackName, String trackDescription,
|
||||
GeoPoint geoPoint, boolean isStart) {
|
||||
String iconUrl;
|
||||
if (isStart) {
|
||||
iconUrl = START_ICON_URL;
|
||||
} else {
|
||||
iconUrl = END_ICON_URL;
|
||||
}
|
||||
String title = trackName + " "
|
||||
+ (isStart ? context.getString(R.string.start)
|
||||
: context.getString(R.string.end));
|
||||
String description = isStart ? "" : trackDescription;
|
||||
return buildMyMapsPlacemarkFeature(title, description, iconUrl, geoPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a MyMapsFeature from a track.
|
||||
*
|
||||
* @param wpt the waypoint
|
||||
* @return a MyMapsFeature
|
||||
*/
|
||||
private static MyMapsFeature buildMyMapsPlacemarkFeature(
|
||||
String title, String description, String iconUrl, GeoPoint geoPoint) {
|
||||
MyMapsFeature myMapsFeature = new MyMapsFeature();
|
||||
myMapsFeature.generateAndroidId();
|
||||
myMapsFeature.setType(MyMapsFeature.MARKER);
|
||||
myMapsFeature.setIconUrl(iconUrl);
|
||||
myMapsFeature.setDescription(description);
|
||||
myMapsFeature.addPoint(geoPoint);
|
||||
if (TextUtils.isEmpty(title)) {
|
||||
// Features must have a name (otherwise GData upload may fail):
|
||||
myMapsFeature.setTitle("-");
|
||||
} else {
|
||||
myMapsFeature.setTitle(title);
|
||||
}
|
||||
myMapsFeature.setDescription(description.replaceAll("\n", "<br>"));
|
||||
return myMapsFeature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a series of waypoints to the given map.
|
||||
*
|
||||
* @param mapId ID of the map to upload to
|
||||
* @param waypoints the waypoints to upload
|
||||
* @return true on success, false otherwise
|
||||
*/
|
||||
public boolean uploadWaypoints(
|
||||
final String mapId, final Iterable<Waypoint> waypoints) {
|
||||
return wrapper.runQuery(new MyMapsGDataWrapper.QueryFunction() {
|
||||
public void query(MapsClient client) {
|
||||
// TODO(rdamazio): Stream through the waypoints in chunks.
|
||||
// I am leaving the number of waypoints very high which should not be a
|
||||
// problem because we don't try to load them into objects all at the
|
||||
// same time.
|
||||
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
||||
|
||||
try {
|
||||
for (Waypoint waypoint : waypoints) {
|
||||
MyMapsFeature feature = buildMyMapsPlacemarkFeature(
|
||||
waypoint.getName(), waypoint.getDescription(), waypoint.getIcon(),
|
||||
getGeoPoint(waypoint.getLocation()));
|
||||
Entry entry = gdataConverter.getEntryForFeature(feature);
|
||||
|
||||
Log.d(MyMapsConstants.TAG,
|
||||
"SendToMyMaps: Creating waypoint.");
|
||||
try {
|
||||
client.createEntry(featureFeed, authToken, entry);
|
||||
Log.d(MyMapsConstants.TAG,
|
||||
"SendToMyMaps: createEntry success!");
|
||||
} catch (IOException e) {
|
||||
Log.w(MyMapsConstants.TAG,
|
||||
"SendToMyMaps: createEntry 1st try failed. Retrying.");
|
||||
|
||||
// Retry once (often IOException is thrown on a timeout):
|
||||
client.createEntry(featureFeed, authToken, entry);
|
||||
Log.d(MyMapsConstants.TAG,
|
||||
"SendToMyMaps: createEntry success on 2nd try!");
|
||||
}
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
Log.w(MyMapsConstants.TAG, "ParseException caught.", e);
|
||||
} catch (HttpException e) {
|
||||
Log.w(MyMapsConstants.TAG, "HttpException caught.", e);
|
||||
} catch (IOException e) {
|
||||
Log.w(MyMapsConstants.TAG, "IOException caught.", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a series of points to the given map.
|
||||
*
|
||||
* @param mapId ID of the map to upload to
|
||||
* @param trackName the name of the track
|
||||
* @param locations the locations to upload
|
||||
* @return true on success, false otherwise
|
||||
*/
|
||||
public boolean uploadTrackPoints(
|
||||
final String mapId, final String trackName, final Collection<Location> locations) {
|
||||
return wrapper.runQuery(new MyMapsGDataWrapper.QueryFunction() {
|
||||
@Override
|
||||
public void query(MapsClient client)
|
||||
throws IOException, Exception {
|
||||
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
||||
Log.d(MyMapsConstants.TAG, "Feature feed url: " + featureFeed);
|
||||
uploadTrackPoints(client, featureFeed, trackName, locations);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean uploadTrackPoints(
|
||||
MapsClient client,
|
||||
String featureFeed,
|
||||
String trackName, Collection<Location> locations)
|
||||
throws IOException, Exception {
|
||||
Entry entry = null;
|
||||
int numLocations = locations.size();
|
||||
if (numLocations < 2) {
|
||||
// Need at least two points for a polyline:
|
||||
Log.w(MyMapsConstants.TAG, "Not uploading too few points");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Put the line:
|
||||
entry = gdataConverter.getEntryForFeature(
|
||||
buildMyMapsLineFeature(trackName, locations));
|
||||
Log.d(MyMapsConstants.TAG,
|
||||
"SendToMyMaps: Creating line " + entry.getTitle());
|
||||
try {
|
||||
client.createEntry(featureFeed, authToken, entry);
|
||||
Log.d(MyMapsConstants.TAG, "SendToMyMaps: createEntry success!");
|
||||
} catch (IOException e) {
|
||||
Log.w(MyMapsConstants.TAG,
|
||||
"SendToMyMaps: createEntry 1st try failed. Trying again.");
|
||||
// Retry once (often IOException is thrown on a timeout):
|
||||
client.createEntry(featureFeed, authToken, entry);
|
||||
Log.d(MyMapsConstants.TAG,
|
||||
"SendToMyMaps: createEntry success on 2nd try!");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a MyMapsFeature from a track.
|
||||
*
|
||||
* @param track the track
|
||||
* @return a MyMapsFeature
|
||||
*/
|
||||
private static MyMapsFeature buildMyMapsLineFeature(String trackName,
|
||||
Iterable<Location> locations) {
|
||||
MyMapsFeature myMapsFeature = new MyMapsFeature();
|
||||
myMapsFeature.generateAndroidId();
|
||||
myMapsFeature.setType(MyMapsFeature.LINE);
|
||||
if (TextUtils.isEmpty(trackName)) {
|
||||
// Features must have a name (otherwise GData upload may fail):
|
||||
myMapsFeature.setTitle("-");
|
||||
} else {
|
||||
myMapsFeature.setTitle(trackName);
|
||||
}
|
||||
myMapsFeature.setColor(0x80FF0000);
|
||||
for (Location loc : locations) {
|
||||
myMapsFeature.addPoint(getGeoPoint(loc));
|
||||
}
|
||||
return myMapsFeature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up after a series of uploads.
|
||||
* This closes the connection to Maps and resets retry counters.
|
||||
*/
|
||||
public void cleanUp() {
|
||||
wrapper.cleanUp();
|
||||
}
|
||||
|
||||
private static GeoPoint getGeoPoint(Location location) {
|
||||
return new GeoPoint((int) (location.getLatitude() * 1E6),
|
||||
(int) (location.getLongitude() * 1E6));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2009 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
|
||||
/**
|
||||
* Constants for My Maps.
|
||||
*/
|
||||
public class MyMapsConstants {
|
||||
static final String TAG = "MapsApi";
|
||||
static final String MAPSHOP_BASE_URL =
|
||||
"http://maps.google.com/maps/ms";
|
||||
public static final String SERVICE_NAME = "local";
|
||||
|
||||
/**
|
||||
* Private constructor to prevent instantiation.
|
||||
*/
|
||||
private MyMapsConstants() { }
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
// Copyright 2009 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
|
||||
import com.google.android.maps.GeoPoint;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* MyMapsFeature contains all of the data associated with a feature in My Maps,
|
||||
* where a feature is a marker, line, or shape. Some of the data is stored in a
|
||||
* {@link MyMapsFeatureMetadata} object so that it can be more efficiently
|
||||
* transmitted to other activities.
|
||||
*/
|
||||
class MyMapsFeature implements Serializable {
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* A line feature displays a line connecting a set of points on the map.
|
||||
*/
|
||||
public static final int LINE = 1;
|
||||
|
||||
/**
|
||||
* A shape feature displays a border defined by connecting a set of points,
|
||||
* including connecting the last to the first, and displays the area
|
||||
* confined by this border.
|
||||
*/
|
||||
public static final int SHAPE = 2;
|
||||
|
||||
/** The local feature id for this feature, if needed. */
|
||||
private String androidId;
|
||||
|
||||
/**
|
||||
* The latitudes of the points of this feature in order, specified in
|
||||
* millionths of a degree north.
|
||||
*/
|
||||
private final Vector<Integer> latitudeE6 = new Vector<Integer>();
|
||||
|
||||
/**
|
||||
* The longitudes of the points of this feature in order, specified in
|
||||
* millionths of a degree east.
|
||||
*/
|
||||
private final Vector<Integer> longitudeE6 = new Vector<Integer>();
|
||||
|
||||
/** The metadata of this feature in a format efficient for transmission. */
|
||||
private MyMapsFeatureMetadata featureInfo = new MyMapsFeatureMetadata();
|
||||
|
||||
/**
|
||||
* Initializes a valid but empty feature. It will default to a
|
||||
* {@link #MARKER} with a blue placemark with a dot as an icon at the
|
||||
* location (0, 0).
|
||||
*/
|
||||
public MyMapsFeature() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new point to the end of this feature.
|
||||
*
|
||||
* @param point The new point to add
|
||||
*/
|
||||
public synchronized void addPoint(GeoPoint point) {
|
||||
latitudeE6.add(point.getLatitudeE6());
|
||||
longitudeE6.add(point.getLongitudeE6());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new local id for this feature based on the current time and
|
||||
* a random number.
|
||||
*/
|
||||
public synchronized void generateAndroidId() {
|
||||
Long time = System.currentTimeMillis();
|
||||
Integer random = (int) (Math.random() * 10000);
|
||||
androidId = time.toString() + "." + random.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current local id for this feature if one is available.
|
||||
*
|
||||
* @return The local id for this feature
|
||||
*/
|
||||
public String getAndroidId() {
|
||||
return androidId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current (html) description of this feature. The description
|
||||
* is stored in the feature metadata.
|
||||
*
|
||||
* @return The description of this feature
|
||||
*/
|
||||
public String getDescription() {
|
||||
return featureInfo.getDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description of this feature. That description is stored in the
|
||||
* feature metadata.
|
||||
*
|
||||
* @param description The new description of this feature
|
||||
*/
|
||||
public synchronized void setDescription(String description) {
|
||||
featureInfo.setDescription(description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the point at the given index for this feature.
|
||||
*
|
||||
* @param index The index of the point desired
|
||||
* @return A {@link GeoPoint} representing the point or null if that point
|
||||
* doesn't exist
|
||||
*/
|
||||
public GeoPoint getPoint(int index) {
|
||||
if (latitudeE6.size() <= index) {
|
||||
return null;
|
||||
}
|
||||
return new GeoPoint(latitudeE6.get(index), longitudeE6.get(index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of points in this feature and return that count.
|
||||
*
|
||||
* @return The number of points in this feature
|
||||
*/
|
||||
public int getPointCount() {
|
||||
return latitudeE6.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the title of this feature. That title is stored in the feature
|
||||
* metadata.
|
||||
*
|
||||
* @return the current title of this feature
|
||||
*/
|
||||
public String getTitle() {
|
||||
return featureInfo.getTitle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the type of this feature. That type is stored in the feature
|
||||
* metadata.
|
||||
*
|
||||
* @return One of {@link #MARKER}, {@link #LINE}, or {@link #SHAPE}
|
||||
* identifying the type of this feature
|
||||
*/
|
||||
public int getType() {
|
||||
return featureInfo.getType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current color of this feature as an ARGB color integer.
|
||||
* That color is stored in the feature metadata.
|
||||
*
|
||||
* @return The ARGB color of this feature
|
||||
*/
|
||||
public int getColor() {
|
||||
return featureInfo.getColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current line width of this feature. That line width is
|
||||
* stored in the feature metadata.
|
||||
*
|
||||
* @return The line width of this feature
|
||||
*/
|
||||
public int getLineWidth() {
|
||||
return featureInfo.getLineWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current fill color of this feature as an ARGB color
|
||||
* integer. That color is stored in the feature metadata.
|
||||
*
|
||||
* @return The ARGB fill color of this feature
|
||||
*/
|
||||
public int getFillColor() {
|
||||
return featureInfo.getFillColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current icon url of this feature. That icon url is stored
|
||||
* in the feature metadata.
|
||||
*
|
||||
* @return The icon url for this feature
|
||||
*/
|
||||
public String getIconUrl() {
|
||||
return featureInfo.getIconUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the title of this feature. That title is stored in the feature
|
||||
* metadata.
|
||||
*
|
||||
* @param title The new title of this feature
|
||||
*/
|
||||
public synchronized void setTitle(String title) {
|
||||
featureInfo.setTitle(title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type of this feature. That type is stored in the feature
|
||||
* metadata.
|
||||
*
|
||||
* @param type The new type of the feature. That type must be one of
|
||||
* {@link #MARKER}, {@link #LINE}, or {@link #SHAPE}
|
||||
*/
|
||||
public synchronized void setType(int type) {
|
||||
featureInfo.setType(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ARGB color of this feature. That color is stored in the feature
|
||||
* metadata.
|
||||
*
|
||||
* @param color The new ARGB color of this feature
|
||||
*/
|
||||
public synchronized void setColor(int color) {
|
||||
featureInfo.setColor(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the icon url of this feature. That icon url is stored in the feature
|
||||
* metadata.
|
||||
*
|
||||
* @param url The new icon url of the feature
|
||||
*/
|
||||
public synchronized void setIconUrl(String url) {
|
||||
featureInfo.setIconUrl(url);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright 2009 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
|
||||
/**
|
||||
* Metadata about a maps feature.
|
||||
*/
|
||||
class MyMapsFeatureMetadata {
|
||||
|
||||
private static final String BLUE_DOT_URL =
|
||||
"http://maps.google.com/mapfiles/ms/micons/blue-dot.png";
|
||||
private static final int DEFAULT_COLOR = 0x800000FF;
|
||||
private static final int DEFAULT_FILL_COLOR = 0xC00000FF;
|
||||
|
||||
private String title;
|
||||
private String description;
|
||||
private int type;
|
||||
private int color;
|
||||
private int lineWidth;
|
||||
private int fillColor;
|
||||
private String iconUrl;
|
||||
|
||||
public MyMapsFeatureMetadata() {
|
||||
title = "";
|
||||
description = "";
|
||||
type = MyMapsFeature.MARKER;
|
||||
color = DEFAULT_COLOR;
|
||||
lineWidth = 5;
|
||||
fillColor = DEFAULT_FILL_COLOR;
|
||||
iconUrl = BLUE_DOT_URL;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(int type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public int getLineWidth() {
|
||||
return lineWidth;
|
||||
}
|
||||
|
||||
public void setLineWidth(int width) {
|
||||
lineWidth = width;
|
||||
}
|
||||
|
||||
public int getFillColor() {
|
||||
return fillColor;
|
||||
}
|
||||
|
||||
public void setFillColor(int color) {
|
||||
fillColor = color;
|
||||
}
|
||||
|
||||
public String getIconUrl() {
|
||||
return iconUrl;
|
||||
}
|
||||
|
||||
public void setIconUrl(String url) {
|
||||
iconUrl = new String(url);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
// Copyright 2009 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
|
||||
import com.google.wireless.gdata.data.Entry;
|
||||
import com.google.wireless.gdata.data.StringUtils;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.util.Log;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
|
||||
/**
|
||||
* Converter from GData objects to MyMaps objects.
|
||||
*/
|
||||
class MyMapsGDataConverter {
|
||||
|
||||
private final XmlSerializer xmlSerializer;
|
||||
|
||||
public MyMapsGDataConverter() throws XmlPullParserException {
|
||||
xmlSerializer = XmlPullParserFactory.newInstance().newSerializer();
|
||||
}
|
||||
|
||||
public static MyMapsMapMetadata getMapMetadataForEntry(
|
||||
MapFeatureEntry entry) {
|
||||
MyMapsMapMetadata metadata = new MyMapsMapMetadata();
|
||||
if ("public".equals(entry.getPrivacy())) {
|
||||
metadata.setSearchable(true);
|
||||
} else {
|
||||
metadata.setSearchable(false);
|
||||
}
|
||||
metadata.setTitle(entry.getTitle());
|
||||
|
||||
String desc = entry.getContent();
|
||||
if (desc.length() > 12) {
|
||||
metadata.setDescription(desc.substring(9, desc.length() - 3));
|
||||
}
|
||||
|
||||
String editUri = entry.getEditUri();
|
||||
if (editUri != null) {
|
||||
metadata.setGDataEditUri(editUri);
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public static String getMapidForEntry(Entry entry) {
|
||||
return MapsClient.getMapIdFromMapEntryId(entry.getId());
|
||||
}
|
||||
|
||||
public static Entry getMapEntryForMetadata(MyMapsMapMetadata metadata) {
|
||||
MapFeatureEntry entry = new MapFeatureEntry();
|
||||
entry.setEditUri(metadata.getGDataEditUri());
|
||||
entry.setTitle(metadata.getTitle());
|
||||
entry.setContent(metadata.getDescription());
|
||||
entry.setPrivacy(metadata.getSearchable() ? "public" : "unlisted");
|
||||
entry.setAuthor("android");
|
||||
entry.setEmail("nobody@google.com");
|
||||
return entry;
|
||||
}
|
||||
|
||||
public MapFeatureEntry getEntryForFeature(MyMapsFeature feature) {
|
||||
MapFeatureEntry entry = new MapFeatureEntry();
|
||||
entry.setTitle(feature.getTitle());
|
||||
entry.setAuthor("android");
|
||||
entry.setEmail("nobody@google.com");
|
||||
entry.setCategoryScheme("http://schemas.google.com/g/2005#kind");
|
||||
entry.setCategory("http://schemas.google.com/g/2008#mapfeature");
|
||||
entry.setEditUri("");
|
||||
if (!StringUtils.isEmpty(feature.getAndroidId())) {
|
||||
entry.setAttribute("_androidId", feature.getAndroidId());
|
||||
}
|
||||
try {
|
||||
StringWriter writer = new StringWriter();
|
||||
xmlSerializer.setOutput(writer);
|
||||
xmlSerializer.startTag(null, "Placemark");
|
||||
xmlSerializer.attribute(null, "xmlns", "http://earth.google.com/kml/2.2");
|
||||
xmlSerializer.startTag(null, "Style");
|
||||
if (feature.getType() == MyMapsFeature.MARKER) {
|
||||
xmlSerializer.startTag(null, "IconStyle");
|
||||
xmlSerializer.startTag(null, "Icon");
|
||||
xmlSerializer.startTag(null, "href");
|
||||
xmlSerializer.text(feature.getIconUrl());
|
||||
xmlSerializer.endTag(null, "href");
|
||||
xmlSerializer.endTag(null, "Icon");
|
||||
xmlSerializer.endTag(null, "IconStyle");
|
||||
} else {
|
||||
xmlSerializer.startTag(null, "LineStyle");
|
||||
xmlSerializer.startTag(null, "color");
|
||||
int color = feature.getColor();
|
||||
// Reverse the color because KML is ABGR and Android is ARGB
|
||||
xmlSerializer.text(Integer.toHexString(
|
||||
Color.argb(Color.alpha(color), Color.blue(color),
|
||||
Color.green(color), Color.red(color))));
|
||||
xmlSerializer.endTag(null, "color");
|
||||
xmlSerializer.startTag(null, "width");
|
||||
xmlSerializer.text(Integer.toString(feature.getLineWidth()));
|
||||
xmlSerializer.endTag(null, "width");
|
||||
xmlSerializer.endTag(null, "LineStyle");
|
||||
|
||||
if (feature.getType() == MyMapsFeature.SHAPE) {
|
||||
xmlSerializer.startTag(null, "PolyStyle");
|
||||
xmlSerializer.startTag(null, "color");
|
||||
int fcolor = feature.getFillColor();
|
||||
// Reverse the color because KML is ABGR and Android is ARGB
|
||||
xmlSerializer.text(Integer.toHexString(Color.argb(Color.alpha(fcolor),
|
||||
Color.blue(fcolor), Color.green(fcolor), Color.red(fcolor))));
|
||||
xmlSerializer.endTag(null, "color");
|
||||
xmlSerializer.startTag(null, "fill");
|
||||
xmlSerializer.text("1");
|
||||
xmlSerializer.endTag(null, "fill");
|
||||
xmlSerializer.startTag(null, "outline");
|
||||
xmlSerializer.text("1");
|
||||
xmlSerializer.endTag(null, "outline");
|
||||
xmlSerializer.endTag(null, "PolyStyle");
|
||||
}
|
||||
}
|
||||
xmlSerializer.endTag(null, "Style");
|
||||
xmlSerializer.startTag(null, "name");
|
||||
xmlSerializer.text(feature.getTitle());
|
||||
xmlSerializer.endTag(null, "name");
|
||||
xmlSerializer.startTag(null, "description");
|
||||
xmlSerializer.cdsect(feature.getDescription());
|
||||
xmlSerializer.endTag(null, "description");
|
||||
StringBuilder pointBuilder = new StringBuilder();
|
||||
for (int i = 0; i < feature.getPointCount(); ++i) {
|
||||
if (i > 0) {
|
||||
pointBuilder.append('\n');
|
||||
}
|
||||
pointBuilder.append(feature.getPoint(i).getLongitudeE6() / 1e6);
|
||||
pointBuilder.append(',');
|
||||
pointBuilder.append(feature.getPoint(i).getLatitudeE6() / 1e6);
|
||||
pointBuilder.append(",0.000000");
|
||||
}
|
||||
String pointString = pointBuilder.toString();
|
||||
if (feature.getType() == MyMapsFeature.MARKER) {
|
||||
xmlSerializer.startTag(null, "Point");
|
||||
xmlSerializer.startTag(null, "coordinates");
|
||||
xmlSerializer.text(pointString);
|
||||
xmlSerializer.endTag(null, "coordinates");
|
||||
xmlSerializer.endTag(null, "Point");
|
||||
} else if (feature.getType() == MyMapsFeature.LINE) {
|
||||
xmlSerializer.startTag(null, "LineString");
|
||||
xmlSerializer.startTag(null, "tessellate");
|
||||
xmlSerializer.text("1");
|
||||
xmlSerializer.endTag(null, "tessellate");
|
||||
xmlSerializer.startTag(null, "coordinates");
|
||||
xmlSerializer.text(pointString);
|
||||
xmlSerializer.endTag(null, "coordinates");
|
||||
xmlSerializer.endTag(null, "LineString");
|
||||
} else {
|
||||
xmlSerializer.startTag(null, "Polygon");
|
||||
xmlSerializer.startTag(null, "outerBoundaryIs");
|
||||
xmlSerializer.startTag(null, "LinearRing");
|
||||
xmlSerializer.startTag(null, "tessellate");
|
||||
xmlSerializer.text("1");
|
||||
xmlSerializer.endTag(null, "tessellate");
|
||||
xmlSerializer.startTag(null, "coordinates");
|
||||
xmlSerializer.text(pointString + "\n"
|
||||
+ Double.toString(feature.getPoint(0).getLongitudeE6() / 1e6)
|
||||
+ ","
|
||||
+ Double.toString(feature.getPoint(0).getLatitudeE6() / 1e6)
|
||||
+ ",0.000000");
|
||||
xmlSerializer.endTag(null, "coordinates");
|
||||
xmlSerializer.endTag(null, "LinearRing");
|
||||
xmlSerializer.endTag(null, "outerBoundaryIs");
|
||||
xmlSerializer.endTag(null, "Polygon");
|
||||
}
|
||||
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();
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
// Copyright 2010 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
|
||||
import com.google.android.apps.mytracks.io.AuthManager;
|
||||
import com.google.android.apps.mytracks.io.gdata.GDataClientFactory;
|
||||
import com.google.android.common.gdata.AndroidXmlParserFactory;
|
||||
import com.google.wireless.gdata.client.GDataClient;
|
||||
import com.google.wireless.gdata.client.HttpException;
|
||||
import com.google.wireless.gdata.parser.xml.SimplePullParser.ParseException;
|
||||
import com.google.wireless.gdata2.ConflictDetectedException;
|
||||
import com.google.wireless.gdata2.client.AuthenticationException;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* MyMapsGDataWrapper provides a wrapper around GData operations that maintains
|
||||
* the GData client, and provides a method to run gdata queries with proper
|
||||
* error handling. After a query is run, the wrapper can be queried about the
|
||||
* error that occurred.
|
||||
*/
|
||||
class MyMapsGDataWrapper {
|
||||
/**
|
||||
* A QueryFunction is passed in when executing a query. The query function
|
||||
* of the class is called with the GData client as a parameter. The
|
||||
* function should execute whatever operations it desires on the client
|
||||
* without concern for whether the client will throw an error.
|
||||
*/
|
||||
interface QueryFunction {
|
||||
public abstract void query(MapsClient client)
|
||||
throws AuthenticationException, IOException, ParseException,
|
||||
ConflictDetectedException, Exception;
|
||||
}
|
||||
|
||||
// The types of error that may be encountered
|
||||
/** No error occurred. */
|
||||
public static final int ERROR_NO_ERROR = 0;
|
||||
/** There was an authentication error, the auth token may be invalid. */
|
||||
public static final int ERROR_AUTH = 1;
|
||||
/** There was an internal error on the server side. */
|
||||
public static final int ERROR_INTERNAL = 2;
|
||||
/** There was an error connecting to the server. */
|
||||
public static final int ERROR_CONNECTION = 3;
|
||||
/** The item queried did not exit. */
|
||||
public static final int ERROR_NOT_FOUND = 4;
|
||||
/** There was an error parsing or serializing locally. */
|
||||
public static final int ERROR_LOCAL = 5;
|
||||
/** There was a conflict, update the entry and try again. */
|
||||
public static final int ERROR_CONFLICT = 6;
|
||||
/**
|
||||
* A query was run after cleaning up the wrapper, so the client was invalid.
|
||||
*/
|
||||
public static final int ERROR_CLEANED_UP = 7;
|
||||
/** An unknown error occurred. */
|
||||
public static final int ERROR_UNKNOWN = 100;
|
||||
|
||||
private final GDataClient androidGdataClient;
|
||||
private final AuthManager auth;
|
||||
private final MapsClient client;
|
||||
|
||||
private String errorMessage;
|
||||
private int errorType;
|
||||
private boolean retryOnAuthFailure;
|
||||
private int retriesPending;
|
||||
private boolean cleanupCalled;
|
||||
|
||||
public MyMapsGDataWrapper(Context context, AuthManager auth) {
|
||||
androidGdataClient = GDataClientFactory.getGDataClient(context);
|
||||
this.auth = auth;
|
||||
client =
|
||||
new MapsClient(androidGdataClient, new XmlMapsGDataParserFactory(
|
||||
new AndroidXmlParserFactory()));
|
||||
errorType = ERROR_NO_ERROR;
|
||||
errorMessage = null;
|
||||
retryOnAuthFailure = false;
|
||||
retriesPending = 0;
|
||||
cleanupCalled = false;
|
||||
}
|
||||
|
||||
public boolean runQuery(final QueryFunction query) {
|
||||
if (client == null) {
|
||||
errorType = ERROR_CLEANED_UP;
|
||||
errorMessage = "GData Wrapper has already been cleaned up!";
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
query.query(client);
|
||||
errorType = ERROR_NO_ERROR;
|
||||
errorMessage = null;
|
||||
return true;
|
||||
} catch (AuthenticationException e) {
|
||||
Log.e(MyMapsConstants.TAG, "Exception", e);
|
||||
errorType = ERROR_AUTH;
|
||||
errorMessage = e.getMessage();
|
||||
} catch (HttpException e) {
|
||||
Log.e(MyMapsConstants.TAG, "HttpException", e);
|
||||
errorMessage = e.getMessage();
|
||||
if (errorMessage != null && errorMessage.contains("401")) {
|
||||
errorType = ERROR_AUTH;
|
||||
} else {
|
||||
errorType = ERROR_CONNECTION;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(MyMapsConstants.TAG, "Exception", e);
|
||||
errorMessage = e.getMessage();
|
||||
if (errorMessage != null && errorMessage.contains("503")) {
|
||||
errorType = ERROR_INTERNAL;
|
||||
} else {
|
||||
errorType = ERROR_CONNECTION;
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
Log.e(MyMapsConstants.TAG, "Exception", e);
|
||||
errorType = ERROR_LOCAL;
|
||||
errorMessage = e.getMessage();
|
||||
} catch (ConflictDetectedException e) {
|
||||
Log.e(MyMapsConstants.TAG, "Exception", e);
|
||||
errorType = ERROR_CONFLICT;
|
||||
errorMessage = e.getMessage();
|
||||
} catch (Exception e) {
|
||||
Log.e(MyMapsConstants.TAG, "Exception", e);
|
||||
errorType = ERROR_UNKNOWN;
|
||||
errorMessage = e.getMessage();
|
||||
e.printStackTrace();
|
||||
}
|
||||
Log.d(MyMapsConstants.TAG, "GData error encountered: " + errorMessage);
|
||||
if (errorType == ERROR_AUTH && auth != null) {
|
||||
Runnable whenFinished = null;
|
||||
if (retryOnAuthFailure) {
|
||||
retriesPending++;
|
||||
whenFinished = new Runnable() {
|
||||
public void run() {
|
||||
retriesPending--;
|
||||
retryOnAuthFailure = false;
|
||||
runQuery(query);
|
||||
if (cleanupCalled && retriesPending == 0) {
|
||||
cleanUp();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
auth.invalidateAndRefresh(whenFinished);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getErrorType() {
|
||||
return errorType;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
// cleanUp must be called when done using this wrapper to close the client.
|
||||
// Note that the cleanup will be delayed if auth failure retries were
|
||||
// requested and there is a pending retry.
|
||||
public void cleanUp() {
|
||||
if (retriesPending == 0 && !cleanupCalled) {
|
||||
androidGdataClient.close();
|
||||
}
|
||||
cleanupCalled = true;
|
||||
}
|
||||
|
||||
public void setRetryOnAuthFailure(boolean retry) {
|
||||
retryOnAuthFailure = retry;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2009 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
|
||||
/**
|
||||
* Metadata about a "my maps" map.
|
||||
*/
|
||||
class MyMapsMapMetadata {
|
||||
|
||||
private String title;
|
||||
private String description;
|
||||
private String gdataEditUri;
|
||||
private boolean searchable;
|
||||
|
||||
public MyMapsMapMetadata() {
|
||||
title = "";
|
||||
description = "";
|
||||
gdataEditUri = "";
|
||||
searchable = false;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = new String(title);
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = new String(description);
|
||||
}
|
||||
|
||||
public boolean getSearchable() {
|
||||
return searchable;
|
||||
}
|
||||
|
||||
public void setSearchable(boolean searchable) {
|
||||
this.searchable = searchable;
|
||||
}
|
||||
|
||||
public String getGDataEditUri() {
|
||||
return gdataEditUri;
|
||||
}
|
||||
|
||||
public void setGDataEditUri(String editUri) {
|
||||
this.gdataEditUri = new String(editUri);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// Copyright 2009 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
|
||||
import com.google.wireless.gdata.data.Entry;
|
||||
import com.google.wireless.gdata.data.Feed;
|
||||
import com.google.wireless.gdata.data.XmlUtils;
|
||||
import com.google.wireless.gdata.parser.ParseException;
|
||||
import com.google.wireless.gdata.parser.xml.XmlGDataParser;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Parser for XML gdata maps data.
|
||||
*/
|
||||
class XmlMapsGDataParser extends XmlGDataParser {
|
||||
|
||||
public XmlMapsGDataParser(InputStream is, XmlPullParser xpp)
|
||||
throws ParseException {
|
||||
super(is, xpp);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Feed createFeed() {
|
||||
return new Feed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Entry createEntry() {
|
||||
return new MapFeatureEntry();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleExtraElementInFeed(Feed feed) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleExtraLinkInEntry(
|
||||
String rel, String type, String href, Entry entry)
|
||||
throws XmlPullParserException, IOException {
|
||||
if (!(entry instanceof MapFeatureEntry)) {
|
||||
throw new IllegalArgumentException("Expected MapFeatureEntry!");
|
||||
}
|
||||
if (rel.endsWith("#view")) {
|
||||
return;
|
||||
}
|
||||
super.handleExtraLinkInEntry(rel, type, href, entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the current entry in the XML document. Assumes that the parser is
|
||||
* currently pointing just after an <entry>.
|
||||
*
|
||||
* @param plainEntry The entry that will be filled.
|
||||
* @throws XmlPullParserException Thrown if the XML cannot be parsed.
|
||||
* @throws IOException Thrown if the underlying inputstream cannot be read.
|
||||
*/
|
||||
@Override
|
||||
protected void handleEntry(Entry plainEntry)
|
||||
throws XmlPullParserException, IOException, ParseException {
|
||||
XmlPullParser parser = getParser();
|
||||
if (!(plainEntry instanceof MapFeatureEntry)) {
|
||||
throw new IllegalArgumentException("Expected MapFeatureEntry!");
|
||||
}
|
||||
MapFeatureEntry entry = (MapFeatureEntry) plainEntry;
|
||||
|
||||
int eventType = parser.getEventType();
|
||||
entry.setPrivacy("public");
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
switch (eventType) {
|
||||
case XmlPullParser.START_TAG:
|
||||
String name = parser.getName();
|
||||
if ("entry".equals(name)) {
|
||||
// stop parsing here.
|
||||
return;
|
||||
} else if ("id".equals(name)) {
|
||||
entry.setId(XmlUtils.extractChildText(parser));
|
||||
} else if ("title".equals(name)) {
|
||||
entry.setTitle(XmlUtils.extractChildText(parser));
|
||||
} else if ("link".equals(name)) {
|
||||
String rel = parser.getAttributeValue(null /* ns */, "rel");
|
||||
String type = parser.getAttributeValue(null /* ns */, "type");
|
||||
String href = parser.getAttributeValue(null /* ns */, "href");
|
||||
if ("edit".equals(rel)) {
|
||||
entry.setEditUri(href);
|
||||
} else if ("alternate".equals(rel) && "text/html".equals(type)) {
|
||||
entry.setHtmlUri(href);
|
||||
} else {
|
||||
handleExtraLinkInEntry(rel, type, href, entry);
|
||||
}
|
||||
} else if ("summary".equals(name)) {
|
||||
entry.setSummary(XmlUtils.extractChildText(parser));
|
||||
} else if ("content".equals(name)) {
|
||||
StringBuilder contentBuilder = new StringBuilder();
|
||||
int parentDepth = parser.getDepth();
|
||||
while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
|
||||
int etype = parser.next();
|
||||
switch (etype) {
|
||||
case XmlPullParser.START_TAG:
|
||||
contentBuilder.append('<');
|
||||
contentBuilder.append(parser.getName());
|
||||
contentBuilder.append('>');
|
||||
break;
|
||||
case XmlPullParser.TEXT:
|
||||
contentBuilder.append("<![CDATA[");
|
||||
contentBuilder.append(parser.getText());
|
||||
contentBuilder.append("]]>");
|
||||
break;
|
||||
case XmlPullParser.END_TAG:
|
||||
if (parser.getDepth() > parentDepth) {
|
||||
contentBuilder.append("</");
|
||||
contentBuilder.append(parser.getName());
|
||||
contentBuilder.append('>');
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (etype == XmlPullParser.END_TAG
|
||||
&& parser.getDepth() == parentDepth) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
entry.setContent(contentBuilder.toString());
|
||||
} else if ("category".equals(name)) {
|
||||
String category = parser.getAttributeValue(null /* ns */, "term");
|
||||
if (category != null && category.length() > 0) {
|
||||
entry.setCategory(category);
|
||||
}
|
||||
String categoryScheme =
|
||||
parser.getAttributeValue(null /* ns */, "scheme");
|
||||
if (categoryScheme != null && category.length() > 0) {
|
||||
entry.setCategoryScheme(categoryScheme);
|
||||
}
|
||||
} else if ("published".equals(name)) {
|
||||
entry.setPublicationDate(XmlUtils.extractChildText(parser));
|
||||
} else if ("updated".equals(name)) {
|
||||
entry.setUpdateDate(XmlUtils.extractChildText(parser));
|
||||
} else if ("deleted".equals(name)) {
|
||||
entry.setDeleted(true);
|
||||
} else if ("draft".equals(name)) {
|
||||
String draft = XmlUtils.extractChildText(parser);
|
||||
entry.setPrivacy("yes".equals(draft) ? "unlisted" : "public");
|
||||
} else if ("customProperty".equals(name)) {
|
||||
String attrName = parser.getAttributeValue(null, "name");
|
||||
String attrValue = XmlUtils.extractChildText(parser);
|
||||
entry.setAttribute(attrName, attrValue);
|
||||
} else if ("deleted".equals(name)) {
|
||||
entry.setDeleted(true);
|
||||
} else {
|
||||
handleExtraElementInEntry(entry);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
eventType = parser.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
// Copyright 2010 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
|
||||
import com.google.wireless.gdata.client.GDataParserFactory;
|
||||
import com.google.wireless.gdata.data.Entry;
|
||||
import com.google.wireless.gdata.parser.GDataParser;
|
||||
import com.google.wireless.gdata.parser.ParseException;
|
||||
import com.google.wireless.gdata.parser.xml.XmlGDataParser;
|
||||
import com.google.wireless.gdata.parser.xml.XmlParserFactory;
|
||||
import com.google.wireless.gdata.serializer.GDataSerializer;
|
||||
import com.google.wireless.gdata.serializer.xml.XmlEntryGDataSerializer;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Factory of Xml parsers for gdata maps data.
|
||||
*/
|
||||
class XmlMapsGDataParserFactory implements GDataParserFactory {
|
||||
private XmlParserFactory xmlFactory;
|
||||
|
||||
public XmlMapsGDataParserFactory(XmlParserFactory xmlFactory) {
|
||||
this.xmlFactory = xmlFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GDataParser createParser(InputStream is) throws ParseException {
|
||||
is = maybeLogCommunication(is);
|
||||
try {
|
||||
return new XmlGDataParser(is, xmlFactory.createParser());
|
||||
} catch (XmlPullParserException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
@Override
|
||||
public GDataParser createParser(Class cls, InputStream is)
|
||||
throws ParseException {
|
||||
is = maybeLogCommunication(is);
|
||||
try {
|
||||
return createParserForClass(cls, is);
|
||||
} catch (XmlPullParserException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream maybeLogCommunication(InputStream is)
|
||||
throws ParseException {
|
||||
if (MapsClient.LOG_COMMUNICATION) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
byte[] buffer = new byte[2048];
|
||||
try {
|
||||
for (int n = is.read(buffer); n >= 0; n = is.read(buffer)) {
|
||||
String part = new String(buffer, 0, n);
|
||||
builder.append(part);
|
||||
Log.d("Response part", part);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ParseException("Could not read stream", e);
|
||||
}
|
||||
String whole = builder.toString();
|
||||
Log.d("Response", whole);
|
||||
is = new ByteArrayInputStream(whole.getBytes());
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
private GDataParser createParserForClass(
|
||||
Class<? extends Entry> cls, InputStream is)
|
||||
throws ParseException, XmlPullParserException {
|
||||
if (cls == MapFeatureEntry.class) {
|
||||
return new XmlMapsGDataParser(is, xmlFactory.createParser());
|
||||
} else {
|
||||
return new XmlGDataParser(is, xmlFactory.createParser());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GDataSerializer createSerializer(Entry en) {
|
||||
if (en.getClass() == MapFeatureEntry.class) {
|
||||
return new XmlMapsGDataSerializer(xmlFactory, en);
|
||||
} else {
|
||||
return new XmlEntryGDataSerializer(xmlFactory, en);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
// Copyright 2010 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
|
||||
import com.google.wireless.gdata.data.Entry;
|
||||
import com.google.wireless.gdata.data.StringUtils;
|
||||
import com.google.wireless.gdata.parser.ParseException;
|
||||
import com.google.wireless.gdata.parser.xml.XmlGDataParser;
|
||||
import com.google.wireless.gdata.parser.xml.XmlParserFactory;
|
||||
import com.google.wireless.gdata.serializer.xml.XmlEntryGDataSerializer;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Serializer of maps data for GData.
|
||||
*/
|
||||
class XmlMapsGDataSerializer extends XmlEntryGDataSerializer {
|
||||
|
||||
private static final String APP_NAMESPACE = "http://www.w3.org/2007/app";
|
||||
|
||||
private MapFeatureEntry entry;
|
||||
private XmlParserFactory factory;
|
||||
private OutputStream stream;
|
||||
|
||||
public XmlMapsGDataSerializer(XmlParserFactory factory, Entry entry) {
|
||||
super(factory, entry);
|
||||
|
||||
this.factory = factory;
|
||||
this.entry = (MapFeatureEntry) entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(OutputStream out, int format)
|
||||
throws IOException, ParseException {
|
||||
XmlSerializer serializer = null;
|
||||
try {
|
||||
serializer = factory.createSerializer();
|
||||
} catch (XmlPullParserException e) {
|
||||
throw new ParseException("Unable to create XmlSerializer.", e);
|
||||
}
|
||||
|
||||
ByteArrayOutputStream printStream;
|
||||
|
||||
if (MapsClient.LOG_COMMUNICATION) {
|
||||
printStream = new ByteArrayOutputStream();
|
||||
|
||||
serializer.setOutput(printStream, "UTF-8");
|
||||
} else {
|
||||
serializer.setOutput(out, "UTF-8");
|
||||
}
|
||||
|
||||
serializer.startDocument("UTF-8", Boolean.FALSE);
|
||||
|
||||
declareEntryNamespaces(serializer);
|
||||
serializer.startTag(XmlGDataParser.NAMESPACE_ATOM_URI, "entry");
|
||||
|
||||
stream = MapsClient.LOG_COMMUNICATION ? printStream : out;
|
||||
serializeEntryContents(serializer, format);
|
||||
|
||||
serializer.endTag(XmlGDataParser.NAMESPACE_ATOM_URI, "entry");
|
||||
serializer.endDocument();
|
||||
serializer.flush();
|
||||
|
||||
if (MapsClient.LOG_COMMUNICATION) {
|
||||
Log.d("Request", printStream.toString());
|
||||
out.write(printStream.toByteArray());
|
||||
stream = out;
|
||||
}
|
||||
}
|
||||
|
||||
private final void declareEntryNamespaces(XmlSerializer serializer)
|
||||
throws IOException {
|
||||
serializer.setPrefix(
|
||||
"" /* default ns */, XmlGDataParser.NAMESPACE_ATOM_URI);
|
||||
serializer.setPrefix(
|
||||
XmlGDataParser.NAMESPACE_GD, XmlGDataParser.NAMESPACE_GD_URI);
|
||||
declareExtraEntryNamespaces(serializer);
|
||||
}
|
||||
|
||||
private final void serializeEntryContents(XmlSerializer serializer,
|
||||
int format) throws IOException {
|
||||
if (format != FORMAT_CREATE) {
|
||||
serializeId(serializer, entry.getId());
|
||||
}
|
||||
|
||||
serializeTitle(serializer, entry.getTitle());
|
||||
|
||||
if (format != FORMAT_CREATE) {
|
||||
serializeLink(serializer,
|
||||
"edit" /* rel */, entry.getEditUri(), null /* type */);
|
||||
serializeLink(serializer,
|
||||
"alternate" /* rel */, entry.getHtmlUri(), "text/html" /* type */);
|
||||
}
|
||||
|
||||
serializeSummary(serializer, entry.getSummary());
|
||||
serializeContent(serializer, entry.getContent());
|
||||
serializeAuthor(serializer, entry.getAuthor(), entry.getEmail());
|
||||
serializeCategory(serializer,
|
||||
entry.getCategory(), entry.getCategoryScheme());
|
||||
|
||||
if (format == FORMAT_FULL) {
|
||||
serializePublicationDate(serializer, entry.getPublicationDate());
|
||||
}
|
||||
|
||||
if (format != FORMAT_CREATE) {
|
||||
serializeUpdateDate(serializer, entry.getUpdateDate());
|
||||
}
|
||||
|
||||
serializeExtraEntryContents(serializer, format);
|
||||
}
|
||||
|
||||
private static void serializeId(XmlSerializer serializer, String id)
|
||||
throws IOException {
|
||||
if (StringUtils.isEmpty(id)) {
|
||||
return;
|
||||
}
|
||||
serializer.startTag(null /* ns */, "id");
|
||||
serializer.text(id);
|
||||
serializer.endTag(null /* ns */, "id");
|
||||
}
|
||||
|
||||
private static void serializeTitle(XmlSerializer serializer, String title)
|
||||
throws IOException {
|
||||
if (StringUtils.isEmpty(title)) {
|
||||
return;
|
||||
}
|
||||
serializer.startTag(null /* ns */, "title");
|
||||
serializer.text(title);
|
||||
serializer.endTag(null /* ns */, "title");
|
||||
}
|
||||
|
||||
public static void serializeLink(XmlSerializer serializer, String rel,
|
||||
String href, String type) throws IOException {
|
||||
if (StringUtils.isEmpty(href)) {
|
||||
return;
|
||||
}
|
||||
serializer.startTag(null /* ns */, "link");
|
||||
serializer.attribute(null /* ns */, "rel", rel);
|
||||
serializer.attribute(null /* ns */, "href", href);
|
||||
if (!StringUtils.isEmpty(type)) {
|
||||
serializer.attribute(null /* ns */, "type", type);
|
||||
}
|
||||
serializer.endTag(null /* ns */, "link");
|
||||
}
|
||||
|
||||
private static void serializeSummary(XmlSerializer serializer, String summary)
|
||||
throws IOException {
|
||||
if (StringUtils.isEmpty(summary)) {
|
||||
return;
|
||||
}
|
||||
serializer.startTag(null /* ns */, "summary");
|
||||
serializer.text(summary);
|
||||
serializer.endTag(null /* ns */, "summary");
|
||||
}
|
||||
|
||||
private void serializeContent(XmlSerializer serializer, String content)
|
||||
throws IOException {
|
||||
if (content == null) {
|
||||
return;
|
||||
}
|
||||
serializer.startTag(null /* ns */, "content");
|
||||
if (content.contains("</Placemark>")) {
|
||||
serializer.attribute(
|
||||
null /* ns */, "type", "application/vnd.google-earth.kml+xml");
|
||||
serializer.flush();
|
||||
stream.write(content.getBytes());
|
||||
} else {
|
||||
serializer.text(content);
|
||||
}
|
||||
serializer.endTag(null /* ns */, "content");
|
||||
}
|
||||
|
||||
private static void serializeAuthor(XmlSerializer serializer, String author,
|
||||
String email) throws IOException {
|
||||
if (StringUtils.isEmpty(author) || StringUtils.isEmpty(email)) {
|
||||
return;
|
||||
}
|
||||
serializer.startTag(null /* ns */, "author");
|
||||
serializer.startTag(null /* ns */, "name");
|
||||
serializer.text(author);
|
||||
serializer.endTag(null /* ns */, "name");
|
||||
serializer.startTag(null /* ns */, "email");
|
||||
serializer.text(email);
|
||||
serializer.endTag(null /* ns */, "email");
|
||||
serializer.endTag(null /* ns */, "author");
|
||||
}
|
||||
|
||||
private static void serializeCategory(XmlSerializer serializer,
|
||||
String category, String categoryScheme) throws IOException {
|
||||
if (StringUtils.isEmpty(category) && StringUtils.isEmpty(categoryScheme)) {
|
||||
return;
|
||||
}
|
||||
serializer.startTag(null /* ns */, "category");
|
||||
if (!StringUtils.isEmpty(category)) {
|
||||
serializer.attribute(null /* ns */, "term", category);
|
||||
}
|
||||
if (!StringUtils.isEmpty(categoryScheme)) {
|
||||
serializer.attribute(null /* ns */, "scheme", categoryScheme);
|
||||
}
|
||||
serializer.endTag(null /* ns */, "category");
|
||||
}
|
||||
|
||||
private static void serializePublicationDate(XmlSerializer serializer,
|
||||
String publicationDate) throws IOException {
|
||||
if (StringUtils.isEmpty(publicationDate)) {
|
||||
return;
|
||||
}
|
||||
serializer.startTag(null /* ns */, "published");
|
||||
serializer.text(publicationDate);
|
||||
serializer.endTag(null /* ns */, "published");
|
||||
}
|
||||
|
||||
private static void serializeUpdateDate(XmlSerializer serializer,
|
||||
String updateDate) throws IOException {
|
||||
if (StringUtils.isEmpty(updateDate)) {
|
||||
return;
|
||||
}
|
||||
serializer.startTag(null /* ns */, "updated");
|
||||
serializer.text(updateDate);
|
||||
serializer.endTag(null /* ns */, "updated");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serializeExtraEntryContents(XmlSerializer serializer,
|
||||
int format) throws IOException {
|
||||
Map<String, String> attrs = entry.getAllAttributes();
|
||||
for (Map.Entry<String, String> attr : attrs.entrySet()) {
|
||||
serializer.startTag("http://schemas.google.com/g/2005", "customProperty");
|
||||
serializer.attribute(null, "name", attr.getKey());
|
||||
serializer.text(attr.getValue());
|
||||
serializer.endTag("http://schemas.google.com/g/2005", "customProperty");
|
||||
}
|
||||
String privacy = entry.getPrivacy();
|
||||
if (!StringUtils.isEmpty(privacy)) {
|
||||
serializer.setPrefix("app", APP_NAMESPACE);
|
||||
if ("public".equals(privacy)) {
|
||||
serializer.startTag(APP_NAMESPACE, "control");
|
||||
serializer.startTag(APP_NAMESPACE, "draft");
|
||||
serializer.text("no");
|
||||
serializer.endTag(APP_NAMESPACE, "draft");
|
||||
serializer.endTag(APP_NAMESPACE, "control");
|
||||
}
|
||||
if ("unlisted".equals(privacy)) {
|
||||
serializer.startTag(APP_NAMESPACE, "control");
|
||||
serializer.startTag(APP_NAMESPACE, "draft");
|
||||
serializer.text("yes");
|
||||
serializer.endTag(APP_NAMESPACE, "draft");
|
||||
serializer.endTag(APP_NAMESPACE, "control");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io.sendtogoogle;
|
||||
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.View;
|
||||
import android.widget.ListView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Creates the dialog used to display the results of sending track data to
|
||||
* Google. The dialog lists the services to which data was uploaded, along
|
||||
* with an indicator of success/failure. If possible, a button is displayed
|
||||
* offering to share the uploaded track.
|
||||
*
|
||||
* Implementation note: This class is a factory, rather than a {@link Dialog}
|
||||
* subclass, because alert dialogs have to be created with
|
||||
* {@link AlertDialog.Builder}. Attempts to subclass {@link AlertDialog}
|
||||
* directly have been unsuccessful, as {@link AlertDialog.Builder} uses a
|
||||
* private subclass to implement most of the interesting behavior.
|
||||
*
|
||||
* @author Matthew Simmons
|
||||
*/
|
||||
public class ResultDialogFactory {
|
||||
private ResultDialogFactory() {}
|
||||
|
||||
/**
|
||||
* Create a send-to-Google result dialog. The caller is responsible for
|
||||
* showing it.
|
||||
*
|
||||
* @param activity the activity associated with the dialog
|
||||
* @param results the results to be displayed in the dialog
|
||||
* @param onOkClickListener the listener to invoke if the OK button is
|
||||
* clicked
|
||||
* @param onShareClickListener the listener to invoke if the Share button is
|
||||
* clicked. If no share listener is provided, the Share button will not
|
||||
* be displayed.
|
||||
* @return the created dialog
|
||||
*/
|
||||
public static AlertDialog makeDialog(Activity activity, List<SendResult> results,
|
||||
DialogInterface.OnClickListener onOkClickListener,
|
||||
DialogInterface.OnClickListener onShareClickListener) {
|
||||
boolean success = true;
|
||||
for (SendResult result : results) {
|
||||
if (!result.isSuccess()) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity)
|
||||
.setView(makeDialogContent(activity, results, success));
|
||||
|
||||
if (success) {
|
||||
builder.setTitle(R.string.success);
|
||||
builder.setIcon(android.R.drawable.ic_dialog_info);
|
||||
} else {
|
||||
builder.setTitle(R.string.error);
|
||||
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
}
|
||||
|
||||
builder.setPositiveButton(activity.getString(R.string.ok), onOkClickListener);
|
||||
if (onShareClickListener != null) {
|
||||
builder.setNeutralButton(activity.getString(R.string.share_track),
|
||||
onShareClickListener);
|
||||
}
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
private static View makeDialogContent(Activity activity, List<SendResult> results,
|
||||
boolean success) {
|
||||
ResultListAdapter resultListAdapter = new ResultListAdapter(activity,
|
||||
R.layout.send_to_google_result_list_item, results);
|
||||
|
||||
View content = activity.getLayoutInflater().inflate(R.layout.send_to_google_result, null);
|
||||
ListView resultList = (ListView) content.findViewById(R.id.send_to_google_result_list);
|
||||
resultList.setAdapter(resultListAdapter);
|
||||
|
||||
content.findViewById(R.id.send_to_google_result_comment)
|
||||
.setVisibility(success ? View.VISIBLE : View.GONE);
|
||||
content.findViewById(R.id.send_to_google_result_error)
|
||||
.setVisibility(success ? View.GONE : View.VISIBLE);
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io.sendtogoogle;
|
||||
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class implements the {@link ListAdapter} used by the send-to-Google
|
||||
* result dialog created by {@link ResultDialogFactory}. It generates views
|
||||
* for each entry in an array of {@link SendResult} instances.
|
||||
* @author Matthew Simmons
|
||||
*/
|
||||
class ResultListAdapter extends ArrayAdapter<SendResult> {
|
||||
public ResultListAdapter(Context context, int textViewResourceId, List<SendResult> results) {
|
||||
super(context, textViewResourceId, results);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View view = convertView;
|
||||
if (view == null) {
|
||||
LayoutInflater inflater
|
||||
= (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
view = inflater.inflate(R.layout.send_to_google_result_list_item, null);
|
||||
}
|
||||
|
||||
SendResult result = getItem(position);
|
||||
setImage(view, result.isSuccess() ? R.drawable.success : R.drawable.failure);
|
||||
setName(view, result.getType().getServiceName());
|
||||
setUrl(view, result.getType().getServiceUrl());
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areAllItemsEnabled() {
|
||||
// We don't want the displayed items to be clickable
|
||||
return false;
|
||||
}
|
||||
|
||||
// The following protected methods exist to be overridden for testing
|
||||
// purposes. Doing so insulates the test class from the details of the
|
||||
// layout.
|
||||
|
||||
protected void setImage(View content, int drawableId) {
|
||||
ImageView imageView = (ImageView) content.findViewById(R.id.send_to_google_result_icon);
|
||||
imageView.setImageDrawable(getContext().getResources().getDrawable(drawableId));
|
||||
}
|
||||
|
||||
protected void setName(View content, int nameId) {
|
||||
setTextViewText(content, R.id.send_to_google_result_name, nameId);
|
||||
}
|
||||
|
||||
protected void setUrl(View content, int urlId) {
|
||||
setTextViewText(content, R.id.send_to_google_result_url, urlId);
|
||||
}
|
||||
|
||||
private void setTextViewText(View content, int viewId, int textId) {
|
||||
TextView textView = (TextView) content.findViewById(viewId);
|
||||
textView.setText(getContext().getString(textId));
|
||||
}
|
||||
};
|
||||
+9
-8
@@ -1,20 +1,23 @@
|
||||
/*
|
||||
* 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;
|
||||
package com.google.android.apps.mytracks.io.sendtogoogle;
|
||||
|
||||
import com.google.android.apps.mytracks.DialogManager;
|
||||
import com.google.android.apps.mytracks.MyTracks;
|
||||
import com.google.android.apps.mytracks.MyTracksSettings;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Dialog;
|
||||
@@ -37,7 +40,7 @@ import android.widget.RadioGroup;
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
public class SendToGoogleDialog extends Dialog {
|
||||
public class SendDialog extends Dialog {
|
||||
|
||||
private RadioGroup groupMyMaps;
|
||||
private RadioButton createNewMapRadioButton;
|
||||
@@ -49,7 +52,7 @@ public class SendToGoogleDialog extends Dialog {
|
||||
private RadioButton sendStatsAndPointsRadioButton;
|
||||
private Button sendButton;
|
||||
|
||||
public SendToGoogleDialog(Context context) {
|
||||
public SendDialog(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@@ -81,8 +84,6 @@ public class SendToGoogleDialog extends Dialog {
|
||||
sendToFusionTablesCheckBox.isChecked() ||
|
||||
sendToDocsCheckBox.isChecked());
|
||||
groupMyMaps.setVisibility(sendToMyMapsCheckBox.isChecked() ? View.VISIBLE : View.INVISIBLE);
|
||||
//groupDocs.setVisibility(
|
||||
// sendToDocsCheckBox.isChecked() ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io.sendtogoogle;
|
||||
|
||||
/**
|
||||
* This class represents the the result of the uploading of track data to a
|
||||
* single service.
|
||||
*
|
||||
* @author Matthew Simmons
|
||||
*/
|
||||
public class SendResult {
|
||||
private final SendType type;
|
||||
private final boolean success;
|
||||
|
||||
/**
|
||||
* @param type the service to which track data was uploaded
|
||||
* @param success true if the uploading succeeded
|
||||
*/
|
||||
public SendResult(SendType type, boolean success) {
|
||||
this.type = type;
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
/** Returns the service to which the track data was uploaded */
|
||||
public SendType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/** Returns true if the uploading succeeded */
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io.sendtogoogle;
|
||||
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
/**
|
||||
* Enumerates the services to which we can upload track data.
|
||||
*
|
||||
* @author Matthew Simmons
|
||||
*/
|
||||
public enum SendType {
|
||||
MYMAPS(R.string.sendtogoogle_google_mymaps,
|
||||
R.string.sendtogoogle_google_mymaps_url),
|
||||
FUSION_TABLES(R.string.sendtogoogle_google_fusiontables,
|
||||
R.string.sendtogoogle_google_fusiontables_url),
|
||||
DOCS(R.string.sendtogoogle_google_docs,
|
||||
R.string.sendtogoogle_google_docs_url);
|
||||
|
||||
private int serviceName;
|
||||
private int serviceUrl;
|
||||
|
||||
private SendType(int serviceName, int serviceUrl) {
|
||||
this.serviceName = serviceName;
|
||||
this.serviceUrl = serviceUrl;
|
||||
}
|
||||
|
||||
/** Returns the resource ID for the printable (short) name of the service */
|
||||
public int getServiceName() {
|
||||
return serviceName;
|
||||
}
|
||||
|
||||
/** Returns the resource ID for the service's URL */
|
||||
public int getServiceUrl() {
|
||||
return serviceUrl;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.google.android.apps.mytracks.services;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.MyTracksSettings;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
@@ -99,7 +101,7 @@ public class StatusAnnouncerTask implements PeriodicTask {
|
||||
* Called when the TTS engine is initialized.
|
||||
*/
|
||||
protected void onTtsInit(int status) {
|
||||
Log.i(Constants.TAG, "TrackRecordingService.TTS init: " + status);
|
||||
Log.i(TAG, "TrackRecordingService.TTS init: " + status);
|
||||
this.ready = status == TextToSpeech.SUCCESS;
|
||||
|
||||
if (ready) {
|
||||
@@ -111,7 +113,7 @@ public class StatusAnnouncerTask implements PeriodicTask {
|
||||
languageAvailability == TextToSpeech.LANG_NOT_SUPPORTED) {
|
||||
// English is probably supported.
|
||||
// TODO: Somehow use announcement strings from English too.
|
||||
Log.w(Constants.TAG, "Default language not available, using English.");
|
||||
Log.w(TAG, "Default language not available, using English.");
|
||||
speechLanguage = Locale.ENGLISH;
|
||||
}
|
||||
tts.setLanguage(speechLanguage);
|
||||
@@ -128,8 +130,26 @@ public class StatusAnnouncerTask implements PeriodicTask {
|
||||
*/
|
||||
@Override
|
||||
public void run(TrackRecordingService service) {
|
||||
if (service == null) {
|
||||
Log.e(TAG, "StatusAnnouncer TrackRecordingService not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
runWithStatistics(service.getTripStatistics());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method exists as a convenience for testing code, allowing said code
|
||||
* to avoid needing to instantiate an entire {@link TrackRecordingService}
|
||||
* just to test the announcer.
|
||||
*/
|
||||
protected void runWithStatistics(TripStatistics statistics) {
|
||||
if (statistics == null) {
|
||||
Log.e(TAG, "StatusAnnouncer stats not initialized.");
|
||||
return;
|
||||
}
|
||||
if (!ready || tts == null) {
|
||||
Log.e(Constants.TAG, "StatusAnnouncer Tts not ready.");
|
||||
Log.e(TAG, "StatusAnnouncer Tts not ready.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -139,12 +159,7 @@ public class StatusAnnouncerTask implements PeriodicTask {
|
||||
return;
|
||||
}
|
||||
|
||||
if (service == null || service.getTripStatistics() == null) {
|
||||
Log.e(Constants.TAG, "StatusAnnouncer stats not initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
String announcement = getAnnouncement(service.getTripStatistics());
|
||||
String announcement = getAnnouncement(statistics);
|
||||
Log.d(Constants.TAG, "Announcement: " + announcement);
|
||||
speakAnnouncment(announcement);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks.services;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
import static com.google.android.apps.mytracks.Constants.RESUME_TRACK_EXTRA_NAME;
|
||||
|
||||
import com.google.android.apps.mytracks.MyTracks;
|
||||
@@ -269,7 +270,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
// Insert failed, most likely because of SqlLite error code 5
|
||||
// (SQLite_BUSY). This is expected to happen extremely rarely (if our
|
||||
// listener gets invoked twice at about the same time).
|
||||
Log.w(Constants.TAG,
|
||||
Log.w(TAG,
|
||||
"Caught SQLiteException: " + e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
@@ -314,15 +315,15 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
try {
|
||||
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||
if (pm == null) {
|
||||
Log.e(Constants.TAG,
|
||||
Log.e(TAG,
|
||||
"TrackRecordingService: Power manager not found!");
|
||||
return;
|
||||
}
|
||||
if (wakeLock == null) {
|
||||
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
|
||||
Constants.TAG);
|
||||
TAG);
|
||||
if (wakeLock == null) {
|
||||
Log.e(Constants.TAG,
|
||||
Log.e(TAG,
|
||||
"TrackRecordingService: Could not create wake lock (null).");
|
||||
return;
|
||||
}
|
||||
@@ -330,12 +331,12 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
if (!wakeLock.isHeld()) {
|
||||
wakeLock.acquire();
|
||||
if (!wakeLock.isHeld()) {
|
||||
Log.e(Constants.TAG,
|
||||
Log.e(TAG,
|
||||
"TrackRecordingService: Could not acquire wake lock.");
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(Constants.TAG,
|
||||
Log.e(TAG,
|
||||
"TrackRecordingService: Caught unexpected exception: "
|
||||
+ e.getMessage(), e);
|
||||
}
|
||||
@@ -376,11 +377,11 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
|
||||
public void registerLocationListener() {
|
||||
if (locationManager == null) {
|
||||
Log.e(Constants.TAG,
|
||||
Log.e(TAG,
|
||||
"TrackRecordingService: Do not have any location manager.");
|
||||
return;
|
||||
}
|
||||
Log.d(Constants.TAG,
|
||||
Log.d(TAG,
|
||||
"Preparing to register location listener w/ TrackRecordingService...");
|
||||
try {
|
||||
long desiredInterval = locationListenerPolicy.getDesiredPollingInterval();
|
||||
@@ -390,23 +391,23 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
// , 0 /* minDistance, get all updates to properly time pauses */
|
||||
TrackRecordingService.this);
|
||||
currentRecordingInterval = desiredInterval;
|
||||
Log.d(Constants.TAG,
|
||||
Log.d(TAG,
|
||||
"...location listener now registered w/ TrackRecordingService @ "
|
||||
+ currentRecordingInterval);
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(Constants.TAG,
|
||||
Log.e(TAG,
|
||||
"Could not register location listener: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterLocationListener() {
|
||||
if (locationManager == null) {
|
||||
Log.e(Constants.TAG,
|
||||
Log.e(TAG,
|
||||
"TrackRecordingService: Do not have any location manager.");
|
||||
return;
|
||||
}
|
||||
locationManager.removeUpdates(this);
|
||||
Log.d(Constants.TAG,
|
||||
Log.d(TAG,
|
||||
"Location listener now unregistered w/ TrackRecordingService.");
|
||||
}
|
||||
|
||||
@@ -419,7 +420,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
}
|
||||
|
||||
private void restoreStats(Track track) {
|
||||
Log.d(Constants.TAG,
|
||||
Log.d(TAG,
|
||||
"Restoring stats of track with ID: " + track.getId());
|
||||
|
||||
TripStatistics stats = track.getStatistics();
|
||||
@@ -465,10 +466,10 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
statsBuilder.pauseAt(stats.getStopTime());
|
||||
statsBuilder.resumeAt(System.currentTimeMillis());
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Could not get track points cursor.");
|
||||
Log.e(TAG, "Could not get track points cursor.");
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(Constants.TAG, "Error while restoring track.", e);
|
||||
Log.e(TAG, "Error while restoring track.", e);
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
@@ -493,12 +494,12 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
}
|
||||
|
||||
private void onLocationChangedAsync(Location location) {
|
||||
Log.d(Constants.TAG, "TrackRecordingService.onLocationChanged");
|
||||
Log.d(TAG, "TrackRecordingService.onLocationChanged");
|
||||
|
||||
try {
|
||||
// Don't record if the service has been asked to pause recording:
|
||||
if (!isRecording) {
|
||||
Log.w(Constants.TAG,
|
||||
Log.w(TAG,
|
||||
"Not recording because recording has been paused.");
|
||||
return;
|
||||
}
|
||||
@@ -506,14 +507,14 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
// This should never happen, but just in case (we really don't want the
|
||||
// service to crash):
|
||||
if (location == null) {
|
||||
Log.w(Constants.TAG,
|
||||
Log.w(TAG,
|
||||
"Location changed, but location is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't record if the accuracy is too bad:
|
||||
if (location.getAccuracy() > minRequiredAccuracy) {
|
||||
Log.d(Constants.TAG,
|
||||
Log.d(TAG,
|
||||
"Not recording. Bad accuracy.");
|
||||
return;
|
||||
}
|
||||
@@ -521,7 +522,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
// At least one track must be available for appending points:
|
||||
recordingTrack = getRecordingTrack();
|
||||
if (recordingTrack == null) {
|
||||
Log.d(Constants.TAG,
|
||||
Log.d(TAG,
|
||||
"Not recording. No track to append to available.");
|
||||
return;
|
||||
}
|
||||
@@ -555,7 +556,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
// maxRecordingDistance = 0
|
||||
if (distanceToLast == 0 && !hasSensorData) {
|
||||
if (isMoving) {
|
||||
Log.d(Constants.TAG, "Found two identical locations.");
|
||||
Log.d(TAG, "Found two identical locations.");
|
||||
isMoving = false;
|
||||
if (lastLocation != null && lastRecordedLocation != null
|
||||
&& !lastRecordedLocation.equals(lastLocation)) {
|
||||
@@ -570,7 +571,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
lastRecordedLocationId++;
|
||||
}
|
||||
} else {
|
||||
Log.d(Constants.TAG,
|
||||
Log.d(TAG,
|
||||
"Not recording. More than two identical locations.");
|
||||
}
|
||||
} else if (distanceToLastRecorded > minRecordingDistance
|
||||
@@ -595,7 +596,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
&& recordingTrack.getStartId() >= 0;
|
||||
if (startNewSegment) {
|
||||
// Insert a separator point to indicate start of new track:
|
||||
Log.d(Constants.TAG, "Inserting a separator.");
|
||||
Log.d(TAG, "Inserting a separator.");
|
||||
Location separator = new Location(Constants.GPS_PROVIDER);
|
||||
separator.setLongitude(0);
|
||||
separator.setLatitude(100);
|
||||
@@ -608,7 +609,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Log.d(Constants.TAG, String.format(
|
||||
Log.d(TAG, String.format(
|
||||
"Not recording. Distance to last recorded point (%f m) is less than"
|
||||
+ " %d m.", distanceToLastRecorded, minRecordingDistance));
|
||||
// Return here so that the location is NOT recorded as the last location.
|
||||
@@ -616,11 +617,11 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
}
|
||||
} catch (Error e) {
|
||||
// Probably important enough to rethrow.
|
||||
Log.e(Constants.TAG, "Error in onLocationChanged", e);
|
||||
Log.e(TAG, "Error in onLocationChanged", e);
|
||||
throw e;
|
||||
} catch (RuntimeException e) {
|
||||
// Safe usually to trap exceptions.
|
||||
Log.e(Constants.TAG,
|
||||
Log.e(TAG,
|
||||
"Trapping exception in onLocationChanged", e);
|
||||
throw e;
|
||||
}
|
||||
@@ -657,7 +658,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
Log.d(Constants.TAG, "TrackRecordingService.onCreate");
|
||||
Log.d(TAG, "TrackRecordingService.onCreate");
|
||||
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
||||
notificationManager =
|
||||
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
@@ -682,7 +683,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
} else {
|
||||
if (recordingTrackId != -1) {
|
||||
// Make sure we have consistent state in shared preferences.
|
||||
Log.w(Constants.TAG, "TrackRecordingService.onCreate: "
|
||||
Log.w(TAG, "TrackRecordingService.onCreate: "
|
||||
+ "Resetting an orphaned recording track = " + recordingTrackId);
|
||||
}
|
||||
prefManager.setRecordingTrack(recordingTrackId = -1);
|
||||
@@ -697,7 +698,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
* the announcements, otherwise this method is no-op.
|
||||
*/
|
||||
private void setUpAnnouncer() {
|
||||
Log.d(Constants.TAG, "TrackRecordingService.setUpAnnouncer: "
|
||||
Log.d(TAG, "TrackRecordingService.setUpAnnouncer: "
|
||||
+ announcementExecuter);
|
||||
if (announcementFrequency != -1 && recordingTrackId != -1) {
|
||||
handler.post(new Runnable() {
|
||||
@@ -723,7 +724,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
}
|
||||
|
||||
private void shutdownAnnouncer() {
|
||||
Log.d(Constants.TAG, "TrackRecordingService.shutdownAnnouncer: "
|
||||
Log.d(TAG, "TrackRecordingService.shutdownAnnouncer: "
|
||||
+ announcementExecuter);
|
||||
if (announcementExecuter != null) {
|
||||
try {
|
||||
@@ -736,7 +737,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
Log.d(Constants.TAG, "TrackRecordingService.onDestroy");
|
||||
Log.d(TAG, "TrackRecordingService.onDestroy");
|
||||
|
||||
isRecording = false;
|
||||
showNotification();
|
||||
@@ -770,19 +771,19 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
Log.d(Constants.TAG, "TrackRecordingService.onBind");
|
||||
Log.d(TAG, "TrackRecordingService.onBind");
|
||||
return binder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onUnbind(Intent intent) {
|
||||
Log.d(Constants.TAG, "TrackRecordingService.onUnbind");
|
||||
Log.d(TAG, "TrackRecordingService.onUnbind");
|
||||
return super.onUnbind(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stopService(Intent name) {
|
||||
Log.d(Constants.TAG, "TrackRecordingService.stopService");
|
||||
Log.d(TAG, "TrackRecordingService.stopService");
|
||||
unregisterLocationListener();
|
||||
return super.stopService(name);
|
||||
}
|
||||
@@ -799,17 +800,17 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
}
|
||||
|
||||
private void handleStartCommand(Intent intent, int startId) {
|
||||
Log.d(Constants.TAG,
|
||||
Log.d(TAG,
|
||||
"TrackRecordingService.handleStartCommand: " + startId);
|
||||
|
||||
// Check if called on phone reboot with resume intent.
|
||||
if (intent != null &&
|
||||
intent.getBooleanExtra(RESUME_TRACK_EXTRA_NAME, false)) {
|
||||
Log.d(Constants.TAG, "TrackRecordingService: requested resume");
|
||||
Log.d(TAG, "TrackRecordingService: requested resume");
|
||||
|
||||
// Make sure that the current track exists and is fresh enough.
|
||||
if (recordingTrack == null || !shouldResumeTrack(recordingTrack)) {
|
||||
Log.i(Constants.TAG,
|
||||
Log.i(TAG,
|
||||
"TrackRecordingService: Not resuming, because the previous track ("
|
||||
+ recordingTrack + ") doesn't exist or is too old");
|
||||
isRecording = false;
|
||||
@@ -818,19 +819,19 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(Constants.TAG, "TrackRecordingService: resuming");
|
||||
Log.i(TAG, "TrackRecordingService: resuming");
|
||||
}
|
||||
}
|
||||
|
||||
private void setAutoResumeTrackRetries(
|
||||
SharedPreferences sharedPreferences, int retryAttempts) {
|
||||
Log.d(Constants.TAG,
|
||||
Log.d(TAG,
|
||||
"Updating auto-resume retry attempts to: " + retryAttempts);
|
||||
prefManager.setAutoResumeTrackCurrentRetry(retryAttempts);
|
||||
}
|
||||
|
||||
private boolean shouldResumeTrack(Track track) {
|
||||
Log.d(Constants.TAG, "shouldResumeTrack: autoResumeTrackTimeout = "
|
||||
Log.d(TAG, "shouldResumeTrack: autoResumeTrackTimeout = "
|
||||
+ autoResumeTrackTimeout);
|
||||
|
||||
// Check if we haven't exceeded the maximum number of retry attempts.
|
||||
@@ -838,11 +839,11 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||
int retries = sharedPreferences.getInt(
|
||||
getString(R.string.auto_resume_track_current_retry_key), 0);
|
||||
Log.d(Constants.TAG,
|
||||
Log.d(TAG,
|
||||
"shouldResumeTrack: Attempting to auto-resume the track ("
|
||||
+ (retries + 1) + "/" + MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS + ")");
|
||||
if (retries >= MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS) {
|
||||
Log.i(Constants.TAG,
|
||||
Log.i(TAG,
|
||||
"shouldResumeTrack: Not resuming because exceeded the maximum "
|
||||
+ "number of auto-resume retries");
|
||||
return false;
|
||||
@@ -854,12 +855,12 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
// Check for special cases.
|
||||
if (autoResumeTrackTimeout == 0) {
|
||||
// Never resume.
|
||||
Log.d(Constants.TAG,
|
||||
Log.d(TAG,
|
||||
"shouldResumeTrack: Auto-resume disabled (never resume)");
|
||||
return false;
|
||||
} else if (autoResumeTrackTimeout == -1) {
|
||||
// Always resume.
|
||||
Log.d(Constants.TAG,
|
||||
Log.d(TAG,
|
||||
"shouldResumeTrack: Auto-resume forced (always resume)");
|
||||
return true;
|
||||
}
|
||||
@@ -867,7 +868,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
// Check if the last modified time is within the acceptable range.
|
||||
long lastModified =
|
||||
track.getStatistics() != null ? track.getStatistics().getStopTime() : 0;
|
||||
Log.d(Constants.TAG,
|
||||
Log.d(TAG,
|
||||
"shouldResumeTrack: lastModified = " + lastModified
|
||||
+ ", autoResumeTrackTimeout: " + autoResumeTrackTimeout);
|
||||
return lastModified > 0 && System.currentTimeMillis() - lastModified <=
|
||||
@@ -897,7 +898,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
}
|
||||
wpt.setTrackId(recordingTrackId);
|
||||
wpt.setLength(length);
|
||||
if (lastValidLocation == null
|
||||
if (lastLocation == null
|
||||
|| statsBuilder == null || statsBuilder.getStatistics() == null) {
|
||||
// A null location is ok, and expected on track start.
|
||||
// Make it an impossible location.
|
||||
@@ -1056,11 +1057,11 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
public byte[] getSensorData() {
|
||||
checkService();
|
||||
if (service.sensorManager == null) {
|
||||
Log.d(Constants.TAG, "No sensor manager for data.");
|
||||
Log.d(TAG, "No sensor manager for data.");
|
||||
return null;
|
||||
}
|
||||
if (service.sensorManager.getSensorDataSet() == null) {
|
||||
Log.d(Constants.TAG, "Sensor data set is null.");
|
||||
Log.d(TAG, "Sensor data set is null.");
|
||||
return null;
|
||||
}
|
||||
return service.sensorManager.getSensorDataSet().toByteArray();
|
||||
@@ -1070,7 +1071,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
public int getSensorState() {
|
||||
checkService();
|
||||
if (service.sensorManager == null) {
|
||||
Log.d(Constants.TAG, "No sensor manager for data.");
|
||||
Log.d(TAG, "No sensor manager for data.");
|
||||
return Sensor.SensorState.NONE.getNumber();
|
||||
}
|
||||
return service.sensorManager.getSensorState().getNumber();
|
||||
@@ -1078,7 +1079,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
}
|
||||
|
||||
public long startNewTrack() {
|
||||
Log.d(Constants.TAG, "TrackRecordingService.startNewTrack");
|
||||
Log.d(TAG, "TrackRecordingService.startNewTrack");
|
||||
if (recordingTrackId != -1 || isRecording) {
|
||||
throw new IllegalStateException("A track is already in progress!");
|
||||
}
|
||||
@@ -1128,7 +1129,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
}
|
||||
|
||||
private void endCurrentTrack() {
|
||||
Log.d(Constants.TAG, "TrackRecordingService.endCurrentTrack");
|
||||
Log.d(TAG, "TrackRecordingService.endCurrentTrack");
|
||||
if (recordingTrackId == -1 || !isRecording) {
|
||||
throw new IllegalStateException("No recording track in progress!");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user