Import kmz or kml tracks

This commit is contained in:
Jimmy Shih
2013-09-04 13:44:39 -07:00
commit e4f6be4938
1119 changed files with 74983 additions and 0 deletions
@@ -0,0 +1,135 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.sendtogoogle;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.os.Bundle;
/**
* The abstract class for activities sending a track to Google.
* <p>
* The activity gets recreated when the screen rotates. To support the activity
* displaying a progress dialog, we do the following:
* <ul>
* <li>use one instance of an AyncTask to send the track</li>
* <li>save that instance as the last non configuration instance of the activity
* </li>
* <li>when a new activity is created, pass the activity to the AsyncTask so
* that the AsyncTask can update the progress dialog of the activity</li>
* </ul>
*
* @author Jimmy Shih
*/
public abstract class AbstractSendActivity extends Activity {
private static final int DIALOG_PROGRESS_ID = 0;
protected SendRequest sendRequest;
private AbstractSendAsyncTask asyncTask;
private ProgressDialog progressDialog;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sendRequest = getIntent().getParcelableExtra(SendRequest.SEND_REQUEST_KEY);
Object retained = getLastNonConfigurationInstance();
if (retained instanceof AbstractSendAsyncTask) {
asyncTask = (AbstractSendAsyncTask) retained;
asyncTask.setActivity(this);
} else {
asyncTask = createAsyncTask();
asyncTask.execute();
}
}
@Override
public Object onRetainNonConfigurationInstance() {
asyncTask.setActivity(null);
return asyncTask;
}
@Override
protected Dialog onCreateDialog(int id) {
if (id != DIALOG_PROGRESS_ID) {
return null;
}
progressDialog = DialogUtils.createHorizontalProgressDialog(
this, R.string.send_google_progress_message, new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
asyncTask.cancel(true);
startNextActivity(false, true);
}
}, getServiceName());
return progressDialog;
}
/**
* Invokes when the associated AsyncTask completes.
*
* @param success true if the AsyncTask is successful
*/
public void onAsyncTaskCompleted(boolean success, String shareUrl) {
sendRequest.setShareUrl(shareUrl);
startNextActivity(success, false);
}
/**
* Shows the progress dialog.
*/
public void showProgressDialog() {
showDialog(DIALOG_PROGRESS_ID);
}
/**
* Sets the progress dialog value.
*
* @param value the dialog value
*/
public void setProgressDialogValue(int value) {
if (progressDialog != null) {
progressDialog.setIndeterminate(false);
progressDialog.setProgress(value);
progressDialog.setMax(100);
}
}
/**
* Creates the AsyncTask.
*/
protected abstract AbstractSendAsyncTask createAsyncTask();
/**
* Gets the service name.
*/
protected abstract String getServiceName();
/**
* Starts the next activity.
*
* @param success true if this activity is successful
* @param isCancel true if it is a cancel request
*/
protected abstract void startNextActivity(boolean success, boolean isCancel);
}
@@ -0,0 +1,143 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.sendtogoogle;
import android.os.AsyncTask;
/**
* The abstract class for AsyncTasks sending a track to Google.
*
* @author Jimmy Shih
*/
public abstract class AbstractSendAsyncTask extends AsyncTask<Void, Integer, Boolean> {
/**
* The activity associated with this AsyncTask.
*/
private AbstractSendActivity activity;
/**
* True if the AsyncTask result is success.
*/
private boolean success;
/**
* The share url from the AsyncTask.
*/
protected String shareUrl;
/**
* True if the AsyncTask has completed.
*/
private boolean completed;
/**
* True if can retry the AsyncTask.
*/
private boolean canRetry;
/**
* Creates an AsyncTask.
*
* @param activity the activity currently associated with this AsyncTask
*/
public AbstractSendAsyncTask(AbstractSendActivity activity) {
this.activity = activity;
success = false;
shareUrl = null;
completed = false;
canRetry = true;
}
/**
* Sets the current activity associated with this AyncTask.
*
* @param activity the current activity, can be null
*/
public void setActivity(AbstractSendActivity activity) {
this.activity = activity;
if (completed && activity != null) {
activity.onAsyncTaskCompleted(success, shareUrl);
}
}
@Override
protected void onPreExecute() {
activity.showProgressDialog();
}
@Override
protected Boolean doInBackground(Void... params) {
try {
return performTask();
} finally {
closeConnection();
}
}
@Override
protected void onProgressUpdate(Integer... values) {
if (activity != null) {
activity.setProgressDialogValue(values[0]);
}
}
@Override
protected void onPostExecute(Boolean result) {
success = result;
completed = true;
if (activity != null) {
activity.onAsyncTaskCompleted(success, shareUrl);
}
}
/**
* Retries the task. First, invalidates the auth token. If can retry, invokes
* {@link #performTask()}. Returns false if cannot retry.
*
* @return the result of the retry.
*/
protected boolean retryTask() {
if (isCancelled()) {
return false;
}
invalidateToken();
if (canRetry) {
canRetry = false;
return performTask();
}
return false;
}
/**
* Closes any AsyncTask connection.
*/
protected abstract void closeConnection();
/**
* Performs the AsyncTask.
*
* @return true if success
*/
protected abstract boolean performTask();
/**
* Invalidates the auth token.
*/
protected abstract void invalidateToken();
}
@@ -0,0 +1,240 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.sendtogoogle;
import android.accounts.Account;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Send request states for sending a track to Google Drive, Google Maps, Google
* Fusion Tables, and Google Spreadsheets.
*
* @author Jimmy Shih
*/
public class SendRequest implements Parcelable {
public static final String SEND_REQUEST_KEY = "sendRequest";
private long trackId = -1L;
private boolean sendDrive = false;
private boolean sendMaps = false;
private boolean sendFusionTables = false;
private boolean sendSpreadsheets = false;
private boolean driveSync = false; // to enable Drive sync
private boolean driveSyncConfirm = false;
private boolean driveShare = false; // to enable Drive share
private String driveShareEmails = null; // for driveShare, emails to share
private boolean driveSharePublic = false; // for driveShare, share as public
private Account account = null;
private boolean driveSuccess = false;
private boolean mapsSuccess = false;
private boolean spreadsheetsSuccess = false;
private boolean fusionTablesSuccess = false;
private String shareUrl = null;
/**
* Creates a new send request.
*
* @param trackId the track id
*/
public SendRequest(long trackId) {
this.trackId = trackId;
}
public long getTrackId() {
return trackId;
}
public boolean isSendDrive() {
return sendDrive;
}
public void setSendDrive(boolean sendDrive) {
this.sendDrive = sendDrive;
}
public boolean isSendMaps() {
return sendMaps;
}
public void setSendMaps(boolean sendMaps) {
this.sendMaps = sendMaps;
}
public boolean isSendFusionTables() {
return sendFusionTables;
}
public void setSendFusionTables(boolean sendFusionTables) {
this.sendFusionTables = sendFusionTables;
}
public boolean isSendSpreadsheets() {
return sendSpreadsheets;
}
public void setSendSpreadsheets(boolean sendSpreadsheets) {
this.sendSpreadsheets = sendSpreadsheets;
}
public boolean isDriveSync() {
return driveSync;
}
public void setDriveSync(boolean driveSync) {
this.driveSync = driveSync;
}
public boolean isDriveSyncConfirm() {
return driveSyncConfirm;
}
public void setDriveSyncConfirm(boolean driveSyncConfirm) {
this.driveSyncConfirm = driveSyncConfirm;
}
public boolean isDriveShare() {
return driveShare;
}
public void setDriveShare(boolean driveShare) {
this.driveShare = driveShare;
}
public String getDriveShareEmails() {
return driveShareEmails;
}
public void setDriveShareEmails(String driveShareEmails) {
this.driveShareEmails = driveShareEmails;
}
public boolean isDriveSharePublic() {
return driveSharePublic;
}
public void setDriveSharePublic(boolean driveSharePublic) {
this.driveSharePublic = driveSharePublic;
}
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
public boolean isDriveSuccess() {
return driveSuccess;
}
public void setDriveSuccess(boolean driveSuccess) {
this.driveSuccess = driveSuccess;
}
public boolean isMapsSuccess() {
return mapsSuccess;
}
public void setMapsSuccess(boolean mapsSuccess) {
this.mapsSuccess = mapsSuccess;
}
public boolean isFusionTablesSuccess() {
return fusionTablesSuccess;
}
public void setFusionTablesSuccess(boolean fusionTablesSuccess) {
this.fusionTablesSuccess = fusionTablesSuccess;
}
public boolean isSpreadsheetsSuccess() {
return spreadsheetsSuccess;
}
public void setSpreadsheetsSuccess(boolean spreadsheetsSuccess) {
this.spreadsheetsSuccess = spreadsheetsSuccess;
}
public String getShareUrl() {
return shareUrl;
}
public void setShareUrl(String shareUrl) {
this.shareUrl = shareUrl;
}
private SendRequest(Parcel in) {
trackId = in.readLong();
sendDrive = in.readByte() == 1;
sendMaps = in.readByte() == 1;
sendFusionTables = in.readByte() == 1;
sendSpreadsheets = in.readByte() == 1;
driveSync = in.readByte() == 1;
driveSyncConfirm = in.readByte() == 1;
driveShare = in.readByte() == 1;
driveShareEmails = in.readString();
driveSharePublic = in.readByte() == 1;
account = in.readParcelable(null);
driveSuccess = in.readByte() == 1;
mapsSuccess = in.readByte() == 1;
fusionTablesSuccess = in.readByte() == 1;
spreadsheetsSuccess = in.readByte() == 1;
shareUrl = in.readString();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeLong(trackId);
out.writeByte((byte) (sendDrive ? 1 : 0));
out.writeByte((byte) (sendMaps ? 1 : 0));
out.writeByte((byte) (sendFusionTables ? 1 : 0));
out.writeByte((byte) (sendSpreadsheets ? 1 : 0));
out.writeByte((byte) (driveSync ? 1 : 0));
out.writeByte((byte) (driveSyncConfirm ? 1 : 0));
out.writeByte((byte) (driveShare ? 1 : 0));
out.writeString(driveShareEmails);
out.writeByte((byte) (driveSharePublic ? 1 : 0));
out.writeParcelable(account, 0);
out.writeByte((byte) (driveSuccess ? 1 : 0));
out.writeByte((byte) (mapsSuccess ? 1 : 0));
out.writeByte((byte) (fusionTablesSuccess ? 1 : 0));
out.writeByte((byte) (spreadsheetsSuccess ? 1 : 0));
out.writeString(shareUrl);
}
public static final Parcelable.Creator<SendRequest>
CREATOR = new Parcelable.Creator<SendRequest>() {
public SendRequest createFromParcel(Parcel in) {
return new SendRequest(in);
}
public SendRequest[] newArray(int size) {
return new SendRequest[size];
}
};
}
@@ -0,0 +1,204 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.sendtogoogle;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.gms.auth.GoogleAuthException;
import com.google.android.maps.mytracks.R;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.google.api.services.drive.DriveScopes;
import com.google.api.services.fusiontables.FusiontablesScopes;
import com.google.common.annotations.VisibleForTesting;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Commons utilities for sending a track to Google.
*
* @author Jimmy Shih
*/
public class SendToGoogleUtils {
private static final String TAG = SendToGoogleUtils.class.getSimpleName();
public static final String DRIVE_SCOPE = DriveScopes.DRIVE;
public static final int DRIVE_NOTIFICATION_ID = 1;
public static final String FUSION_TABLES_SCOPE = FusiontablesScopes.FUSIONTABLES;
public static final int FUSION_TABLES_NOTIFICATION_ID = 2;
public static final String SPREADSHEETS_SCOPE = "https://spreadsheets.google.com/feeds";
public static final int SPREADSHEETS_NOTIFICATION_ID = 3;
private SendToGoogleUtils() {}
/**
* Prepares a list of locations to send to Google Maps or Google Fusion
* Tables. Splits the locations into segments if necessary.
*
* @param track the track
* @param locations the list of locations
* @return an array of split segments.
*/
public static ArrayList<Track> prepareLocations(Track track, List<Location> locations) {
ArrayList<Track> splitTracks = new ArrayList<Track>();
// Create a new segment
Track segment = createNewSegment(
track, locations.size() > 0 ? locations.get(0).getTime() : -1L);
for (Location location : locations) {
/*
* Latitude is greater than 90 if the location is a pause/resume
* separator.
*/
if (location.getLatitude() > 90) {
endSegment(segment, location.getTime(), splitTracks);
segment = createNewSegment(track, location.getTime());
} else {
segment.addLocation(location);
}
}
endSegment(segment, locations.size() > 0 ? locations.get(locations.size() - 1).getTime() : -1L,
splitTracks);
return splitTracks;
}
/**
* Creates a new segment for a track.
*
* @param track the track
* @param startTime the segment start time
*/
private static Track createNewSegment(Track track, long startTime) {
Track segment = new Track();
segment.setId(track.getId());
segment.setName(track.getName());
segment.setDescription("");
segment.setCategory(track.getCategory());
TripStatistics segmentTripStatistics = segment.getTripStatistics();
segmentTripStatistics.setStartTime(startTime);
return segment;
}
/**
* Ends a segment. Adds to the array of track segments if the segment is
* valid.
*
* @param segment the segment
* @param stopTime the stop time
* @param splitTracks the array of track segments
*/
@VisibleForTesting
static boolean endSegment(Track segment, long stopTime, ArrayList<Track> splitTracks) {
// Make sure the segment has at least 2 points
if (segment.getLocations().size() < 2) {
Log.d(TAG, "segment has less than 2 points");
return false;
}
// Set its stop time
segment.getTripStatistics().setStopTime(stopTime);
/*
* Decimate to 2 meter precision. Google Maps and Google Fusion Tables do
* not like the locations to be too precise.
*/
LocationUtils.decimate(segment, 2.0);
splitTracks.add(segment);
return true;
}
/**
* Gets the google account credential.
*
* @param context the context
* @param accountName the account name
* @param scope the scope
*/
public static GoogleAccountCredential getGoogleAccountCredential(
Context context, String accountName, String scope) throws IOException, GoogleAuthException {
GoogleAccountCredential credential = GoogleAccountCredential.usingOAuth2(context, scope);
credential.setSelectedAccountName(accountName);
credential.getToken();
return credential;
}
/**
* Gets the OAuth2 token.
*
* @param context the context
* @param accountName the account name
* @param scope the scope
*/
public static String getToken(Context context, String accountName, String scope)
throws IOException, GoogleAuthException {
GoogleAccountCredential credential = GoogleAccountCredential.usingOAuth2(context, scope);
credential.setSelectedAccountName(accountName);
return credential.getToken();
}
/**
* Sends a notification to request permission.
*
* @param context the context
* @param accountName the account name
* @param intent the intent
* @param notificaitonId the notification id
*/
public static void sendNotification(
Context context, String accountName, Intent intent, int notificaitonId) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).addFlags(Intent.FLAG_FROM_BACKGROUND);
PendingIntent pendingIntent = PendingIntent.getActivity(
context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context).setAutoCancel(true)
.setContentIntent(pendingIntent)
.setContentText(context.getString(R.string.permission_request_message, accountName))
.setContentTitle(context.getString(R.string.permission_request_title))
.setSmallIcon(android.R.drawable.ic_dialog_alert)
.setTicker(context.getString(R.string.permission_request_title));
NotificationManager notificationManager = (NotificationManager) context.getSystemService(
Context.NOTIFICATION_SERVICE);
notificationManager.notify(notificaitonId, builder.build());
}
/**
* Cancels any notification to request a permission.
*
* @param context the context
* @param notificationId the notification id
*/
public static void cancelNotification(Context context, int notificationId) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(
Context.NOTIFICATION_SERVICE);
notificationManager.cancel(notificationId);
}
}
@@ -0,0 +1,144 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.sendtogoogle;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.fragments.ChooseActivityDialogFragment;
import com.google.android.apps.mytracks.fragments.ChooseActivityDialogFragment.ChooseActivityCaller;
import com.google.android.apps.mytracks.util.IntentUtils;
import com.google.android.maps.mytracks.R;
import com.google.common.annotations.VisibleForTesting;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.View;
/**
* A dialog to show the result of uploading to Google services.
*
* @author Jimmy Shih
*/
public class UploadResultActivity extends FragmentActivity implements ChooseActivityCaller {
private static final String TAG = UploadResultActivity.class.getSimpleName();
@VisibleForTesting
static final int DIALOG_RESULT_ID = 0;
@VisibleForTesting
protected View view;
private SendRequest sendRequest;
private String shareUrl;
private Dialog resultDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sendRequest = getIntent().getParcelableExtra(SendRequest.SEND_REQUEST_KEY);
shareUrl = sendRequest.getShareUrl();
Track track = MyTracksProviderUtils.Factory.get(this).getTrack(sendRequest.getTrackId());
if (track == null) {
Log.d(TAG, "No track for " + sendRequest.getTrackId());
finish();
return;
}
if (sendRequest.isDriveSuccess() && shareUrl != null) {
new ChooseActivityDialogFragment().show(
getSupportFragmentManager(), ChooseActivityDialogFragment.CHOOSE_ACTIVITY_DIALOG_TAG);
return;
}
showDialog(DIALOG_RESULT_ID);
}
@Override
protected Dialog onCreateDialog(int id) {
if (id != DIALOG_RESULT_ID) {
return null;
}
int serviceName;
int serviceUrl;
boolean success;
if (sendRequest.isSendDrive()) {
serviceName = R.string.export_google_drive;
serviceUrl = R.string.export_google_drive_url;
success = sendRequest.isDriveSuccess();
} else if (sendRequest.isSendMaps()) {
serviceName = R.string.export_google_maps;
serviceUrl = R.string.export_google_maps_url;
success = sendRequest.isMapsSuccess();
} else if (sendRequest.isSendFusionTables()) {
serviceName = R.string.export_google_fusion_tables;
serviceUrl = R.string.export_google_fusion_tables_url;
success = sendRequest.isFusionTablesSuccess();
} else {
serviceName = R.string.export_google_spreadsheets;
serviceUrl = R.string.export_google_spreadsheets_url;
success = sendRequest.isSpreadsheetsSuccess();
}
String message = getString(
success ? R.string.export_google_success : R.string.export_google_error,
getString(serviceName), getString(serviceUrl));
AlertDialog.Builder builder = new AlertDialog.Builder(this).setCancelable(true)
.setIcon(success ? R.drawable.ic_dialog_success : android.R.drawable.ic_dialog_alert)
.setMessage(message)
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
finish();
}
})
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
})
.setTitle(success ? R.string.generic_success_title : R.string.generic_error_title);
// Add a Share URL button if shareUrl exists
if (success && shareUrl != null) {
builder.setNegativeButton(
R.string.share_track_share_url, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new ChooseActivityDialogFragment().show(getSupportFragmentManager(),
ChooseActivityDialogFragment.CHOOSE_ACTIVITY_DIALOG_TAG);
}
});
}
resultDialog = builder.create();
return resultDialog;
}
@Override
public void onChooseActivityDone(String packageName, String className) {
if (packageName != null && className != null) {
Intent intent = IntentUtils.newShareUrlIntent(
this, sendRequest.getTrackId(), shareUrl, packageName, className);
startActivity(intent);
}
finish();
}
}