forked from upstream-mirrors/OpenTracks
Merge to head
This commit is contained in:
@@ -26,7 +26,10 @@ import android.util.Log;
|
||||
|
||||
/**
|
||||
* Choose which account to upload track information to.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*
|
||||
* @deprecated To be removed.
|
||||
*/
|
||||
public class AccountChooser {
|
||||
|
||||
|
||||
@@ -188,9 +188,8 @@ public class ChartActivity extends Activity implements TrackDataListener {
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
chartSettingsMenuItem =
|
||||
menu.add(0, Constants.MENU_CHART_SETTINGS, 0,
|
||||
R.string.menu_chart_view_chart_settings);
|
||||
chartSettingsMenuItem = menu.add(Menu.NONE, Constants.MENU_CHART_SETTINGS, Menu.NONE,
|
||||
R.string.menu_chart_view_chart_settings);
|
||||
chartSettingsMenuItem.setIcon(R.drawable.chart_settings);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -805,7 +805,7 @@ public class ChartView extends View {
|
||||
String s =
|
||||
(mode == Mode.BY_DISTANCE)
|
||||
? (shortFormat ? X_SHORT_FORMAT.format(x) : X_FORMAT.format(x))
|
||||
: StringUtils.formatTime((long) x);
|
||||
: StringUtils.formatElapsedTime((long) x);
|
||||
c.drawText(s,
|
||||
getX(x),
|
||||
effectiveHeight + UNIT_BORDER + topBorder,
|
||||
|
||||
@@ -35,25 +35,28 @@ public abstract class Constants {
|
||||
|
||||
/*
|
||||
* onActivityResult request codes:
|
||||
*
|
||||
* TODO: These should be moved to the
|
||||
* activities that call startActivityForResult. Thus do not need to keep a
|
||||
* global order across all activities.
|
||||
*/
|
||||
|
||||
public static final int GET_LOGIN = 0;
|
||||
public static final int GET_MAP = 1;
|
||||
public static final int CHOOSE_MAP = 1;
|
||||
public static final int SHOW_TRACK = 2;
|
||||
public static final int AUTHENTICATE_TO_MY_MAPS = 3;
|
||||
public static final int AUTHENTICATE_TO_FUSION_TABLES = 4;
|
||||
public static final int AUTHENTICATE_TO_DOCLIST = 5;
|
||||
public static final int AUTHENTICATE_TO_TRIX = 6;
|
||||
public static final int SHARE_GPX_FILE = 7;
|
||||
public static final int SHARE_KML_FILE = 8;
|
||||
public static final int SHARE_CSV_FILE = 9;
|
||||
public static final int SHARE_TCX_FILE = 10;
|
||||
public static final int SAVE_GPX_FILE = 11;
|
||||
public static final int SAVE_KML_FILE = 12;
|
||||
public static final int SAVE_CSV_FILE = 13;
|
||||
public static final int SAVE_TCX_FILE = 14;
|
||||
public static final int SHOW_WAYPOINT = 15;
|
||||
public static final int WELCOME = 16;
|
||||
public static final int AUTHENTICATE_TO_MAPS = 3;
|
||||
public static final int SHARE_GPX_FILE = 4;
|
||||
public static final int SHARE_KML_FILE = 5;
|
||||
public static final int SHARE_CSV_FILE = 6;
|
||||
public static final int SHARE_TCX_FILE = 7;
|
||||
public static final int SAVE_GPX_FILE = 8;
|
||||
public static final int SAVE_KML_FILE = 9;
|
||||
public static final int SAVE_CSV_FILE = 10;
|
||||
public static final int SAVE_TCX_FILE = 11;
|
||||
public static final int SHOW_WAYPOINT = 12;
|
||||
public static final int WELCOME = 13;
|
||||
public static final int CHOOSE_ACCOUNT = 14;
|
||||
public static final int SEND_DOCS = 15;
|
||||
public static final int SEND_FUSION_TABLES = 16;
|
||||
|
||||
/*
|
||||
* Menu ids:
|
||||
@@ -72,17 +75,18 @@ public abstract class Constants {
|
||||
public static final int MENU_SEND_TO_GOOGLE = 102;
|
||||
public static final int MENU_SHARE = 103;
|
||||
public static final int MENU_SHOW = 104;
|
||||
public static final int MENU_SHARE_LINK = 200;
|
||||
public static final int MENU_SHARE_GPX_FILE = 201;
|
||||
public static final int MENU_SHARE_KML_FILE = 202;
|
||||
public static final int MENU_SHARE_CSV_FILE = 203;
|
||||
public static final int MENU_SHARE_TCX_FILE = 204;
|
||||
public static final int MENU_WRITE_TO_SD_CARD = 205;
|
||||
public static final int MENU_SAVE_GPX_FILE = 206;
|
||||
public static final int MENU_SAVE_KML_FILE = 207;
|
||||
public static final int MENU_SAVE_CSV_FILE = 208;
|
||||
public static final int MENU_SAVE_TCX_FILE = 209;
|
||||
public static final int MENU_CLEAR_MAP = 210;
|
||||
public static final int MENU_SHARE_MAP = 200;
|
||||
public static final int MENU_SHARE_FUSION_TABLE = 201;
|
||||
public static final int MENU_SHARE_GPX_FILE = 202;
|
||||
public static final int MENU_SHARE_KML_FILE = 203;
|
||||
public static final int MENU_SHARE_CSV_FILE = 204;
|
||||
public static final int MENU_SHARE_TCX_FILE = 205;
|
||||
public static final int MENU_WRITE_TO_SD_CARD = 206;
|
||||
public static final int MENU_SAVE_GPX_FILE = 207;
|
||||
public static final int MENU_SAVE_KML_FILE = 208;
|
||||
public static final int MENU_SAVE_CSV_FILE = 209;
|
||||
public static final int MENU_SAVE_TCX_FILE = 210;
|
||||
public static final int MENU_CLEAR_MAP = 211;
|
||||
|
||||
/**
|
||||
* The number of distance readings to smooth to get a stable signal.
|
||||
|
||||
@@ -99,7 +99,7 @@ public class ExportAllTracks {
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
new Thread(runner, "SendToMyMaps").start();
|
||||
new Thread(runner, "ExportAllTracks").start();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ import com.google.android.apps.mytracks.content.TrackDataListener;
|
||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.io.file.SaveActivity;
|
||||
import com.google.android.apps.mytracks.io.sendtogoogle.SendActivity;
|
||||
import com.google.android.apps.mytracks.io.sendtogoogle.SendType;
|
||||
import com.google.android.apps.mytracks.io.sendtogoogle.UploadServiceChooserActivity;
|
||||
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
@@ -41,6 +42,7 @@ import android.content.Intent;
|
||||
import android.location.Location;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
@@ -421,57 +423,68 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
||||
private final OnCreateContextMenuListener contextMenuListener =
|
||||
new OnCreateContextMenuListener() {
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v,
|
||||
ContextMenuInfo menuInfo) {
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
menu.setHeaderTitle(R.string.track_list_context_menu_title);
|
||||
menu.add(0, Constants.MENU_EDIT, 0,
|
||||
R.string.track_list_edit_track);
|
||||
menu.add(Menu.NONE, Constants.MENU_EDIT, Menu.NONE, R.string.track_list_edit_track);
|
||||
if (!dataHub.isRecordingSelected()) {
|
||||
String saveFileFormat = getString(R.string.track_list_save_file);
|
||||
String shareFileFormat = getString(R.string.track_list_share_file);
|
||||
String fileTypes[] = getResources().getStringArray(R.array.file_types);
|
||||
|
||||
menu.add(0, Constants.MENU_SEND_TO_GOOGLE, 0,
|
||||
menu.add(Menu.NONE, Constants.MENU_SEND_TO_GOOGLE, Menu.NONE,
|
||||
R.string.track_list_send_google);
|
||||
SubMenu share = menu.addSubMenu(0, Constants.MENU_SHARE, 0,
|
||||
R.string.track_list_share_track);
|
||||
share.add(0, Constants.MENU_SHARE_LINK, 0,
|
||||
R.string.track_list_share_url);
|
||||
SubMenu share = menu.addSubMenu(
|
||||
Menu.NONE, Constants.MENU_SHARE, Menu.NONE, R.string.track_list_share_track);
|
||||
share.add(
|
||||
0, Constants.MENU_SHARE_GPX_FILE, 0, String.format(shareFileFormat, fileTypes[0]));
|
||||
share.add(
|
||||
0, Constants.MENU_SHARE_KML_FILE, 0, String.format(shareFileFormat, fileTypes[1]));
|
||||
share.add(
|
||||
0, Constants.MENU_SHARE_CSV_FILE, 0, String.format(shareFileFormat, fileTypes[2]));
|
||||
share.add(
|
||||
0, Constants.MENU_SHARE_TCX_FILE, 0, String.format(shareFileFormat, fileTypes[3]));
|
||||
SubMenu save = menu.addSubMenu(0,
|
||||
Constants.MENU_WRITE_TO_SD_CARD, 0,
|
||||
R.string.track_list_save_sd);
|
||||
save.add(
|
||||
0, Constants.MENU_SAVE_GPX_FILE, 0, String.format(saveFileFormat, fileTypes[0]));
|
||||
save.add(
|
||||
0, Constants.MENU_SAVE_KML_FILE, 0, String.format(saveFileFormat, fileTypes[1]));
|
||||
save.add(
|
||||
0, Constants.MENU_SAVE_CSV_FILE, 0, String.format(saveFileFormat, fileTypes[2]));
|
||||
save.add(
|
||||
0, Constants.MENU_SAVE_TCX_FILE, 0, String.format(saveFileFormat, fileTypes[3]));
|
||||
menu.add(0, Constants.MENU_CLEAR_MAP, 0,
|
||||
R.string.track_list_clear_map);
|
||||
menu.add(0, Constants.MENU_DELETE, 0,
|
||||
R.string.track_list_delete_track);
|
||||
Menu.NONE, Constants.MENU_SHARE_MAP, Menu.NONE, R.string.track_list_share_map);
|
||||
share.add(Menu.NONE, Constants.MENU_SHARE_FUSION_TABLE, Menu.NONE,
|
||||
R.string.track_list_share_fusion_table);
|
||||
share.add(Menu.NONE, Constants.MENU_SHARE_GPX_FILE, Menu.NONE,
|
||||
String.format(shareFileFormat, fileTypes[0]));
|
||||
share.add(Menu.NONE, Constants.MENU_SHARE_KML_FILE, Menu.NONE,
|
||||
String.format(shareFileFormat, fileTypes[1]));
|
||||
share.add(Menu.NONE, Constants.MENU_SHARE_CSV_FILE, Menu.NONE,
|
||||
String.format(shareFileFormat, fileTypes[2]));
|
||||
share.add(Menu.NONE, Constants.MENU_SHARE_TCX_FILE, Menu.NONE,
|
||||
String.format(shareFileFormat, fileTypes[3]));
|
||||
SubMenu save = menu.addSubMenu(
|
||||
Menu.NONE, Constants.MENU_WRITE_TO_SD_CARD, Menu.NONE, R.string.track_list_save_sd);
|
||||
save.add(Menu.NONE, Constants.MENU_SAVE_GPX_FILE, Menu.NONE,
|
||||
String.format(saveFileFormat, fileTypes[0]));
|
||||
save.add(Menu.NONE, Constants.MENU_SAVE_KML_FILE, Menu.NONE,
|
||||
String.format(saveFileFormat, fileTypes[1]));
|
||||
save.add(Menu.NONE, Constants.MENU_SAVE_CSV_FILE, Menu.NONE,
|
||||
String.format(saveFileFormat, fileTypes[2]));
|
||||
save.add(Menu.NONE, Constants.MENU_SAVE_TCX_FILE, Menu.NONE,
|
||||
String.format(saveFileFormat, fileTypes[3]));
|
||||
menu.add(Menu.NONE, Constants.MENU_CLEAR_MAP, Menu.NONE, R.string.track_list_clear_map);
|
||||
menu.add(Menu.NONE, Constants.MENU_DELETE, Menu.NONE, R.string.track_list_delete_track);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemSelected(int featureId, MenuItem item) {
|
||||
Intent intent;
|
||||
long trackId = dataHub.getSelectedTrackId();
|
||||
switch (item.getItemId()) {
|
||||
case Constants.MENU_SEND_TO_GOOGLE:
|
||||
SendActivity.sendToGoogle(this, dataHub.getSelectedTrackId(), false);
|
||||
intent = new Intent(this, UploadServiceChooserActivity.class);
|
||||
intent.putExtra(UploadServiceChooserActivity.TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case Constants.MENU_SHARE_LINK:
|
||||
SendActivity.sendToGoogle(this, dataHub.getSelectedTrackId(), true);
|
||||
case Constants.MENU_SHARE_MAP:
|
||||
intent = new Intent(this, UploadServiceChooserActivity.class);
|
||||
intent.putExtra(UploadServiceChooserActivity.TRACK_ID, trackId);
|
||||
intent.putExtra(UploadServiceChooserActivity.SEND_TYPE, (Parcelable) SendType.MAPS);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case Constants.MENU_SHARE_FUSION_TABLE:
|
||||
intent = new Intent(this, UploadServiceChooserActivity.class);
|
||||
intent.putExtra(UploadServiceChooserActivity.TRACK_ID, trackId);
|
||||
intent.putExtra(
|
||||
UploadServiceChooserActivity.SEND_TYPE, (Parcelable) SendType.FUSION_TABLES);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case Constants.MENU_SAVE_GPX_FILE:
|
||||
case Constants.MENU_SAVE_KML_FILE:
|
||||
@@ -481,19 +494,18 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
||||
case Constants.MENU_SHARE_KML_FILE:
|
||||
case Constants.MENU_SHARE_CSV_FILE:
|
||||
case Constants.MENU_SHARE_TCX_FILE:
|
||||
SaveActivity.handleExportTrackAction(this, dataHub.getSelectedTrackId(),
|
||||
Constants.getActionFromMenuId(item.getItemId()));
|
||||
SaveActivity.handleExportTrackAction(
|
||||
this, trackId, Constants.getActionFromMenuId(item.getItemId()));
|
||||
return true;
|
||||
case Constants.MENU_EDIT: {
|
||||
Intent intent = new Intent(this, TrackDetail.class);
|
||||
intent.putExtra(TrackDetail.TRACK_ID, dataHub.getSelectedTrackId());
|
||||
intent = new Intent(this, TrackDetail.class);
|
||||
intent.putExtra(TrackDetail.TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
case Constants.MENU_DELETE: {
|
||||
Uri uri = ContentUris.withAppendedId(
|
||||
TracksColumns.CONTENT_URI, dataHub.getSelectedTrackId());
|
||||
Intent intent = new Intent(Intent.ACTION_DELETE, uri);
|
||||
Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId);
|
||||
intent = new Intent(Intent.ACTION_DELETE, uri);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
@@ -508,11 +520,11 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
myLocation = menu.add(0, Constants.MENU_MY_LOCATION, 0,
|
||||
R.string.menu_map_view_my_location);
|
||||
myLocation = menu.add(
|
||||
Menu.NONE, Constants.MENU_MY_LOCATION, Menu.NONE, R.string.menu_map_view_my_location);
|
||||
myLocation.setIcon(android.R.drawable.ic_menu_mylocation);
|
||||
toggleLayers = menu.add(0, Constants.MENU_TOGGLE_LAYERS, 0,
|
||||
R.string.menu_map_view_satellite_mode);
|
||||
toggleLayers = menu.add(
|
||||
Menu.NONE, Constants.MENU_TOGGLE_LAYERS, Menu.NONE, R.string.menu_map_view_satellite_mode);
|
||||
toggleLayers.setIcon(android.R.drawable.ic_menu_mapmode);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.io.AuthManager;
|
||||
import com.google.android.apps.mytracks.io.AuthManager.AuthCallback;
|
||||
import com.google.android.apps.mytracks.io.AuthManagerFactory;
|
||||
import com.google.android.apps.mytracks.io.mymaps.MapsFacade;
|
||||
import com.google.android.apps.mytracks.io.mymaps.MyMapsConstants;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* Activity which displays the list of current My Maps tracks for the user.
|
||||
* Returns RESULT_OK if the user picked a map, and returns "mapid" as an extra.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class MyMapsList extends Activity implements MapsFacade.MapsListCallback {
|
||||
private static final int GET_LOGIN = 1;
|
||||
|
||||
public static final String EXTRA_ACCOUNT_NAME = "accountName";
|
||||
public static final String EXTRA_ACCOUNT_TYPE = "accountType";
|
||||
|
||||
private MapsFacade mapsClient;
|
||||
private AuthManager auth;
|
||||
private MyMapsListAdapter listAdapter;
|
||||
|
||||
private final OnItemClickListener clickListener =
|
||||
new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View v, int position,
|
||||
long id) {
|
||||
Intent result = new Intent();
|
||||
result.putExtra("mapid", (String) listAdapter.getItem(position));
|
||||
setResult(RESULT_OK, result);
|
||||
finish();
|
||||
}
|
||||
};
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
|
||||
auth = AuthManagerFactory.getAuthManager(this, GET_LOGIN, null, true,
|
||||
MyMapsConstants.SERVICE_NAME);
|
||||
|
||||
setContentView(R.layout.list);
|
||||
|
||||
listAdapter = new MyMapsListAdapter(this);
|
||||
|
||||
ListView list = (ListView) findViewById(R.id.maplist);
|
||||
list.setOnItemClickListener(clickListener);
|
||||
list.setAdapter(listAdapter);
|
||||
|
||||
startLogin();
|
||||
}
|
||||
|
||||
private void startLogin() {
|
||||
// Starts in the UI thread.
|
||||
// TODO fix this for non-froyo devices.
|
||||
if (AuthManagerFactory.useModernAuthManager()) {
|
||||
Intent intent = getIntent();
|
||||
String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
|
||||
String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
|
||||
if (accountName == null || accountType == null) {
|
||||
Log.e(TAG, "Didn't receive account name or type");
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
doLogin(auth.getAccountObject(accountName, accountType));
|
||||
} else {
|
||||
doLogin(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void doLogin(final Object account) {
|
||||
// Starts in the UI thread.
|
||||
auth.doLogin(new AuthCallback() {
|
||||
@Override
|
||||
public void onAuthResult(boolean success) {
|
||||
if (!success) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Runs in UI thread.
|
||||
mapsClient = new MapsFacade(MyMapsList.this, auth);
|
||||
|
||||
startLookup();
|
||||
}
|
||||
}, account);
|
||||
}
|
||||
|
||||
private void startLookup() {
|
||||
// Starts in the UI thread.
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Communication with Maps happens in its own thread.
|
||||
// This will call onReceivedMapListing below.
|
||||
final boolean success = mapsClient.getMapsList(MyMapsList.this);
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Updating the UI when done happens in the UI thread.
|
||||
onLookupDone(success);
|
||||
}
|
||||
});
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedMapListing(final String mapId, final String title,
|
||||
final String description, final boolean isPublic) {
|
||||
// Starts in the communication thread.
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Updating the list with new contents happens in the UI thread.
|
||||
listAdapter.addMapListing(mapId, title, description, isPublic);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onLookupDone(boolean success) {
|
||||
// Starts in the UI thread.
|
||||
findViewById(R.id.loading).setVisibility(View.GONE);
|
||||
if (!success) {
|
||||
findViewById(R.id.failed).setVisibility(View.VISIBLE);
|
||||
}
|
||||
TextView emptyView = (TextView) findViewById(R.id.mapslist_empty);
|
||||
ListView list = (ListView) findViewById(R.id.maplist);
|
||||
list.setEmptyView(emptyView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode,
|
||||
Intent data) {
|
||||
if (requestCode == GET_LOGIN) {
|
||||
auth.authResult(resultCode, data);
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.database.DataSetObserver;
|
||||
import android.graphics.Color;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.Vector;
|
||||
|
||||
public class MyMapsListAdapter implements ListAdapter {
|
||||
private Vector<String[]> mapsList;
|
||||
private Vector<Boolean> publicList;
|
||||
private Set<DataSetObserver> observerSet;
|
||||
private final Activity activity;
|
||||
|
||||
public MyMapsListAdapter(Activity activity) {
|
||||
this.activity = activity;
|
||||
|
||||
mapsList = new Vector<String[]>();
|
||||
publicList = new Vector<Boolean>();
|
||||
observerSet = new HashSet<DataSetObserver>();
|
||||
}
|
||||
|
||||
public void addMapListing(String mapId, String title,
|
||||
String description, boolean isPublic) {
|
||||
synchronized (mapsList) {
|
||||
// Search through the maps list to see if it has the mapid and
|
||||
// remove it if so, so that we can replace it with updated info
|
||||
for (int i = 0; i < mapsList.size(); ++i) {
|
||||
if (mapId.equals(mapsList.get(i)[0])) {
|
||||
mapsList.remove(i);
|
||||
publicList.remove(i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
mapsList.add(new String[] { mapId, title, description });
|
||||
publicList.add(isPublic);
|
||||
}
|
||||
|
||||
Iterator<DataSetObserver> iter = observerSet.iterator();
|
||||
while (iter.hasNext()) {
|
||||
iter.next().onChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public String[] getMapListingArray(int position) {
|
||||
return mapsList.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areAllItemsEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mapsList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return mapsList.get(position)[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
if (convertView == null) {
|
||||
convertView =
|
||||
activity.getLayoutInflater().inflate(R.layout.listitem, parent, false);
|
||||
}
|
||||
|
||||
String[] map = mapsList.get(position);
|
||||
((TextView) convertView.findViewById(R.id.maplistitem)).setText(map[1]);
|
||||
((TextView) convertView.findViewById(R.id.maplistdesc)).setText(map[2]);
|
||||
TextView publicUnlisted =
|
||||
(TextView) convertView.findViewById(R.id.maplistpublic);
|
||||
if (publicList.get(position)) {
|
||||
publicUnlisted.setTextColor(Color.RED);
|
||||
publicUnlisted.setText(R.string.my_maps_list_public_label);
|
||||
} else {
|
||||
publicUnlisted.setTextColor(Color.GREEN);
|
||||
publicUnlisted.setText(R.string.my_maps_list_unlisted_label);
|
||||
}
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewTypeCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return mapsList.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDataSetObserver(DataSetObserver observer) {
|
||||
observerSet.add(observer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterDataSetObserver(DataSetObserver observer) {
|
||||
observerSet.remove(observer);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection
|
||||
import com.google.android.apps.mytracks.services.sensors.SensorManager;
|
||||
import com.google.android.apps.mytracks.services.sensors.SensorManagerFactory;
|
||||
import com.google.android.apps.mytracks.services.sensors.SensorUtils;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
@@ -32,8 +33,6 @@ import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
@@ -44,7 +43,6 @@ import java.util.TimerTask;
|
||||
*/
|
||||
public class SensorStateActivity extends Activity {
|
||||
|
||||
private static final DateFormat TIMESTAMP_FORMAT = DateFormat.getTimeInstance(DateFormat.SHORT);
|
||||
private static final long REFRESH_PERIOD_MS = 250;
|
||||
|
||||
private final StatsUtilities utils;
|
||||
@@ -263,7 +261,7 @@ public class SensorStateActivity extends Activity {
|
||||
* @param sds sensor data set
|
||||
*/
|
||||
private String getLastSensorTime(Sensor.SensorDataSet sds) {
|
||||
return TIMESTAMP_FORMAT.format(new Date(sds.getCreationTime()));
|
||||
return StringUtils.formatTime(this, sds.getCreationTime());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -153,7 +153,7 @@ public class StatsUtilities {
|
||||
}
|
||||
|
||||
public void setTime(int id, long l) {
|
||||
setText(id, StringUtils.formatTime(l));
|
||||
setText(id, StringUtils.formatElapsedTime(l));
|
||||
}
|
||||
|
||||
public void setGrade(int id, double d) {
|
||||
|
||||
@@ -19,7 +19,8 @@ import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||
import com.google.android.apps.mytracks.io.file.SaveActivity;
|
||||
import com.google.android.apps.mytracks.io.sendtogoogle.SendActivity;
|
||||
import com.google.android.apps.mytracks.io.sendtogoogle.SendType;
|
||||
import com.google.android.apps.mytracks.io.sendtogoogle.UploadServiceChooserActivity;
|
||||
import com.google.android.apps.mytracks.services.ServiceUtils;
|
||||
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
@@ -35,6 +36,7 @@ import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
@@ -52,7 +54,7 @@ import android.widget.TextView;
|
||||
/**
|
||||
* A list activity displaying all the recorded tracks. There's a context
|
||||
* menu (via long press) displaying various options such as showing, editing,
|
||||
* deleting, sending to MyMaps, or writing to SD card.
|
||||
* deleting, sending to Google, or writing to SD card.
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
@@ -75,50 +77,45 @@ public class TrackList extends ListActivity
|
||||
private final OnCreateContextMenuListener contextMenuListener =
|
||||
new OnCreateContextMenuListener() {
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v,
|
||||
ContextMenuInfo menuInfo) {
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
menu.setHeaderTitle(R.string.track_list_context_menu_title);
|
||||
AdapterView.AdapterContextMenuInfo info =
|
||||
(AdapterView.AdapterContextMenuInfo) menuInfo;
|
||||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
|
||||
contextPosition = info.position;
|
||||
trackId = TrackList.this.listView.getAdapter().getItemId(
|
||||
contextPosition);
|
||||
menu.add(0, Constants.MENU_SHOW, 0,
|
||||
R.string.track_list_show_on_map);
|
||||
menu.add(0, Constants.MENU_EDIT, 0,
|
||||
R.string.track_list_edit_track);
|
||||
trackId = TrackList.this.listView.getAdapter().getItemId(contextPosition);
|
||||
menu.add(Menu.NONE, Constants.MENU_SHOW, Menu.NONE, R.string.track_list_show_on_map);
|
||||
menu.add(Menu.NONE, Constants.MENU_EDIT, Menu.NONE, R.string.track_list_edit_track);
|
||||
if (!isRecording() || trackId != recordingTrackId) {
|
||||
String saveFileFormat = getString(R.string.track_list_save_file);
|
||||
String shareFileFormat = getString(R.string.track_list_share_file);
|
||||
String fileTypes[] = getResources().getStringArray(R.array.file_types);
|
||||
|
||||
menu.add(0, Constants.MENU_SEND_TO_GOOGLE, 0,
|
||||
menu.add(Menu.NONE, Constants.MENU_SEND_TO_GOOGLE, Menu.NONE,
|
||||
R.string.track_list_send_google);
|
||||
SubMenu share = menu.addSubMenu(0, Constants.MENU_SHARE, 0,
|
||||
R.string.track_list_share_track);
|
||||
share.add(0, Constants.MENU_SHARE_LINK, 0,
|
||||
R.string.track_list_share_url);
|
||||
SubMenu share = menu.addSubMenu(
|
||||
Menu.NONE, Constants.MENU_SHARE, Menu.NONE, R.string.track_list_share_track);
|
||||
share.add(
|
||||
0, Constants.MENU_SHARE_GPX_FILE, 0, String.format(shareFileFormat, fileTypes[0]));
|
||||
share.add(
|
||||
0, Constants.MENU_SHARE_KML_FILE, 0, String.format(shareFileFormat, fileTypes[1]));
|
||||
share.add(
|
||||
0, Constants.MENU_SHARE_CSV_FILE, 0, String.format(shareFileFormat, fileTypes[2]));
|
||||
share.add(
|
||||
0, Constants.MENU_SHARE_TCX_FILE, 0, String.format(shareFileFormat, fileTypes[3]));
|
||||
SubMenu save = menu.addSubMenu(0,
|
||||
Constants.MENU_WRITE_TO_SD_CARD, 0,
|
||||
R.string.track_list_save_sd);
|
||||
save.add(
|
||||
0, Constants.MENU_SAVE_GPX_FILE, 0, String.format(saveFileFormat, fileTypes[0]));
|
||||
save.add(
|
||||
0, Constants.MENU_SAVE_KML_FILE, 0, String.format(saveFileFormat, fileTypes[1]));
|
||||
save.add(
|
||||
0, Constants.MENU_SAVE_CSV_FILE, 0, String.format(saveFileFormat, fileTypes[2]));
|
||||
save.add(
|
||||
0, Constants.MENU_SAVE_TCX_FILE, 0, String.format(saveFileFormat, fileTypes[3]));
|
||||
menu.add(0, Constants.MENU_DELETE, 0,
|
||||
R.string.track_list_delete_track);
|
||||
Menu.NONE, Constants.MENU_SHARE_MAP, Menu.NONE, R.string.track_list_share_map);
|
||||
share.add(Menu.NONE, Constants.MENU_SHARE_FUSION_TABLE, Menu.NONE,
|
||||
R.string.track_list_share_fusion_table);
|
||||
share.add(Menu.NONE, Constants.MENU_SHARE_GPX_FILE, Menu.NONE,
|
||||
String.format(shareFileFormat, fileTypes[0]));
|
||||
share.add(Menu.NONE, Constants.MENU_SHARE_KML_FILE, Menu.NONE,
|
||||
String.format(shareFileFormat, fileTypes[1]));
|
||||
share.add(Menu.NONE, Constants.MENU_SHARE_CSV_FILE, Menu.NONE,
|
||||
String.format(shareFileFormat, fileTypes[2]));
|
||||
share.add(Menu.NONE, Constants.MENU_SHARE_TCX_FILE, Menu.NONE,
|
||||
String.format(shareFileFormat, fileTypes[3]));
|
||||
SubMenu save = menu.addSubMenu(
|
||||
Menu.NONE, Constants.MENU_WRITE_TO_SD_CARD, Menu.NONE, R.string.track_list_save_sd);
|
||||
save.add(Menu.NONE, Constants.MENU_SAVE_GPX_FILE, Menu.NONE,
|
||||
String.format(saveFileFormat, fileTypes[0]));
|
||||
save.add(Menu.NONE, Constants.MENU_SAVE_KML_FILE, Menu.NONE,
|
||||
String.format(saveFileFormat, fileTypes[1]));
|
||||
save.add(Menu.NONE, Constants.MENU_SAVE_CSV_FILE, Menu.NONE,
|
||||
String.format(saveFileFormat, fileTypes[2]));
|
||||
save.add(Menu.NONE, Constants.MENU_SAVE_TCX_FILE, Menu.NONE,
|
||||
String.format(saveFileFormat, fileTypes[3]));
|
||||
menu.add(Menu.NONE, Constants.MENU_DELETE, Menu.NONE, R.string.track_list_delete_track);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -162,13 +159,14 @@ public class TrackList extends ListActivity
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemSelected(int featureId, MenuItem item) {
|
||||
Intent intent;
|
||||
switch (item.getItemId()) {
|
||||
case Constants.MENU_SHOW: {
|
||||
onListItemClick(null, null, 0, trackId);
|
||||
return true;
|
||||
}
|
||||
case Constants.MENU_EDIT: {
|
||||
Intent intent = new Intent(this, TrackDetail.class);
|
||||
intent = new Intent(this, TrackDetail.class);
|
||||
intent.putExtra(TrackDetail.TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
@@ -177,10 +175,22 @@ public class TrackList extends ListActivity
|
||||
case Constants.MENU_WRITE_TO_SD_CARD:
|
||||
return false;
|
||||
case Constants.MENU_SEND_TO_GOOGLE:
|
||||
SendActivity.sendToGoogle(this, trackId, false);
|
||||
intent = new Intent(this, UploadServiceChooserActivity.class);
|
||||
intent.putExtra(UploadServiceChooserActivity.TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case Constants.MENU_SHARE_LINK:
|
||||
SendActivity.sendToGoogle(this, trackId, true);
|
||||
case Constants.MENU_SHARE_MAP:
|
||||
intent = new Intent(this, UploadServiceChooserActivity.class);
|
||||
intent.putExtra(UploadServiceChooserActivity.TRACK_ID, trackId);
|
||||
intent.putExtra(UploadServiceChooserActivity.SEND_TYPE, (Parcelable) SendType.MAPS);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case Constants.MENU_SHARE_FUSION_TABLE:
|
||||
intent = new Intent(this, UploadServiceChooserActivity.class);
|
||||
intent.putExtra(UploadServiceChooserActivity.TRACK_ID, trackId);
|
||||
intent.putExtra(
|
||||
UploadServiceChooserActivity.SEND_TYPE, (Parcelable) SendType.FUSION_TABLES);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case Constants.MENU_SAVE_GPX_FILE:
|
||||
case Constants.MENU_SAVE_KML_FILE:
|
||||
@@ -194,7 +204,7 @@ public class TrackList extends ListActivity
|
||||
Constants.getActionFromMenuId(item.getItemId()));
|
||||
return true;
|
||||
case Constants.MENU_DELETE: {
|
||||
Intent intent = new Intent(Intent.ACTION_DELETE);
|
||||
intent = new Intent(Intent.ACTION_DELETE);
|
||||
Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId);
|
||||
intent.setDataAndType(uri, TracksColumns.CONTENT_ITEMTYPE);
|
||||
startActivity(intent);
|
||||
@@ -322,7 +332,7 @@ public class TrackList extends ListActivity
|
||||
TextView textView = (TextView) view;
|
||||
if (columnIndex == startTimeIdx) {
|
||||
long time = cursor.getLong(startTimeIdx);
|
||||
textView.setText(StringUtils.formatDateTime(time));
|
||||
textView.setText(StringUtils.formatDateTime(TrackList.this, time));
|
||||
} else if (columnIndex == totalDistanceIdx) {
|
||||
double length = cursor.getDouble(totalDistanceIdx);
|
||||
String lengthUnit = null;
|
||||
@@ -343,7 +353,7 @@ public class TrackList extends ListActivity
|
||||
}
|
||||
}
|
||||
textView.setText(String.format("%s %.2f %s",
|
||||
StringUtils.formatTime(cursor.getLong(totalTimeIdx)),
|
||||
StringUtils.formatElapsedTime(cursor.getLong(totalTimeIdx)),
|
||||
length,
|
||||
lengthUnit));
|
||||
} else {
|
||||
|
||||
@@ -47,7 +47,7 @@ public class WaypointDetails extends Activity
|
||||
public static final String WAYPOINT_ID_EXTRA = "com.google.android.apps.mytracks.WAYPOINT_ID";
|
||||
|
||||
/**
|
||||
* The id of the way point being edited (taken from bundle, "waypointid")
|
||||
* The id of the way point being edited (taken from bundle with the above name).
|
||||
*/
|
||||
private Long waypointId;
|
||||
|
||||
|
||||
@@ -84,14 +84,14 @@ public class WaypointsList extends ListActivity
|
||||
Waypoint waypoint = providerUtils.getWaypoint(info.id);
|
||||
if (waypoint != null) {
|
||||
int type = waypoint.getType();
|
||||
menu.add(0, Constants.MENU_SHOW, 0,
|
||||
R.string.marker_list_show_on_map);
|
||||
menu.add(0, Constants.MENU_EDIT, 0,
|
||||
R.string.marker_list_edit_marker);
|
||||
menu.add(0, Constants.MENU_DELETE, 0,
|
||||
R.string.marker_list_delete_marker).setEnabled(
|
||||
recordingTrackId < 0 || type == Waypoint.TYPE_WAYPOINT || type == Waypoint.TYPE_STATISTICS ||
|
||||
info.id != providerUtils.getLastWaypointId(recordingTrackId));
|
||||
menu.add(Menu.NONE, Constants.MENU_SHOW, Menu.NONE, R.string.marker_list_show_on_map);
|
||||
menu.add(Menu.NONE, Constants.MENU_EDIT, Menu.NONE, R.string.marker_list_edit_marker);
|
||||
MenuItem deleteMenu = menu.add(
|
||||
Menu.NONE, Constants.MENU_DELETE, Menu.NONE, R.string.marker_list_delete_marker);
|
||||
deleteMenu.setEnabled(recordingTrackId < 0
|
||||
|| type == Waypoint.TYPE_WAYPOINT
|
||||
|| type == Waypoint.TYPE_STATISTICS
|
||||
|| info.id != providerUtils.getLastWaypointId(recordingTrackId));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -281,7 +281,7 @@ public class WaypointsList extends ListActivity
|
||||
if (time == 0) {
|
||||
textView.setVisibility(View.GONE);
|
||||
} else {
|
||||
textView.setText(StringUtils.formatDateTime(time));
|
||||
textView.setText(StringUtils.formatDateTime(WaypointsList.this, time));
|
||||
textView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else if (columnIndex == typeIdx) {
|
||||
|
||||
@@ -1,280 +0,0 @@
|
||||
/*
|
||||
* Copyright 2008 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.ProgressIndicator;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.io.docs.DocsHelper;
|
||||
import com.google.android.apps.mytracks.io.gdata.GDataClientFactory;
|
||||
import com.google.android.apps.mytracks.io.gdata.GDataWrapper;
|
||||
import com.google.android.common.gdata.AndroidXmlParserFactory;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.wireless.gdata.client.GDataClient;
|
||||
import com.google.wireless.gdata.client.GDataServiceClient;
|
||||
import com.google.wireless.gdata.docs.DocumentsClient;
|
||||
import com.google.wireless.gdata.docs.SpreadsheetsClient;
|
||||
import com.google.wireless.gdata.docs.XmlDocsGDataParserFactory;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A helper class used to transmit tracks statistics to Google Docs/Trix.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class SendToDocs {
|
||||
/** The GData service name for Google Spreadsheets (aka Trix) */
|
||||
public static final String GDATA_SERVICE_NAME_TRIX = "wise";
|
||||
|
||||
/** The GData service name for the Google Docs Document List */
|
||||
public static final String GDATA_SERVICE_NAME_DOCLIST = "writely";
|
||||
|
||||
private final Activity activity;
|
||||
private final AuthManager trixAuth;
|
||||
private final AuthManager docListAuth;
|
||||
private final ProgressIndicator progressIndicator;
|
||||
private final boolean metricUnits;
|
||||
|
||||
private boolean success = true;
|
||||
private Runnable onCompletion = null;
|
||||
|
||||
|
||||
public SendToDocs(Activity activity, AuthManager trixAuth,
|
||||
AuthManager docListAuth, ProgressIndicator progressIndicator) {
|
||||
this.activity = activity;
|
||||
this.trixAuth = trixAuth;
|
||||
this.docListAuth = docListAuth;
|
||||
this.progressIndicator = progressIndicator;
|
||||
|
||||
SharedPreferences preferences = activity.getSharedPreferences(
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
if (preferences != null) {
|
||||
metricUnits =
|
||||
preferences.getBoolean(activity.getString(R.string.metric_units_key),
|
||||
true);
|
||||
} else {
|
||||
metricUnits = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void sendToDocs(final long trackId) {
|
||||
Log.d(Constants.TAG,
|
||||
"Sending to Google Docs: trackId = " + trackId);
|
||||
new Thread("SendToGoogleDocs") {
|
||||
@Override
|
||||
public void run() {
|
||||
doUpload(trackId);
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
private void doUpload(long trackId) {
|
||||
success = false;
|
||||
try {
|
||||
if (trackId == -1) {
|
||||
Log.w(Constants.TAG, "Cannot get track id.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the track from the provider:
|
||||
Track track =
|
||||
MyTracksProviderUtils.Factory.get(activity).getTrack(trackId);
|
||||
if (track == null) {
|
||||
Log.w(Constants.TAG, "Cannot get track.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Transmit track stats via GData feed:
|
||||
// -------------------------------
|
||||
|
||||
Log.d(Constants.TAG, "SendToDocs: Uploading to spreadsheet");
|
||||
success = uploadToDocs(track);
|
||||
Log.d(Constants.TAG, "SendToDocs: Done.");
|
||||
} finally {
|
||||
if (onCompletion != null) {
|
||||
activity.runOnUiThread(onCompletion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean wasSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setOnCompletion(Runnable onCompletion) {
|
||||
this.onCompletion = onCompletion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads the statistics about a track to Google Docs using the docs GData
|
||||
* feed.
|
||||
*
|
||||
* @param track the track
|
||||
*/
|
||||
private boolean uploadToDocs(Track track) {
|
||||
GDataWrapper<GDataServiceClient> docListWrapper = new GDataWrapper<GDataServiceClient>();
|
||||
docListWrapper.setAuthManager(docListAuth);
|
||||
docListWrapper.setRetryOnAuthFailure(true);
|
||||
|
||||
GDataWrapper<GDataServiceClient> trixWrapper = new GDataWrapper<GDataServiceClient>();
|
||||
trixWrapper.setAuthManager(trixAuth);
|
||||
trixWrapper.setRetryOnAuthFailure(true);
|
||||
|
||||
DocsHelper docsHelper = new DocsHelper();
|
||||
|
||||
GDataClient androidClient = null;
|
||||
try {
|
||||
androidClient = GDataClientFactory.getGDataClient(activity);
|
||||
SpreadsheetsClient gdataClient = new SpreadsheetsClient(androidClient,
|
||||
new XmlDocsGDataParserFactory(new AndroidXmlParserFactory()));
|
||||
trixWrapper.setClient(gdataClient);
|
||||
Log.d(Constants.TAG,
|
||||
"GData connection prepared: " + this.docListAuth);
|
||||
String sheetTitle = "My Tracks";
|
||||
|
||||
if (track.getCategory() != null && !track.getCategory().equals("")) {
|
||||
sheetTitle += "-" + track.getCategory();
|
||||
}
|
||||
|
||||
DocumentsClient docsGdataClient = new DocumentsClient(androidClient,
|
||||
new XmlDocsGDataParserFactory(new AndroidXmlParserFactory()));
|
||||
docListWrapper.setClient(docsGdataClient);
|
||||
|
||||
// First try to find the spreadsheet:
|
||||
String spreadsheetId = null;
|
||||
try {
|
||||
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
|
||||
sheetTitle);
|
||||
} catch (IOException e) {
|
||||
Log.i(Constants.TAG, "Spreadsheet lookup failed.", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spreadsheetId == null) {
|
||||
progressIndicator.setProgressValue(65);
|
||||
// Waiting a few seconds and trying again. Maybe the server just had a
|
||||
// hickup (unfortunately that happens quite a lot...).
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(Constants.TAG, "Sleep interrupted", e);
|
||||
}
|
||||
|
||||
try {
|
||||
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
|
||||
sheetTitle);
|
||||
} catch (IOException e) {
|
||||
Log.i(Constants.TAG, "2nd spreadsheet lookup failed.", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// We were unable to find an existing spreadsheet, so create a new one.
|
||||
progressIndicator.setProgressValue(70);
|
||||
if (spreadsheetId == null) {
|
||||
Log.i(Constants.TAG, "Creating new spreadsheet: " + sheetTitle);
|
||||
|
||||
try {
|
||||
spreadsheetId = docsHelper.createSpreadsheet(activity, docListWrapper,
|
||||
sheetTitle);
|
||||
} catch (IOException e) {
|
||||
Log.i(Constants.TAG, "Failed to create new spreadsheet "
|
||||
+ sheetTitle, e);
|
||||
return false;
|
||||
}
|
||||
progressIndicator.setProgressValue(80);
|
||||
|
||||
if (spreadsheetId == null) {
|
||||
progressIndicator.setProgressValue(85);
|
||||
// The previous creation might have succeeded even though GData
|
||||
// reported an error. Seems to be a know bug,
|
||||
// see http://code.google.com/p/gdata-issues/issues/detail?id=929
|
||||
// Try to find the created spreadsheet:
|
||||
Log.w(Constants.TAG,
|
||||
"Create might have failed. Trying to find created document.");
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(Constants.TAG, "Sleep interrupted", e);
|
||||
}
|
||||
|
||||
try {
|
||||
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
|
||||
sheetTitle);
|
||||
} catch (IOException e) {
|
||||
Log.i(Constants.TAG, "Failed create-failed lookup", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spreadsheetId == null) {
|
||||
progressIndicator.setProgressValue(87);
|
||||
// Re-try
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(Constants.TAG, "Sleep interrupted", e);
|
||||
}
|
||||
|
||||
try {
|
||||
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
|
||||
sheetTitle);
|
||||
} catch (IOException e) {
|
||||
Log.i(Constants.TAG, "Failed create-failed relookup", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (spreadsheetId == null) {
|
||||
Log.i(Constants.TAG,
|
||||
"Creating new spreadsheet really failed.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String worksheetId = null;
|
||||
try {
|
||||
worksheetId = docsHelper.getWorksheetId(trixWrapper, spreadsheetId);
|
||||
if (worksheetId == null) {
|
||||
throw new IOException("Worksheet ID lookup returned empty");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.i(Constants.TAG, "Looking up worksheet id failed.", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
progressIndicator.setProgressValue(90);
|
||||
|
||||
docsHelper.addTrackRow(activity, trixAuth, spreadsheetId, worksheetId,
|
||||
track, metricUnits);
|
||||
Log.i(Constants.TAG, "Done uploading to docs.");
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Unable to upload docs.", e);
|
||||
return false;
|
||||
} finally {
|
||||
if (androidClient != null) {
|
||||
androidClient.close();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,728 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.ProgressIndicator;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.io.gdata.GDataWrapper;
|
||||
import com.google.android.apps.mytracks.io.gdata.GDataWrapper.QueryFunction;
|
||||
import com.google.android.apps.mytracks.io.sendtogoogle.SendType;
|
||||
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.apps.mytracks.util.SystemUtils;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.api.client.googleapis.GoogleHeaders;
|
||||
import com.google.api.client.googleapis.MethodOverride;
|
||||
import com.google.api.client.http.GenericUrl;
|
||||
import com.google.api.client.http.HttpRequest;
|
||||
import com.google.api.client.http.HttpRequestFactory;
|
||||
import com.google.api.client.http.HttpResponse;
|
||||
import com.google.api.client.http.HttpResponseException;
|
||||
import com.google.api.client.http.HttpTransport;
|
||||
import com.google.api.client.http.InputStreamContent;
|
||||
import com.google.api.client.util.Strings;
|
||||
import com.google.common.io.LineReader;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.location.Location;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* A helper class used to transmit tracks to Google Fusion Tables.
|
||||
* A new instance should be used for each upload.
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
public class SendToFusionTables implements Runnable {
|
||||
|
||||
private static final String CONTENT_TYPE = "application/x-www-form-urlencoded";
|
||||
|
||||
/**
|
||||
* Listener invoked when sending to fusion tables completes.
|
||||
*/
|
||||
public interface OnSendCompletedListener {
|
||||
void onSendCompleted(String tableId, boolean success);
|
||||
}
|
||||
|
||||
/** The GData service id for Fusion Tables. */
|
||||
public static final String SERVICE_ID = "fusiontables";
|
||||
|
||||
/** The path for viewing a map visualization of a table. */
|
||||
private static final String FUSIONTABLES_MAP =
|
||||
"https://www.google.com/fusiontables/embedviz?" +
|
||||
"viz=MAP&q=select+col0,+col1,+col2,+col3+from+%s+&h=false&" +
|
||||
"lat=%f&lng=%f&z=%d&t=1&l=col2";
|
||||
|
||||
/** Standard base feed url for Fusion Tables. */
|
||||
private static final String FUSIONTABLES_BASE_FEED_URL =
|
||||
"https://www.google.com/fusiontables/api/query";
|
||||
|
||||
private static final int MAX_POINTS_PER_UPLOAD = 2048;
|
||||
|
||||
private static final String GDATA_VERSION = "2";
|
||||
|
||||
/** Response line which identifies a table ID. */
|
||||
private static final String TABLE_ID_LINE = "tableid";
|
||||
|
||||
// This class reports upload status to the user as a completion percentage
|
||||
// using a progress bar. Progress is defined as follows:
|
||||
//
|
||||
// 0% Getting track metadata
|
||||
// 5% Creating Fusion Table (GData to FT server)
|
||||
// 10%-90% Uploading the track data to Fusion Tables
|
||||
// 95% Uploading waypoints
|
||||
// 100% Done
|
||||
private static final int PROGRESS_INITIALIZATION = 0;
|
||||
private static final int PROGRESS_FUSION_TABLE_CREATE = 5;
|
||||
private static final int PROGRESS_UPLOAD_DATA_MIN = 10;
|
||||
private static final int PROGRESS_UPLOAD_DATA_MAX = 90;
|
||||
private static final int PROGRESS_UPLOAD_WAYPOINTS = 95;
|
||||
private static final int PROGRESS_COMPLETE = 100;
|
||||
|
||||
private final Activity context;
|
||||
private final AuthManager auth;
|
||||
private final long trackId;
|
||||
private final ProgressIndicator progressIndicator;
|
||||
private final OnSendCompletedListener onCompletion;
|
||||
private final StringUtils stringUtils;
|
||||
private final MyTracksProviderUtils providerUtils;
|
||||
|
||||
// Progress status
|
||||
private int totalLocationsRead;
|
||||
private int totalLocationsPrepared;
|
||||
private int totalLocationsUploaded;
|
||||
private int totalLocations;
|
||||
private int totalSegmentsUploaded;
|
||||
|
||||
private HttpRequestFactory httpRequestFactory;
|
||||
private String tableId;
|
||||
|
||||
private static String MARKER_TYPE_START = "large_green";
|
||||
private static String MARKER_TYPE_END = "large_red";
|
||||
private static String MARKER_TYPE_WAYPOINT = "large_yellow";
|
||||
|
||||
public SendToFusionTables(Activity context, AuthManager auth, long trackId,
|
||||
ProgressIndicator progressIndicator, OnSendCompletedListener onCompletion) {
|
||||
this.context = context;
|
||||
this.auth = auth;
|
||||
this.trackId = trackId;
|
||||
this.progressIndicator = progressIndicator;
|
||||
this.onCompletion = onCompletion;
|
||||
this.stringUtils = new StringUtils(context);
|
||||
this.providerUtils = MyTracksProviderUtils.Factory.get(context);
|
||||
|
||||
HttpTransport transport = ApiFeatures.getInstance().getApiAdapter().getHttpTransport();
|
||||
httpRequestFactory = transport.createRequestFactory(new MethodOverride());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d(TAG, "Sending to Fusion tables: trackId = " + trackId);
|
||||
doUpload();
|
||||
}
|
||||
|
||||
public static String getMapVisualizationUrl(Track track) {
|
||||
if (track == null || track.getStatistics() == null || track.getTableId() == null) {
|
||||
Log.w(TAG, "Unable to get track URL");
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO(leifhendrik): Determine correct bounding box and zoom level that will show the entire track.
|
||||
TripStatistics stats = track.getStatistics();
|
||||
double latE6 = stats.getBottom() + (stats.getTop() - stats.getBottom()) / 2;
|
||||
double lonE6 = stats.getLeft() + (stats.getRight() - stats.getLeft()) / 2;
|
||||
int z = 15;
|
||||
|
||||
// We explicitly format with Locale.US because we need the latitude and
|
||||
// longitude to be formatted in a locale-independent manner. Specifically,
|
||||
// we need the decimal separator to be a period rather than a comma.
|
||||
return String.format(Locale.US, FUSIONTABLES_MAP, track.getTableId(),
|
||||
latE6 / 1.E6, lonE6 / 1.E6, z);
|
||||
}
|
||||
|
||||
private void doUpload() {
|
||||
boolean success = true;
|
||||
try {
|
||||
progressIndicator.setProgressValue(PROGRESS_INITIALIZATION);
|
||||
progressIndicator.setProgressMessage(
|
||||
context.getString(R.string.send_google_progress_reading_track));
|
||||
|
||||
// Get the track meta-data
|
||||
Track track = providerUtils.getTrack(trackId);
|
||||
if (track == null) {
|
||||
Log.w(TAG, "Cannot get track.");
|
||||
return;
|
||||
}
|
||||
|
||||
String originalDescription = track.getDescription();
|
||||
|
||||
// Create a new table:
|
||||
progressIndicator.setProgressValue(PROGRESS_FUSION_TABLE_CREATE);
|
||||
String creatingFormat = context.getString(R.string.send_google_progress_creating);
|
||||
String serviceName = context.getString(SendType.FUSION_TABLES.getServiceName());
|
||||
progressIndicator.setProgressMessage(String.format(creatingFormat, serviceName));
|
||||
if (!createNewTable(track) || !makeTableUnlisted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
progressIndicator.setProgressValue(PROGRESS_UPLOAD_DATA_MIN);
|
||||
String sendingFormat = context.getString(R.string.send_google_progress_sending);
|
||||
progressIndicator.setProgressMessage(String.format(sendingFormat, serviceName));
|
||||
|
||||
// Upload all of the segments of the track plus start/end markers
|
||||
if (!uploadAllTrackPoints(track, originalDescription)) {
|
||||
return;
|
||||
}
|
||||
|
||||
progressIndicator.setProgressValue(PROGRESS_UPLOAD_WAYPOINTS);
|
||||
|
||||
// Upload all the waypoints.
|
||||
if (!uploadWaypoints(track)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "SendToFusionTables: Done: " + success);
|
||||
progressIndicator.setProgressValue(PROGRESS_COMPLETE);
|
||||
} finally {
|
||||
final boolean finalSuccess = success;
|
||||
context.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (onCompletion != null) {
|
||||
onCompletion.onSendCompleted(tableId, finalSuccess);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new table.
|
||||
* If successful sets {@link #tableId}.
|
||||
*
|
||||
* @return true in case of success.
|
||||
*/
|
||||
private boolean createNewTable(Track track) {
|
||||
Log.d(TAG, "Creating a new fusion table.");
|
||||
String query = "CREATE TABLE '" + sqlEscape(track.getName()) +
|
||||
"' (name:STRING,description:STRING,geometry:LOCATION,marker:STRING)";
|
||||
|
||||
List<String> resultLines = new ArrayList<String>();
|
||||
boolean success = runUpdate(query, resultLines);
|
||||
if (success && resultLines.size() >= 2 && resultLines.get(0).equals(TABLE_ID_LINE)) {
|
||||
tableId = resultLines.get(1);
|
||||
Log.d(TAG, "tableId = " + tableId);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
private boolean makeTableUnlisted() {
|
||||
Log.d(TAG, "Setting visibility to unlisted.");
|
||||
String query = "UPDATE TABLE " + tableId + " SET VISIBILITY = UNLISTED";
|
||||
return runUpdate(query, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats given values SQL style. Escapes single quotes with a backslash.
|
||||
*
|
||||
* @param values the values to format
|
||||
* @return the values formatted as: ('value1','value2',...,'value_n').
|
||||
*/
|
||||
private static String values(String... values) {
|
||||
StringBuilder builder = new StringBuilder("(");
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
if (i > 0) {
|
||||
builder.append(',');
|
||||
}
|
||||
builder.append('\'');
|
||||
builder.append(sqlEscape(values[i]));
|
||||
builder.append('\'');
|
||||
}
|
||||
builder.append(')');
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static String sqlEscape(String value) {
|
||||
return value.replaceAll("'", "''");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new row representing a marker.
|
||||
*
|
||||
* @param name the marker name
|
||||
* @param description the marker description
|
||||
* @param location the marker location
|
||||
* @return true in case of success.
|
||||
*/
|
||||
private boolean createNewPoint(String name, String description, Location location,
|
||||
String marker) {
|
||||
Log.d(TAG, "Creating a new row with a point.");
|
||||
String query = "INSERT INTO " + tableId + " (name,description,geometry,marker) VALUES "
|
||||
+ values(name, description, getKmlPoint(location), marker);
|
||||
return runUpdate(query, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new row representing a line segment.
|
||||
*
|
||||
* @param track the track/segment to draw
|
||||
* @return true in case of success.
|
||||
*/
|
||||
private boolean createNewLineString(Track track) {
|
||||
Log.d(TAG, "Creating a new row with a point.");
|
||||
String query = "INSERT INTO " + tableId
|
||||
+ " (name,description,geometry) VALUES "
|
||||
+ values(track.getName(), track.getDescription(), getKmlLineString(track));
|
||||
return runUpdate(query, null);
|
||||
}
|
||||
|
||||
private boolean uploadAllTrackPoints(final Track track, String originalDescription) {
|
||||
SharedPreferences preferences = context.getSharedPreferences(
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
boolean metricUnits = true;
|
||||
if (preferences != null) {
|
||||
metricUnits = preferences.getBoolean(context.getString(R.string.metric_units_key), true);
|
||||
}
|
||||
|
||||
Cursor locationsCursor = providerUtils.getLocationsCursor(track.getId(), 0, -1, false);
|
||||
try {
|
||||
if (locationsCursor == null || !locationsCursor.moveToFirst()) {
|
||||
Log.w(TAG, "Unable to get any points to upload");
|
||||
return false;
|
||||
}
|
||||
|
||||
totalLocationsRead = 0;
|
||||
totalLocationsPrepared = 0;
|
||||
totalLocationsUploaded = 0;
|
||||
totalLocations = locationsCursor.getCount();
|
||||
totalSegmentsUploaded = 0;
|
||||
|
||||
// Limit the number of elevation readings. Ideally we would want around 250.
|
||||
int elevationSamplingFrequency =
|
||||
Math.max(1, (int) (totalLocations / 250.0));
|
||||
Log.d(TAG,
|
||||
"Using elevation sampling factor: " + elevationSamplingFrequency
|
||||
+ " on " + totalLocations);
|
||||
double totalDistance = 0;
|
||||
|
||||
Vector<Double> distances = new Vector<Double>();
|
||||
Vector<Double> elevations = new Vector<Double>();
|
||||
DoubleBuffer elevationBuffer = new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR);
|
||||
|
||||
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
|
||||
Location lastLocation = null;
|
||||
do {
|
||||
if (totalLocationsRead % 100 == 0) {
|
||||
updateTrackDataUploadProgress();
|
||||
}
|
||||
|
||||
Location loc = providerUtils.createLocation(locationsCursor);
|
||||
locations.add(loc);
|
||||
|
||||
if (totalLocationsRead == 0) {
|
||||
// Put a marker at the first point of the first valid segment:
|
||||
String name = track.getName() + " " + context.getString(R.string.marker_label_start);
|
||||
createNewPoint(name, "", loc, MARKER_TYPE_START);
|
||||
}
|
||||
|
||||
// Add to the elevation profile.
|
||||
if (loc != null && LocationUtils.isValidLocation(loc)) {
|
||||
// All points go into the smoothing buffer...
|
||||
elevationBuffer.setNext(metricUnits ? loc.getAltitude()
|
||||
: loc.getAltitude() * UnitConversions.M_TO_FT);
|
||||
if (lastLocation != null) {
|
||||
double dist = lastLocation.distanceTo(loc);
|
||||
totalDistance += dist;
|
||||
}
|
||||
|
||||
// ...but only a few points are really used to keep the url short.
|
||||
if (totalLocationsRead % elevationSamplingFrequency == 0) {
|
||||
distances.add(totalDistance);
|
||||
elevations.add(elevationBuffer.getAverage());
|
||||
}
|
||||
}
|
||||
|
||||
// If the location was not valid, it's a segment split, so make sure the
|
||||
// distance between the previous segment and the new one is not accounted
|
||||
// for in the next iteration.
|
||||
lastLocation = loc;
|
||||
|
||||
// Every now and then, upload the accumulated points
|
||||
if (totalLocationsRead % MAX_POINTS_PER_UPLOAD == MAX_POINTS_PER_UPLOAD - 1) {
|
||||
if (!prepareAndUploadPoints(track, locations)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
totalLocationsRead++;
|
||||
} while (locationsCursor.moveToNext());
|
||||
|
||||
// Do a final upload with what's left
|
||||
if (!prepareAndUploadPoints(track, locations)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Put an end marker at the last point of the last valid segment:
|
||||
if (lastLocation != null) {
|
||||
track.setDescription("<p>" + originalDescription + "</p><p>"
|
||||
+ stringUtils.generateTrackDescription(track, distances, elevations)
|
||||
+ "</p>");
|
||||
String name = track.getName() + " " + context.getString(R.string.marker_label_end);
|
||||
return createNewPoint(name, track.getDescription(), lastLocation, MARKER_TYPE_END);
|
||||
}
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
if (locationsCursor != null) {
|
||||
locationsCursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given location to the string in the format:
|
||||
* longitude,latitude[,altitude]
|
||||
*
|
||||
* @param location the location to be added
|
||||
* @param builder the string builder to use
|
||||
*/
|
||||
private void appendCoordinate(Location location, StringBuilder builder) {
|
||||
builder
|
||||
.append(location.getLongitude())
|
||||
.append(",")
|
||||
.append(location.getLatitude());
|
||||
if (location.hasAltitude()) {
|
||||
builder.append(",");
|
||||
builder.append(location.getAltitude());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a KML Point tag for the given location.
|
||||
*
|
||||
* @param location The location.
|
||||
* @return the kml.
|
||||
*/
|
||||
private String getKmlPoint(Location location) {
|
||||
StringBuilder builder = new StringBuilder("<Point><coordinates>");
|
||||
appendCoordinate(location, builder);
|
||||
builder.append("</coordinates></Point>");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a KML LineString.
|
||||
*
|
||||
* @param track the track.
|
||||
* @return the KML LineString.
|
||||
*/
|
||||
private String getKmlLineString(Track track) {
|
||||
StringBuilder builder = new StringBuilder("<LineString><coordinates>");
|
||||
for (Location location : track.getLocations()) {
|
||||
appendCoordinate(location, builder);
|
||||
builder.append(' ');
|
||||
}
|
||||
builder.append("</coordinates></LineString>");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private boolean prepareAndUploadPoints(Track track, List<Location> locations) {
|
||||
updateTrackDataUploadProgress();
|
||||
|
||||
int numLocations = locations.size();
|
||||
if (numLocations < 2) {
|
||||
Log.d(TAG, "Not preparing/uploading too few points");
|
||||
totalLocationsUploaded += numLocations;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prepare/pre-process the points
|
||||
ArrayList<Track> splitTracks = prepareLocations(track, locations);
|
||||
|
||||
// Start uploading them
|
||||
for (Track splitTrack : splitTracks) {
|
||||
if (totalSegmentsUploaded > 1) {
|
||||
splitTrack.setName(splitTrack.getName() + " "
|
||||
+ String.format(
|
||||
context.getString(R.string.send_google_track_part_label), totalSegmentsUploaded));
|
||||
}
|
||||
totalSegmentsUploaded++;
|
||||
Log.d(TAG,
|
||||
"SendToFusionTables: Prepared feature for upload w/ "
|
||||
+ splitTrack.getLocations().size() + " points.");
|
||||
|
||||
// Transmit tracks via GData feed:
|
||||
// -------------------------------
|
||||
Log.d(TAG,
|
||||
"SendToFusionTables: Uploading to table " + tableId + " w/ auth " + auth);
|
||||
if (!uploadTrackPoints(splitTrack)) {
|
||||
Log.e(TAG, "Uploading failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
locations.clear();
|
||||
totalLocationsUploaded += numLocations;
|
||||
updateTrackDataUploadProgress();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a buffer of locations for transmission to google fusion tables.
|
||||
*
|
||||
* @param track the original track with meta data
|
||||
* @param locations locations on the track
|
||||
* @return an array of tracks each with a sub section of the points in the
|
||||
* original buffer
|
||||
*/
|
||||
private ArrayList<Track> prepareLocations(
|
||||
Track track, Iterable<Location> locations) {
|
||||
ArrayList<Track> splitTracks = new ArrayList<Track>();
|
||||
|
||||
// Create segments from each full track:
|
||||
Track segment = new Track();
|
||||
TripStatistics segmentStats = segment.getStatistics();
|
||||
TripStatistics trackStats = track.getStatistics();
|
||||
segment.setId(track.getId());
|
||||
segment.setName(track.getName());
|
||||
segment.setDescription(/* track.getDescription() */ "");
|
||||
segment.setCategory(track.getCategory());
|
||||
segmentStats.setStartTime(trackStats.getStartTime());
|
||||
segmentStats.setStopTime(trackStats.getStopTime());
|
||||
boolean startNewTrackSegment = false;
|
||||
for (Location loc : locations) {
|
||||
if (totalLocationsPrepared % 100 == 0) {
|
||||
updateTrackDataUploadProgress();
|
||||
}
|
||||
if (loc.getLatitude() > 90) {
|
||||
startNewTrackSegment = true;
|
||||
}
|
||||
|
||||
if (startNewTrackSegment) {
|
||||
// Close up the last segment.
|
||||
prepareTrackSegment(segment, splitTracks);
|
||||
|
||||
Log.d(TAG,
|
||||
"MyTracksSendToFusionTables: Starting new track segment...");
|
||||
startNewTrackSegment = false;
|
||||
segment = new Track();
|
||||
segment.setId(track.getId());
|
||||
segment.setName(track.getName());
|
||||
segment.setDescription(/* track.getDescription() */ "");
|
||||
segment.setCategory(track.getCategory());
|
||||
}
|
||||
|
||||
if (loc.getLatitude() <= 90) {
|
||||
segment.addLocation(loc);
|
||||
if (segmentStats.getStartTime() < 0) {
|
||||
segmentStats.setStartTime(loc.getTime());
|
||||
}
|
||||
}
|
||||
totalLocationsPrepared++;
|
||||
}
|
||||
|
||||
prepareTrackSegment(segment, splitTracks);
|
||||
|
||||
return splitTracks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a track segment for sending to google fusion tables.
|
||||
* The main steps are:
|
||||
* - correcting end time
|
||||
* - decimating locations
|
||||
* - splitting into smaller tracks.
|
||||
*
|
||||
* The final track pieces will be put in the array list splitTracks.
|
||||
*
|
||||
* @param segment the original segment of the track
|
||||
* @param splitTracks an array of smaller track segments
|
||||
*/
|
||||
private void prepareTrackSegment(
|
||||
Track segment, ArrayList<Track> splitTracks) {
|
||||
TripStatistics segmentStats = segment.getStatistics();
|
||||
if (segmentStats.getStopTime() < 0
|
||||
&& segment.getLocations().size() > 0) {
|
||||
segmentStats.setStopTime(segment.getLocations().size() - 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Decimate to 2 meter precision. Fusion tables doesn't like too many
|
||||
* points:
|
||||
*/
|
||||
LocationUtils.decimate(segment, 2.0);
|
||||
|
||||
/* If the track still has > 2500 points, split it in pieces: */
|
||||
final int maxPoints = 2500;
|
||||
if (segment.getLocations().size() > maxPoints) {
|
||||
splitTracks.addAll(LocationUtils.split(segment, maxPoints));
|
||||
} else if (segment.getLocations().size() >= 2) {
|
||||
splitTracks.add(segment);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean uploadTrackPoints(Track splitTrack) {
|
||||
int numLocations = splitTrack.getLocations().size();
|
||||
if (numLocations < 2) {
|
||||
// Need at least two points for a polyline:
|
||||
Log.w(TAG, "Not uploading too few points");
|
||||
return true;
|
||||
}
|
||||
return createNewLineString(splitTrack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads all of the waypoints associated with this track to a table.
|
||||
*
|
||||
* @param track The track to upload waypoints for.
|
||||
*
|
||||
* @return True on success.
|
||||
*/
|
||||
private boolean uploadWaypoints(final Track track) {
|
||||
// TODO: 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.
|
||||
boolean success = true;
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = providerUtils.getWaypointsCursor(
|
||||
track.getId(), 0,
|
||||
Constants.MAX_LOADED_WAYPOINTS_POINTS);
|
||||
if (c != null) {
|
||||
if (c.moveToFirst()) {
|
||||
// This will skip the 1st waypoint (it carries the stats for the
|
||||
// last segment).
|
||||
while (c.moveToNext()) {
|
||||
Waypoint wpt = providerUtils.createWaypoint(c);
|
||||
Log.d(TAG, "SendToFusionTables: Creating waypoint.");
|
||||
success = createNewPoint(wpt.getName(), wpt.getDescription(), wpt.getLocation(),
|
||||
MARKER_TYPE_WAYPOINT);
|
||||
if (!success) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!success) {
|
||||
Log.w(TAG, "SendToFusionTables: upload waypoints failed.");
|
||||
}
|
||||
return success;
|
||||
} finally {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTrackDataUploadProgress() {
|
||||
// The percent of the total that represents the completed part of this
|
||||
// segment. We calculate it as an absolute percentage, and then scale it
|
||||
// to fit the completion percentage range alloted to track data upload.
|
||||
double totalPercentage =
|
||||
(totalLocationsRead + totalLocationsPrepared + totalLocationsUploaded)
|
||||
/ (totalLocations * 3.0);
|
||||
|
||||
double scaledPercentage = totalPercentage
|
||||
* (PROGRESS_UPLOAD_DATA_MAX - PROGRESS_UPLOAD_DATA_MIN) + PROGRESS_UPLOAD_DATA_MIN;
|
||||
|
||||
progressIndicator.setProgressValue((int) scaledPercentage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an update query. Handles authentication.
|
||||
*
|
||||
* @param query The given SQL like query
|
||||
* @param resultLines a list to fill with the query result's lines (or null to ignore results)
|
||||
* @return true in case of success
|
||||
*/
|
||||
private boolean runUpdate(final String query, final List<String> resultLines) {
|
||||
GDataWrapper<HttpRequestFactory> wrapper = new GDataWrapper<HttpRequestFactory>();
|
||||
wrapper.setAuthManager(auth);
|
||||
wrapper.setRetryOnAuthFailure(true);
|
||||
wrapper.setClient(httpRequestFactory);
|
||||
Log.d(TAG, "GData connection prepared: " + this.auth);
|
||||
wrapper.runQuery(new QueryFunction<HttpRequestFactory>() {
|
||||
@Override
|
||||
public void query(HttpRequestFactory factory)
|
||||
throws IOException, GDataWrapper.ParseException, GDataWrapper.HttpException,
|
||||
GDataWrapper.AuthenticationException {
|
||||
GenericUrl url = new GenericUrl(FUSIONTABLES_BASE_FEED_URL);
|
||||
|
||||
String sql = "sql=" + URLEncoder.encode(query, "UTF-8");
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(Strings.toBytesUtf8(sql));
|
||||
InputStreamContent isc = new InputStreamContent(null, inputStream );
|
||||
|
||||
HttpRequest request = factory.buildPostRequest(url, isc);
|
||||
|
||||
GoogleHeaders headers = new GoogleHeaders();
|
||||
headers.setApplicationName("Google-MyTracks-" + SystemUtils.getMyTracksVersion(context));
|
||||
headers.gdataVersion = GDATA_VERSION;
|
||||
headers.setGoogleLogin(auth.getAuthToken());
|
||||
headers.setContentType(CONTENT_TYPE);
|
||||
request.setHeaders(headers);
|
||||
|
||||
Log.d(TAG, "Running update query " + url.toString() + ": " + sql);
|
||||
HttpResponse response;
|
||||
try {
|
||||
response = request.execute();
|
||||
} catch (HttpResponseException e) {
|
||||
throw new GDataWrapper.HttpException(e.getResponse().getStatusCode(),
|
||||
e.getResponse().getStatusMessage());
|
||||
}
|
||||
boolean success = response.isSuccessStatusCode();
|
||||
if (success) {
|
||||
if (resultLines != null) {
|
||||
InputStreamReader resultReader = new InputStreamReader(response.getContent(), "UTF8");
|
||||
LineReader lineReader = new LineReader(resultReader);
|
||||
String line;
|
||||
while ((line = lineReader.readLine()) != null) {
|
||||
resultLines.add(line);
|
||||
}
|
||||
resultReader.close();
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG,
|
||||
"Query failed: " + response.getStatusMessage() + " ("
|
||||
+ response.getStatusCode() + ")");
|
||||
throw new GDataWrapper.HttpException(
|
||||
response.getStatusCode(), response.getStatusMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
return wrapper.getErrorType() == GDataWrapper.ERROR_NO_ERROR;
|
||||
}
|
||||
}
|
||||
+14
-15
@@ -22,7 +22,7 @@ import com.google.android.apps.mytracks.ProgressIndicator;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.io.mymaps.MapsFacade;
|
||||
import com.google.android.apps.mytracks.io.maps.MapsFacade;
|
||||
import com.google.android.apps.mytracks.io.sendtogoogle.SendType;
|
||||
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
@@ -43,7 +43,7 @@ import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* A helper class used to transmit tracks to Google MyMaps.
|
||||
* A helper class used to transmit tracks to Google Maps.
|
||||
* A new instance should be used for each upload.
|
||||
*
|
||||
* IMPORTANT: while this code is Apache-licensed, please notice that usage of
|
||||
@@ -53,7 +53,7 @@ import java.util.Vector;
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
public class SendToMyMaps implements Runnable {
|
||||
public class SendToMaps implements Runnable {
|
||||
|
||||
public static final String NEW_MAP_ID = "new";
|
||||
private static final int MAX_POINTS_PER_UPLOAD = 2048;
|
||||
@@ -80,7 +80,7 @@ public class SendToMyMaps implements Runnable {
|
||||
void onSendCompleted(String mapId, boolean success);
|
||||
}
|
||||
|
||||
public SendToMyMaps(Activity context, String mapId, AuthManager auth,
|
||||
public SendToMaps(Activity context, String mapId, AuthManager auth,
|
||||
long trackId, ProgressIndicator progressIndicator,
|
||||
OnSendCompletedListener onCompletion) {
|
||||
this.context = context;
|
||||
@@ -95,7 +95,7 @@ public class SendToMyMaps implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d(TAG, "Sending to MyMaps: trackId = " + trackId);
|
||||
Log.d(TAG, "Sending to Maps: trackId = " + trackId);
|
||||
doUpload();
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ public class SendToMyMaps implements Runnable {
|
||||
}
|
||||
|
||||
String creatingFormat = context.getString(R.string.send_google_progress_creating);
|
||||
String serviceName = context.getString(SendType.MYMAPS.getServiceName());
|
||||
String serviceName = context.getString(SendType.MAPS.getServiceName());
|
||||
progressIndicator.setProgressMessage(String.format(creatingFormat, serviceName));
|
||||
|
||||
StringBuilder mapIdBuilder = new StringBuilder();
|
||||
@@ -170,11 +170,11 @@ public class SendToMyMaps implements Runnable {
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
Log.w(TAG, "SendToMyMaps: upload waypoints failed.");
|
||||
Log.w(TAG, "SendToMaps: upload waypoints failed.");
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "SendToMyMaps: Done: " + success);
|
||||
Log.d(TAG, "SendToMaps: Done: " + success);
|
||||
progressIndicator.setProgressValue(100);
|
||||
} finally {
|
||||
if (mapsClient != null) {
|
||||
@@ -321,23 +321,22 @@ public class SendToMyMaps implements Runnable {
|
||||
|
||||
// Start uploading them
|
||||
String sendingFormat = context.getString(R.string.send_google_progress_sending);
|
||||
String serviceName = context.getString(SendType.MYMAPS.getServiceName());
|
||||
String serviceName = context.getString(SendType.MAPS.getServiceName());
|
||||
progressIndicator.setProgressMessage(String.format(sendingFormat, serviceName));
|
||||
for (Track splitTrack : splitTracks) {
|
||||
if (totalSegmentsUploaded > 1) {
|
||||
splitTrack.setName(splitTrack.getName() + " "
|
||||
+ String.format(
|
||||
context.getString(R.string.send_google_track_part_label), totalSegmentsUploaded));
|
||||
splitTrack.setName(context.getString(
|
||||
R.string.send_google_track_part_label, splitTrack.getName(), totalSegmentsUploaded));
|
||||
}
|
||||
totalSegmentsUploaded++;
|
||||
Log.d(TAG,
|
||||
"SendToMyMaps: Prepared feature for upload w/ "
|
||||
"SendToMaps: Prepared feature for upload w/ "
|
||||
+ splitTrack.getLocations().size() + " points.");
|
||||
|
||||
// Transmit tracks via GData feed:
|
||||
// -------------------------------
|
||||
Log.d(TAG,
|
||||
"SendToMyMaps: Uploading to map " + mapId + " w/ auth " + auth);
|
||||
"SendToMaps: Uploading to map " + mapId + " w/ auth " + auth);
|
||||
if (!mapsClient.uploadTrackPoints(mapId, splitTrack.getName(), splitTrack.getLocations())) {
|
||||
Log.e(TAG, "Uploading failed");
|
||||
return false;
|
||||
@@ -386,7 +385,7 @@ public class SendToMyMaps implements Runnable {
|
||||
prepareTrackSegment(segment, splitTracks);
|
||||
|
||||
Log.d(TAG,
|
||||
"MyTracksSendToMyMaps: Starting new track segment...");
|
||||
"MyTracksSendToMaps: Starting new track segment...");
|
||||
startNewTrackSegment = false;
|
||||
segment = new Track();
|
||||
segment.setId(track.getId());
|
||||
@@ -156,7 +156,7 @@ public class BackupActivityHelper {
|
||||
// Make a user-visible version of the backup filenames
|
||||
final String backupDateStrs[] = new String[backupDates.length];
|
||||
for (int i = 0; i < backupDates.length; i++) {
|
||||
backupDateStrs[i] = StringUtils.formatDateTime(backupDates[i].getTime());
|
||||
backupDateStrs[i] = StringUtils.formatDateTime(activity, backupDates[i].getTime());
|
||||
}
|
||||
|
||||
// Show a dialog for the user to pick which backup to restore
|
||||
|
||||
@@ -1,371 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io.docs;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.io.AuthManager;
|
||||
import com.google.android.apps.mytracks.io.gdata.GDataWrapper;
|
||||
import com.google.android.apps.mytracks.io.gdata.GDataWrapper.QueryFunction;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.ResourceUtils;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.wireless.gdata.client.GDataServiceClient;
|
||||
import com.google.wireless.gdata.client.HttpException;
|
||||
import com.google.wireless.gdata.data.Entry;
|
||||
import com.google.wireless.gdata.docs.SpreadsheetsClient;
|
||||
import com.google.wireless.gdata.docs.SpreadsheetsClient.WorksheetEntry;
|
||||
import com.google.wireless.gdata.parser.GDataParser;
|
||||
import com.google.wireless.gdata.parser.ParseException;
|
||||
import com.google.wireless.gdata2.client.AuthenticationException;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* This class contains helper methods for interacting with Google Docs and
|
||||
* Google Spreadsheets.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Matthew Simmons
|
||||
*/
|
||||
public class DocsHelper {
|
||||
private static final String DOCS_FEED_URL =
|
||||
"https://docs.google.com/feeds/documents/private/full";
|
||||
private static final String DOCS_SPREADSHEET_URL =
|
||||
"https://docs.google.com/feeds/documents/private/full/spreadsheet%3A";
|
||||
private static final String DOCS_WORKSHEETS_URL_FORMAT =
|
||||
"https://spreadsheets.google.com/feeds/worksheets/%s/private/full";
|
||||
private static final String DOCS_MY_SPREADSHEETS_FEED_URL =
|
||||
"https://docs.google.com/feeds/documents/private/full?category=mine,spreadsheet";
|
||||
private static final String DOCS_SPREADSHEET_URL_FORMAT =
|
||||
"https://spreadsheets.google.com/feeds/list/%s/%s/private/full";
|
||||
|
||||
private static final String CONTENT_TYPE_PARAM = "Content-Type";
|
||||
private static final String OPENDOCUMENT_SPREADSHEET_MIME_TYPE =
|
||||
"application/x-vnd.oasis.opendocument.spreadsheet";
|
||||
private static final String ATOM_FEED_MIME_TYPE = "application/atom+xml";
|
||||
|
||||
/**
|
||||
* Creates a new MyTracks spreadsheet with the given name.
|
||||
*
|
||||
* @param context The context associated with this request.
|
||||
* @param docListWrapper The GData handle for the Document List service.
|
||||
* @param name The name for the newly-created spreadsheet.
|
||||
* @return The spreadsheet ID, if one is created. {@code null} will be
|
||||
* returned if a GData error didn't occur, but no spreadsheet ID was
|
||||
* returned.
|
||||
*/
|
||||
public String createSpreadsheet(final Context context,
|
||||
final GDataWrapper<GDataServiceClient> docListWrapper, final String name) throws IOException {
|
||||
final AtomicReference<String> idSaver = new AtomicReference<String>();
|
||||
|
||||
boolean success = docListWrapper.runQuery(new QueryFunction<GDataServiceClient>() {
|
||||
@Override
|
||||
public void query(GDataServiceClient client) throws IOException,
|
||||
GDataWrapper.AuthenticationException {
|
||||
// Construct and send request
|
||||
URL url = new URL(DOCS_FEED_URL);
|
||||
URLConnection conn = url.openConnection();
|
||||
conn.addRequestProperty(CONTENT_TYPE_PARAM,
|
||||
OPENDOCUMENT_SPREADSHEET_MIME_TYPE);
|
||||
conn.addRequestProperty("Slug", name);
|
||||
conn.addRequestProperty("Authorization",
|
||||
"GoogleLogin auth=" +
|
||||
docListWrapper.getAuthManager().getAuthToken());
|
||||
conn.setDoOutput(true);
|
||||
OutputStream os = conn.getOutputStream();
|
||||
ResourceUtils.readBinaryFileToOutputStream(
|
||||
context, R.raw.mytracks_empty_spreadsheet, os);
|
||||
|
||||
// Get the response
|
||||
// TODO: The following is a horrible ugly hack.
|
||||
// Hopefully we can retire it when there is a proper gdata api.
|
||||
BufferedReader rd = null;
|
||||
String line;
|
||||
StringBuilder resultBuilder = new StringBuilder();
|
||||
try {
|
||||
rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
while ((line = rd.readLine()) != null) {
|
||||
resultBuilder.append(line);
|
||||
}
|
||||
os.close();
|
||||
rd.close();
|
||||
} catch (FileNotFoundException e) {
|
||||
// The GData API sometimes throws an error, even though creation of
|
||||
// the document succeeded. In that case let's just return. The caller
|
||||
// then needs to check if the doc actually exists.
|
||||
return;
|
||||
} finally {
|
||||
os.close();
|
||||
if (rd != null) {
|
||||
rd.close();
|
||||
}
|
||||
}
|
||||
|
||||
String result = resultBuilder.toString();
|
||||
// Try to find the id.
|
||||
int idTagIndex = result.indexOf("<id>");
|
||||
if (idTagIndex == -1) {
|
||||
return;
|
||||
}
|
||||
int idTagCloseIndex = result.indexOf("</id>", idTagIndex);
|
||||
if (idTagCloseIndex == -1) {
|
||||
return;
|
||||
}
|
||||
int idStringStart = result.indexOf(DOCS_SPREADSHEET_URL, idTagIndex);
|
||||
if (idStringStart == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
String id = result.substring(
|
||||
idStringStart + DOCS_SPREADSHEET_URL.length(), idTagCloseIndex);
|
||||
Log.i(Constants.TAG, "Created new spreadsheet: " + id);
|
||||
idSaver.set(id);
|
||||
}});
|
||||
|
||||
if (!success) {
|
||||
throw newIOException(docListWrapper,
|
||||
"Failed to create new spreadsheet.");
|
||||
}
|
||||
|
||||
return idSaver.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the ID of a spreadsheet with the given name.
|
||||
*
|
||||
* @param docListWrapper The GData handle for the Document List service.
|
||||
* @param title The name of the spreadsheet whose ID is to be retrieved.
|
||||
* @return The spreadsheet ID, if it can be retrieved. {@code null} will
|
||||
* be returned if no spreadsheet exists by the given name.
|
||||
* @throws IOException If an error occurs during the GData request.
|
||||
*/
|
||||
public String requestSpreadsheetId(final GDataWrapper<GDataServiceClient> docListWrapper,
|
||||
final String title) throws IOException {
|
||||
final AtomicReference<String> idSaver = new AtomicReference<String>();
|
||||
|
||||
boolean result = docListWrapper.runQuery(new QueryFunction<GDataServiceClient>() {
|
||||
@Override
|
||||
public void query(GDataServiceClient client)
|
||||
throws IOException, GDataWrapper.ParseException, GDataWrapper.HttpException,
|
||||
GDataWrapper.AuthenticationException {
|
||||
GDataParser listParser;
|
||||
try {
|
||||
listParser = client.getParserForFeed(Entry.class,
|
||||
DOCS_MY_SPREADSHEETS_FEED_URL,
|
||||
docListWrapper.getAuthManager().getAuthToken());
|
||||
listParser.init();
|
||||
|
||||
while (listParser.hasMoreData()) {
|
||||
Entry entry = listParser.readNextEntry(null);
|
||||
String entryTitle = entry.getTitle();
|
||||
Log.i(Constants.TAG, "Found docs entry: " + entryTitle);
|
||||
if (entryTitle.equals(title)) {
|
||||
String entryId = entry.getId();
|
||||
int lastSlash = entryId.lastIndexOf('/');
|
||||
idSaver.set(entryId.substring(lastSlash + 15));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
throw new GDataWrapper.ParseException(e);
|
||||
} catch (HttpException e) {
|
||||
throw new GDataWrapper.HttpException(e.getStatusCode(), e.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
throw newIOException(docListWrapper,
|
||||
"Failed to retrieve spreadsheet list.");
|
||||
}
|
||||
|
||||
return idSaver.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the ID of the first worksheet in the named spreadsheet.
|
||||
*
|
||||
* @param trixWrapper The GData handle for the spreadsheet service.
|
||||
* @param spreadsheetId The GData ID for the given spreadsheet.
|
||||
* @return The worksheet ID, if it can be retrieved. {@code null} will be
|
||||
* returned if the GData request returns without error, but without an
|
||||
* ID.
|
||||
* @throws IOException If an error occurs during the GData request.
|
||||
*/
|
||||
public String getWorksheetId(final GDataWrapper<GDataServiceClient> trixWrapper,
|
||||
final String spreadsheetId) throws IOException {
|
||||
final AtomicReference<String> idSaver = new AtomicReference<String>();
|
||||
|
||||
boolean result = trixWrapper.runQuery(new QueryFunction<GDataServiceClient>() {
|
||||
@Override
|
||||
public void query(GDataServiceClient client)
|
||||
throws GDataWrapper.AuthenticationException, IOException, GDataWrapper.ParseException, GDataWrapper.HttpException {
|
||||
String uri = String.format(DOCS_WORKSHEETS_URL_FORMAT, spreadsheetId);
|
||||
GDataParser sheetParser;
|
||||
try {
|
||||
sheetParser = ((SpreadsheetsClient) client).getParserForWorksheetsFeed(uri,
|
||||
trixWrapper.getAuthManager().getAuthToken());
|
||||
sheetParser.init();
|
||||
if (!sheetParser.hasMoreData()) {
|
||||
Log.i(Constants.TAG, "Found no worksheets");
|
||||
return;
|
||||
}
|
||||
|
||||
// Grab the first worksheet.
|
||||
WorksheetEntry worksheetEntry =
|
||||
(WorksheetEntry) sheetParser.readNextEntry(new WorksheetEntry());
|
||||
|
||||
int lastSlash = worksheetEntry.getId().lastIndexOf('/');
|
||||
idSaver.set(worksheetEntry.getId().substring(lastSlash + 1));
|
||||
} catch (ParseException e) {
|
||||
throw new GDataWrapper.ParseException(e);
|
||||
} catch (AuthenticationException e) {
|
||||
throw new GDataWrapper.AuthenticationException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
throw newIOException(trixWrapper, "Failed to retrieve worksheet ID.");
|
||||
}
|
||||
|
||||
return idSaver.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a row to a worksheet containing the stats for a given track.
|
||||
*
|
||||
* @param context The context associated with this request.
|
||||
* @param trixAuth The GData authorization for the spreadsheet service.
|
||||
* @param spreadsheetId The spreadsheet to be modified.
|
||||
* @param worksheetId The worksheet to be modified.
|
||||
* @param track The track whose stats are to be written.
|
||||
* @param metricUnits True if metric units are to be used. If false,
|
||||
* imperial units will be used.
|
||||
* @throws IOException If an error occurs while updating the worksheet.
|
||||
*/
|
||||
public void addTrackRow(Context context, AuthManager trixAuth,
|
||||
String spreadsheetId, String worksheetId, Track track,
|
||||
boolean metricUnits) throws IOException {
|
||||
String worksheetUri = String.format(DOCS_SPREADSHEET_URL_FORMAT,
|
||||
spreadsheetId, worksheetId);
|
||||
TripStatistics stats = track.getStatistics();
|
||||
|
||||
String distanceUnit = context.getString(metricUnits ?
|
||||
R.string.unit_kilometer : R.string.unit_mile);
|
||||
String speedUnit = context.getString(metricUnits ?
|
||||
R.string.unit_kilometer_per_hour : R.string.unit_mile_per_hour);
|
||||
String elevationUnit = context.getString(metricUnits ?
|
||||
R.string.unit_meter : R.string.unit_feet);
|
||||
|
||||
// Prepare the Post-Text we are going to send.
|
||||
DocsTagBuilder tagBuilder = new DocsTagBuilder(metricUnits)
|
||||
.append("name", track.getName())
|
||||
.append("description", track.getDescription())
|
||||
.append("date", StringUtils.formatDateTime(stats.getStartTime()))
|
||||
.append("totaltime", StringUtils.formatTimeAlwaysShowingHours(
|
||||
stats.getTotalTime()))
|
||||
.append("movingtime", StringUtils.formatTimeAlwaysShowingHours(
|
||||
stats.getMovingTime()))
|
||||
.appendLargeUnits("distance", stats.getTotalDistance() / 1000)
|
||||
.append("distanceunit", distanceUnit)
|
||||
.appendLargeUnits("averagespeed", stats.getAverageSpeed() * 3.6)
|
||||
.appendLargeUnits("averagemovingspeed",
|
||||
stats.getAverageMovingSpeed() * 3.6)
|
||||
.appendLargeUnits("maxspeed", stats.getMaxSpeed() * 3.6)
|
||||
.append("speedunit", speedUnit)
|
||||
.appendSmallUnits("elevationgain", stats.getTotalElevationGain())
|
||||
.appendSmallUnits("minelevation", stats.getMinElevation())
|
||||
.appendSmallUnits("maxelevation", stats.getMaxElevation())
|
||||
.append("elevationunit", elevationUnit);
|
||||
|
||||
if (track.getMapId().length() > 0) {
|
||||
tagBuilder.append("map", String.format("%s?msa=0&msid=%s",
|
||||
Constants.MAPSHOP_BASE_URL, track.getMapId()));
|
||||
}
|
||||
|
||||
String postText = new StringBuilder()
|
||||
.append("<entry xmlns='http://www.w3.org/2005/Atom' "
|
||||
+ "xmlns:gsx='http://schemas.google.com/spreadsheets/"
|
||||
+ "2006/extended'>")
|
||||
.append(tagBuilder.build())
|
||||
.append("</entry>")
|
||||
.toString();
|
||||
|
||||
Log.i(Constants.TAG,
|
||||
"Inserting at: " + spreadsheetId + " => " + worksheetUri);
|
||||
|
||||
Log.i(Constants.TAG, postText);
|
||||
|
||||
writeRowData(trixAuth, worksheetUri, postText);
|
||||
|
||||
Log.i(Constants.TAG, "Post finished.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes spreadsheet row data to the indicated worksheet.
|
||||
*
|
||||
* @param trixAuth The GData authorization for the spreadsheet service.
|
||||
* @param worksheetUri The URI of the worksheet to be altered.
|
||||
* @param postText The XML tags describing the change to be made.
|
||||
* @throws IOException Thrown if an error occurs during the write.
|
||||
*/
|
||||
protected void writeRowData(AuthManager trixAuth, String worksheetUri,
|
||||
String postText) throws IOException {
|
||||
// No need for a wrapper because we know that the authorization was good
|
||||
// enough to get this far.
|
||||
URL url = new URL(worksheetUri);
|
||||
URLConnection conn = url.openConnection();
|
||||
conn.addRequestProperty(CONTENT_TYPE_PARAM, ATOM_FEED_MIME_TYPE);
|
||||
conn.addRequestProperty("Authorization",
|
||||
"GoogleLogin auth=" + trixAuth.getAuthToken());
|
||||
conn.setDoOutput(true);
|
||||
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
|
||||
wr.write(postText);
|
||||
wr.flush();
|
||||
|
||||
// Get the response.
|
||||
// TODO: Should we parse the response, rather than simply throwing it away?
|
||||
BufferedReader rd =
|
||||
new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
String line;
|
||||
while ((line = rd.readLine()) != null) {
|
||||
// Process line.
|
||||
Log.i(Constants.TAG, "r: " + line);
|
||||
}
|
||||
wr.close();
|
||||
rd.close();
|
||||
}
|
||||
|
||||
private static IOException newIOException(GDataWrapper<GDataServiceClient> wrapper,
|
||||
String message) {
|
||||
return new IOException(String.format("%s: %d: %s", message,
|
||||
wrapper.getErrorType(), wrapper.getErrorMessage()));
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io.docs;
|
||||
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* <p>This class builds a string of XML tags used to talk to Docs using GData.
|
||||
*
|
||||
* <p>Sample Usage:
|
||||
*
|
||||
* <code>
|
||||
* String tags = new DocsTagBuilder(false)
|
||||
* .append("tagName", "tagValue")
|
||||
* .appendLargeUnits("bigTagName", 1.0)
|
||||
* .build();
|
||||
* </code>
|
||||
*
|
||||
* <p>results in:
|
||||
*
|
||||
* <code>
|
||||
* <gsx:tagName><![CDATA[tagValue]]></gsx:tagName>
|
||||
* <gsx:bigTagName><![CDATA[1.00]]></gsx:bigTagName>
|
||||
* </code>
|
||||
*
|
||||
* @author Matthew Simmons
|
||||
*/
|
||||
class DocsTagBuilder {
|
||||
|
||||
// Google Docs can only parse numbers in the English locale.
|
||||
private static final NumberFormat LARGE_UNIT_FORMAT = NumberFormat.getNumberInstance(
|
||||
Locale.ENGLISH);
|
||||
private static final NumberFormat SMALL_UNIT_FORMAT = NumberFormat.getIntegerInstance(
|
||||
Locale.ENGLISH);
|
||||
|
||||
static {
|
||||
LARGE_UNIT_FORMAT.setMaximumFractionDigits(2);
|
||||
LARGE_UNIT_FORMAT.setMinimumFractionDigits(2);
|
||||
}
|
||||
|
||||
protected final boolean metricUnits;
|
||||
protected final StringBuilder stringBuilder;
|
||||
|
||||
/**
|
||||
* @param metricUnits True if metric units are to be used. If false,
|
||||
* imperial units will be used.
|
||||
*/
|
||||
DocsTagBuilder(boolean metricUnits) {
|
||||
this.metricUnits = metricUnits;
|
||||
this.stringBuilder = new StringBuilder();
|
||||
}
|
||||
|
||||
/** Appends a tag containing a string value */
|
||||
DocsTagBuilder append(String name, String value) {
|
||||
appendTag(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a tag containing a numeric value. The value will be formatted
|
||||
* according to the large distance of the specified measurement system (i.e.
|
||||
* kilometers or miles).
|
||||
*
|
||||
* @param name The tag name.
|
||||
* @param d The value to be formatted, in kilometers.
|
||||
*/
|
||||
DocsTagBuilder appendLargeUnits(String name, double d) {
|
||||
double value = metricUnits ? d : (d * UnitConversions.KM_TO_MI);
|
||||
appendTag(name, LARGE_UNIT_FORMAT.format(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a tag containing a numeric value. The value will be formatted
|
||||
* according to the small distance of the specified measurement system (i.e.
|
||||
* meters or feet).
|
||||
*
|
||||
* @param name The tag name.
|
||||
* @param d The value to be formatted, in meters.
|
||||
*/
|
||||
DocsTagBuilder appendSmallUnits(String name, double d) {
|
||||
double value = metricUnits ? d : (d * UnitConversions.M_TO_FT);
|
||||
appendTag(name, SMALL_UNIT_FORMAT.format(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns a string containing all tags which have been added. */
|
||||
String build() {
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
private void appendTag(String name, String value) {
|
||||
stringBuilder.append("<gsx:")
|
||||
.append(name)
|
||||
.append(">")
|
||||
.append(StringUtils.stringAsCData(value))
|
||||
.append("</gsx:")
|
||||
.append(name)
|
||||
.append(">");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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.docs;
|
||||
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* An activity to send a track to Google Docs.
|
||||
*
|
||||
* @author jshih@google.com (Jimmy Shih)
|
||||
*/
|
||||
public class SendDocsActivity extends Activity {
|
||||
|
||||
// parameters in the input intent
|
||||
public static final String ACCOUNT = "account";
|
||||
public static final String TRACK_ID = "trackId";
|
||||
|
||||
// parameters in the output intent
|
||||
public static final String SUCCESS = "success";
|
||||
|
||||
private static final int PROGRESS_DIALOG = 1;
|
||||
|
||||
private SendDocsAsyncTask asyncTask;
|
||||
private ProgressDialog progressDialog;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Object retained = getLastNonConfigurationInstance();
|
||||
if (retained instanceof SendDocsAsyncTask) {
|
||||
asyncTask = (SendDocsAsyncTask) retained;
|
||||
asyncTask.setActivity(this);
|
||||
} else {
|
||||
Intent intent = getIntent();
|
||||
Account account = intent.getParcelableExtra(ACCOUNT);
|
||||
if (account == null) {
|
||||
setResult(RESULT_OK, new Intent().putExtra(SUCCESS, false));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
long trackId = intent.getLongExtra(TRACK_ID, -1L);
|
||||
if (trackId == -1L) {
|
||||
setResult(RESULT_OK, new Intent().putExtra(SUCCESS, false));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
asyncTask = new SendDocsAsyncTask(this, account, trackId);
|
||||
asyncTask.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onRetainNonConfigurationInstance() {
|
||||
asyncTask.setActivity(null);
|
||||
return asyncTask;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case PROGRESS_DIALOG:
|
||||
progressDialog = new ProgressDialog(this);
|
||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
|
||||
progressDialog.setTitle(getString(
|
||||
R.string.send_google_progress_title, getString(R.string.send_google_docs)));
|
||||
progressDialog.setMax(100);
|
||||
progressDialog.setProgress(0);
|
||||
progressDialog.setCancelable(true);
|
||||
progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
asyncTask.cancel(true);
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
return progressDialog;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes when the associated AsyncTask completes.
|
||||
*
|
||||
* @param success true if success
|
||||
*/
|
||||
public void onAsyncTaskCompleted(boolean success) {
|
||||
Intent intent = new Intent().putExtra(SUCCESS, success);
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the progress dialog.
|
||||
*/
|
||||
public void showProgressDialog() {
|
||||
showDialog(PROGRESS_DIALOG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the progress dialog value.
|
||||
*/
|
||||
public void setProgressDialogValue(int value) {
|
||||
if (progressDialog != null) {
|
||||
progressDialog.setProgress(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
/*
|
||||
* 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.docs;
|
||||
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.io.gdata.GDataClientFactory;
|
||||
import com.google.android.common.gdata.AndroidXmlParserFactory;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.wireless.gdata.client.GDataClient;
|
||||
import com.google.wireless.gdata.client.HttpException;
|
||||
import com.google.wireless.gdata.docs.DocumentsClient;
|
||||
import com.google.wireless.gdata.docs.SpreadsheetsClient;
|
||||
import com.google.wireless.gdata.docs.XmlDocsGDataParserFactory;
|
||||
import com.google.wireless.gdata.parser.ParseException;
|
||||
import com.google.wireless.gdata2.client.AuthenticationException;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.AuthenticatorException;
|
||||
import android.accounts.OperationCanceledException;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* AsyncTask to send a track to Google Docs.
|
||||
*
|
||||
* @author jshih@google.com (Jimmy Shih)
|
||||
*/
|
||||
public class SendDocsAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
private static final int PROGRESS_GET_SPREADSHEET_ID = 0;
|
||||
private static final int PROGRESS_CREATE_SPREADSHEET = 25;
|
||||
private static final int PROGRESS_GET_WORKSHEET_ID = 50;
|
||||
private static final int PROGRESS_ADD_TRACK_INFO = 75;
|
||||
private static final int PROGRESS_COMPLETE = 100;
|
||||
|
||||
private static final String TAG = SendDocsAsyncTask.class.getSimpleName();
|
||||
|
||||
private SendDocsActivity activity;
|
||||
private final Account account;
|
||||
private final long trackId;
|
||||
private final Context context;
|
||||
private final MyTracksProviderUtils myTracksProviderUtils;
|
||||
private final GDataClient gDataClient;
|
||||
private final DocumentsClient documentsClient;
|
||||
private final SpreadsheetsClient spreadsheetsClient;
|
||||
|
||||
/**
|
||||
* True if can retry sending to Google Docs.
|
||||
*/
|
||||
private boolean canRetry;
|
||||
|
||||
/**
|
||||
* True if the AsyncTask has completed.
|
||||
*/
|
||||
private boolean completed;
|
||||
|
||||
/**
|
||||
* True if the result is success.
|
||||
*/
|
||||
private boolean success;
|
||||
|
||||
// The following variables are for per upload states
|
||||
private String documentsAuthToken;
|
||||
private String spreadsheetsAuthToken;
|
||||
private String spreadsheetId;
|
||||
private String worksheetId;
|
||||
|
||||
public SendDocsAsyncTask(SendDocsActivity activity, Account account, long trackId) {
|
||||
this.activity = activity;
|
||||
this.account = account;
|
||||
this.trackId = trackId;
|
||||
|
||||
context = activity.getApplicationContext();
|
||||
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
|
||||
gDataClient = GDataClientFactory.getGDataClient(context);
|
||||
documentsClient = new DocumentsClient(
|
||||
gDataClient, new XmlDocsGDataParserFactory(new AndroidXmlParserFactory()));
|
||||
spreadsheetsClient = new SpreadsheetsClient(
|
||||
gDataClient, new XmlDocsGDataParserFactory(new AndroidXmlParserFactory()));
|
||||
|
||||
canRetry = true;
|
||||
completed = false;
|
||||
success = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the activity associated with this AyncTask.
|
||||
*
|
||||
* @param activity the activity.
|
||||
*/
|
||||
public void setActivity(SendDocsActivity activity) {
|
||||
this.activity = activity;
|
||||
if (completed && activity != null) {
|
||||
activity.onAsyncTaskCompleted(success);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
activity.showProgressDialog();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
return doUpload();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... values) {
|
||||
if (activity != null) {
|
||||
activity.setProgressDialogValue(values[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
closeClient();
|
||||
success = result;
|
||||
completed = true;
|
||||
if (activity != null) {
|
||||
activity.onAsyncTaskCompleted(success);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
closeClient();
|
||||
}
|
||||
|
||||
private void closeClient() {
|
||||
if (gDataClient != null) {
|
||||
gDataClient.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a track to Google Docs.
|
||||
*
|
||||
* @return true if success.
|
||||
*/
|
||||
private boolean doUpload() {
|
||||
// Reset the per upload states
|
||||
documentsAuthToken = null;
|
||||
spreadsheetsAuthToken = null;
|
||||
spreadsheetId = null;
|
||||
worksheetId = null;
|
||||
|
||||
try {
|
||||
documentsAuthToken = AccountManager.get(context).blockingGetAuthToken(
|
||||
account, documentsClient.getServiceName(), false);
|
||||
spreadsheetsAuthToken = AccountManager.get(context).blockingGetAuthToken(
|
||||
account, spreadsheetsClient.getServiceName(), false);
|
||||
} catch (OperationCanceledException e) {
|
||||
Log.d(TAG, "Unable to get auth token", e);
|
||||
return retryUpload();
|
||||
} catch (AuthenticatorException e) {
|
||||
Log.d(TAG, "Unable to get auth token", e);
|
||||
return retryUpload();
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to get auth token", e);
|
||||
return retryUpload();
|
||||
}
|
||||
|
||||
Track track = myTracksProviderUtils.getTrack(trackId);
|
||||
if (track == null) {
|
||||
Log.d(TAG, "Track is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
String title = context.getString(R.string.my_tracks_app_name);
|
||||
if (track.getCategory() != null && !track.getCategory().equals("")) {
|
||||
title += "-" + track.getCategory();
|
||||
}
|
||||
|
||||
// Get the spreadsheet ID
|
||||
publishProgress(PROGRESS_GET_SPREADSHEET_ID);
|
||||
if (!fetchSpreadSheetId(title, false)) {
|
||||
return retryUpload();
|
||||
}
|
||||
|
||||
// Create a new spreadsheet if necessary
|
||||
publishProgress(PROGRESS_CREATE_SPREADSHEET);
|
||||
if (spreadsheetId == null) {
|
||||
if (!createSpreadSheet(title)) {
|
||||
Log.d(TAG, "Unable to create a new spreadsheet");
|
||||
return false;
|
||||
}
|
||||
|
||||
// The previous creation might have succeeded even though GData
|
||||
// reported an error. Seems to be a know bug.
|
||||
// See http://code.google.com/p/gdata-issues/issues/detail?id=929
|
||||
// Try to find the created spreadsheet.
|
||||
if (spreadsheetId == null) {
|
||||
if (!fetchSpreadSheetId(title, true)) {
|
||||
Log.d(TAG, "Unable to check if the new spreadsheet is created");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spreadsheetId == null) {
|
||||
Log.d(TAG, "Unable to create a new spreadsheet");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the worksheet ID
|
||||
publishProgress(PROGRESS_GET_WORKSHEET_ID);
|
||||
if (!fetchWorksheetId()) {
|
||||
return retryUpload();
|
||||
}
|
||||
if (worksheetId == null) {
|
||||
Log.d(TAG, "Unable to get a worksheet ID");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add the track info
|
||||
publishProgress(PROGRESS_ADD_TRACK_INFO);
|
||||
if (!addTrackInfo(track)) {
|
||||
Log.d(TAG, "Unable to add track info");
|
||||
return false;
|
||||
}
|
||||
|
||||
publishProgress(PROGRESS_COMPLETE);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the spreadsheet id. Sets the instance variable
|
||||
* {@link SendDocsAsyncTask#spreadsheetId}.
|
||||
*
|
||||
* @param title the spreadsheet title
|
||||
* @param waitFirst wait before checking
|
||||
* @return true if completes.
|
||||
*/
|
||||
private boolean fetchSpreadSheetId(String title, boolean waitFirst) {
|
||||
if (isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (waitFirst) {
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException e) {
|
||||
Log.d(TAG, "Unable to wait", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
spreadsheetId = SendDocsUtils.getSpreadsheetId(title, documentsClient, documentsAuthToken);
|
||||
} catch (ParseException e) {
|
||||
Log.d(TAG, "Unable to fetch spreadsheet ID", e);
|
||||
return false;
|
||||
} catch (HttpException e) {
|
||||
Log.d(TAG, "Unable to fetch spreadsheet ID", e);
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to fetch spreadsheet ID", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spreadsheetId == null) {
|
||||
// Waiting a few seconds and trying again. Maybe the server just had a
|
||||
// hickup (unfortunately that happens quite a lot...).
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException e) {
|
||||
Log.d(TAG, "Unable to wait", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
spreadsheetId = SendDocsUtils.getSpreadsheetId(title, documentsClient, documentsAuthToken);
|
||||
} catch (ParseException e) {
|
||||
Log.d(TAG, "Unable to fetch spreadsheet ID", e);
|
||||
return false;
|
||||
} catch (HttpException e) {
|
||||
Log.d(TAG, "Unable to fetch spreadsheet ID", e);
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to fetch spreadsheet ID", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a spreadsheet. If successful, sets the instance variable
|
||||
* {@link SendDocsAsyncTask#spreadsheetId}.
|
||||
*
|
||||
* @param spreadsheetTitle the spreadsheet title
|
||||
* @return true if completes.
|
||||
*/
|
||||
private boolean createSpreadSheet(String spreadsheetTitle) {
|
||||
if (isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
spreadsheetId = SendDocsUtils.createSpreadsheet(spreadsheetTitle, documentsAuthToken, context);
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to create spreadsheet", e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the worksheet ID. Sets the instance variable
|
||||
* {@link SendDocsAsyncTask#worksheetId}.
|
||||
*
|
||||
* @return true if completes.
|
||||
*/
|
||||
private boolean fetchWorksheetId() {
|
||||
if (isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
worksheetId = SendDocsUtils.getWorksheetId(
|
||||
spreadsheetId, spreadsheetsClient, spreadsheetsAuthToken);
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to fetch worksheet ID", e);
|
||||
return false;
|
||||
} catch (AuthenticationException e) {
|
||||
Log.d(TAG, "Unable to fetch worksheet ID", e);
|
||||
return false;
|
||||
} catch (ParseException e) {
|
||||
Log.d(TAG, "Unable to fetch worksheet ID", e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds track info to a worksheet.
|
||||
*
|
||||
* @param track the track
|
||||
* @return true if completes.
|
||||
*/
|
||||
private boolean addTrackInfo(Track track) {
|
||||
if (isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
SendDocsUtils.addTrackInfo(track, spreadsheetId, worksheetId, spreadsheetsAuthToken, context);
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to add track info", e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retries upload. Invalidates the authToken. If can retry, invokes
|
||||
* {@link SendDocsAsyncTask#doUpload()}. Returns false if cannot retry.
|
||||
*/
|
||||
private boolean retryUpload() {
|
||||
if (isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AccountManager.get(context).invalidateAuthToken(
|
||||
documentsClient.getServiceName(), documentsAuthToken);
|
||||
AccountManager.get(context).invalidateAuthToken(
|
||||
spreadsheetsClient.getServiceName(), spreadsheetsAuthToken);
|
||||
if (canRetry) {
|
||||
canRetry = false;
|
||||
return doUpload();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
/*
|
||||
* 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.docs;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.ResourceUtils;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.wireless.gdata.client.HttpException;
|
||||
import com.google.wireless.gdata.data.Entry;
|
||||
import com.google.wireless.gdata.docs.DocumentsClient;
|
||||
import com.google.wireless.gdata.docs.SpreadsheetsClient;
|
||||
import com.google.wireless.gdata.docs.SpreadsheetsClient.WorksheetEntry;
|
||||
import com.google.wireless.gdata.parser.GDataParser;
|
||||
import com.google.wireless.gdata.parser.ParseException;
|
||||
import com.google.wireless.gdata2.client.AuthenticationException;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLEncoder;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Utilities for sending a track to Google Docs.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Matthew Simmons
|
||||
*/
|
||||
public class SendDocsUtils {
|
||||
private static final String GET_SPREADSHEET_BY_TITLE_URI =
|
||||
"https://docs.google.com/feeds/documents/private/full?"
|
||||
+ "category=mine,spreadsheet&title=%s&title-exact=true";
|
||||
private static final String CREATE_SPREADSHEET_URI =
|
||||
"https://docs.google.com/feeds/documents/private/full";
|
||||
private static final String GET_WORKSHEETS_URI =
|
||||
"https://spreadsheets.google.com/feeds/worksheets/%s/private/full";
|
||||
private static final String GET_WORKSHEET_URI =
|
||||
"https://spreadsheets.google.com/feeds/list/%s/%s/private/full";
|
||||
|
||||
private static final String SPREADSHEET_ID_PREFIX =
|
||||
"https://docs.google.com/feeds/documents/private/full/spreadsheet%3A";
|
||||
|
||||
private static final String CONTENT_TYPE = "Content-Type";
|
||||
private static final String ATOM_FEED_MIME_TYPE = "application/atom+xml";
|
||||
private static final String OPENDOCUMENT_SPREADSHEET_MIME_TYPE =
|
||||
"application/x-vnd.oasis.opendocument.spreadsheet";
|
||||
|
||||
private static final String AUTHORIZATION = "Authorization";
|
||||
private static final String AUTHORIZATION_PREFIX = "GoogleLogin auth=";
|
||||
|
||||
private static final String SLUG = "Slug";
|
||||
|
||||
// Google Docs can only parse numbers in the English locale.
|
||||
private static final NumberFormat NUMBER_FORMAT = NumberFormat.getNumberInstance(Locale.ENGLISH);
|
||||
private static final NumberFormat INTEGER_FORMAT = NumberFormat.getIntegerInstance(
|
||||
Locale.ENGLISH);
|
||||
|
||||
static {
|
||||
NUMBER_FORMAT.setMaximumFractionDigits(2);
|
||||
NUMBER_FORMAT.setMinimumFractionDigits(2);
|
||||
}
|
||||
|
||||
private static final String TAG = SendDocsUtils.class.getSimpleName();
|
||||
|
||||
private SendDocsUtils() {}
|
||||
|
||||
/**
|
||||
* Gets the spreadsheet id of a spreadsheet. Returns null if the spreadsheet
|
||||
* doesn't exist.
|
||||
*
|
||||
* @param title the title of the spreadsheet
|
||||
* @param documentsClient the documents client
|
||||
* @param authToken the auth token
|
||||
* @return spreadsheet id or null if it doesn't exist.
|
||||
*/
|
||||
public static String getSpreadsheetId(
|
||||
String title, DocumentsClient documentsClient, String authToken)
|
||||
throws IOException, ParseException, HttpException {
|
||||
GDataParser gDataParser = null;
|
||||
try {
|
||||
String uri = String.format(GET_SPREADSHEET_BY_TITLE_URI, URLEncoder.encode(title));
|
||||
gDataParser = documentsClient.getParserForFeed(Entry.class, uri, authToken);
|
||||
gDataParser.init();
|
||||
|
||||
while (gDataParser.hasMoreData()) {
|
||||
Entry entry = gDataParser.readNextEntry(null);
|
||||
String entryTitle = entry.getTitle();
|
||||
if (entryTitle.equals(title)) {
|
||||
return getEntryId(entry);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
if (gDataParser != null) {
|
||||
gDataParser.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the id from an entry. Returns null if not available.
|
||||
*
|
||||
* @param entry the entry
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static String getEntryId(Entry entry) {
|
||||
String entryId = entry.getId();
|
||||
if (entryId.startsWith(SPREADSHEET_ID_PREFIX)) {
|
||||
return entryId.substring(SPREADSHEET_ID_PREFIX.length());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new spreadsheet with the given title. Returns the spreadsheet ID
|
||||
* if successful. Returns null otherwise. Note that it is possible that a new
|
||||
* spreadsheet is created, but the returned ID is null.
|
||||
*
|
||||
* @param title the title
|
||||
* @param authToken the auth token
|
||||
* @param context the context
|
||||
*/
|
||||
public static String createSpreadsheet(String title, String authToken, Context context)
|
||||
throws IOException {
|
||||
URL url = new URL(CREATE_SPREADSHEET_URI);
|
||||
URLConnection conn = url.openConnection();
|
||||
conn.addRequestProperty(CONTENT_TYPE, OPENDOCUMENT_SPREADSHEET_MIME_TYPE);
|
||||
conn.addRequestProperty(SLUG, title);
|
||||
conn.addRequestProperty(AUTHORIZATION, AUTHORIZATION_PREFIX + authToken);
|
||||
conn.setDoOutput(true);
|
||||
OutputStream outputStream = conn.getOutputStream();
|
||||
ResourceUtils.readBinaryFileToOutputStream(
|
||||
context, R.raw.mytracks_empty_spreadsheet, outputStream);
|
||||
|
||||
// Get the response
|
||||
BufferedReader bufferedReader = null;
|
||||
StringBuilder resultBuilder = new StringBuilder();
|
||||
try {
|
||||
bufferedReader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
String line;
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
resultBuilder.append(line);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// The GData API sometimes throws an error, even though creation of
|
||||
// the document succeeded. In that case let's just return. The caller
|
||||
// then needs to check if the doc actually exists.
|
||||
Log.d(TAG, "Unable to read result after creating a spreadsheet", e);
|
||||
return null;
|
||||
} finally {
|
||||
outputStream.close();
|
||||
if (bufferedReader != null) {
|
||||
bufferedReader.close();
|
||||
}
|
||||
}
|
||||
return getNewSpreadsheetId(resultBuilder.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the spreadsheet id from a create spreadsheet result.
|
||||
*
|
||||
* @param result the create spreadsheet result
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static String getNewSpreadsheetId(String result) {
|
||||
int idTagIndex = result.indexOf("<id>");
|
||||
if (idTagIndex == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int idTagCloseIndex = result.indexOf("</id>", idTagIndex);
|
||||
if (idTagCloseIndex == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int idStringStart = result.indexOf(SPREADSHEET_ID_PREFIX, idTagIndex);
|
||||
if (idStringStart == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result.substring(idStringStart + SPREADSHEET_ID_PREFIX.length(), idTagCloseIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first worksheet ID of a spreadsheet. Returns null if not
|
||||
* available.
|
||||
*
|
||||
* @param spreadsheetId the spreadsheet ID
|
||||
* @param spreadsheetClient the spreadsheet client
|
||||
* @param authToken the auth token
|
||||
*/
|
||||
public static String getWorksheetId(
|
||||
String spreadsheetId, SpreadsheetsClient spreadsheetClient, String authToken)
|
||||
throws IOException, AuthenticationException, ParseException {
|
||||
GDataParser gDataParser = null;
|
||||
try {
|
||||
String uri = String.format(GET_WORKSHEETS_URI, spreadsheetId);
|
||||
gDataParser = spreadsheetClient.getParserForWorksheetsFeed(uri, authToken);
|
||||
gDataParser.init();
|
||||
if (!gDataParser.hasMoreData()) {
|
||||
Log.d(TAG, "No worksheet");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the first worksheet
|
||||
WorksheetEntry worksheetEntry =
|
||||
(WorksheetEntry) gDataParser.readNextEntry(new WorksheetEntry());
|
||||
return getWorksheetEntryId(worksheetEntry);
|
||||
} finally {
|
||||
if (gDataParser != null) {
|
||||
gDataParser.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the worksheet id from a worksheet entry. Returns null if not available.
|
||||
*
|
||||
* @param entry the worksheet entry
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static String getWorksheetEntryId(WorksheetEntry entry) {
|
||||
String id = entry.getId();
|
||||
int lastSlash = id.lastIndexOf('/');
|
||||
if (lastSlash == -1) {
|
||||
Log.d(TAG, "No id");
|
||||
return null;
|
||||
}
|
||||
return id.substring(lastSlash + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a track's info as a row in a worksheet.
|
||||
*
|
||||
* @param track the track
|
||||
* @param spreadsheetId the spreadsheet ID
|
||||
* @param worksheetId the worksheet ID
|
||||
* @param authToken the auth token
|
||||
* @param context the context
|
||||
*/
|
||||
public static void addTrackInfo(
|
||||
Track track, String spreadsheetId, String worksheetId, String authToken, Context context)
|
||||
throws IOException {
|
||||
String worksheetUri = String.format(GET_WORKSHEET_URI, spreadsheetId, worksheetId);
|
||||
SharedPreferences prefs = context.getSharedPreferences(
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
boolean metricUnits = prefs.getBoolean(context.getString(R.string.metric_units_key), true);
|
||||
|
||||
addRow(worksheetUri, getRowContent(track, metricUnits, context), authToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the row content containing the track's info.
|
||||
*
|
||||
* @param track the track
|
||||
* @param metricUnits true to use metric
|
||||
* @param context the context
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static String getRowContent(Track track, boolean metricUnits, Context context) {
|
||||
TripStatistics stats = track.getStatistics();
|
||||
|
||||
String distanceUnit = context.getString(
|
||||
metricUnits ? R.string.unit_kilometer : R.string.unit_mile);
|
||||
String speedUnit = context.getString(
|
||||
metricUnits ? R.string.unit_kilometer_per_hour : R.string.unit_mile_per_hour);
|
||||
String elevationUnit = context.getString(
|
||||
metricUnits ? R.string.unit_meter : R.string.unit_feet);
|
||||
|
||||
StringBuilder builder = new StringBuilder().append("<entry xmlns='http://www.w3.org/2005/Atom' "
|
||||
+ "xmlns:gsx='http://schemas.google.com/spreadsheets/2006/extended'>");
|
||||
appendTag(builder, "name", track.getName());
|
||||
appendTag(builder, "description", track.getDescription());
|
||||
appendTag(builder, "date", StringUtils.formatDateTime(context, stats.getStartTime()));
|
||||
appendTag(builder, "totaltime", StringUtils.formatElapsedTime(stats.getTotalTime()));
|
||||
appendTag(builder, "movingtime", StringUtils.formatElapsedTime(stats.getMovingTime()));
|
||||
appendTag(builder, "distance", getDistance(stats.getTotalDistance(), metricUnits));
|
||||
appendTag(builder, "distanceunit", distanceUnit);
|
||||
appendTag(builder, "averagespeed", getSpeed(stats.getAverageSpeed(), metricUnits));
|
||||
appendTag(builder, "averagemovingspeed", getSpeed(stats.getAverageMovingSpeed(), metricUnits));
|
||||
appendTag(builder, "maxspeed", getSpeed(stats.getMaxSpeed(), metricUnits));
|
||||
appendTag(builder, "speedunit", speedUnit);
|
||||
appendTag(builder, "elevationgain", getElevation(stats.getTotalElevationGain(), metricUnits));
|
||||
appendTag(builder, "minelevation", getElevation(stats.getMinElevation(), metricUnits));
|
||||
appendTag(builder, "maxelevation", getElevation(stats.getMaxElevation(), metricUnits));
|
||||
appendTag(builder, "elevationunit", elevationUnit);
|
||||
|
||||
if (track.getMapId().length() > 0) {
|
||||
appendTag(builder, "map",
|
||||
String.format("%s?msa=0&msid=%s", Constants.MAPSHOP_BASE_URL, track.getMapId()));
|
||||
}
|
||||
|
||||
builder.append("</entry>");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a name-value pair as a gsx tag to a string builder.
|
||||
*
|
||||
* @param stringBuilder the string builder
|
||||
* @param name the name
|
||||
* @param value the value
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static void appendTag(StringBuilder stringBuilder, String name, String value) {
|
||||
stringBuilder
|
||||
.append("<gsx:")
|
||||
.append(name)
|
||||
.append(">")
|
||||
.append(StringUtils.stringAsCData(value))
|
||||
.append("</gsx:")
|
||||
.append(name)
|
||||
.append(">");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the distance. Performs unit conversion and formatting.
|
||||
*
|
||||
* @param distanceInMeter the distance in meters
|
||||
* @param metricUnits true to use metric
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final String getDistance(double distanceInMeter, boolean metricUnits) {
|
||||
double distanceInKilometer = distanceInMeter / 1000.0;
|
||||
double distance = metricUnits ? distanceInKilometer
|
||||
: distanceInKilometer * UnitConversions.KM_TO_MI;
|
||||
return NUMBER_FORMAT.format(distance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the speed. Performs unit conversion and formatting.
|
||||
*
|
||||
* @param speedInMeterPerSecond the speed in meters per second
|
||||
* @param metricUnits true to use metric
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final String getSpeed(double speedInMeterPerSecond, boolean metricUnits) {
|
||||
double speedInKilometerPerHour = speedInMeterPerSecond * 3.6;
|
||||
double speed = metricUnits ? speedInKilometerPerHour
|
||||
: speedInKilometerPerHour * UnitConversions.KMH_TO_MPH;
|
||||
return NUMBER_FORMAT.format(speed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the elevation. Performs unit conversion and formatting.
|
||||
*
|
||||
* @param elevationInMeter the elevation value in meters
|
||||
* @param metricUnits true to use metric
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final String getElevation(double elevationInMeter, boolean metricUnits) {
|
||||
double elevation = metricUnits ? elevationInMeter : elevationInMeter * UnitConversions.M_TO_FT;
|
||||
return INTEGER_FORMAT.format(elevation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a row to a Google Spreadsheet worksheet.
|
||||
*
|
||||
* @param worksheetUri the worksheet URI
|
||||
* @param rowContent the row content
|
||||
* @param authToken the auth token
|
||||
*/
|
||||
private static final void addRow(String worksheetUri, String rowContent, String authToken)
|
||||
throws IOException {
|
||||
URL url = new URL(worksheetUri);
|
||||
URLConnection conn = url.openConnection();
|
||||
conn.addRequestProperty(CONTENT_TYPE, ATOM_FEED_MIME_TYPE);
|
||||
conn.addRequestProperty(AUTHORIZATION, AUTHORIZATION_PREFIX + authToken);
|
||||
conn.setDoOutput(true);
|
||||
OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream());
|
||||
writer.write(rowContent);
|
||||
writer.flush();
|
||||
|
||||
// Get the response
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
while ((reader.readLine()) != null) {
|
||||
// Just read till the end
|
||||
}
|
||||
writer.close();
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* 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.maps;
|
||||
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnCancelListener;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* An activity to choose a Google Map.
|
||||
*
|
||||
* @author jshih@google.com (Jimmy Shih)
|
||||
*/
|
||||
public class ChooseMapActivity extends Activity {
|
||||
|
||||
// parameters in the input intent
|
||||
public static final String ACCOUNT = "account";
|
||||
|
||||
// parameters in the output intent
|
||||
public static final String MAP_ID = "mapId";
|
||||
|
||||
private static final int PROGRESS_DIALOG = 1;
|
||||
private static final int ERROR_DIALOG = 2;
|
||||
|
||||
private ChooseMapAsyncTask asyncTask;
|
||||
private ProgressDialog progressDialog;
|
||||
private ArrayAdapter<ListItem> arrayAdapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.choose_map);
|
||||
|
||||
arrayAdapter = new ArrayAdapter<ListItem>(this, R.layout.choose_map_item, new ArrayList<
|
||||
ListItem>()) {
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
if (convertView == null) {
|
||||
convertView = getLayoutInflater().inflate(R.layout.choose_map_item, parent, false);
|
||||
}
|
||||
MapsMapMetadata mapData = getItem(position).getMapData();
|
||||
|
||||
TextView title = (TextView) convertView.findViewById(R.id.choose_map_list_item_title);
|
||||
title.setText(mapData.getTitle());
|
||||
|
||||
TextView description = (TextView) convertView.findViewById(
|
||||
R.id.choose_map_list_item_description);
|
||||
String descriptionText = mapData.getDescription();
|
||||
if (descriptionText == null || descriptionText.equals("")) {
|
||||
description.setVisibility(View.GONE);
|
||||
} else {
|
||||
description.setVisibility(View.VISIBLE);
|
||||
description.setText(descriptionText);
|
||||
}
|
||||
|
||||
TextView searchStatus = (TextView) convertView.findViewById(
|
||||
R.id.choose_map_list_item_search_status);
|
||||
searchStatus.setTextColor(mapData.getSearchable() ? Color.RED : Color.GREEN);
|
||||
searchStatus.setText(mapData.getSearchable() ? R.string.maps_list_public_label
|
||||
: R.string.maps_list_unlisted_label);
|
||||
return convertView;
|
||||
}
|
||||
};
|
||||
|
||||
ListView list = (ListView) findViewById(R.id.choose_map_list_view);
|
||||
list.setEmptyView(findViewById(R.id.choose_map_empty_view));
|
||||
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
setResult(
|
||||
RESULT_OK, new Intent().putExtra(MAP_ID, arrayAdapter.getItem(position).getMapId()));
|
||||
finish();
|
||||
}
|
||||
});
|
||||
list.setAdapter(arrayAdapter);
|
||||
|
||||
Object retained = getLastNonConfigurationInstance();
|
||||
if (retained instanceof ChooseMapAsyncTask) {
|
||||
asyncTask = (ChooseMapAsyncTask) retained;
|
||||
asyncTask.setActivity(this);
|
||||
} else {
|
||||
Intent intent = getIntent();
|
||||
Account account = intent.getParcelableExtra(ACCOUNT);
|
||||
if (account == null) {
|
||||
setResult(RESULT_OK, new Intent().putExtra(MAP_ID, (String) null));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
asyncTask = new ChooseMapAsyncTask(this, account);
|
||||
asyncTask.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onRetainNonConfigurationInstance() {
|
||||
asyncTask.setActivity(null);
|
||||
return asyncTask;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case PROGRESS_DIALOG:
|
||||
progressDialog = new ProgressDialog(this);
|
||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
progressDialog.setMessage(getString(R.string.maps_list_loading));
|
||||
progressDialog.setCancelable(true);
|
||||
progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
asyncTask.cancel(true);
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
|
||||
progressDialog.setTitle(R.string.generic_progress_title);
|
||||
return progressDialog;
|
||||
case ERROR_DIALOG:
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
builder.setTitle(R.string.generic_error_title);
|
||||
builder.setMessage(R.string.maps_list_error);
|
||||
builder.setPositiveButton(R.string.generic_ok, new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int arg1) {
|
||||
setResult(RESULT_OK, new Intent().putExtra(MAP_ID, (String) null));
|
||||
finish();
|
||||
}
|
||||
});
|
||||
builder.setOnCancelListener(new OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
setResult(RESULT_OK, new Intent().putExtra(MAP_ID, (String) null));
|
||||
finish();
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes when the associated AsyncTask completes.
|
||||
*
|
||||
* @param success true if success
|
||||
* @param mapIds an array of map ids
|
||||
* @param mapData an array of map data
|
||||
*/
|
||||
public void onAsyncTaskCompleted(
|
||||
boolean success, ArrayList<String> mapIds, ArrayList<MapsMapMetadata> mapData) {
|
||||
removeDialog(PROGRESS_DIALOG);
|
||||
if (success) {
|
||||
arrayAdapter.clear();
|
||||
// To prevent displaying the emptyView message momentarily before the
|
||||
// arrayAdapter is set, don't set the emptyView message in the xml layout.
|
||||
// Instead, set it only when needed.
|
||||
if (mapIds.size() == 0) {
|
||||
TextView emptyView = (TextView) findViewById(R.id.choose_map_empty_view);
|
||||
emptyView.setText(R.string.maps_list_no_maps);
|
||||
} else {
|
||||
for (int i = 0; i < mapIds.size(); i++) {
|
||||
arrayAdapter.add(new ListItem(mapIds.get(i), mapData.get(i)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showDialog(ERROR_DIALOG);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the progress dialog.
|
||||
*/
|
||||
public void showProgressDialog() {
|
||||
showDialog(PROGRESS_DIALOG);
|
||||
}
|
||||
|
||||
/**
|
||||
* A class containing {@link ChooseMapActivity} list item.
|
||||
*
|
||||
* @author jshih@google.com (Jimmy Shih)
|
||||
*/
|
||||
private class ListItem {
|
||||
private String mapId;
|
||||
private MapsMapMetadata mapData;
|
||||
|
||||
private ListItem(String mapId, MapsMapMetadata mapData) {
|
||||
this.mapId = mapId;
|
||||
this.mapData = mapData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the map id.
|
||||
*/
|
||||
public String getMapId() {
|
||||
return mapId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the map data.
|
||||
*/
|
||||
public MapsMapMetadata getMapData() {
|
||||
return mapData;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* 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.maps;
|
||||
|
||||
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.GDataParser;
|
||||
import com.google.wireless.gdata.parser.ParseException;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.AuthenticatorException;
|
||||
import android.accounts.OperationCanceledException;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* AsyncTask for {@link ChooseMapActivity} to get all the maps from Google Maps.
|
||||
*
|
||||
* @author jshih@google.com (Jimmy Shih)
|
||||
*/
|
||||
public class ChooseMapAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
private static final String TAG = ChooseMapAsyncTask.class.getSimpleName();
|
||||
|
||||
private ChooseMapActivity activity;
|
||||
private final Account account;
|
||||
private final Context context;
|
||||
private final GDataClient gDataClient;
|
||||
private final MapsClient mapsClient;
|
||||
|
||||
/**
|
||||
* True if can retry sending to Google Fusion Tables.
|
||||
*/
|
||||
private boolean canRetry;
|
||||
|
||||
/**
|
||||
* True if the AsyncTask has completed.
|
||||
*/
|
||||
private boolean completed;
|
||||
|
||||
/**
|
||||
* True if the result is success.
|
||||
*/
|
||||
private boolean success;
|
||||
|
||||
// The following variables are for per request states
|
||||
private String authToken;
|
||||
private ArrayList<String> mapIds;
|
||||
private ArrayList<MapsMapMetadata> mapData;
|
||||
|
||||
public ChooseMapAsyncTask(ChooseMapActivity activity, Account account) {
|
||||
this.activity = activity;
|
||||
this.account = account;
|
||||
|
||||
context = activity.getApplicationContext();
|
||||
gDataClient = GDataClientFactory.getGDataClient(context);
|
||||
mapsClient = new MapsClient(
|
||||
gDataClient, new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
|
||||
|
||||
canRetry = true;
|
||||
completed = false;
|
||||
success = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the activity associated with this AyncTask.
|
||||
*
|
||||
* @param activity the activity.
|
||||
*/
|
||||
public void setActivity(ChooseMapActivity activity) {
|
||||
this.activity = activity;
|
||||
if (completed && activity != null) {
|
||||
activity.onAsyncTaskCompleted(success, mapIds, mapData);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
activity.showProgressDialog();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
return getMaps();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
closeClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
closeClient();
|
||||
success = result;
|
||||
completed = true;
|
||||
if (activity != null) {
|
||||
activity.onAsyncTaskCompleted(success, mapIds, mapData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the gdata client.
|
||||
*/
|
||||
private void closeClient() {
|
||||
if (gDataClient != null) {
|
||||
gDataClient.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the maps from Google Maps.
|
||||
*
|
||||
* @return true if success.
|
||||
*/
|
||||
private boolean getMaps() {
|
||||
// Reset the per request states
|
||||
authToken = null;
|
||||
mapIds = new ArrayList<String>();
|
||||
mapData = new ArrayList<MapsMapMetadata>();
|
||||
|
||||
try {
|
||||
authToken = AccountManager.get(context).blockingGetAuthToken(
|
||||
account, MapsConstants.SERVICE_NAME, false);
|
||||
} catch (OperationCanceledException e) {
|
||||
Log.d(TAG, "Unable to get auth token", e);
|
||||
return retryUpload();
|
||||
} catch (AuthenticatorException e) {
|
||||
Log.d(TAG, "Unable to get auth token", e);
|
||||
return retryUpload();
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to get auth token", e);
|
||||
return retryUpload();
|
||||
}
|
||||
|
||||
if (isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
GDataParser gDataParser = null;
|
||||
try {
|
||||
gDataParser = mapsClient.getParserForFeed(
|
||||
MapFeatureEntry.class, MapsClient.getMapsFeed(), authToken);
|
||||
gDataParser.init();
|
||||
while (gDataParser.hasMoreData()) {
|
||||
MapFeatureEntry entry = (MapFeatureEntry) gDataParser.readNextEntry(null);
|
||||
mapIds.add(MapsGDataConverter.getMapidForEntry(entry));
|
||||
mapData.add(MapsGDataConverter.getMapMetadataForEntry(entry));
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
Log.d(TAG, "Unable to get maps", e);
|
||||
return retryUpload();
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to get maps", e);
|
||||
return retryUpload();
|
||||
} catch (HttpException e) {
|
||||
Log.d(TAG, "Unable to get maps", e);
|
||||
return retryUpload();
|
||||
} finally {
|
||||
if (gDataParser != null) {
|
||||
gDataParser.close();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retries upload. Invalidates the authToken. If can retry, invokes
|
||||
* {@link ChooseMapAsyncTask#getMaps()}. Returns false if cannot retry.
|
||||
*/
|
||||
private boolean retryUpload() {
|
||||
if (isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AccountManager.get(context).invalidateAuthToken(MapsConstants.SERVICE_NAME, authToken);
|
||||
if (canRetry) {
|
||||
canRetry = false;
|
||||
return getMaps();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// Copyright 2010 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
package com.google.android.apps.mytracks.io.maps;
|
||||
|
||||
import com.google.wireless.gdata.data.Entry;
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
// Copyright 2010 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
package com.google.android.apps.mytracks.io.maps;
|
||||
|
||||
import com.google.wireless.gdata.client.GDataClient;
|
||||
import com.google.wireless.gdata.client.GDataParserFactory;
|
||||
@@ -33,11 +33,11 @@ class MapsClient extends GDataServiceClient {
|
||||
|
||||
@Override
|
||||
public String getServiceName() {
|
||||
return MyMapsConstants.SERVICE_NAME;
|
||||
return MapsConstants.SERVICE_NAME;
|
||||
}
|
||||
|
||||
public static String buildMapUrl(String mapId) {
|
||||
return MyMapsConstants.MAPSHOP_BASE_URL + "?msa=0&msid=" + mapId;
|
||||
return MapsConstants.MAPSHOP_BASE_URL + "?msa=0&msid=" + mapId;
|
||||
}
|
||||
|
||||
public static String getMapsFeed() {
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
// Copyright 2009 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
package com.google.android.apps.mytracks.io.maps;
|
||||
|
||||
/**
|
||||
* Constants for My Maps.
|
||||
* Constants for Google Maps.
|
||||
*/
|
||||
public class MyMapsConstants {
|
||||
public class MapsConstants {
|
||||
static final String TAG = "MapsApi";
|
||||
static final String MAPSHOP_BASE_URL =
|
||||
"https://maps.google.com/maps/ms";
|
||||
@@ -13,5 +13,5 @@ public class MyMapsConstants {
|
||||
/**
|
||||
* Private constructor to prevent instantiation.
|
||||
*/
|
||||
private MyMapsConstants() { }
|
||||
private MapsConstants() { }
|
||||
}
|
||||
+75
-115
@@ -1,5 +1,5 @@
|
||||
// Copyright 2011 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
package com.google.android.apps.mytracks.io.maps;
|
||||
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.io.AuthManager;
|
||||
@@ -8,7 +8,6 @@ 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;
|
||||
@@ -28,34 +27,25 @@ import org.xmlpull.v1.XmlPullParserException;
|
||||
*/
|
||||
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 MapsGDataWrapper wrapper;
|
||||
private final MapsGDataConverter gdataConverter;
|
||||
private final String authToken;
|
||||
|
||||
public MapsFacade(Context context, AuthManager auth) {
|
||||
this.context = context;
|
||||
this.authToken = auth.getAuthToken();
|
||||
|
||||
wrapper = new MyMapsGDataWrapper(context, auth);
|
||||
wrapper = new MapsGDataWrapper(context, auth);
|
||||
wrapper.setRetryOnAuthFailure(true);
|
||||
|
||||
try {
|
||||
gdataConverter = new MyMapsGDataConverter();
|
||||
gdataConverter = new MapsGDataConverter();
|
||||
} catch (XmlPullParserException e) {
|
||||
throw new IllegalStateException("Unable to create maps data converter", e);
|
||||
}
|
||||
@@ -65,35 +55,6 @@ public class MapsFacade {
|
||||
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.
|
||||
*
|
||||
@@ -111,24 +72,24 @@ public class MapsFacade {
|
||||
throw new IllegalArgumentException("mapIdBuilder should be empty");
|
||||
}
|
||||
|
||||
return wrapper.runQuery(new MyMapsGDataWrapper.QueryFunction() {
|
||||
return wrapper.runQuery(new MapsGDataWrapper.QueryFunction() {
|
||||
@Override
|
||||
public void query(MapsClient client) throws IOException, Exception {
|
||||
Log.d(MyMapsConstants.TAG, "Creating a new map.");
|
||||
Log.d(MapsConstants.TAG, "Creating a new map.");
|
||||
String mapFeed = MapsClient.getMapsFeed();
|
||||
Log.d(MyMapsConstants.TAG, "Map feed is " + mapFeed);
|
||||
MyMapsMapMetadata metaData = new MyMapsMapMetadata();
|
||||
Log.d(MapsConstants.TAG, "Map feed is " + mapFeed);
|
||||
MapsMapMetadata metaData = new MapsMapMetadata();
|
||||
metaData.setTitle(title);
|
||||
metaData.setDescription(description + " - "
|
||||
+ category + " - " + StringUtils.getCreatedByMyTracks(context, false));
|
||||
metaData.setSearchable(isPublic);
|
||||
Entry entry = MyMapsGDataConverter.getMapEntryForMetadata(metaData);
|
||||
Log.d(MyMapsConstants.TAG, "Title: " + entry.getTitle());
|
||||
Entry entry = MapsGDataConverter.getMapEntryForMetadata(metaData);
|
||||
Log.d(MapsConstants.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);
|
||||
Log.d(MapsConstants.TAG, "New map id is: " + mapId);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -145,7 +106,7 @@ public class MapsFacade {
|
||||
*/
|
||||
public boolean uploadMarker(final String mapId, final String trackName,
|
||||
final String trackDescription, final Location loc, final boolean isStart) {
|
||||
return wrapper.runQuery(new MyMapsGDataWrapper.QueryFunction() {
|
||||
return wrapper.runQuery(new MapsGDataWrapper.QueryFunction() {
|
||||
@Override
|
||||
public void query(MapsClient client)
|
||||
throws IOException, Exception {
|
||||
@@ -165,34 +126,34 @@ public class MapsFacade {
|
||||
String trackName, String trackDescription,
|
||||
GeoPoint geoPoint,
|
||||
boolean isStart) throws IOException, Exception {
|
||||
MyMapsFeature feature =
|
||||
buildMyMapsPlacemarkFeature(trackName, trackDescription, geoPoint, isStart);
|
||||
MapsFeature feature =
|
||||
buildMapsPlacemarkFeature(trackName, trackDescription, geoPoint, isStart);
|
||||
Entry entry = gdataConverter.getEntryForFeature(feature);
|
||||
Log.d(MyMapsConstants.TAG, "SendToMyMaps: Creating placemark "
|
||||
Log.d(MapsConstants.TAG, "SendToMaps: Creating placemark "
|
||||
+ entry.getTitle());
|
||||
try {
|
||||
client.createEntry(featureFeed, authToken, entry);
|
||||
Log.d(MyMapsConstants.TAG, "SendToMyMaps: createEntry success!");
|
||||
Log.d(MapsConstants.TAG, "SendToMaps: createEntry success!");
|
||||
} catch (IOException e) {
|
||||
Log.w(MyMapsConstants.TAG,
|
||||
"SendToMyMaps: createEntry 1st try failed. Trying again.");
|
||||
Log.w(MapsConstants.TAG,
|
||||
"SendToMaps: 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!");
|
||||
Log.d(MapsConstants.TAG,
|
||||
"SendToMaps: createEntry success on 2nd try!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a placemark MyMapsFeature from a track.
|
||||
* Builds a placemark MapsFeature from a track.
|
||||
*
|
||||
* @param trackName the track
|
||||
* @param trackDescription the track description
|
||||
* @param geoPoint the geo point
|
||||
* @param isStart true if it's the start of the track, or false for end
|
||||
* @return a MyMapsFeature
|
||||
* @return a MapsFeature
|
||||
*/
|
||||
private MyMapsFeature buildMyMapsPlacemarkFeature(
|
||||
private MapsFeature buildMapsPlacemarkFeature(
|
||||
String trackName, String trackDescription,
|
||||
GeoPoint geoPoint, boolean isStart) {
|
||||
String iconUrl;
|
||||
@@ -201,38 +162,37 @@ public class MapsFacade {
|
||||
} else {
|
||||
iconUrl = END_ICON_URL;
|
||||
}
|
||||
String title = trackName + " "
|
||||
+ (isStart ? context.getString(R.string.marker_label_start)
|
||||
: context.getString(R.string.marker_label_end));
|
||||
String title = isStart ? context.getString(R.string.marker_label_start, trackName) :
|
||||
context.getString(R.string.marker_label_end, trackName);
|
||||
String description = isStart ? "" : trackDescription;
|
||||
return buildMyMapsPlacemarkFeature(title, description, iconUrl, geoPoint);
|
||||
return buildMapsPlacemarkFeature(title, description, iconUrl, geoPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a MyMapsFeature from a waypoint.
|
||||
* Builds a MapsFeature from a waypoint.
|
||||
*
|
||||
* @param title the title
|
||||
* @param description the description
|
||||
* @param iconUrl the icon url
|
||||
* @param geoPoint the waypoint
|
||||
* @return a MyMapsFeature
|
||||
* @return a MapsFeature
|
||||
*/
|
||||
private static MyMapsFeature buildMyMapsPlacemarkFeature(
|
||||
private static MapsFeature buildMapsPlacemarkFeature(
|
||||
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);
|
||||
MapsFeature mapsFeature = new MapsFeature();
|
||||
mapsFeature.generateAndroidId();
|
||||
mapsFeature.setType(MapsFeature.MARKER);
|
||||
mapsFeature.setIconUrl(iconUrl);
|
||||
mapsFeature.setDescription(description);
|
||||
mapsFeature.addPoint(geoPoint);
|
||||
if (TextUtils.isEmpty(title)) {
|
||||
// Features must have a name (otherwise GData upload may fail):
|
||||
myMapsFeature.setTitle("-");
|
||||
mapsFeature.setTitle("-");
|
||||
} else {
|
||||
myMapsFeature.setTitle(title);
|
||||
mapsFeature.setTitle(title);
|
||||
}
|
||||
myMapsFeature.setDescription(description.replaceAll("\n", "<br>"));
|
||||
return myMapsFeature;
|
||||
mapsFeature.setDescription(description.replaceAll("\n", "<br>"));
|
||||
return mapsFeature;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,7 +204,7 @@ public class MapsFacade {
|
||||
*/
|
||||
public boolean uploadWaypoints(
|
||||
final String mapId, final Iterable<Waypoint> waypoints) {
|
||||
return wrapper.runQuery(new MyMapsGDataWrapper.QueryFunction() {
|
||||
return wrapper.runQuery(new MapsGDataWrapper.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
|
||||
@@ -254,33 +214,33 @@ public class MapsFacade {
|
||||
|
||||
try {
|
||||
for (Waypoint waypoint : waypoints) {
|
||||
MyMapsFeature feature = buildMyMapsPlacemarkFeature(
|
||||
MapsFeature feature = buildMapsPlacemarkFeature(
|
||||
waypoint.getName(), waypoint.getDescription(), waypoint.getIcon(),
|
||||
getGeoPoint(waypoint.getLocation()));
|
||||
Entry entry = gdataConverter.getEntryForFeature(feature);
|
||||
|
||||
Log.d(MyMapsConstants.TAG,
|
||||
"SendToMyMaps: Creating waypoint.");
|
||||
Log.d(MapsConstants.TAG,
|
||||
"SendToMaps: Creating waypoint.");
|
||||
try {
|
||||
client.createEntry(featureFeed, authToken, entry);
|
||||
Log.d(MyMapsConstants.TAG,
|
||||
"SendToMyMaps: createEntry success!");
|
||||
Log.d(MapsConstants.TAG,
|
||||
"SendToMaps: createEntry success!");
|
||||
} catch (IOException e) {
|
||||
Log.w(MyMapsConstants.TAG,
|
||||
"SendToMyMaps: createEntry 1st try failed. Retrying.");
|
||||
Log.w(MapsConstants.TAG,
|
||||
"SendToMaps: 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!");
|
||||
Log.d(MapsConstants.TAG,
|
||||
"SendToMaps: createEntry success on 2nd try!");
|
||||
}
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
Log.w(MyMapsConstants.TAG, "ParseException caught.", e);
|
||||
Log.w(MapsConstants.TAG, "ParseException caught.", e);
|
||||
} catch (HttpException e) {
|
||||
Log.w(MyMapsConstants.TAG, "HttpException caught.", e);
|
||||
Log.w(MapsConstants.TAG, "HttpException caught.", e);
|
||||
} catch (IOException e) {
|
||||
Log.w(MyMapsConstants.TAG, "IOException caught.", e);
|
||||
Log.w(MapsConstants.TAG, "IOException caught.", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -296,12 +256,12 @@ public class MapsFacade {
|
||||
*/
|
||||
public boolean uploadTrackPoints(
|
||||
final String mapId, final String trackName, final Collection<Location> locations) {
|
||||
return wrapper.runQuery(new MyMapsGDataWrapper.QueryFunction() {
|
||||
return wrapper.runQuery(new MapsGDataWrapper.QueryFunction() {
|
||||
@Override
|
||||
public void query(MapsClient client)
|
||||
throws IOException, Exception {
|
||||
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
||||
Log.d(MyMapsConstants.TAG, "Feature feed url: " + featureFeed);
|
||||
Log.d(MapsConstants.TAG, "Feature feed url: " + featureFeed);
|
||||
uploadTrackPoints(client, featureFeed, trackName, locations);
|
||||
}
|
||||
});
|
||||
@@ -316,52 +276,52 @@ public class MapsFacade {
|
||||
int numLocations = locations.size();
|
||||
if (numLocations < 2) {
|
||||
// Need at least two points for a polyline:
|
||||
Log.w(MyMapsConstants.TAG, "Not uploading too few points");
|
||||
Log.w(MapsConstants.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());
|
||||
buildMapsLineFeature(trackName, locations));
|
||||
Log.d(MapsConstants.TAG,
|
||||
"SendToMaps: Creating line " + entry.getTitle());
|
||||
try {
|
||||
client.createEntry(featureFeed, authToken, entry);
|
||||
Log.d(MyMapsConstants.TAG, "SendToMyMaps: createEntry success!");
|
||||
Log.d(MapsConstants.TAG, "SendToMaps: createEntry success!");
|
||||
} catch (IOException e) {
|
||||
Log.w(MyMapsConstants.TAG,
|
||||
"SendToMyMaps: createEntry 1st try failed. Trying again.");
|
||||
Log.w(MapsConstants.TAG,
|
||||
"SendToMaps: 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!");
|
||||
Log.d(MapsConstants.TAG,
|
||||
"SendToMaps: createEntry success on 2nd try!");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a MyMapsFeature from a track.
|
||||
* Builds a MapsFeature from a track.
|
||||
*
|
||||
* @param trackName the track name
|
||||
* @param locations locations on the track
|
||||
* @return a MyMapsFeature
|
||||
* @return a MapsFeature
|
||||
*/
|
||||
private static MyMapsFeature buildMyMapsLineFeature(String trackName,
|
||||
private static MapsFeature buildMapsLineFeature(String trackName,
|
||||
Iterable<Location> locations) {
|
||||
MyMapsFeature myMapsFeature = new MyMapsFeature();
|
||||
myMapsFeature.generateAndroidId();
|
||||
myMapsFeature.setType(MyMapsFeature.LINE);
|
||||
MapsFeature mapsFeature = new MapsFeature();
|
||||
mapsFeature.generateAndroidId();
|
||||
mapsFeature.setType(MapsFeature.LINE);
|
||||
if (TextUtils.isEmpty(trackName)) {
|
||||
// Features must have a name (otherwise GData upload may fail):
|
||||
myMapsFeature.setTitle("-");
|
||||
mapsFeature.setTitle("-");
|
||||
} else {
|
||||
myMapsFeature.setTitle(trackName);
|
||||
mapsFeature.setTitle(trackName);
|
||||
}
|
||||
myMapsFeature.setColor(0x80FF0000);
|
||||
mapsFeature.setColor(0x80FF0000);
|
||||
for (Location loc : locations) {
|
||||
myMapsFeature.addPoint(getGeoPoint(loc));
|
||||
mapsFeature.addPoint(getGeoPoint(loc));
|
||||
}
|
||||
return myMapsFeature;
|
||||
return mapsFeature;
|
||||
}
|
||||
|
||||
/**
|
||||
+7
-7
@@ -1,5 +1,5 @@
|
||||
// Copyright 2009 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
package com.google.android.apps.mytracks.io.maps;
|
||||
|
||||
import com.google.android.maps.GeoPoint;
|
||||
|
||||
@@ -7,12 +7,12 @@ import java.util.Random;
|
||||
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
|
||||
* MapsFeature contains all of the data associated with a feature in Google
|
||||
* Maps, where a feature is a marker, line, or shape. Some of the data is stored
|
||||
* in a {@link MapsFeatureMetadata} object so that it can be more efficiently
|
||||
* transmitted to other activities.
|
||||
*/
|
||||
class MyMapsFeature {
|
||||
class MapsFeature {
|
||||
|
||||
private static final long serialVersionUID = 8439035544430497236L;
|
||||
|
||||
@@ -47,7 +47,7 @@ class MyMapsFeature {
|
||||
private final Vector<Integer> longitudeE6 = new Vector<Integer>();
|
||||
|
||||
/** The metadata of this feature in a format efficient for transmission. */
|
||||
private MyMapsFeatureMetadata featureInfo = new MyMapsFeatureMetadata();
|
||||
private MapsFeatureMetadata featureInfo = new MapsFeatureMetadata();
|
||||
|
||||
private final Random random = new Random();
|
||||
|
||||
@@ -56,7 +56,7 @@ class MyMapsFeature {
|
||||
* {@link #MARKER} with a blue placemark with a dot as an icon at the
|
||||
* location (0, 0).
|
||||
*/
|
||||
public MyMapsFeature() {
|
||||
public MapsFeature() {
|
||||
}
|
||||
|
||||
/**
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
// Copyright 2009 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
package com.google.android.apps.mytracks.io.maps;
|
||||
|
||||
/**
|
||||
* Metadata about a maps feature.
|
||||
*/
|
||||
class MyMapsFeatureMetadata {
|
||||
class MapsFeatureMetadata {
|
||||
|
||||
private static final String BLUE_DOT_URL =
|
||||
"http://maps.google.com/mapfiles/ms/micons/blue-dot.png";
|
||||
@@ -19,10 +19,10 @@ class MyMapsFeatureMetadata {
|
||||
private int fillColor;
|
||||
private String iconUrl;
|
||||
|
||||
public MyMapsFeatureMetadata() {
|
||||
public MapsFeatureMetadata() {
|
||||
title = "";
|
||||
description = "";
|
||||
type = MyMapsFeature.MARKER;
|
||||
type = MapsFeature.MARKER;
|
||||
color = DEFAULT_COLOR;
|
||||
lineWidth = 5;
|
||||
fillColor = DEFAULT_FILL_COLOR;
|
||||
+16
-20
@@ -1,5 +1,5 @@
|
||||
// Copyright 2009 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
package com.google.android.apps.mytracks.io.maps;
|
||||
|
||||
import com.google.wireless.gdata.data.Entry;
|
||||
import com.google.wireless.gdata.data.StringUtils;
|
||||
@@ -7,38 +7,34 @@ import com.google.wireless.gdata.data.StringUtils;
|
||||
import android.graphics.Color;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
|
||||
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.
|
||||
* Converter from GData objects to Maps objects.
|
||||
*/
|
||||
class MyMapsGDataConverter {
|
||||
class MapsGDataConverter {
|
||||
|
||||
private final XmlSerializer xmlSerializer;
|
||||
|
||||
public MyMapsGDataConverter() throws XmlPullParserException {
|
||||
public MapsGDataConverter() throws XmlPullParserException {
|
||||
xmlSerializer = XmlPullParserFactory.newInstance().newSerializer();
|
||||
}
|
||||
|
||||
public static MyMapsMapMetadata getMapMetadataForEntry(
|
||||
public static MapsMapMetadata getMapMetadataForEntry(
|
||||
MapFeatureEntry entry) {
|
||||
MyMapsMapMetadata metadata = new MyMapsMapMetadata();
|
||||
MapsMapMetadata metadata = new MapsMapMetadata();
|
||||
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));
|
||||
}
|
||||
metadata.setDescription(entry.getSummary());
|
||||
|
||||
String editUri = entry.getEditUri();
|
||||
if (editUri != null) {
|
||||
@@ -52,7 +48,7 @@ class MyMapsGDataConverter {
|
||||
return MapsClient.getMapIdFromMapEntryId(entry.getId());
|
||||
}
|
||||
|
||||
public static Entry getMapEntryForMetadata(MyMapsMapMetadata metadata) {
|
||||
public static Entry getMapEntryForMetadata(MapsMapMetadata metadata) {
|
||||
MapFeatureEntry entry = new MapFeatureEntry();
|
||||
entry.setEditUri(metadata.getGDataEditUri());
|
||||
entry.setTitle(metadata.getTitle());
|
||||
@@ -63,7 +59,7 @@ class MyMapsGDataConverter {
|
||||
return entry;
|
||||
}
|
||||
|
||||
public MapFeatureEntry getEntryForFeature(MyMapsFeature feature) {
|
||||
public MapFeatureEntry getEntryForFeature(MapsFeature feature) {
|
||||
MapFeatureEntry entry = new MapFeatureEntry();
|
||||
entry.setTitle(feature.getTitle());
|
||||
entry.setAuthor("android");
|
||||
@@ -80,7 +76,7 @@ class MyMapsGDataConverter {
|
||||
xmlSerializer.startTag(null, "Placemark");
|
||||
xmlSerializer.attribute(null, "xmlns", "http://earth.google.com/kml/2.2");
|
||||
xmlSerializer.startTag(null, "Style");
|
||||
if (feature.getType() == MyMapsFeature.MARKER) {
|
||||
if (feature.getType() == MapsFeature.MARKER) {
|
||||
xmlSerializer.startTag(null, "IconStyle");
|
||||
xmlSerializer.startTag(null, "Icon");
|
||||
xmlSerializer.startTag(null, "href");
|
||||
@@ -102,7 +98,7 @@ class MyMapsGDataConverter {
|
||||
xmlSerializer.endTag(null, "width");
|
||||
xmlSerializer.endTag(null, "LineStyle");
|
||||
|
||||
if (feature.getType() == MyMapsFeature.SHAPE) {
|
||||
if (feature.getType() == MapsFeature.SHAPE) {
|
||||
xmlSerializer.startTag(null, "PolyStyle");
|
||||
xmlSerializer.startTag(null, "color");
|
||||
int fcolor = feature.getFillColor();
|
||||
@@ -137,13 +133,13 @@ class MyMapsGDataConverter {
|
||||
pointBuilder.append(",0.000000");
|
||||
}
|
||||
String pointString = pointBuilder.toString();
|
||||
if (feature.getType() == MyMapsFeature.MARKER) {
|
||||
if (feature.getType() == MapsFeature.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) {
|
||||
} else if (feature.getType() == MapsFeature.LINE) {
|
||||
xmlSerializer.startTag(null, "LineString");
|
||||
xmlSerializer.startTag(null, "tessellate");
|
||||
xmlSerializer.text("1");
|
||||
+11
-11
@@ -1,5 +1,5 @@
|
||||
// Copyright 2010 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
package com.google.android.apps.mytracks.io.maps;
|
||||
|
||||
import com.google.android.apps.mytracks.io.AuthManager;
|
||||
import com.google.android.apps.mytracks.io.AuthManager.AuthCallback;
|
||||
@@ -17,12 +17,12 @@ import android.util.Log;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* MyMapsGDataWrapper provides a wrapper around GData operations that maintains
|
||||
* MapsGDataWrapper 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 {
|
||||
class MapsGDataWrapper {
|
||||
/**
|
||||
* 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
|
||||
@@ -67,7 +67,7 @@ class MyMapsGDataWrapper {
|
||||
private int retriesPending;
|
||||
private boolean cleanupCalled;
|
||||
|
||||
public MyMapsGDataWrapper(Context context, AuthManager auth) {
|
||||
public MapsGDataWrapper(Context context, AuthManager auth) {
|
||||
androidGdataClient = GDataClientFactory.getGDataClient(context);
|
||||
this.auth = auth;
|
||||
client =
|
||||
@@ -92,11 +92,11 @@ class MyMapsGDataWrapper {
|
||||
errorMessage = null;
|
||||
return true;
|
||||
} catch (AuthenticationException e) {
|
||||
Log.e(MyMapsConstants.TAG, "Exception", e);
|
||||
Log.e(MapsConstants.TAG, "Exception", e);
|
||||
errorType = ERROR_AUTH;
|
||||
errorMessage = e.getMessage();
|
||||
} catch (HttpException e) {
|
||||
Log.e(MyMapsConstants.TAG, "HttpException", e);
|
||||
Log.e(MapsConstants.TAG, "HttpException", e);
|
||||
errorMessage = e.getMessage();
|
||||
if (errorMessage != null && errorMessage.contains("401")) {
|
||||
errorType = ERROR_AUTH;
|
||||
@@ -104,7 +104,7 @@ class MyMapsGDataWrapper {
|
||||
errorType = ERROR_CONNECTION;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(MyMapsConstants.TAG, "Exception", e);
|
||||
Log.e(MapsConstants.TAG, "Exception", e);
|
||||
errorMessage = e.getMessage();
|
||||
if (errorMessage != null && errorMessage.contains("503")) {
|
||||
errorType = ERROR_INTERNAL;
|
||||
@@ -112,20 +112,20 @@ class MyMapsGDataWrapper {
|
||||
errorType = ERROR_CONNECTION;
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
Log.e(MyMapsConstants.TAG, "Exception", e);
|
||||
Log.e(MapsConstants.TAG, "Exception", e);
|
||||
errorType = ERROR_LOCAL;
|
||||
errorMessage = e.getMessage();
|
||||
} catch (ConflictDetectedException e) {
|
||||
Log.e(MyMapsConstants.TAG, "Exception", e);
|
||||
Log.e(MapsConstants.TAG, "Exception", e);
|
||||
errorType = ERROR_CONFLICT;
|
||||
errorMessage = e.getMessage();
|
||||
} catch (Exception e) {
|
||||
Log.e(MyMapsConstants.TAG, "Exception", e);
|
||||
Log.e(MapsConstants.TAG, "Exception", e);
|
||||
errorType = ERROR_UNKNOWN;
|
||||
errorMessage = e.getMessage();
|
||||
e.printStackTrace();
|
||||
}
|
||||
Log.d(MyMapsConstants.TAG, "GData error encountered: " + errorMessage);
|
||||
Log.d(MapsConstants.TAG, "GData error encountered: " + errorMessage);
|
||||
if (errorType == ERROR_AUTH && auth != null) {
|
||||
AuthCallback whenFinished = null;
|
||||
if (retryOnAuthFailure) {
|
||||
+4
-4
@@ -1,17 +1,17 @@
|
||||
// Copyright 2009 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
package com.google.android.apps.mytracks.io.maps;
|
||||
|
||||
/**
|
||||
* Metadata about a "my maps" map.
|
||||
* Metadata about a Google Maps map.
|
||||
*/
|
||||
class MyMapsMapMetadata {
|
||||
class MapsMapMetadata {
|
||||
|
||||
private String title;
|
||||
private String description;
|
||||
private String gdataEditUri;
|
||||
private boolean searchable;
|
||||
|
||||
public MyMapsMapMetadata() {
|
||||
public MapsMapMetadata() {
|
||||
title = "";
|
||||
description = "";
|
||||
gdataEditUri = "";
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// Copyright 2009 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
package com.google.android.apps.mytracks.io.maps;
|
||||
|
||||
import com.google.wireless.gdata.data.Entry;
|
||||
import com.google.wireless.gdata.data.Feed;
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// Copyright 2010 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
package com.google.android.apps.mytracks.io.maps;
|
||||
|
||||
import com.google.wireless.gdata.client.GDataParserFactory;
|
||||
import com.google.wireless.gdata.data.Entry;
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// Copyright 2010 Google Inc. All Rights Reserved.
|
||||
package com.google.android.apps.mytracks.io.mymaps;
|
||||
package com.google.android.apps.mytracks.io.maps;
|
||||
|
||||
import com.google.wireless.gdata.data.StringUtils;
|
||||
import com.google.wireless.gdata.parser.ParseException;
|
||||
@@ -1,50 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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.Constants;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* A chooser to select an account. To be called with
|
||||
* {@link Activity#startActivityForResult(Intent, int)}. Returns
|
||||
* {@link Activity#RESULT_CANCELED} if the user cancels the activity. Otherwise,
|
||||
* returns {@link Activity#RESULT_OK} with an intent containing the selected
|
||||
* account. The selected account is stored as an {@link Account} in the
|
||||
* {@link AccountChooserActivity#ACCOUNT} item of the intent. The selected
|
||||
* account can be null.
|
||||
*
|
||||
* @author jshih@google.com (Jimmy Shih)
|
||||
*/
|
||||
public class AccountChooserActivity extends Activity {
|
||||
|
||||
/**
|
||||
* Key for storing an {@link Account} in an {@link Intent}.
|
||||
*/
|
||||
public static final String ACCOUNT = "account";
|
||||
|
||||
private static final int NO_ACCOUNT_DIALOG = 1;
|
||||
private static final int CHOOSE_ACCOUNT_DIALOG = 2;
|
||||
|
||||
private Account[] accounts;
|
||||
private int selectedAccountIndex;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
accounts = AccountManager.get(this).getAccountsByType(Constants.ACCOUNT_TYPE);
|
||||
|
||||
if (accounts.length == 1) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(ACCOUNT, accounts[0]);
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
SharedPreferences prefs = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
String preferredAccount = prefs.getString(getString(R.string.preferred_account_key), "");
|
||||
|
||||
selectedAccountIndex = -1;
|
||||
for (int i = 0; i < accounts.length; i++) {
|
||||
if (accounts[i].name.equals(preferredAccount)) {
|
||||
selectedAccountIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (accounts.length == 0) {
|
||||
showDialog(NO_ACCOUNT_DIALOG);
|
||||
} else {
|
||||
showDialog(CHOOSE_ACCOUNT_DIALOG);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
AlertDialog.Builder builder;
|
||||
switch (id) {
|
||||
case NO_ACCOUNT_DIALOG:
|
||||
builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.send_google_no_account_title);
|
||||
builder.setMessage(R.string.send_google_no_account_message);
|
||||
builder.setCancelable(true);
|
||||
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
builder.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(ACCOUNT, (Account) null);
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
case CHOOSE_ACCOUNT_DIALOG:
|
||||
builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.send_google_choose_account_title);
|
||||
|
||||
String[] choices = new String[accounts.length];
|
||||
for (int i = 0; i < accounts.length; i++) {
|
||||
choices[i] = accounts[i].name;
|
||||
}
|
||||
builder.setSingleChoiceItems(choices, selectedAccountIndex, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
selectedAccountIndex = which;
|
||||
}
|
||||
});
|
||||
|
||||
builder.setCancelable(true);
|
||||
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(R.string.generic_cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
builder.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Account account = null;
|
||||
if (selectedAccountIndex != -1) {
|
||||
account = accounts[selectedAccountIndex];
|
||||
SharedPreferences prefs = getSharedPreferences(
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
Editor editor = prefs.edit();
|
||||
editor.putString(getString(R.string.preferred_account_key), account.name);
|
||||
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor);
|
||||
}
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(ACCOUNT, account);
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
-109
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io.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,
|
||||
DialogInterface.OnCancelListener onCancelListener) {
|
||||
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.generic_success_title);
|
||||
builder.setIcon(android.R.drawable.ic_dialog_info);
|
||||
} else {
|
||||
builder.setTitle(R.string.generic_error_title);
|
||||
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
}
|
||||
|
||||
builder.setPositiveButton(activity.getString(R.string.generic_ok), onOkClickListener);
|
||||
if (onShareClickListener != null) {
|
||||
builder.setNegativeButton(activity.getString(R.string.send_google_result_share_url),
|
||||
onShareClickListener);
|
||||
}
|
||||
|
||||
builder.setOnCancelListener(onCancelListener);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io.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));
|
||||
}
|
||||
};
|
||||
@@ -18,44 +18,30 @@ package com.google.android.apps.mytracks.io.sendtogoogle;
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.analytics.GoogleAnalyticsTracker;
|
||||
import com.google.android.apps.mytracks.AccountChooser;
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.MyMapsList;
|
||||
import com.google.android.apps.mytracks.ProgressIndicator;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||
import com.google.android.apps.mytracks.io.AuthManager;
|
||||
import com.google.android.apps.mytracks.io.AuthManager.AuthCallback;
|
||||
import com.google.android.apps.mytracks.io.AuthManagerFactory;
|
||||
import com.google.android.apps.mytracks.io.SendToDocs;
|
||||
import com.google.android.apps.mytracks.io.SendToFusionTables;
|
||||
import com.google.android.apps.mytracks.io.SendToFusionTables.OnSendCompletedListener;
|
||||
import com.google.android.apps.mytracks.io.SendToMyMaps;
|
||||
import com.google.android.apps.mytracks.io.mymaps.MapsFacade;
|
||||
import com.google.android.apps.mytracks.io.mymaps.MyMapsConstants;
|
||||
import com.google.android.apps.mytracks.io.SendToMaps;
|
||||
import com.google.android.apps.mytracks.io.docs.SendDocsActivity;
|
||||
import com.google.android.apps.mytracks.io.maps.ChooseMapActivity;
|
||||
import com.google.android.apps.mytracks.io.maps.MapsConstants;
|
||||
import com.google.android.apps.mytracks.io.maps.MapsFacade;
|
||||
import com.google.android.apps.mytracks.util.SystemUtils;
|
||||
import com.google.android.apps.mytracks.util.UriUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnCancelListener;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Helper activity for managing the sending of tracks to Google services.
|
||||
@@ -63,96 +49,82 @@ import java.util.List;
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class SendActivity extends Activity implements ProgressIndicator {
|
||||
|
||||
// Items in the intent that starts the activity.
|
||||
public static final String TRACK_ID = "trackId";
|
||||
public static final String SHARE_URL = "shareUrl";
|
||||
public static final String SEND_MAPS = "sendMaps";
|
||||
public static final String SEND_FUSION_TABLES = "sendFusionTables";
|
||||
public static final String SEND_DOCS = "sendDocs";
|
||||
public static final String CREATE_MAP = "createMap";
|
||||
|
||||
// Keys for saved state variables.
|
||||
private static final String STATE_SEND_TO_MAPS = "mapsSend";
|
||||
private static final String STATE_SEND_TO_FUSION_TABLES = "fusionSend";
|
||||
private static final String STATE_SEND_TO_DOCS = "docsSend";
|
||||
private static final String STATE_ACCOUNT = "account";
|
||||
private static final String STATE_STATE = "state";
|
||||
private static final String STATE_DOCS_SUCCESS = "docsSuccess";
|
||||
private static final String STATE_FUSION_SUCCESS = "fusionSuccess";
|
||||
private static final String STATE_MAPS_SUCCESS = "mapsSuccess";
|
||||
private static final String STATE_STATE = "state";
|
||||
private static final String EXTRA_SHARE_LINK = "shareLink";
|
||||
private static final String STATE_ACCOUNT_TYPE = "accountType";
|
||||
private static final String STATE_ACCOUNT_NAME = "accountName";
|
||||
private static final String STATE_TABLE_ID = "tableId";
|
||||
private static final String STATE_MAP_ID = "mapId";
|
||||
|
||||
/** States for the state machine that defines the upload process. */
|
||||
private enum SendState {
|
||||
SEND_OPTIONS,
|
||||
CHOOSE_ACCOUNT,
|
||||
START,
|
||||
AUTHENTICATE_MAPS,
|
||||
PICK_MAP,
|
||||
SEND_TO_MAPS,
|
||||
SEND_TO_MAPS_DONE,
|
||||
AUTHENTICATE_FUSION_TABLES,
|
||||
SEND_TO_FUSION_TABLES,
|
||||
SEND_TO_FUSION_TABLES_DONE,
|
||||
AUTHENTICATE_DOCS,
|
||||
AUTHENTICATE_TRIX,
|
||||
SEND_TO_DOCS,
|
||||
SEND_TO_DOCS_DONE,
|
||||
SHOW_RESULTS,
|
||||
SHARE_LINK,
|
||||
FINISH,
|
||||
DONE,
|
||||
NOT_READY
|
||||
}
|
||||
|
||||
private static final int SEND_DIALOG = 1;
|
||||
private static final int PROGRESS_DIALOG = 2;
|
||||
/* @VisibleForTesting */
|
||||
static final int DONE_DIALOG = 3;
|
||||
|
||||
// UI
|
||||
private static final int PROGRESS_DIALOG = 1;
|
||||
private ProgressDialog progressDialog;
|
||||
|
||||
// Services
|
||||
// Set in Activity.onCreate
|
||||
private MyTracksProviderUtils providerUtils;
|
||||
private SharedPreferences sharedPreferences;
|
||||
private GoogleAnalyticsTracker tracker;
|
||||
|
||||
// Authentication
|
||||
private AuthManager lastAuth;
|
||||
private final HashMap<String, AuthManager> authMap = new HashMap<String, AuthManager>();
|
||||
private AccountChooser accountChooser;
|
||||
private String lastAccountName;
|
||||
private String lastAccountType;
|
||||
|
||||
// Send request information.
|
||||
private boolean shareRequested = false;
|
||||
// Send request information. Set by the intent that starts the activity.
|
||||
private long sendTrackId;
|
||||
private boolean sendToMyMaps;
|
||||
private boolean sendToMyMapsNewMap;
|
||||
private boolean shareRequest;
|
||||
private boolean sendToMaps;
|
||||
private boolean sendToMapsNewMap;
|
||||
private boolean sendToFusionTables;
|
||||
private boolean sendToDocs;
|
||||
|
||||
// Send result information, used by results dialog.
|
||||
private boolean sendToMyMapsSuccess = false;
|
||||
private boolean sendToFusionTablesSuccess = false;
|
||||
private boolean sendToDocsSuccess = false;
|
||||
|
||||
// Send result information, used to share a link.
|
||||
private String sendToMyMapsMapId;
|
||||
private String sendToFusionTablesTableId;
|
||||
|
||||
|
||||
// Authentication information.
|
||||
private Account account;
|
||||
private HashMap<String, AuthManager> authMap = new HashMap<String, AuthManager>();
|
||||
|
||||
// Current sending state.
|
||||
private SendState currentState;
|
||||
|
||||
private final OnCancelListener finishOnCancelListener = new OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface arg0) {
|
||||
onAllDone();
|
||||
}
|
||||
};
|
||||
|
||||
// Send result information. Used by the results dialog.
|
||||
private boolean sendToMapsSuccess;
|
||||
private boolean sendToFusionTablesSuccess;
|
||||
private boolean sendToDocsSuccess;
|
||||
private String sendToMapsMapId;
|
||||
private String sendToFusionTablesTableId;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
Log.d(TAG, "SendActivity.onCreate");
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (!handleIntent()) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
||||
sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
|
||||
tracker = GoogleAnalyticsTracker.getInstance();
|
||||
// Start the tracker in manual dispatch mode...
|
||||
@@ -172,14 +144,6 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
resetState();
|
||||
}
|
||||
|
||||
// Only consider the intent if we're not restoring from a previous state.
|
||||
if (currentState == SendState.SEND_OPTIONS) {
|
||||
if (!handleIntent()) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the state machine, at the start or restored state.
|
||||
Log.w(TAG, "Starting at state " + currentState);
|
||||
executeStateMachine(currentState);
|
||||
@@ -187,123 +151,62 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
|
||||
private boolean handleIntent() {
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getAction();
|
||||
String type = intent.getType();
|
||||
Uri data = intent.getData();
|
||||
if (!Intent.ACTION_SEND.equals(action) ||
|
||||
!TracksColumns.CONTENT_ITEMTYPE.equals(type) ||
|
||||
!UriUtils.matchesContentUri(data, TracksColumns.CONTENT_URI)) {
|
||||
Log.e(TAG, "Got bad send intent: " + intent);
|
||||
sendTrackId = intent.getLongExtra(TRACK_ID, -1L);
|
||||
if (sendTrackId == -1L) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sendTrackId = ContentUris.parseId(data);
|
||||
shareRequested = intent.getBooleanExtra(EXTRA_SHARE_LINK, false);
|
||||
|
||||
sendToMaps = intent.getBooleanExtra(SEND_MAPS, false);
|
||||
sendToFusionTables = intent.getBooleanExtra(SEND_FUSION_TABLES, false);
|
||||
sendToDocs = intent.getBooleanExtra(SEND_DOCS, false);
|
||||
if (!sendToMaps && !sendToFusionTables && !sendToDocs) {
|
||||
return false;
|
||||
}
|
||||
sendToMapsNewMap = intent.getBooleanExtra(CREATE_MAP, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case SEND_DIALOG:
|
||||
return createSendDialog();
|
||||
case PROGRESS_DIALOG:
|
||||
return createProgressDialog();
|
||||
case DONE_DIALOG:
|
||||
return createDoneDialog();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialog(int id, Dialog dialog) {
|
||||
switch (id) {
|
||||
case SEND_DIALOG:
|
||||
SendDialog sendDialog = (SendDialog) dialog;
|
||||
sendDialog.setDocsEnabled(!shareRequested);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private Dialog createSendDialog() {
|
||||
final SendDialog sendDialog = new SendDialog(this);
|
||||
sendDialog.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (which != DialogInterface.BUTTON_POSITIVE) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
dialog.dismiss();
|
||||
|
||||
sendToMyMaps = sendDialog.getSendToMyMaps();
|
||||
sendToMyMapsNewMap = sendDialog.getCreateNewMap();
|
||||
sendToFusionTables = sendDialog.getSendToFusionTables();
|
||||
sendToDocs = sendDialog.getSendToDocs();
|
||||
|
||||
executeStateMachine(SendState.START);
|
||||
}
|
||||
});
|
||||
sendDialog.setOnCancelListener(finishOnCancelListener);
|
||||
return sendDialog;
|
||||
}
|
||||
|
||||
private void restoreInstanceState(Bundle savedInstanceState) {
|
||||
account = savedInstanceState.getParcelable(STATE_ACCOUNT);
|
||||
|
||||
currentState = SendState.values()[savedInstanceState.getInt(STATE_STATE)];
|
||||
|
||||
sendToMyMaps = savedInstanceState.getBoolean(STATE_SEND_TO_MAPS);
|
||||
sendToFusionTables = savedInstanceState.getBoolean(STATE_SEND_TO_FUSION_TABLES);
|
||||
sendToDocs = savedInstanceState.getBoolean(STATE_SEND_TO_DOCS);
|
||||
|
||||
sendToMyMapsSuccess = savedInstanceState.getBoolean(STATE_MAPS_SUCCESS);
|
||||
sendToMapsSuccess = savedInstanceState.getBoolean(STATE_MAPS_SUCCESS);
|
||||
sendToFusionTablesSuccess = savedInstanceState.getBoolean(STATE_FUSION_SUCCESS);
|
||||
sendToDocsSuccess = savedInstanceState.getBoolean(STATE_DOCS_SUCCESS);
|
||||
|
||||
sendToMyMapsMapId = savedInstanceState.getString(STATE_MAP_ID);
|
||||
sendToMapsMapId = savedInstanceState.getString(STATE_MAP_ID);
|
||||
sendToFusionTablesTableId = savedInstanceState.getString(STATE_TABLE_ID);
|
||||
|
||||
lastAccountName = savedInstanceState.getString(STATE_ACCOUNT_NAME);
|
||||
lastAccountType = savedInstanceState.getString(STATE_ACCOUNT_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
outState.putParcelable(STATE_ACCOUNT, account);
|
||||
|
||||
outState.putInt(STATE_STATE, currentState.ordinal());
|
||||
|
||||
outState.putBoolean(STATE_MAPS_SUCCESS, sendToMyMapsSuccess);
|
||||
outState.putBoolean(STATE_MAPS_SUCCESS, sendToMapsSuccess);
|
||||
outState.putBoolean(STATE_FUSION_SUCCESS, sendToFusionTablesSuccess);
|
||||
outState.putBoolean(STATE_DOCS_SUCCESS, sendToDocsSuccess);
|
||||
|
||||
outState.putString(STATE_MAP_ID, sendToMyMapsMapId);
|
||||
outState.putString(STATE_MAP_ID, sendToMapsMapId);
|
||||
outState.putString(STATE_TABLE_ID, sendToFusionTablesTableId);
|
||||
|
||||
outState.putString(STATE_ACCOUNT_NAME, lastAccountName);
|
||||
outState.putString(STATE_ACCOUNT_TYPE, lastAccountType);
|
||||
|
||||
// TODO: Ideally we should serialize/restore the authenticator map and lastAuth somehow,
|
||||
// but it's highly unlikely we'll get killed while an auth dialog is displayed.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
Log.d(TAG, "SendActivity.onStop, state=" + currentState);
|
||||
tracker.dispatch();
|
||||
tracker.stop();
|
||||
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
Log.d(TAG, "SendActivity.onDestroy, state=" + currentState);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private void executeStateMachine(SendState startState) {
|
||||
@@ -321,8 +224,8 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
|
||||
private SendState executeState(SendState state) {
|
||||
switch (state) {
|
||||
case SEND_OPTIONS:
|
||||
return showSendOptions();
|
||||
case CHOOSE_ACCOUNT:
|
||||
return chooseAccount();
|
||||
case START:
|
||||
return startSend();
|
||||
case AUTHENTICATE_MAPS:
|
||||
@@ -333,24 +236,16 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
return sendToGoogleMaps();
|
||||
case SEND_TO_MAPS_DONE:
|
||||
return onSendToGoogleMapsDone();
|
||||
case AUTHENTICATE_FUSION_TABLES:
|
||||
return authenticateToFusionTables();
|
||||
case SEND_TO_FUSION_TABLES:
|
||||
return sendToFusionTables();
|
||||
case SEND_TO_FUSION_TABLES_DONE:
|
||||
return onSendToFusionTablesDone();
|
||||
case AUTHENTICATE_DOCS:
|
||||
return authenticateToGoogleDocs();
|
||||
case AUTHENTICATE_TRIX:
|
||||
return authenticateToGoogleTrix();
|
||||
case SEND_TO_DOCS:
|
||||
return sendToGoogleDocs();
|
||||
case SEND_TO_DOCS_DONE:
|
||||
return onSendToGoogleDocsDone();
|
||||
case SHOW_RESULTS:
|
||||
return onSendToGoogleDone();
|
||||
case SHARE_LINK:
|
||||
return shareLink();
|
||||
case FINISH:
|
||||
return onAllDone();
|
||||
default:
|
||||
@@ -359,26 +254,24 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
}
|
||||
}
|
||||
|
||||
private SendState showSendOptions() {
|
||||
showDialog(SEND_DIALOG);
|
||||
|
||||
private SendState chooseAccount() {
|
||||
Intent intent = new Intent(this, AccountChooserActivity.class);
|
||||
startActivityForResult(intent, Constants.CHOOSE_ACCOUNT);
|
||||
return SendState.NOT_READY;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initiates the process to send tracks to google.
|
||||
* This is called once the user has selected sending options via the
|
||||
* SendToGoogleDialog.
|
||||
*/
|
||||
private SendState startSend() {
|
||||
showDialog(PROGRESS_DIALOG);
|
||||
|
||||
if (sendToMyMaps) {
|
||||
if (sendToMaps) {
|
||||
return SendState.AUTHENTICATE_MAPS;
|
||||
} else if (sendToFusionTables) {
|
||||
return SendState.AUTHENTICATE_FUSION_TABLES;
|
||||
return SendState.SEND_TO_FUSION_TABLES;
|
||||
} else if (sendToDocs) {
|
||||
return SendState.AUTHENTICATE_DOCS;
|
||||
return SendState.SEND_TO_DOCS;
|
||||
} else {
|
||||
Log.w(TAG, "Nowhere to upload to");
|
||||
return SendState.FINISH;
|
||||
@@ -399,21 +292,19 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
|
||||
private SendState authenticateToGoogleMaps() {
|
||||
Log.d(TAG, "SendActivity.authenticateToGoogleMaps");
|
||||
showDialog(PROGRESS_DIALOG);
|
||||
progressDialog.setProgress(0);
|
||||
progressDialog.setMessage(getAuthenticatingProgressMessage(SendType.MYMAPS));
|
||||
authenticate(Constants.AUTHENTICATE_TO_MY_MAPS, MyMapsConstants.SERVICE_NAME);
|
||||
// AUTHENTICATE_TO_MY_MAPS callback calls sendToGoogleMaps
|
||||
progressDialog.setMessage(getAuthenticatingProgressMessage(SendType.MAPS));
|
||||
authenticate(Constants.AUTHENTICATE_TO_MAPS, MapsConstants.SERVICE_NAME);
|
||||
// AUTHENTICATE_TO_MAPS callback calls sendToGoogleMaps
|
||||
return SendState.NOT_READY;
|
||||
}
|
||||
|
||||
private SendState pickMap() {
|
||||
if (!sendToMyMapsNewMap) {
|
||||
// Ask the user to choose a map to upload into
|
||||
Intent listIntent = new Intent(this, MyMapsList.class);
|
||||
listIntent.putExtra(MyMapsList.EXTRA_ACCOUNT_NAME, lastAccountName);
|
||||
listIntent.putExtra(MyMapsList.EXTRA_ACCOUNT_TYPE, lastAccountType);
|
||||
startActivityForResult(listIntent, Constants.GET_MAP);
|
||||
// The callback for GET_MAP calls authenticateToGoogleMaps
|
||||
if (!sendToMapsNewMap) {
|
||||
Intent intent = new Intent(this, ChooseMapActivity.class);
|
||||
intent.putExtra(ChooseMapActivity.ACCOUNT, account);
|
||||
startActivityForResult(intent, Constants.CHOOSE_MAP);
|
||||
return SendState.NOT_READY;
|
||||
} else {
|
||||
return SendState.SEND_TO_MAPS;
|
||||
@@ -423,12 +314,12 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
private SendState sendToGoogleMaps() {
|
||||
tracker.trackPageView("/send/maps");
|
||||
|
||||
SendToMyMaps.OnSendCompletedListener onCompletion = new SendToMyMaps.OnSendCompletedListener() {
|
||||
SendToMaps.OnSendCompletedListener onCompletion = new SendToMaps.OnSendCompletedListener() {
|
||||
@Override
|
||||
public void onSendCompleted(String mapId, boolean success) {
|
||||
sendToMyMapsSuccess = success;
|
||||
if (sendToMyMapsSuccess) {
|
||||
sendToMyMapsMapId = mapId;
|
||||
sendToMapsSuccess = success;
|
||||
if (sendToMapsSuccess) {
|
||||
sendToMapsMapId = mapId;
|
||||
// Update the map id for this track:
|
||||
try {
|
||||
Track track = providerUtils.getTrack(sendTrackId);
|
||||
@@ -449,119 +340,52 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
}
|
||||
};
|
||||
|
||||
if (sendToMyMapsMapId == null) {
|
||||
sendToMyMapsMapId = SendToMyMaps.NEW_MAP_ID;
|
||||
if (sendToMapsMapId == null) {
|
||||
sendToMapsMapId = SendToMaps.NEW_MAP_ID;
|
||||
}
|
||||
|
||||
final SendToMyMaps sender = new SendToMyMaps(this, sendToMyMapsMapId, lastAuth,
|
||||
sendTrackId, this /*progressIndicator*/, onCompletion);
|
||||
final SendToMaps sender = new SendToMaps(this, sendToMapsMapId,
|
||||
getAuthManager(MapsConstants.SERVICE_NAME), sendTrackId, this /*progressIndicator*/,
|
||||
onCompletion);
|
||||
|
||||
new Thread(sender, "SendToMyMaps").start();
|
||||
new Thread(sender, "SendToMaps").start();
|
||||
|
||||
return SendState.NOT_READY;
|
||||
}
|
||||
|
||||
private SendState onSendToGoogleMapsDone() {
|
||||
if (sendToFusionTables) {
|
||||
return SendState.AUTHENTICATE_FUSION_TABLES;
|
||||
return SendState.SEND_TO_FUSION_TABLES;
|
||||
} else if (sendToDocs) {
|
||||
return SendState.AUTHENTICATE_DOCS;
|
||||
return SendState.SEND_TO_DOCS;
|
||||
} else {
|
||||
return SendState.SHOW_RESULTS;
|
||||
}
|
||||
}
|
||||
|
||||
private SendState authenticateToFusionTables() {
|
||||
progressDialog.setProgress(0);
|
||||
progressDialog.setMessage(getAuthenticatingProgressMessage(SendType.FUSION_TABLES));
|
||||
authenticate(Constants.AUTHENTICATE_TO_FUSION_TABLES, SendToFusionTables.SERVICE_ID);
|
||||
// AUTHENTICATE_TO_FUSION_TABLES callback calls sendToFusionTables
|
||||
return SendState.NOT_READY;
|
||||
}
|
||||
|
||||
private SendState sendToFusionTables() {
|
||||
tracker.trackPageView("/send/fusion_tables");
|
||||
|
||||
OnSendCompletedListener onCompletion = new OnSendCompletedListener() {
|
||||
@Override
|
||||
public void onSendCompleted(String tableId, boolean success) {
|
||||
sendToFusionTablesSuccess = success;
|
||||
if (sendToFusionTablesSuccess) {
|
||||
sendToFusionTablesTableId = tableId;
|
||||
// Update the table id for this track:
|
||||
try {
|
||||
Track track = providerUtils.getTrack(sendTrackId);
|
||||
if (track != null) {
|
||||
track.setTableId(tableId);
|
||||
providerUtils.updateTrack(track);
|
||||
} else {
|
||||
Log.w(TAG, "Updating table id failed.");
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
// If that fails whatever reasons we'll just log an error, but
|
||||
// continue.
|
||||
Log.w(TAG, "Updating table id failed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
executeStateMachine(SendState.SEND_TO_FUSION_TABLES_DONE);
|
||||
}
|
||||
};
|
||||
|
||||
final SendToFusionTables sender = new SendToFusionTables(this, lastAuth,
|
||||
sendTrackId, this /*progressIndicator*/, onCompletion);
|
||||
|
||||
new Thread(sender, "SendToFusionTables").start();
|
||||
|
||||
Intent intent = new Intent(this, SendFusionTablesActivity.class)
|
||||
.putExtra(SendFusionTablesActivity.ACCOUNT, account)
|
||||
.putExtra(SendFusionTablesActivity.TRACK_ID, sendTrackId);
|
||||
startActivityForResult(intent, Constants.SEND_FUSION_TABLES);
|
||||
return SendState.NOT_READY;
|
||||
}
|
||||
|
||||
private SendState onSendToFusionTablesDone() {
|
||||
if (sendToDocs) {
|
||||
return SendState.AUTHENTICATE_DOCS;
|
||||
return SendState.SEND_TO_DOCS;
|
||||
} else {
|
||||
return SendState.SHOW_RESULTS;
|
||||
}
|
||||
}
|
||||
|
||||
private SendState authenticateToGoogleDocs() {
|
||||
setProgressValue(0);
|
||||
setProgressMessage(getAuthenticatingProgressMessage(SendType.DOCS));
|
||||
authenticate(Constants.AUTHENTICATE_TO_DOCLIST, SendToDocs.GDATA_SERVICE_NAME_DOCLIST);
|
||||
// AUTHENTICATE_TO_DOCLIST callback calls authenticateToGoogleTrix
|
||||
return SendState.NOT_READY;
|
||||
}
|
||||
|
||||
private SendState authenticateToGoogleTrix() {
|
||||
setProgressValue(30);
|
||||
setProgressMessage(getAuthenticatingProgressMessage(SendType.DOCS));
|
||||
authenticate(Constants.AUTHENTICATE_TO_TRIX, SendToDocs.GDATA_SERVICE_NAME_TRIX);
|
||||
// AUTHENTICATE_TO_TRIX callback calls sendToGoogleDocs
|
||||
return SendState.NOT_READY;
|
||||
}
|
||||
|
||||
private SendState sendToGoogleDocs() {
|
||||
Log.d(TAG, "Sending to Docs....");
|
||||
tracker.trackPageView("/send/docs");
|
||||
|
||||
setProgressValue(50);
|
||||
String format = getString(R.string.send_google_progress_sending);
|
||||
String serviceName = getString(SendType.DOCS.getServiceName());
|
||||
setProgressMessage(String.format(format, serviceName));
|
||||
final SendToDocs sender = new SendToDocs(this,
|
||||
authMap.get(SendToDocs.GDATA_SERVICE_NAME_TRIX),
|
||||
authMap.get(SendToDocs.GDATA_SERVICE_NAME_DOCLIST),
|
||||
this);
|
||||
Runnable onCompletion = new Runnable() {
|
||||
public void run() {
|
||||
setProgressValue(100);
|
||||
sendToDocsSuccess = sender.wasSuccess();
|
||||
executeStateMachine(SendState.SEND_TO_DOCS_DONE);
|
||||
}
|
||||
};
|
||||
sender.setOnCompletion(onCompletion);
|
||||
sender.sendToDocs(sendTrackId);
|
||||
|
||||
Intent intent = new Intent(this, SendDocsActivity.class)
|
||||
.putExtra(SendFusionTablesActivity.ACCOUNT, account)
|
||||
.putExtra(SendFusionTablesActivity.TRACK_ID, sendTrackId);
|
||||
startActivityForResult(intent, Constants.SEND_DOCS);
|
||||
return SendState.NOT_READY;
|
||||
}
|
||||
|
||||
@@ -571,208 +395,38 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
|
||||
private SendState onSendToGoogleDone() {
|
||||
tracker.dispatch();
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d(TAG, "Sending to Google done.");
|
||||
dismissDialog(PROGRESS_DIALOG);
|
||||
dismissDialog(SEND_DIALOG);
|
||||
|
||||
// Ensure a new done dialog is created each time.
|
||||
// This is required because the send results must be available at the
|
||||
// time the dialog is created.
|
||||
removeDialog(DONE_DIALOG);
|
||||
showDialog(DONE_DIALOG);
|
||||
}
|
||||
});
|
||||
|
||||
return SendState.NOT_READY;
|
||||
String mapsUrl = sendToMaps && sendToMapsSuccess
|
||||
? MapsFacade.buildMapUrl(sendToMapsMapId)
|
||||
: null;
|
||||
String fusionTablesUrl = sendToFusionTables && sendToFusionTablesSuccess
|
||||
? SendFusionTablesUtils.getMapUrl(providerUtils.getTrack(sendTrackId))
|
||||
: null;
|
||||
Intent intent = new Intent(this, UploadResultActivity.class)
|
||||
.putExtra(UploadResultActivity.HAS_MAPS_RESULT, sendToMaps)
|
||||
.putExtra(UploadResultActivity.HAS_FUSION_TABLES_RESULT, sendToFusionTables)
|
||||
.putExtra(UploadResultActivity.HAS_DOCS_RESULT, sendToDocs)
|
||||
.putExtra(UploadResultActivity.MAPS_SUCCESS, sendToMapsSuccess)
|
||||
.putExtra(UploadResultActivity.FUSION_TABLES_SUCCESS, sendToFusionTablesSuccess)
|
||||
.putExtra(UploadResultActivity.DOCS_SUCCESS, sendToDocsSuccess)
|
||||
.putExtra(UploadResultActivity.SHARE_REQUEST, shareRequest)
|
||||
.putExtra(UploadResultActivity.MAPS_URL, mapsUrl)
|
||||
.putExtra(UploadResultActivity.FUSION_TABLES_URL, fusionTablesUrl);
|
||||
startActivity(intent);
|
||||
return SendState.FINISH;
|
||||
}
|
||||
|
||||
private Dialog createDoneDialog() {
|
||||
Log.d(TAG, "Creating done dialog");
|
||||
// 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.
|
||||
|
||||
List<SendResult> results = makeSendToGoogleResults();
|
||||
final boolean canShare = sendToFusionTablesTableId != null || sendToMyMapsMapId != null;
|
||||
|
||||
final OnClickListener finishListener = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
executeStateMachine(SendState.FINISH);
|
||||
}
|
||||
};
|
||||
|
||||
DialogInterface.OnClickListener doShareListener = null;
|
||||
if (canShare) {
|
||||
doShareListener = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
|
||||
executeStateMachine(SendState.SHARE_LINK);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
DialogInterface.OnClickListener onOkListener = (canShare && shareRequested)
|
||||
? doShareListener : finishListener;
|
||||
DialogInterface.OnClickListener onShareListener = (canShare && !shareRequested)
|
||||
? doShareListener : null;
|
||||
|
||||
return ResultDialogFactory.makeDialog(this, results, onOkListener, onShareListener, finishOnCancelListener);
|
||||
}
|
||||
|
||||
|
||||
private SendState onAllDone() {
|
||||
Log.d(TAG, "All sending done.");
|
||||
removeDialog(PROGRESS_DIALOG);
|
||||
removeDialog(SEND_DIALOG);
|
||||
removeDialog(DONE_DIALOG);
|
||||
progressDialog = null;
|
||||
finish();
|
||||
return SendState.DONE;
|
||||
}
|
||||
|
||||
private SendState shareLink() {
|
||||
String url = null;
|
||||
if (sendToMyMaps && sendToMyMapsSuccess) {
|
||||
// Prefer a link to My Maps
|
||||
url = MapsFacade.buildMapUrl(sendToMyMapsMapId);
|
||||
} else if (sendToFusionTables && sendToFusionTablesSuccess) {
|
||||
// Otherwise try using the link to fusion tables
|
||||
url = getFusionTablesUrl(sendTrackId);
|
||||
}
|
||||
|
||||
if (url != null) {
|
||||
shareLinkToMap(url);
|
||||
} else {
|
||||
Log.w(TAG, "Failed to share link");
|
||||
}
|
||||
|
||||
return SendState.FINISH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shares a link to a My Map or Fusion Table via external app (email, gmail, ...)
|
||||
* A chooser with apps that support text/plain will be shown to the user.
|
||||
*/
|
||||
private void shareLinkToMap(String url) {
|
||||
boolean shareUrlOnly = sharedPreferences.getBoolean(
|
||||
getString(R.string.share_url_only_key), false);
|
||||
String msg = shareUrlOnly ? url : String.format(
|
||||
getResources().getText(R.string.share_track_url_body_format).toString(), url);
|
||||
|
||||
Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
||||
shareIntent.setType("text/plain");
|
||||
shareIntent.putExtra(Intent.EXTRA_SUBJECT,
|
||||
getResources().getText(R.string.share_track_subject).toString());
|
||||
shareIntent.putExtra(Intent.EXTRA_TEXT, msg);
|
||||
startActivity(Intent.createChooser(shareIntent,
|
||||
getResources().getText(R.string.share_track_picker_title).toString()));
|
||||
}
|
||||
|
||||
protected String getFusionTablesUrl(long trackId) {
|
||||
Track track = providerUtils.getTrack(trackId);
|
||||
return SendToFusionTables.getMapVisualizationUrl(track);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list of {@link SendResult} instances based on the set of
|
||||
* services selected in {@link SendDialog} and the results as known to
|
||||
* this class.
|
||||
*/
|
||||
private List<SendResult> makeSendToGoogleResults() {
|
||||
List<SendResult> results = new ArrayList<SendResult>();
|
||||
if (sendToMyMaps) {
|
||||
results.add(new SendResult(SendType.MYMAPS, sendToMyMapsSuccess));
|
||||
}
|
||||
if (sendToFusionTables) {
|
||||
results.add(new SendResult(SendType.FUSION_TABLES,
|
||||
sendToFusionTablesSuccess));
|
||||
}
|
||||
if (sendToDocs) {
|
||||
results.add(new SendResult(SendType.DOCS, sendToDocsSuccess));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Initializes the authentication manager which obtains an authentication
|
||||
* token, prompting the user for a login and password if needed.
|
||||
*/
|
||||
private void authenticate(final int requestCode, final String service) {
|
||||
lastAuth = authMap.get(service);
|
||||
if (lastAuth == null) {
|
||||
Log.i(TAG, "Creating a new authentication for service: " + service);
|
||||
lastAuth = AuthManagerFactory.getAuthManager(this,
|
||||
Constants.GET_LOGIN,
|
||||
null,
|
||||
true,
|
||||
service);
|
||||
authMap.put(service, lastAuth);
|
||||
}
|
||||
|
||||
Log.d(TAG, "Logging in to " + service + "...");
|
||||
if (AuthManagerFactory.useModernAuthManager()) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
chooseAccount(requestCode, service);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
doLogin(requestCode, service, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void chooseAccount(final int requestCode, final String service) {
|
||||
if (accountChooser == null) {
|
||||
accountChooser = new AccountChooser();
|
||||
|
||||
// Restore state if necessary.
|
||||
if (lastAccountName != null && lastAccountType != null) {
|
||||
accountChooser.setChosenAccount(lastAccountName, lastAccountType);
|
||||
}
|
||||
}
|
||||
|
||||
accountChooser.chooseAccount(SendActivity.this,
|
||||
new AccountChooser.AccountHandler() {
|
||||
@Override
|
||||
public void onAccountSelected(Account account) {
|
||||
if (account == null) {
|
||||
dismissDialog(PROGRESS_DIALOG);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
lastAccountName = account.name;
|
||||
lastAccountType = account.type;
|
||||
doLogin(requestCode, service, account);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void doLogin(final int requestCode, final String service, final Object account) {
|
||||
lastAuth.doLogin(new AuthCallback() {
|
||||
AuthManager authManager = getAuthManager(service);
|
||||
authManager.doLogin(new AuthCallback() {
|
||||
@Override
|
||||
public void onAuthResult(boolean success) {
|
||||
Log.i(TAG, "Login success for " + service + ": " + success);
|
||||
@@ -791,27 +445,50 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
final Intent results) {
|
||||
SendState nextState = null;
|
||||
switch (requestCode) {
|
||||
case Constants.GET_LOGIN: {
|
||||
if (resultCode == RESULT_CANCELED || lastAuth == null) {
|
||||
case Constants.CHOOSE_MAP: {
|
||||
if (resultCode == RESULT_CANCELED) {
|
||||
nextState = SendState.FINISH;
|
||||
break;
|
||||
}
|
||||
|
||||
// This will invoke onAuthResult appropriately.
|
||||
lastAuth.authResult(resultCode, results);
|
||||
sendToMapsMapId = results.getStringExtra(ChooseMapActivity.MAP_ID);
|
||||
if (sendToMapsMapId == null) {
|
||||
nextState = SendState.FINISH;
|
||||
break;
|
||||
}
|
||||
nextState = SendState.SEND_TO_MAPS;
|
||||
break;
|
||||
}
|
||||
case Constants.GET_MAP: {
|
||||
// User picked a map to upload to
|
||||
Log.d(TAG, "Get map result: " + resultCode);
|
||||
if (resultCode == RESULT_OK) {
|
||||
if (results.hasExtra("mapid")) {
|
||||
sendToMyMapsMapId = results.getStringExtra("mapid");
|
||||
}
|
||||
nextState = SendState.SEND_TO_MAPS;
|
||||
} else {
|
||||
case Constants.CHOOSE_ACCOUNT: {
|
||||
if (resultCode == RESULT_CANCELED) {
|
||||
nextState = SendState.FINISH;
|
||||
break;
|
||||
}
|
||||
account = results.getParcelableExtra(AccountChooserActivity.ACCOUNT);
|
||||
if (account == null) {
|
||||
nextState = SendState.FINISH;
|
||||
break;
|
||||
}
|
||||
nextState = SendState.START;
|
||||
break;
|
||||
}
|
||||
case Constants.SEND_DOCS: {
|
||||
if (resultCode == RESULT_CANCELED) {
|
||||
nextState = SendState.FINISH;
|
||||
break;
|
||||
}
|
||||
sendToDocsSuccess = results.getBooleanExtra(SendDocsActivity.SUCCESS, false);
|
||||
nextState = SendState.SEND_TO_DOCS_DONE;
|
||||
break;
|
||||
}
|
||||
case Constants.SEND_FUSION_TABLES: {
|
||||
if (resultCode == RESULT_CANCELED) {
|
||||
nextState = SendState.FINISH;
|
||||
break;
|
||||
}
|
||||
sendToFusionTablesSuccess = results.getBooleanExtra(
|
||||
SendFusionTablesActivity.SUCCESS, false);
|
||||
sendToFusionTablesTableId = results.getStringExtra(SendFusionTablesActivity.TABLE_ID);
|
||||
nextState = SendState.SEND_TO_FUSION_TABLES_DONE;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -828,23 +505,10 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
private void onLoginSuccess(int requestCode) {
|
||||
SendState nextState;
|
||||
switch (requestCode) {
|
||||
case Constants.AUTHENTICATE_TO_MY_MAPS:
|
||||
// Authenticated with Google My Maps
|
||||
case Constants.AUTHENTICATE_TO_MAPS:
|
||||
// Authenticated with Google Maps
|
||||
nextState = SendState.PICK_MAP;
|
||||
break;
|
||||
case Constants.AUTHENTICATE_TO_FUSION_TABLES:
|
||||
// Authenticated with Google Fusion Tables
|
||||
nextState = SendState.SEND_TO_FUSION_TABLES;
|
||||
break;
|
||||
case Constants.AUTHENTICATE_TO_DOCLIST:
|
||||
// Authenticated with Google Docs
|
||||
nextState = SendState.AUTHENTICATE_TRIX;
|
||||
break;
|
||||
case Constants.AUTHENTICATE_TO_TRIX:
|
||||
// Authenticated with Trix
|
||||
nextState = SendState.SEND_TO_DOCS;
|
||||
break;
|
||||
|
||||
default: {
|
||||
Log.e(TAG, "Unrequested login code: " + requestCode);
|
||||
return;
|
||||
@@ -854,15 +518,30 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
executeStateMachine(nextState);
|
||||
}
|
||||
|
||||
private AuthManager getAuthManager(String service) {
|
||||
AuthManager authManager = authMap.get(service);
|
||||
if (authManager == null) {
|
||||
authManager = AuthManagerFactory.getAuthManager(this,
|
||||
Constants.GET_LOGIN,
|
||||
null,
|
||||
true,
|
||||
service);
|
||||
authMap.put(service, authManager);
|
||||
}
|
||||
return authManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets status information for sending to MyMaps/Docs.
|
||||
* Resets status information for sending to Maps/Fusion Tables/Docs.
|
||||
*/
|
||||
private void resetState() {
|
||||
currentState = SendState.SEND_OPTIONS;
|
||||
sendToMyMapsMapId = null;
|
||||
sendToMyMapsSuccess = true;
|
||||
sendToFusionTablesSuccess = true;
|
||||
sendToDocsSuccess = true;
|
||||
account = null;
|
||||
authMap.clear();
|
||||
currentState = SendState.CHOOSE_ACCOUNT;
|
||||
sendToMapsSuccess = false;
|
||||
sendToFusionTablesSuccess = false;
|
||||
sendToDocsSuccess = false;
|
||||
sendToMapsMapId = null;
|
||||
sendToFusionTablesTableId = null;
|
||||
}
|
||||
|
||||
@@ -904,14 +583,4 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
public void clearProgressMessage() {
|
||||
progressDialog.setMessage("");
|
||||
}
|
||||
|
||||
public static void sendToGoogle(Context ctx, long trackId, boolean shareLink) {
|
||||
Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId);
|
||||
|
||||
Intent intent = new Intent(ctx, SendActivity.class);
|
||||
intent.setAction(Intent.ACTION_SEND);
|
||||
intent.setDataAndType(uri, TracksColumns.CONTENT_ITEMTYPE);
|
||||
intent.putExtra(SendActivity.EXTRA_SHARE_LINK, shareLink);
|
||||
ctx.startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
/*
|
||||
* Copyright 2008 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io.sendtogoogle;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.TableRow;
|
||||
|
||||
/**
|
||||
* A dialog where the user can choose where to send the tracks to, i.e.
|
||||
* to Google My Maps, Google Docs, etc.
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
public class SendDialog extends Dialog {
|
||||
|
||||
private RadioButton newMapRadioButton;
|
||||
private RadioButton existingMapRadioButton;
|
||||
private CheckBox myMapsCheckBox;
|
||||
private CheckBox fusionTablesCheckBox;
|
||||
private CheckBox docsCheckBox;
|
||||
private TableRow docsTableRow;
|
||||
private boolean docsEnabled;
|
||||
|
||||
private OnClickListener clickListener;
|
||||
|
||||
public SendDialog(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
setContentView(R.layout.mytracks_send_to_google);
|
||||
|
||||
final Button sendButton = (Button) findViewById(R.id.send_google_send_now);
|
||||
|
||||
final RadioGroup myMapsGroup = (RadioGroup) findViewById(R.id.send_google_my_maps_group);
|
||||
newMapRadioButton = (RadioButton) findViewById(R.id.send_google_new_map);
|
||||
existingMapRadioButton = (RadioButton) findViewById(R.id.send_google_existing_map);
|
||||
|
||||
myMapsCheckBox = (CheckBox) findViewById(R.id.send_google_my_maps);
|
||||
fusionTablesCheckBox = (CheckBox) findViewById(R.id.send_google_fusion_tables);
|
||||
docsCheckBox = (CheckBox) findViewById(R.id.send_google_docs);
|
||||
|
||||
docsTableRow = (TableRow) findViewById(R.id.send_google_docs_table_row);
|
||||
|
||||
Button cancel = (Button) findViewById(R.id.send_google_cancel);
|
||||
cancel.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
if (clickListener != null) {
|
||||
clickListener.onClick(SendDialog.this, BUTTON_NEGATIVE);
|
||||
}
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
Button send = (Button) findViewById(R.id.send_google_send_now);
|
||||
send.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
if (clickListener != null) {
|
||||
clickListener.onClick(SendDialog.this, BUTTON_POSITIVE);
|
||||
}
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
OnCheckedChangeListener checkBoxListener = new OnCheckedChangeListener() {
|
||||
public void onCheckedChanged(CompoundButton button, boolean checked) {
|
||||
sendButton.setEnabled(getSendToMyMaps() || getSendToFusionTables() || getSendToDocs());
|
||||
myMapsGroup.setVisibility(getSendToMyMaps() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
};
|
||||
|
||||
myMapsCheckBox.setOnCheckedChangeListener(checkBoxListener);
|
||||
fusionTablesCheckBox.setOnCheckedChangeListener(checkBoxListener);
|
||||
docsCheckBox.setOnCheckedChangeListener(checkBoxListener);
|
||||
|
||||
SharedPreferences prefs = getContext().getSharedPreferences(
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
if (prefs != null) {
|
||||
boolean pickExistingMap = prefs.getBoolean(
|
||||
getContext().getString(R.string.pick_existing_map_key), false);
|
||||
|
||||
newMapRadioButton.setChecked(!pickExistingMap);
|
||||
existingMapRadioButton.setChecked(pickExistingMap);
|
||||
|
||||
myMapsCheckBox.setChecked(
|
||||
prefs.getBoolean(getContext().getString(R.string.send_to_my_maps_key), true));
|
||||
fusionTablesCheckBox.setChecked(
|
||||
prefs.getBoolean(getContext().getString(R.string.send_to_fusion_tables_key), true));
|
||||
docsCheckBox.setChecked(
|
||||
prefs.getBoolean(getContext().getString(R.string.send_to_docs_key), true));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
SharedPreferences prefs = getContext().getSharedPreferences(
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
if (prefs != null) {
|
||||
Editor editor = prefs.edit();
|
||||
if (editor != null) {
|
||||
editor.putBoolean(
|
||||
getContext().getString(R.string.pick_existing_map_key), !getCreateNewMap());
|
||||
editor.putBoolean(getContext().getString(R.string.send_to_my_maps_key), getSendToMyMaps());
|
||||
editor.putBoolean(
|
||||
getContext().getString(R.string.send_to_fusion_tables_key), getSendToFusionTables());
|
||||
if (docsEnabled) {
|
||||
editor.putBoolean(getContext().getString(R.string.send_to_docs_key), getSendToDocs());
|
||||
}
|
||||
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To enable the Google Docs option.
|
||||
*
|
||||
* @param docsEnabled true to enable the Google Docs option
|
||||
*/
|
||||
public void setDocsEnabled(boolean docsEnabled) {
|
||||
this.docsEnabled = docsEnabled;
|
||||
docsTableRow.setVisibility(docsEnabled ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
public void setOnClickListener(OnClickListener clickListener) {
|
||||
this.clickListener = clickListener;
|
||||
}
|
||||
|
||||
public boolean getCreateNewMap() {
|
||||
return newMapRadioButton.isChecked();
|
||||
}
|
||||
|
||||
public boolean getSendToMyMaps() {
|
||||
return myMapsCheckBox.isChecked();
|
||||
}
|
||||
|
||||
public boolean getSendToFusionTables() {
|
||||
return fusionTablesCheckBox.isChecked();
|
||||
}
|
||||
|
||||
public boolean getSendToDocs() {
|
||||
return docsEnabled && docsCheckBox.isChecked();
|
||||
}
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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.maps.mytracks.R;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* An activity to send a track to Google Fusion Tables.
|
||||
* <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 jshih@google.com (Jimmy Shih)
|
||||
*/
|
||||
public class SendFusionTablesActivity extends Activity {
|
||||
|
||||
// parameters in the input intent
|
||||
public static final String ACCOUNT = "account";
|
||||
public static final String TRACK_ID = "trackId";
|
||||
|
||||
// parameters in the output intent
|
||||
public static final String SUCCESS = "success";
|
||||
public static final String TABLE_ID = "tableId";
|
||||
|
||||
private static final int PROGRESS_DIALOG = 1;
|
||||
|
||||
private SendFusionTablesAsyncTask asyncTask;
|
||||
private ProgressDialog progressDialog;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Object retained = getLastNonConfigurationInstance();
|
||||
if (retained instanceof SendFusionTablesAsyncTask) {
|
||||
asyncTask = (SendFusionTablesAsyncTask) retained;
|
||||
asyncTask.setActivity(this);
|
||||
} else {
|
||||
Intent intent = getIntent();
|
||||
Account account = intent.getParcelableExtra(ACCOUNT);
|
||||
if (account == null) {
|
||||
setResult(
|
||||
RESULT_OK, new Intent().putExtra(SUCCESS, false).putExtra(TABLE_ID, (String) null));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
long trackId = intent.getLongExtra(TRACK_ID, -1L);
|
||||
if (trackId == -1L) {
|
||||
setResult(
|
||||
RESULT_OK, new Intent().putExtra(SUCCESS, false).putExtra(TABLE_ID, (String) null));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
asyncTask = new SendFusionTablesAsyncTask(this, account, trackId);
|
||||
asyncTask.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onRetainNonConfigurationInstance() {
|
||||
asyncTask.setActivity(null);
|
||||
return asyncTask;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case PROGRESS_DIALOG:
|
||||
progressDialog = new ProgressDialog(this);
|
||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
|
||||
progressDialog.setTitle(getString(
|
||||
R.string.send_google_progress_title, getString(R.string.send_google_fusion_tables)));
|
||||
progressDialog.setMax(100);
|
||||
progressDialog.setProgress(0);
|
||||
progressDialog.setCancelable(true);
|
||||
progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
asyncTask.cancel(true);
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
return progressDialog;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes when the associated AsyncTask completes.
|
||||
*
|
||||
* @param success true if success
|
||||
* @param tableId tableId if available
|
||||
*/
|
||||
public void onAsyncTaskCompleted(boolean success, String tableId) {
|
||||
setResult(RESULT_OK, new Intent().putExtra(SUCCESS, success).putExtra(TABLE_ID, tableId));
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the progress dialog.
|
||||
*/
|
||||
public void showProgressDialog() {
|
||||
showDialog(PROGRESS_DIALOG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the progress dialog value.
|
||||
*/
|
||||
public void setProgressDialogValue(int value) {
|
||||
if (progressDialog != null) {
|
||||
progressDialog.setProgress(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+624
@@ -0,0 +1,624 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
package com.google.android.apps.mytracks.io.sendtogoogle;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
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.stats.DoubleBuffer;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.apps.mytracks.util.SystemUtils;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.api.client.googleapis.GoogleHeaders;
|
||||
import com.google.api.client.googleapis.MethodOverride;
|
||||
import com.google.api.client.http.GenericUrl;
|
||||
import com.google.api.client.http.HttpRequest;
|
||||
import com.google.api.client.http.HttpRequestFactory;
|
||||
import com.google.api.client.http.HttpResponse;
|
||||
import com.google.api.client.http.HttpTransport;
|
||||
import com.google.api.client.http.InputStreamContent;
|
||||
import com.google.api.client.util.Strings;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.AuthenticatorException;
|
||||
import android.accounts.OperationCanceledException;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.location.Location;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* AsyncTask to send a track to Google Fusion Tables.
|
||||
*
|
||||
* @author jshih@google.com (Jimmy Shih)
|
||||
*/
|
||||
public class SendFusionTablesAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
|
||||
private static final String APP_NAME_PREFIX = "Google-MyTracks-";
|
||||
private static final String SQL_KEY = "sql=";
|
||||
private static final String CONTENT_TYPE = "application/x-www-form-urlencoded";
|
||||
private static final String SERVICE_ID = "fusiontables";
|
||||
private static final String FUSION_TABLES_BASE_URL =
|
||||
"https://www.google.com/fusiontables/api/query";
|
||||
private static final int MAX_POINTS_PER_UPLOAD = 2048;
|
||||
private static final String GDATA_VERSION = "2";
|
||||
|
||||
private static final int PROGRESS_CREATE_TABLE = 0;
|
||||
private static final int PROGRESS_UNLIST_TABLE = 5;
|
||||
private static final int PROGRESS_UPLOAD_DATA_MIN = 10;
|
||||
private static final int PROGRESS_UPLOAD_DATA_MAX = 90;
|
||||
private static final int PROGRESS_UPLOAD_WAYPOINTS = 95;
|
||||
private static final int PROGRESS_COMPLETE = 100;
|
||||
|
||||
// See http://support.google.com/fusiontables/bin/answer.py?hl=en&answer=185991
|
||||
private static final String MARKER_TYPE_START = "large_green";
|
||||
private static final String MARKER_TYPE_END = "large_red";
|
||||
private static final String MARKER_TYPE_WAYPOINT = "large_yellow";
|
||||
|
||||
private static final String TAG = SendFusionTablesAsyncTask.class.getSimpleName();
|
||||
|
||||
private SendFusionTablesActivity activity;
|
||||
|
||||
private final Context context;
|
||||
private final Account account;
|
||||
private final long trackId;
|
||||
private final MyTracksProviderUtils myTracksProviderUtils;
|
||||
private final HttpRequestFactory httpRequestFactory;
|
||||
|
||||
/**
|
||||
* True if can retry sending to Google Fusion Tables.
|
||||
*/
|
||||
private boolean canRetry;
|
||||
|
||||
/**
|
||||
* True if the AsyncTask has completed.
|
||||
*/
|
||||
private boolean completed;
|
||||
|
||||
/**
|
||||
* True if the result is success.
|
||||
*/
|
||||
private boolean success;
|
||||
|
||||
// The following variables are for per upload states
|
||||
private String authToken;
|
||||
private String tableId;
|
||||
int currentSegment;
|
||||
|
||||
public SendFusionTablesAsyncTask(
|
||||
SendFusionTablesActivity activity, Account account, long trackId) {
|
||||
this.activity = activity;
|
||||
this.account = account;
|
||||
this.trackId = trackId;
|
||||
|
||||
context = activity.getApplicationContext();
|
||||
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
|
||||
HttpTransport transport = ApiFeatures.getInstance().getApiAdapter().getHttpTransport();
|
||||
httpRequestFactory = transport.createRequestFactory(new MethodOverride());
|
||||
|
||||
canRetry = true;
|
||||
completed = false;
|
||||
success = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the activity associated with this AyncTask.
|
||||
*
|
||||
* @param activity the activity.
|
||||
*/
|
||||
public void setActivity(SendFusionTablesActivity activity) {
|
||||
this.activity = activity;
|
||||
if (completed && activity != null) {
|
||||
activity.onAsyncTaskCompleted(success, tableId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
activity.showProgressDialog();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
return doUpload();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... values) {
|
||||
if (activity != null) {
|
||||
activity.setProgressDialogValue(values[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
success = result;
|
||||
completed = true;
|
||||
if (success) {
|
||||
Track track = myTracksProviderUtils.getTrack(trackId);
|
||||
if (track != null) {
|
||||
track.setTableId(tableId);
|
||||
myTracksProviderUtils.updateTrack(track);
|
||||
} else {
|
||||
Log.d(TAG, "No track");
|
||||
}
|
||||
}
|
||||
if (activity != null) {
|
||||
activity.onAsyncTaskCompleted(success, tableId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a track to Google Fusion Tables.
|
||||
*
|
||||
* @return true if success.
|
||||
*/
|
||||
private boolean doUpload() {
|
||||
// Reset the per upload states
|
||||
authToken = null;
|
||||
tableId = null;
|
||||
currentSegment = 1;
|
||||
|
||||
try {
|
||||
authToken = AccountManager.get(context).blockingGetAuthToken(account, SERVICE_ID, false);
|
||||
} catch (OperationCanceledException e) {
|
||||
Log.d(TAG, "Unable to get auth token", e);
|
||||
return retryUpload();
|
||||
} catch (AuthenticatorException e) {
|
||||
Log.d(TAG, "Unable to get auth token", e);
|
||||
return retryUpload();
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to get auth token", e);
|
||||
return retryUpload();
|
||||
}
|
||||
|
||||
Track track = myTracksProviderUtils.getTrack(trackId);
|
||||
if (track == null) {
|
||||
Log.d(TAG, "Track is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a new table
|
||||
publishProgress(PROGRESS_CREATE_TABLE);
|
||||
if (!createNewTable(track)) {
|
||||
// Retry upload in case the auth token is invalid
|
||||
return retryUpload();
|
||||
}
|
||||
|
||||
// Unlist table
|
||||
publishProgress(PROGRESS_UNLIST_TABLE);
|
||||
if (!unlistTable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Upload all the track points plus the start and end markers
|
||||
publishProgress(PROGRESS_UPLOAD_DATA_MIN);
|
||||
if (!uploadAllTrackPoints(track)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Upload all the waypoints
|
||||
publishProgress(PROGRESS_UPLOAD_WAYPOINTS);
|
||||
if (!uploadWaypoints()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
publishProgress(PROGRESS_COMPLETE);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retries upload. Invalidates the authToken. If can retry, invokes
|
||||
* {@link SendFusionTablesAsyncTask#doUpload()}. Returns false if cannot
|
||||
* retry.
|
||||
*/
|
||||
private boolean retryUpload() {
|
||||
if (isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AccountManager.get(context).invalidateAuthToken(SERVICE_ID, authToken);
|
||||
if (canRetry) {
|
||||
canRetry = false;
|
||||
return doUpload();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new table.
|
||||
*
|
||||
* @param track the track
|
||||
* @return true if success.
|
||||
*/
|
||||
private boolean createNewTable(Track track) {
|
||||
String query = "CREATE TABLE '" + SendFusionTablesUtils.escapeSqlString(track.getName())
|
||||
+ "' (name:STRING,description:STRING,geometry:LOCATION,marker:STRING)";
|
||||
return sendQuery(query, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlists a table.
|
||||
*
|
||||
* @return true if success.
|
||||
*/
|
||||
private boolean unlistTable() {
|
||||
String query = "UPDATE TABLE " + tableId + " SET VISIBILITY = UNLISTED";
|
||||
return sendQuery(query, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads all the points in a track.
|
||||
*
|
||||
* @param track the track
|
||||
* @return true if success.
|
||||
*/
|
||||
private boolean uploadAllTrackPoints(Track track) {
|
||||
Cursor locationsCursor = null;
|
||||
try {
|
||||
SharedPreferences prefs = context.getSharedPreferences(
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
boolean metricUnits = prefs.getBoolean(context.getString(R.string.metric_units_key), true);
|
||||
|
||||
locationsCursor = myTracksProviderUtils.getLocationsCursor(trackId, 0, -1, false);
|
||||
if (locationsCursor == null) {
|
||||
Log.d(TAG, "Location cursor is null");
|
||||
return false;
|
||||
}
|
||||
int locationsCount = locationsCursor.getCount();
|
||||
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
|
||||
Location lastLocation = null;
|
||||
|
||||
// Limit the number of elevation readings. Ideally we would want around
|
||||
// 250.
|
||||
int elevationSamplingFrequency = Math.max(1, (int) (locationsCount / 250.0));
|
||||
double totalDistance = 0;
|
||||
DoubleBuffer elevationBuffer = new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR);
|
||||
Vector<Double> distances = new Vector<Double>();
|
||||
Vector<Double> elevations = new Vector<Double>();
|
||||
|
||||
for (int i = 0; i < locationsCount; i++) {
|
||||
locationsCursor.moveToPosition(i);
|
||||
|
||||
Location location = myTracksProviderUtils.createLocation(locationsCursor);
|
||||
locations.add(location);
|
||||
|
||||
int readCount = i + 1;
|
||||
|
||||
if (readCount == 1) {
|
||||
// Create a start marker
|
||||
String name = context.getString(R.string.marker_label_start, track.getName());
|
||||
if (!createNewPoint(name, "", location, MARKER_TYPE_START)) {
|
||||
Log.d(TAG, "Unable to create the start marker");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Add to the distances and elevations vectors
|
||||
if (LocationUtils.isValidLocation(location)) {
|
||||
// All points go into the smoothing buffer
|
||||
elevationBuffer.setNext(metricUnits ? location.getAltitude()
|
||||
: location.getAltitude() * UnitConversions.M_TO_FT);
|
||||
if (lastLocation != null) {
|
||||
totalDistance += (double) lastLocation.distanceTo(location);
|
||||
}
|
||||
if (readCount % elevationSamplingFrequency == 0) {
|
||||
distances.add(totalDistance);
|
||||
elevations.add(elevationBuffer.getAverage());
|
||||
}
|
||||
}
|
||||
|
||||
// Upload periodically
|
||||
if (readCount % MAX_POINTS_PER_UPLOAD == MAX_POINTS_PER_UPLOAD) {
|
||||
if (!prepareAndUploadPoints(track, locations, false)) {
|
||||
Log.d(TAG, "Unable to upload points");
|
||||
return false;
|
||||
}
|
||||
updateProgress(readCount, locationsCount);
|
||||
locations.clear();
|
||||
}
|
||||
lastLocation = location;
|
||||
}
|
||||
|
||||
// Do a final upload with the remaining locations
|
||||
if (!prepareAndUploadPoints(track, locations, true)) {
|
||||
Log.d(TAG, "Unable to upload points");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create an end marker
|
||||
if (lastLocation != null) {
|
||||
StringUtils stringUtils = new StringUtils(context);
|
||||
track.setDescription("<p>" + track.getDescription() + "</p><p>"
|
||||
+ stringUtils.generateTrackDescription(track, distances, elevations) + "</p>");
|
||||
String name = context.getString(R.string.marker_label_end, track.getName());
|
||||
if (!createNewPoint(name, track.getDescription(), lastLocation, MARKER_TYPE_END)) {
|
||||
Log.d(TAG, "Unable to create the end marker");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
if (locationsCursor != null) {
|
||||
locationsCursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares and uploads a list of locations from a track.
|
||||
*
|
||||
* @param track the track
|
||||
* @param locations the locations from the track
|
||||
* @param lastBatch true if it is the last batch of locations
|
||||
*/
|
||||
private boolean prepareAndUploadPoints(Track track, List<Location> locations, boolean lastBatch) {
|
||||
// Prepare locations
|
||||
ArrayList<Track> splitTracks = prepareLocations(track, locations);
|
||||
|
||||
// Upload segments
|
||||
boolean onlyOneSegment = lastBatch && currentSegment == 1 && splitTracks.size() == 1;
|
||||
for (Track splitTrack : splitTracks) {
|
||||
if (!onlyOneSegment) {
|
||||
splitTrack.setName(context.getString(
|
||||
R.string.send_google_track_part_label, splitTrack.getName(), currentSegment));
|
||||
}
|
||||
if (!createNewLineString(splitTrack)) {
|
||||
Log.d(TAG, "Upload points failed");
|
||||
return false;
|
||||
}
|
||||
currentSegment++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a list of locations to send to 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.
|
||||
*/
|
||||
private ArrayList<Track> prepareLocations(Track track, List<Location> locations) {
|
||||
ArrayList<Track> splitTracks = new ArrayList<Track>();
|
||||
|
||||
// Create a new segment
|
||||
Track segment = new Track();
|
||||
segment.setId(track.getId());
|
||||
segment.setName(track.getName());
|
||||
segment.setDescription("");
|
||||
segment.setCategory(track.getCategory());
|
||||
|
||||
TripStatistics segmentStats = segment.getStatistics();
|
||||
TripStatistics trackStats = track.getStatistics();
|
||||
segmentStats.setStartTime(trackStats.getStartTime());
|
||||
segmentStats.setStopTime(trackStats.getStopTime());
|
||||
boolean startNewTrackSegment = false;
|
||||
for (Location loc : locations) {
|
||||
// Latitude is greater than 90 if the location is invalid. Do not add to the
|
||||
// segment.
|
||||
if (loc.getLatitude() > 90) {
|
||||
startNewTrackSegment = true;
|
||||
}
|
||||
|
||||
if (startNewTrackSegment) {
|
||||
// Close the last segment
|
||||
prepareTrackSegment(segment, splitTracks);
|
||||
|
||||
startNewTrackSegment = false;
|
||||
segment = new Track();
|
||||
segment.setId(track.getId());
|
||||
segment.setName(track.getName());
|
||||
segment.setDescription("");
|
||||
segment.setCategory(track.getCategory());
|
||||
segmentStats = segment.getStatistics();
|
||||
}
|
||||
|
||||
if (loc.getLatitude() <= 90) {
|
||||
segment.addLocation(loc);
|
||||
|
||||
// For a new segment, sets its start time using the first available
|
||||
// location time.
|
||||
if (segmentStats.getStartTime() < 0) {
|
||||
segmentStats.setStartTime(loc.getTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prepareTrackSegment(segment, splitTracks);
|
||||
|
||||
return splitTracks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a track segment for sending to Google Fusion Tables. The main
|
||||
* steps are:
|
||||
* <ul>
|
||||
* <li>make sure the segment has at least 2 points</li>
|
||||
* <li>set the segment stop time if necessary</li>
|
||||
* <li>decimate locations precision</li>
|
||||
* </ul>
|
||||
* The prepared track will be added to the splitTracks.
|
||||
*
|
||||
* @param segment the track segment
|
||||
* @param splitTracks an array of track segments
|
||||
*/
|
||||
private void prepareTrackSegment(Track segment, ArrayList<Track> splitTracks) {
|
||||
// Make sure the segment has at least 2 points
|
||||
if (segment.getLocations().size() < 2) {
|
||||
Log.d(TAG, "segment has less than 2 points");
|
||||
return;
|
||||
}
|
||||
|
||||
// For a new segment, sets it stop time
|
||||
TripStatistics segmentStats = segment.getStatistics();
|
||||
if (segmentStats.getStopTime() < 0) {
|
||||
Location lastLocation = segment.getLocations().get(segment.getLocations().size() - 1);
|
||||
segmentStats.setStopTime(lastLocation.getTime());
|
||||
}
|
||||
|
||||
// Decimate to 2 meter precision. Fusion Tables doesn't like too many
|
||||
// points.
|
||||
LocationUtils.decimate(segment, 2.0);
|
||||
|
||||
splitTracks.add(segment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads all the waypoints.
|
||||
*
|
||||
* @return true if success.
|
||||
*/
|
||||
private boolean uploadWaypoints() {
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = myTracksProviderUtils.getWaypointsCursor(
|
||||
trackId, 0, Constants.MAX_LOADED_WAYPOINTS_POINTS);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
// This will skip the first waypoint (it carries the stats for the
|
||||
// track).
|
||||
while (cursor.moveToNext()) {
|
||||
Waypoint wpt = myTracksProviderUtils.createWaypoint(cursor);
|
||||
if (!createNewPoint(
|
||||
wpt.getName(), wpt.getDescription(), wpt.getLocation(), MARKER_TYPE_WAYPOINT)) {
|
||||
Log.d(TAG, "Upload waypoints failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new row in Google Fusion Tables representing a marker as a
|
||||
* point.
|
||||
*
|
||||
* @param name the marker name
|
||||
* @param description the marker description
|
||||
* @param location the marker location
|
||||
* @param type the marker type
|
||||
* @return true if success.
|
||||
*/
|
||||
private boolean createNewPoint(
|
||||
String name, String description, Location location, String type) {
|
||||
String query = "INSERT INTO " + tableId + " (name,description,geometry,marker) VALUES "
|
||||
+ SendFusionTablesUtils.formatSqlValues(
|
||||
name, description, SendFusionTablesUtils.getKmlPoint(location), type);
|
||||
return sendQuery(query, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new row in Google Fusion Tables representing the track as a
|
||||
* line segment.
|
||||
*
|
||||
* @param track the track
|
||||
* @return true if success.
|
||||
*/
|
||||
private boolean createNewLineString(Track track) {
|
||||
String query = "INSERT INTO " + tableId + " (name,description,geometry) VALUES "
|
||||
+ SendFusionTablesUtils.formatSqlValues(track.getName(), track.getDescription(),
|
||||
SendFusionTablesUtils.getKmlLineString(track.getLocations()));
|
||||
return sendQuery(query, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a query to Google Fusion Tables.
|
||||
*
|
||||
* @param query the Fusion Tables SQL query
|
||||
* @param setTableId true to set the table id
|
||||
* @return true if success.
|
||||
*/
|
||||
private boolean sendQuery(String query, boolean setTableId) {
|
||||
Log.d(TAG, "SendQuery: " + query);
|
||||
|
||||
if (isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GenericUrl url = new GenericUrl(FUSION_TABLES_BASE_URL);
|
||||
String sql = SQL_KEY + URLEncoder.encode(query);
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(Strings.toBytesUtf8(sql));
|
||||
InputStreamContent inputStreamContent = new InputStreamContent(null, inputStream);
|
||||
HttpRequest request;
|
||||
try {
|
||||
request = httpRequestFactory.buildPostRequest(url, inputStreamContent);
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to build request", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
GoogleHeaders headers = new GoogleHeaders();
|
||||
headers.setApplicationName(APP_NAME_PREFIX + SystemUtils.getMyTracksVersion(context));
|
||||
headers.gdataVersion = GDATA_VERSION;
|
||||
headers.setGoogleLogin(authToken);
|
||||
headers.setContentType(CONTENT_TYPE);
|
||||
request.setHeaders(headers);
|
||||
|
||||
HttpResponse response;
|
||||
try {
|
||||
response = request.execute();
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to execute request", e);
|
||||
return false;
|
||||
}
|
||||
boolean isSuccess = response.isSuccessStatusCode();
|
||||
if (isSuccess) {
|
||||
InputStream content;
|
||||
try {
|
||||
content = response.getContent();
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to get response", e);
|
||||
return false;
|
||||
}
|
||||
if (setTableId) {
|
||||
tableId = SendFusionTablesUtils.getTableId(content);
|
||||
if (tableId == null) {
|
||||
Log.d(TAG, "tableId is null");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG,
|
||||
"sendQuery failed: " + response.getStatusMessage() + ": " + response.getStatusCode());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the progress based on the number of locations uploaded.
|
||||
*
|
||||
* @param uploaded the number of uploaded locations
|
||||
* @param total the number of total locations
|
||||
*/
|
||||
private void updateProgress(int uploaded, int total) {
|
||||
double totalPercentage = uploaded / total;
|
||||
double scaledPercentage = totalPercentage
|
||||
* (PROGRESS_UPLOAD_DATA_MAX - PROGRESS_UPLOAD_DATA_MIN) + PROGRESS_UPLOAD_DATA_MIN;
|
||||
publishProgress((int) scaledPercentage);
|
||||
}
|
||||
}
|
||||
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* 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.api.client.util.Strings;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import android.location.Location;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Utilities for sending a track to Google Fusion Tables.
|
||||
*
|
||||
* @author jshih@google.com (Jimmy Shih)
|
||||
*/
|
||||
public class SendFusionTablesUtils {
|
||||
|
||||
private static final String UTF8 = "UTF8";
|
||||
private static final String TABLE_ID = "tableid";
|
||||
private static final String MAP_URL = "https://www.google.com/fusiontables/embedviz?"
|
||||
+ "viz=MAP&q=select+col0,+col1,+col2,+col3+from+%s+&h=false&lat=%f&lng=%f&z=%d&t=1&l=col2";
|
||||
private static final String TAG = SendFusionTablesUtils.class.getSimpleName();
|
||||
|
||||
private SendFusionTablesUtils() {}
|
||||
|
||||
/**
|
||||
* Gets the url to visualize a fusion table on a map.
|
||||
*
|
||||
* @param track the track
|
||||
* @return the url.
|
||||
*/
|
||||
public static String getMapUrl(Track track) {
|
||||
if (track == null || track.getStatistics() == null || track.getTableId() == null) {
|
||||
Log.e(TAG, "Invalid track");
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO(jshih): Determine the correct bounding box and zoom level that
|
||||
// will show the entire track.
|
||||
TripStatistics stats = track.getStatistics();
|
||||
double latE6 = stats.getBottom() + (stats.getTop() - stats.getBottom()) / 2;
|
||||
double lonE6 = stats.getLeft() + (stats.getRight() - stats.getLeft()) / 2;
|
||||
int z = 15;
|
||||
|
||||
// We explicitly format with Locale.US because we need the latitude and
|
||||
// longitude to be formatted in a locale-independent manner. Specifically,
|
||||
// we need the decimal separator to be a period rather than a comma.
|
||||
return String.format(
|
||||
Locale.US, MAP_URL, track.getTableId(), latE6 / 1.E6, lonE6 / 1.E6, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an array of values as a SQL VALUES like
|
||||
* ('value1','value2',...,'value_n'). Escapes single quotes with two single
|
||||
* quotes.
|
||||
*
|
||||
* @param values an array of values to format
|
||||
* @return the formated SQL VALUES.
|
||||
*/
|
||||
public static String formatSqlValues(String... values) {
|
||||
StringBuilder builder = new StringBuilder("(");
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
if (i > 0) {
|
||||
builder.append(',');
|
||||
}
|
||||
builder.append('\'');
|
||||
builder.append(escapeSqlString(values[i]));
|
||||
builder.append('\'');
|
||||
}
|
||||
builder.append(")");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a SQL string. Escapes single quotes with two single quotes.
|
||||
*
|
||||
* @param string the string
|
||||
* @return the escaped string.
|
||||
*/
|
||||
public static String escapeSqlString(String string) {
|
||||
return string.replaceAll("'", "''");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a KML Point value representing a location.
|
||||
*
|
||||
* @param location the location
|
||||
* @return the KML Point value.
|
||||
*/
|
||||
public static String getKmlPoint(Location location) {
|
||||
StringBuilder builder = new StringBuilder("<Point><coordinates>");
|
||||
if (location != null) {
|
||||
appendLocation(location, builder);
|
||||
}
|
||||
builder.append("</coordinates></Point>");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a KML LineString value representing an array of locations.
|
||||
*
|
||||
* @param locations the locations.
|
||||
* @return the KML LineString value.
|
||||
*/
|
||||
public static String getKmlLineString(ArrayList<Location> locations) {
|
||||
StringBuilder builder = new StringBuilder("<LineString><coordinates>");
|
||||
if (locations != null) {
|
||||
for (int i = 0; i < locations.size(); i++) {
|
||||
if (i != 0) {
|
||||
builder.append(' ');
|
||||
}
|
||||
appendLocation(locations.get(i), builder);
|
||||
}
|
||||
}
|
||||
builder.append("</coordinates></LineString>");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a location to a string builder using "longitude,latitude[,altitude]" format.
|
||||
*
|
||||
* @param location the location
|
||||
* @param builder the string builder
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static void appendLocation(Location location, StringBuilder builder) {
|
||||
builder.append(location.getLongitude()).append(",").append(location.getLatitude());
|
||||
if (location.hasAltitude()) {
|
||||
builder.append(",");
|
||||
builder.append(location.getAltitude());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the table id from an input streawm.
|
||||
*
|
||||
* @param inputStream input stream
|
||||
* @return table id or null if not available.
|
||||
*/
|
||||
public static String getTableId(InputStream inputStream) {
|
||||
if (inputStream == null) {
|
||||
Log.d(TAG, "inputStream is null");
|
||||
return null;
|
||||
}
|
||||
byte[] result = new byte[1024];
|
||||
int read;
|
||||
try {
|
||||
read = inputStream.read(result);
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Unable to read result", e);
|
||||
return null;
|
||||
}
|
||||
if (read == -1) {
|
||||
Log.d(TAG, "no data read");
|
||||
return null;
|
||||
}
|
||||
String s;
|
||||
try {
|
||||
s = new String(result, 0, read, UTF8);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Log.d(TAG, "Unable to parse result", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] lines = s.split(Strings.LINE_SEPARATOR);
|
||||
if (lines.length > 1 && lines[0].equals(TABLE_ID)) {
|
||||
// returns the next line
|
||||
return lines[1];
|
||||
} else {
|
||||
Log.d(TAG, "Response is not valid: " + s);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io.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;
|
||||
}
|
||||
}
|
||||
@@ -17,14 +17,17 @@ package com.google.android.apps.mytracks.io.sendtogoogle;
|
||||
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Enumerates the services to which we can upload track data.
|
||||
*
|
||||
* @author Matthew Simmons
|
||||
*/
|
||||
public enum SendType {
|
||||
MYMAPS(R.string.send_google_my_maps,
|
||||
R.string.send_google_my_maps_url),
|
||||
public enum SendType implements Parcelable {
|
||||
MAPS(R.string.send_google_maps,
|
||||
R.string.send_google_maps_url),
|
||||
FUSION_TABLES(R.string.send_google_fusion_tables,
|
||||
R.string.send_google_fusion_tables_url),
|
||||
DOCS(R.string.send_google_docs,
|
||||
@@ -47,4 +50,27 @@ public enum SendType {
|
||||
public int getServiceUrl() {
|
||||
return serviceUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(ordinal());
|
||||
}
|
||||
|
||||
public static final Creator<SendType> CREATOR = new Creator<SendType>() {
|
||||
|
||||
@Override
|
||||
public SendType createFromParcel(Parcel source) {
|
||||
return SendType.values()[source.readInt()];
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendType[] newArray(int size) {
|
||||
return new SendType[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* 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.Constants;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* A dialog to show the result of uploading to Google services.
|
||||
*
|
||||
* @author jshih@google.com (Jimmy Shih)
|
||||
*/
|
||||
public class UploadResultActivity extends Activity {
|
||||
|
||||
public static final String HAS_MAPS_RESULT = "hasMapsResult";
|
||||
public static final String HAS_FUSION_TABLES_RESULT = "hasFusionTablesResult";
|
||||
public static final String HAS_DOCS_RESULT = "hasDocsResult";
|
||||
|
||||
public static final String MAPS_SUCCESS = "mapsSuccess";
|
||||
public static final String FUSION_TABLES_SUCCESS = "fusionTablesSuccess";
|
||||
public static final String DOCS_SUCCESS = "docsSuccess";
|
||||
|
||||
public static final String SHARE_REQUEST = "shareRequest";
|
||||
|
||||
public static final String MAPS_URL = "mapsUrl";
|
||||
public static final String FUSION_TABLES_URL = "fusionTablesUrl";
|
||||
|
||||
private static final String TEXT_PLAIN_TYPE = "text/plain";
|
||||
private static final int RESULT_DIALOG = 1;
|
||||
|
||||
private boolean hasMapsResult;
|
||||
private boolean hasFusionTablesResult;
|
||||
private boolean hasDocsResult;
|
||||
|
||||
private boolean mapsSuccess;
|
||||
private boolean fusionTablesSuccess;
|
||||
private boolean docsSuccess;
|
||||
|
||||
private boolean shareRequest;
|
||||
private String shareUrl;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent intent = getIntent();
|
||||
hasMapsResult = intent.getBooleanExtra(HAS_MAPS_RESULT, false);
|
||||
hasFusionTablesResult = intent.getBooleanExtra(HAS_FUSION_TABLES_RESULT, false);
|
||||
hasDocsResult = intent.getBooleanExtra(HAS_DOCS_RESULT, false);
|
||||
if (!hasMapsResult && !hasFusionTablesResult && !hasDocsResult) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
mapsSuccess = intent.getBooleanExtra(MAPS_SUCCESS, false);
|
||||
fusionTablesSuccess = intent.getBooleanExtra(FUSION_TABLES_SUCCESS, false);
|
||||
docsSuccess = intent.getBooleanExtra(DOCS_SUCCESS, false);
|
||||
|
||||
shareRequest = intent.getBooleanExtra(SHARE_REQUEST, false);
|
||||
|
||||
String mapsUrl = intent.getStringExtra(MAPS_URL);
|
||||
String fusionTablesUrl = intent.getStringExtra(FUSION_TABLES_URL);
|
||||
shareUrl = mapsUrl != null ? mapsUrl : fusionTablesUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
showDialog(RESULT_DIALOG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case RESULT_DIALOG:
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
View view = getLayoutInflater().inflate(R.layout.upload_result, null);
|
||||
builder.setView(view);
|
||||
|
||||
LinearLayout mapsResult = (LinearLayout) view.findViewById(R.id.upload_result_maps_result);
|
||||
LinearLayout fusionTablesResult = (LinearLayout) view.findViewById(
|
||||
R.id.upload_result_fusion_tables_result);
|
||||
LinearLayout docsResult = (LinearLayout) view.findViewById(R.id.upload_result_docs_result);
|
||||
|
||||
ImageView mapsResultIcon = (ImageView) view.findViewById(
|
||||
R.id.upload_result_maps_result_icon);
|
||||
ImageView fusionTablesResultIcon = (ImageView) view.findViewById(
|
||||
R.id.upload_result_fusion_tables_result_icon);
|
||||
ImageView docsResultIcon = (ImageView) view.findViewById(
|
||||
R.id.upload_result_docs_result_icon);
|
||||
|
||||
TextView successFooter = (TextView) view.findViewById(R.id.upload_result_success_footer);
|
||||
TextView errorFooter = (TextView) view.findViewById(R.id.upload_result_error_footer);
|
||||
|
||||
boolean hasError = false;
|
||||
if (!hasMapsResult) {
|
||||
mapsResult.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (!mapsSuccess) {
|
||||
mapsResultIcon.setImageResource(R.drawable.failure);
|
||||
hasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasFusionTablesResult) {
|
||||
fusionTablesResult.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (!fusionTablesSuccess) {
|
||||
fusionTablesResultIcon.setImageResource(R.drawable.failure);
|
||||
hasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasDocsResult) {
|
||||
docsResult.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (!docsSuccess) {
|
||||
docsResultIcon.setImageResource(R.drawable.failure);
|
||||
hasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
builder.setTitle(R.string.generic_error_title);
|
||||
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
successFooter.setVisibility(View.GONE);
|
||||
} else {
|
||||
builder.setTitle(R.string.generic_success_title);
|
||||
builder.setIcon(android.R.drawable.ic_dialog_info);
|
||||
errorFooter.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
builder.setCancelable(true);
|
||||
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
builder.setPositiveButton(
|
||||
getString(R.string.generic_ok), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (shareRequest && shareUrl != null) {
|
||||
startShareUrlActivity(shareUrl);
|
||||
}
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
// Add a Share URL button if not a shareRequest and shareUrl
|
||||
// exists
|
||||
if (!shareRequest && shareUrl != null) {
|
||||
builder.setNegativeButton(getString(R.string.send_google_result_share_url),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
startShareUrlActivity(shareUrl);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
return builder.create();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an activity to share the url.
|
||||
*
|
||||
* @param url the url
|
||||
*/
|
||||
private void startShareUrlActivity(String url) {
|
||||
SharedPreferences prefs = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
boolean shareUrlOnly = prefs.getBoolean(getString(R.string.share_url_only_key), false);
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.setType(TEXT_PLAIN_TYPE);
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_track_subject));
|
||||
intent.putExtra(Intent.EXTRA_TEXT,
|
||||
shareUrlOnly ? url : getString(R.string.share_track_url_body_format, url));
|
||||
startActivity(Intent.createChooser(intent, getString(R.string.share_track_picker_title)));
|
||||
}
|
||||
}
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* 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.Constants;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.TableRow;
|
||||
|
||||
/**
|
||||
* A chooser to select the Google services to upload a track to.
|
||||
*
|
||||
* @author jshih@google.com (Jimmy Shih)
|
||||
*/
|
||||
public class UploadServiceChooserActivity extends Activity {
|
||||
|
||||
/**
|
||||
* The track id.
|
||||
*/
|
||||
public static final String TRACK_ID = "trackId";
|
||||
|
||||
/**
|
||||
* The send type. Null to send to all Google services.
|
||||
*/
|
||||
public static final String SEND_TYPE = "sendType";
|
||||
|
||||
private static final int SERVICE_PICKER_DIALOG = 1;
|
||||
|
||||
private long trackId;
|
||||
private SendType sendType;
|
||||
|
||||
private TableRow mapsTableRow;
|
||||
private TableRow fusionTablesTableRow;
|
||||
private TableRow docsTableRow;
|
||||
|
||||
private CheckBox mapsCheckBox;
|
||||
private CheckBox fusionTablesCheckBox;
|
||||
private CheckBox docsCheckBox;
|
||||
|
||||
private TableRow mapsOptionTableRow;
|
||||
private RadioButton newMapRadioButton;
|
||||
private RadioButton existingMapRadioButton;
|
||||
|
||||
private Button cancel;
|
||||
private Button send;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent intent = getIntent();
|
||||
trackId = intent.getLongExtra(TRACK_ID, -1L);
|
||||
sendType = intent.getParcelableExtra(SEND_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
showDialog(SERVICE_PICKER_DIALOG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case SERVICE_PICKER_DIALOG:
|
||||
final Dialog dialog = new Dialog(this);
|
||||
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
dialog.setContentView(R.layout.upload_service_chooser);
|
||||
dialog.setCancelable(true);
|
||||
dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface d) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
mapsTableRow = (TableRow) dialog.findViewById(R.id.send_google_maps_row);
|
||||
fusionTablesTableRow = (TableRow) dialog.findViewById(R.id.send_google_fusion_tables_row);
|
||||
docsTableRow = (TableRow) dialog.findViewById(R.id.send_google_docs_row);
|
||||
|
||||
mapsCheckBox = (CheckBox) dialog.findViewById(R.id.send_google_maps);
|
||||
fusionTablesCheckBox = (CheckBox) dialog.findViewById(R.id.send_google_fusion_tables);
|
||||
docsCheckBox = (CheckBox) dialog.findViewById(R.id.send_google_docs);
|
||||
|
||||
mapsOptionTableRow = (TableRow) dialog.findViewById(R.id.send_google_maps_option_row);
|
||||
newMapRadioButton = (RadioButton) dialog.findViewById(R.id.send_google_new_map);
|
||||
existingMapRadioButton = (RadioButton) dialog.findViewById(R.id.send_google_existing_map);
|
||||
|
||||
// Setup checkboxes
|
||||
OnCheckedChangeListener checkBoxListener = new OnCheckedChangeListener() {
|
||||
public void onCheckedChanged(CompoundButton button, boolean checked) {
|
||||
updateStateBySelection();
|
||||
}
|
||||
};
|
||||
mapsCheckBox.setOnCheckedChangeListener(checkBoxListener);
|
||||
fusionTablesCheckBox.setOnCheckedChangeListener(checkBoxListener);
|
||||
docsCheckBox.setOnCheckedChangeListener(checkBoxListener);
|
||||
|
||||
// Setup buttons
|
||||
cancel = (Button) dialog.findViewById(R.id.send_google_cancel);
|
||||
cancel.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
send = (Button) dialog.findViewById(R.id.send_google_send_now);
|
||||
send.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
saveState();
|
||||
Intent intent = new Intent(UploadServiceChooserActivity.this, SendActivity.class);
|
||||
intent.putExtra(SendActivity.TRACK_ID, trackId);
|
||||
intent.putExtra(SendActivity.SHARE_URL, sendType != null);
|
||||
intent.putExtra(SendActivity.SEND_MAPS, sendMaps());
|
||||
intent.putExtra(SendActivity.SEND_FUSION_TABLES, sendFusionTables());
|
||||
intent.putExtra(SendActivity.SEND_DOCS, sendDocs());
|
||||
intent.putExtra(SendActivity.CREATE_MAP, !existingMapRadioButton.isChecked());
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
// Setup initial state
|
||||
initState();
|
||||
|
||||
// Update state based on sendType
|
||||
updateStateBySendType();
|
||||
|
||||
// Update state based on current user selection
|
||||
updateStateBySelection();
|
||||
|
||||
return dialog;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the UI state based on the shared preferences.
|
||||
*/
|
||||
private void initState() {
|
||||
SharedPreferences prefs = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
boolean pickExistingMap = prefs.getBoolean(getString(R.string.pick_existing_map_key), false);
|
||||
|
||||
newMapRadioButton.setChecked(!pickExistingMap);
|
||||
existingMapRadioButton.setChecked(pickExistingMap);
|
||||
|
||||
mapsCheckBox.setChecked(prefs.getBoolean(getString(R.string.send_to_maps_key), true));
|
||||
fusionTablesCheckBox.setChecked(
|
||||
prefs.getBoolean(getString(R.string.send_to_fusion_tables_key), true));
|
||||
docsCheckBox.setChecked(prefs.getBoolean(getString(R.string.send_to_docs_key), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI state based on the sendType.
|
||||
*/
|
||||
private void updateStateBySendType() {
|
||||
if (sendType == SendType.MAPS) {
|
||||
mapsCheckBox.setChecked(true);
|
||||
} else if (sendType == SendType.FUSION_TABLES) {
|
||||
fusionTablesCheckBox.setChecked(true);
|
||||
} else if (sendType == SendType.DOCS) {
|
||||
docsCheckBox.setChecked(true);
|
||||
} else {
|
||||
// sendType == null
|
||||
}
|
||||
mapsTableRow.setVisibility(showMaps() ? View.VISIBLE : View.GONE);
|
||||
fusionTablesTableRow.setVisibility(showFusionTables() ? View.VISIBLE : View.GONE);
|
||||
docsTableRow.setVisibility(showDocs() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI state based on the current selection.
|
||||
*/
|
||||
private void updateStateBySelection() {
|
||||
mapsOptionTableRow.setVisibility(sendMaps() ? View.VISIBLE : View.GONE);
|
||||
send.setEnabled(sendMaps() || sendFusionTables() || sendDocs());
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the UI state to the shared preferences.
|
||||
*/
|
||||
private void saveState() {
|
||||
SharedPreferences prefs = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
Editor editor = prefs.edit();
|
||||
editor.putBoolean(
|
||||
getString(R.string.pick_existing_map_key), existingMapRadioButton.isChecked());
|
||||
if (sendType == null) {
|
||||
editor.putBoolean(getString(R.string.send_to_maps_key), sendMaps());
|
||||
editor.putBoolean(getString(R.string.send_to_fusion_tables_key), sendFusionTables());
|
||||
editor.putBoolean(getString(R.string.send_to_docs_key), sendDocs());
|
||||
}
|
||||
ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true to show the Google Maps option.
|
||||
*/
|
||||
private boolean showMaps() {
|
||||
return sendType == null || sendType == SendType.MAPS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true to show the Google Fusion Tables option.
|
||||
*/
|
||||
private boolean showFusionTables() {
|
||||
return sendType == null || sendType == SendType.FUSION_TABLES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true to show the Google Docs option.
|
||||
*/
|
||||
private boolean showDocs() {
|
||||
return sendType == null || sendType == SendType.DOCS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true to send to Google Maps.
|
||||
*/
|
||||
private boolean sendMaps() {
|
||||
return showMaps() && mapsCheckBox.isChecked();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true to send to Google Fusion Tables.
|
||||
*/
|
||||
private boolean sendFusionTables() {
|
||||
return showFusionTables() && fusionTablesCheckBox.isChecked();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true to send to Google Docs.
|
||||
*/
|
||||
private boolean sendDocs() {
|
||||
return showDocs() && docsCheckBox.isChecked();
|
||||
}
|
||||
}
|
||||
+35
-18
@@ -18,43 +18,60 @@ package com.google.android.apps.mytracks.services;
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
/**
|
||||
* Creates a default track name based on the current default track name policy.
|
||||
*
|
||||
* Creates a default track name based on the track name setting.
|
||||
*
|
||||
* @author Matthew Simmons
|
||||
*/
|
||||
class DefaultTrackNameFactory {
|
||||
public class DefaultTrackNameFactory {
|
||||
|
||||
@VisibleForTesting
|
||||
static final String ISO_8601_FORMAT = "yyyy-MM-dd HH:mm";
|
||||
private final Context context;
|
||||
|
||||
DefaultTrackNameFactory(Context context) {
|
||||
public DefaultTrackNameFactory(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new track name.
|
||||
*
|
||||
* @param trackId The ID for the current track.
|
||||
* @param startTime The start time, in milliseconds since the epoch, of the
|
||||
* current track.
|
||||
* @return The new track name.
|
||||
* Gets the default track name.
|
||||
*
|
||||
* @param trackId the track id
|
||||
* @param startTime the track start time
|
||||
*/
|
||||
String newTrackName(long trackId, long startTime) {
|
||||
if (useTimestampTrackName()) {
|
||||
return StringUtils.formatDateTime(startTime);
|
||||
public String getDefaultTrackName(long trackId, long startTime) {
|
||||
String trackNameSetting = getTrackNameSetting();
|
||||
|
||||
if (trackNameSetting.equals(
|
||||
context.getString(R.string.settings_recording_track_name_date_local_value))) {
|
||||
return StringUtils.formatDateTime(context, startTime);
|
||||
} else if (trackNameSetting.equals(
|
||||
context.getString(R.string.settings_recording_track_name_date_iso_8601_value))) {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat(ISO_8601_FORMAT);
|
||||
return dateFormat.format(startTime);
|
||||
} else {
|
||||
// trackNameSetting equals
|
||||
// R.string.settings_recording_track_name_number_value
|
||||
return String.format(context.getString(R.string.track_name_format), trackId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Determines whether the preferences allow a timestamp-based track name */
|
||||
protected boolean useTimestampTrackName() {
|
||||
SharedPreferences prefs = context.getSharedPreferences(
|
||||
/**
|
||||
* Gets the track name setting.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
String getTrackNameSetting() {
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
return prefs.getBoolean(
|
||||
context.getString(R.string.timestamp_track_name_key), true);
|
||||
return sharedPreferences.getString(
|
||||
context.getString(R.string.track_name_key),
|
||||
context.getString(R.string.settings_recording_track_name_date_local_value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -586,7 +586,7 @@ public class TrackRecordingService extends Service {
|
||||
Uri trackUri = providerUtils.insertTrack(track);
|
||||
recordingTrackId = Long.parseLong(trackUri.getLastPathSegment());
|
||||
track.setId(recordingTrackId);
|
||||
track.setName(new DefaultTrackNameFactory(this).newTrackName(
|
||||
track.setName(new DefaultTrackNameFactory(this).getDefaultTrackName(
|
||||
recordingTrackId, startTime));
|
||||
track.setCategory(getDefaultActivityType(this));
|
||||
isRecording = true;
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
package com.google.android.apps.mytracks.util;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import android.os.Environment;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.TimeZone;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Utilities for dealing with files.
|
||||
@@ -30,17 +30,14 @@ import java.util.regex.Pattern;
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class FileUtils {
|
||||
/**
|
||||
* The maximum length of a filename, as per the FAT32 specification.
|
||||
*/
|
||||
private static final int MAX_FILENAME_LENGTH = 260;
|
||||
|
||||
/**
|
||||
* A set of characters that are prohibited from being in file names.
|
||||
* The maximum FAT32 path length. See the FAT32 spec at
|
||||
* http://msdn.microsoft.com/en-us/windows/hardware/gg463080
|
||||
*/
|
||||
private static final Pattern PROHIBITED_CHAR_PATTERN =
|
||||
Pattern.compile("[^ A-Za-z0-9_.()-]+");
|
||||
|
||||
@VisibleForTesting
|
||||
static final int MAX_FAT32_PATH_LENGTH = 260;
|
||||
|
||||
/**
|
||||
* Timestamp format in UTC time zone.
|
||||
*/
|
||||
@@ -77,20 +74,6 @@ public class FileUtils {
|
||||
Environment.getExternalStorageState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the input string and make sure it is a valid fat32 file name.
|
||||
*
|
||||
* @param name the name to normalize
|
||||
* @return the sanitized name
|
||||
*/
|
||||
String sanitizeName(String name) {
|
||||
String cleaned = PROHIBITED_CHAR_PATTERN.matcher(name).replaceAll("");
|
||||
|
||||
return (cleaned.length() > MAX_FILENAME_LENGTH)
|
||||
? cleaned.substring(0, MAX_FILENAME_LENGTH)
|
||||
: cleaned.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the given directory exists by creating it and its parents if
|
||||
* necessary.
|
||||
@@ -125,29 +108,110 @@ public class FileUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a filename with the given base name (prefix) and the given
|
||||
* extension, possibly adding a suffix to ensure the file doesn't exist.
|
||||
* Builds a filename with the given base and the given extension, possibly
|
||||
* adding a suffix to ensure the file doesn't exist.
|
||||
*
|
||||
* @param directory the directory the file will live in
|
||||
* @param fileBaseName the prefix for the file name
|
||||
* @param extension the file's extension
|
||||
* @param directory the directory the filename will be located in
|
||||
* @param base the base for the filename
|
||||
* @param extension the extension for the filename
|
||||
* @param suffix the first numeric suffix to try to use, or 0 for none
|
||||
* @return the complete file name, without the directory
|
||||
* @return the complete filename, without the directory
|
||||
*/
|
||||
private String buildUniqueFileName(File directory, String fileBaseName,
|
||||
String extension, int suffix) {
|
||||
String suffixedBaseName = fileBaseName;
|
||||
private String buildUniqueFileName(File directory, String base, String extension, int suffix) {
|
||||
String suffixName = "";
|
||||
if (suffix > 0) {
|
||||
suffixedBaseName += " (" + Integer.toString(suffix) + ")";
|
||||
suffixName += "(" + Integer.toString(suffix) + ")";
|
||||
}
|
||||
suffixName += "." + extension;
|
||||
|
||||
String fullName = suffixedBaseName + "." + extension;
|
||||
String sanitizedName = sanitizeName(fullName);
|
||||
if (!fileExists(directory, sanitizedName)) {
|
||||
return sanitizedName;
|
||||
String baseName = sanitizeFileName(base);
|
||||
baseName = truncateFileName(directory, baseName, suffixName);
|
||||
String fullName = baseName + suffixName;
|
||||
|
||||
if (!fileExists(directory, fullName)) {
|
||||
return fullName;
|
||||
}
|
||||
return buildUniqueFileName(directory, base, extension, suffix + 1);
|
||||
}
|
||||
|
||||
return buildUniqueFileName(directory, fileBaseName, extension, suffix + 1);
|
||||
/**
|
||||
* Sanitizes the name as a valid fat32 filename. For simplicity, fat32
|
||||
* filename characters may be any combination of letters, digits, or
|
||||
* characters with code point values greater than 127. Replaces the invalid
|
||||
* characters with "_" and collapses multiple "_" together.
|
||||
*
|
||||
* @param name name
|
||||
*/
|
||||
@VisibleForTesting
|
||||
String sanitizeFileName(String name) {
|
||||
StringBuffer buffer = new StringBuffer(name.length());
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
int codePoint = name.codePointAt(i);
|
||||
char character = name.charAt(i);
|
||||
if (Character.isLetterOrDigit(character) || codePoint > 127 || isSpecialFat32(character)) {
|
||||
buffer.appendCodePoint(codePoint);
|
||||
} else {
|
||||
buffer.append("_");
|
||||
}
|
||||
}
|
||||
String result = buffer.toString();
|
||||
return result.replaceAll("_+", "_");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if it is a special FAT32 character.
|
||||
*
|
||||
* @param character the character
|
||||
*/
|
||||
private boolean isSpecialFat32(char character) {
|
||||
switch (character) {
|
||||
case '$':
|
||||
case '%':
|
||||
case '\'':
|
||||
case '-':
|
||||
case '_':
|
||||
case '@':
|
||||
case '~':
|
||||
case '`':
|
||||
case '!':
|
||||
case '(':
|
||||
case ')':
|
||||
case '{':
|
||||
case '}':
|
||||
case '^':
|
||||
case '#':
|
||||
case '&':
|
||||
case '+':
|
||||
case ',':
|
||||
case ';':
|
||||
case '=':
|
||||
case '[':
|
||||
case ']':
|
||||
case ' ':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncates the name if necessary so the filename path length (directory +
|
||||
* name + suffix) meets the Fat32 path limit.
|
||||
*
|
||||
* @param directory directory
|
||||
* @param name name
|
||||
* @param suffix suffix
|
||||
*/
|
||||
@VisibleForTesting
|
||||
String truncateFileName(File directory, String name, String suffix) {
|
||||
// 1 at the end accounts for the FAT32 filename trailing NUL character
|
||||
int requiredLength = directory.getPath().length() + suffix.length() + 1;
|
||||
if (name.length() + requiredLength > MAX_FAT32_PATH_LENGTH) {
|
||||
int limit = MAX_FAT32_PATH_LENGTH - requiredLength;
|
||||
return name.substring(0, limit);
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.text.format.DateUtils;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.NumberFormat;
|
||||
@@ -46,35 +47,34 @@ public class StringUtils implements DescriptionGenerator {
|
||||
private final Context context;
|
||||
|
||||
/**
|
||||
* Formats the time based on user locale.
|
||||
*
|
||||
* @param time time
|
||||
*/
|
||||
public static String formatDateTime(long time) {
|
||||
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
|
||||
return formatter.format(new Date(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a number of milliseconds as a string.
|
||||
* Formats the date and time based on user's phone date/time preferences.
|
||||
*
|
||||
* @param time - A period of time in milliseconds.
|
||||
* @return A string of the format M:SS, MM:SS or HH:MM:SS
|
||||
* @param context the context
|
||||
* @param time the time in milliseconds
|
||||
*/
|
||||
public static String formatTime(long time) {
|
||||
return formatTimeInternal(time, false);
|
||||
public static String formatDateTime(Context context, long time) {
|
||||
DateFormat dateFormatter = android.text.format.DateFormat.getDateFormat(context);
|
||||
return dateFormatter.format(new Date(time)) + " " + formatTime(context, time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a number of milliseconds as a string. To be used when we need the
|
||||
* hours to be shown even when it is zero, e.g. exporting data to a
|
||||
* spreadsheet.
|
||||
* Formats the time based on user's phone date/time preferences.
|
||||
*
|
||||
* @param time - A period of time in milliseconds
|
||||
* @return A string of the format HH:MM:SS even if time is less than 1 hour
|
||||
* @param context the context
|
||||
* @param time the time in milliseconds
|
||||
*/
|
||||
public static String formatTimeAlwaysShowingHours(long time) {
|
||||
return formatTimeInternal(time, true);
|
||||
public static String formatTime(Context context, long time) {
|
||||
DateFormat timeFormatter = android.text.format.DateFormat.getTimeFormat(context);
|
||||
return timeFormatter.format(new Date(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the elapsed timed in the form "MM:SS" or "H:MM:SS".
|
||||
*
|
||||
* @param time the time in milliseconds
|
||||
*/
|
||||
public static String formatElapsedTime(long time) {
|
||||
return DateUtils.formatElapsedTime(time / 1000);
|
||||
}
|
||||
|
||||
private static final NumberFormat SINGLE_DECIMAL_PLACE_FORMAT = NumberFormat.getNumberInstance();
|
||||
@@ -182,35 +182,6 @@ public class StringUtils implements DescriptionGenerator {
|
||||
return time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a number of milliseconds as a string.
|
||||
*
|
||||
* @param time - A period of time in milliseconds
|
||||
* @param alwaysShowHours - Whether to display 00 hours if time is less than 1
|
||||
* hour
|
||||
* @return A string of the format HH:MM:SS
|
||||
*/
|
||||
private static String formatTimeInternal(long time, boolean alwaysShowHours) {
|
||||
int[] parts = getTimeParts(time);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (parts[2] > 0 || alwaysShowHours) {
|
||||
builder.append(parts[2]);
|
||||
builder.append(':');
|
||||
if (parts[1] <= 9) {
|
||||
builder.append("0");
|
||||
}
|
||||
}
|
||||
|
||||
builder.append(parts[1]);
|
||||
builder.append(':');
|
||||
if (parts[0] <= 9) {
|
||||
builder.append("0");
|
||||
}
|
||||
builder.append(parts[0]);
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the time as an array of parts.
|
||||
*/
|
||||
@@ -328,11 +299,11 @@ public class StringUtils implements DescriptionGenerator {
|
||||
|
||||
// Line 3
|
||||
context.getString(R.string.stat_total_time),
|
||||
StringUtils.formatTime(trackStats.getTotalTime()),
|
||||
StringUtils.formatElapsedTime(trackStats.getTotalTime()),
|
||||
|
||||
// Line 4
|
||||
context.getString(R.string.stat_moving_time),
|
||||
StringUtils.formatTime(trackStats.getMovingTime()),
|
||||
StringUtils.formatElapsedTime(trackStats.getMovingTime()),
|
||||
|
||||
// Line 5
|
||||
averageSpeed, averageMovingSpeed, maxSpeed,
|
||||
@@ -360,7 +331,7 @@ public class StringUtils implements DescriptionGenerator {
|
||||
|
||||
// Line 11
|
||||
context.getString(R.string.send_google_recorded),
|
||||
StringUtils.formatDateTime(trackStats.getStartTime()),
|
||||
StringUtils.formatDateTime(context, trackStats.getStartTime()),
|
||||
|
||||
// Line 12
|
||||
context.getString(R.string.track_detail_activity_type_hint), category,
|
||||
@@ -470,9 +441,9 @@ public class StringUtils implements DescriptionGenerator {
|
||||
distanceInKm, context.getString(R.string.unit_kilometer),
|
||||
distanceInMiles, context.getString(R.string.unit_mile),
|
||||
context.getString(R.string.stat_total_time),
|
||||
StringUtils.formatTime(stats.getTotalTime()),
|
||||
StringUtils.formatElapsedTime(stats.getTotalTime()),
|
||||
context.getString(R.string.stat_moving_time),
|
||||
StringUtils.formatTime(stats.getMovingTime()),
|
||||
StringUtils.formatElapsedTime(stats.getMovingTime()),
|
||||
context.getString(R.string.stat_average_speed),
|
||||
averageSpeedInKmh, context.getString(R.string.unit_kilometer_per_hour),
|
||||
averageSpeedInMph, context.getString(R.string.unit_mile_per_hour),
|
||||
|
||||
@@ -224,7 +224,7 @@ public class TrackWidgetProvider
|
||||
StringUtils.formatSingleDecimalPlace(displayDistance) + " " + this.distanceLabel;
|
||||
|
||||
// convert ms to minutes
|
||||
String time = StringUtils.formatTime(stats.getMovingTime());
|
||||
String time = StringUtils.formatElapsedTime(stats.getMovingTime());
|
||||
String speed = unknown;
|
||||
if (!Double.isNaN(stats.getAverageMovingSpeed())) {
|
||||
// Convert m/s to km/h
|
||||
@@ -236,7 +236,7 @@ public class TrackWidgetProvider
|
||||
speed = StringUtils.formatSingleDecimalPlace(displaySpeed) + " " + this.speedLabel;
|
||||
} else {
|
||||
long displayPace = (long) (3600000.0 / displaySpeed);
|
||||
speed = StringUtils.formatTime(displayPace) + " " + paceLabel;
|
||||
speed = StringUtils.formatElapsedTime(displayPace) + " " + paceLabel;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user