forked from upstream-mirrors/OpenTracks
Merge to head
This commit is contained in:
@@ -285,6 +285,7 @@ limitations under the License.
|
|||||||
<activity android:name="com.google.android.apps.mytracks.io.docs.SendDocsActivity"/>
|
<activity android:name="com.google.android.apps.mytracks.io.docs.SendDocsActivity"/>
|
||||||
<activity android:name="com.google.android.apps.mytracks.io.maps.ChooseMapActivity"
|
<activity android:name="com.google.android.apps.mytracks.io.maps.ChooseMapActivity"
|
||||||
android:label="@string/maps_list_title"/>
|
android:label="@string/maps_list_title"/>
|
||||||
|
<activity android:name="com.google.android.apps.mytracks.io.maps.SendMapsActivity"/>
|
||||||
<activity android:name="com.google.android.apps.mytracks.io.sendtogoogle.AccountChooserActivity"/>
|
<activity android:name="com.google.android.apps.mytracks.io.sendtogoogle.AccountChooserActivity"/>
|
||||||
<activity android:name="com.google.android.apps.mytracks.io.sendtogoogle.SendFusionTablesActivity"/>
|
<activity android:name="com.google.android.apps.mytracks.io.sendtogoogle.SendFusionTablesActivity"/>
|
||||||
<activity android:name="com.google.android.apps.mytracks.io.sendtogoogle.UploadResultActivity"/>
|
<activity android:name="com.google.android.apps.mytracks.io.sendtogoogle.UploadResultActivity"/>
|
||||||
|
|||||||
@@ -693,6 +693,43 @@ limitations under the License.
|
|||||||
My Tracks: Imported %1$d of %2$s from %3$s.
|
My Tracks: Imported %1$d of %2$s from %3$s.
|
||||||
</string>
|
</string>
|
||||||
|
|
||||||
|
<!-- Maps List -->
|
||||||
|
<string name="maps_list_error"
|
||||||
|
translation_description="When sending a track to an existing Google Map, the text on the
|
||||||
|
'Maps' list page informing the user that his maps cannot be loaded at this time.">
|
||||||
|
Your maps could not be loaded at this time.
|
||||||
|
</string>
|
||||||
|
<string name="maps_list_loading"
|
||||||
|
translation_description="When sending a track to an existing Google Map, the progress dialog
|
||||||
|
message informing the user that his maps are being loaded.">
|
||||||
|
Loading your Google Maps.
|
||||||
|
</string>
|
||||||
|
<string name="maps_list_no_maps"
|
||||||
|
translation_description="When sending a track to an existing Google Map, the text on the
|
||||||
|
'Maps' list page informing the user that he doesn't have any maps.">
|
||||||
|
You don\'t have any maps.
|
||||||
|
</string>
|
||||||
|
<string name="maps_list_pick_map"
|
||||||
|
translation_description="When sending a track to an existing Google Map, the text on the
|
||||||
|
'Maps' list page asking the user to pick one of his maps.">
|
||||||
|
Pick a map:
|
||||||
|
</string>
|
||||||
|
<string name="maps_list_public_label"
|
||||||
|
translation_description="When sending a track to an existing Google Map, in the 'Maps'
|
||||||
|
list page, the label next to the maps that are public, as opposed to being unlisted.">
|
||||||
|
Public
|
||||||
|
</string>
|
||||||
|
<string name="maps_list_title"
|
||||||
|
translation_description="When sending a track to an existing Google Map, the title of the
|
||||||
|
page listing the user's maps in Google Maps.">
|
||||||
|
Your Google Maps
|
||||||
|
</string>
|
||||||
|
<string name="maps_list_unlisted_label"
|
||||||
|
translation_description="When sending a track to an existing Google Map, in the 'Maps'
|
||||||
|
list page, the label next to the maps that are unlisted, as opposed to being public.">
|
||||||
|
Unlisted
|
||||||
|
</string>
|
||||||
|
|
||||||
<!-- Marker -->
|
<!-- Marker -->
|
||||||
<string name="marker_detail_marker_description"
|
<string name="marker_detail_marker_description"
|
||||||
translation_description="In the detail page for a waypoint marker, the text field for the marker
|
translation_description="In the detail page for a waypoint marker, the text field for the marker
|
||||||
@@ -824,43 +861,6 @@ limitations under the License.
|
|||||||
Waypoint
|
Waypoint
|
||||||
</string>
|
</string>
|
||||||
|
|
||||||
<!-- Maps List -->
|
|
||||||
<string name="maps_list_error"
|
|
||||||
translation_description="When sending a track to an existing Google Map, the text on the
|
|
||||||
'Maps' list page informing the user that his maps cannot be loaded at this time.">
|
|
||||||
Your maps could not be loaded at this time.
|
|
||||||
</string>
|
|
||||||
<string name="maps_list_loading"
|
|
||||||
translation_description="When sending a track to an existing Google Map, the progress dialog
|
|
||||||
message informing the user that his maps are being loaded.">
|
|
||||||
Loading your Google Maps.
|
|
||||||
</string>
|
|
||||||
<string name="maps_list_no_maps"
|
|
||||||
translation_description="When sending a track to an existing Google Map, the text on the
|
|
||||||
'Maps' list page informing the user that he doesn't have any maps.">
|
|
||||||
You don\'t have any maps.
|
|
||||||
</string>
|
|
||||||
<string name="maps_list_pick_map"
|
|
||||||
translation_description="When sending a track to an existing Google Map, the text on the
|
|
||||||
'Maps' list page asking the user to pick one of his maps.">
|
|
||||||
Pick a map:
|
|
||||||
</string>
|
|
||||||
<string name="maps_list_public_label"
|
|
||||||
translation_description="When sending a track to an existing Google Map, in the 'Maps'
|
|
||||||
list page, the label next to the maps that are public, as opposed to being unlisted.">
|
|
||||||
Public
|
|
||||||
</string>
|
|
||||||
<string name="maps_list_title"
|
|
||||||
translation_description="When sending a track to an existing Google Map, the title of the
|
|
||||||
page listing the user's maps in Google Maps.">
|
|
||||||
Your Google Maps
|
|
||||||
</string>
|
|
||||||
<string name="maps_list_unlisted_label"
|
|
||||||
translation_description="When sending a track to an existing Google Map, in the 'Maps'
|
|
||||||
list page, the label next to the maps that are unlisted, as opposed to being public.">
|
|
||||||
Unlisted
|
|
||||||
</string>
|
|
||||||
|
|
||||||
<!-- Menu Items -->
|
<!-- Menu Items -->
|
||||||
<string name="menu_aggregated_statistics"
|
<string name="menu_aggregated_statistics"
|
||||||
translation_description="In the menu, to go to the aggregated statistics page.
|
translation_description="In the menu, to go to the aggregated statistics page.
|
||||||
@@ -1179,25 +1179,6 @@ limitations under the License.
|
|||||||
found.">
|
found.">
|
||||||
No account found
|
No account found
|
||||||
</string>
|
</string>
|
||||||
<string name="send_google_progress_authenticating"
|
|
||||||
translation_description="When sending a track to a Google service, the message on the progress
|
|
||||||
dialog indicating authenticating to the service.
|
|
||||||
|
|
||||||
%1$s: service name. 'Google Maps', 'Google Fusion Tables', or 'Google Docs'.">
|
|
||||||
Authenticating to %1$s...
|
|
||||||
</string>
|
|
||||||
<string name="send_google_progress_creating"
|
|
||||||
translation_description="When sending a track to a Google service, the message on the progress
|
|
||||||
dialog indicating creating a new service item.
|
|
||||||
|
|
||||||
%1$s: service name. 'Google Maps', 'Google Fusion Tables', or 'Google Docs'.">
|
|
||||||
Creating a new %1$s item...
|
|
||||||
</string>
|
|
||||||
<string name="send_google_progress_preparing_track"
|
|
||||||
translation_description="When sending a track to Google Maps, the message on the progress
|
|
||||||
dialog indicating My Tracks is preparing the track's data for upload.">
|
|
||||||
Preparing track...
|
|
||||||
</string>
|
|
||||||
<string name="send_google_progress_title"
|
<string name="send_google_progress_title"
|
||||||
translation_description="When sending a track to a Google service, the title on the progress
|
translation_description="When sending a track to a Google service, the title on the progress
|
||||||
dialog.
|
dialog.
|
||||||
@@ -1205,18 +1186,6 @@ limitations under the License.
|
|||||||
%1$s: service name. 'Google Maps', 'Google Fusion Tables', or 'Google Docs'.">
|
%1$s: service name. 'Google Maps', 'Google Fusion Tables', or 'Google Docs'.">
|
||||||
Sending to %1$s. Please wait...
|
Sending to %1$s. Please wait...
|
||||||
</string>
|
</string>
|
||||||
<string name="send_google_progress_reading_track"
|
|
||||||
translation_description="When sending a track to Google, the message on the progress dialog
|
|
||||||
indicating My Tracks is reading the track from its database.">
|
|
||||||
Reading track...
|
|
||||||
</string>
|
|
||||||
<string name="send_google_progress_sending"
|
|
||||||
translation_description="When sending a track to Google service, the message on the progress
|
|
||||||
dialog indicating My Tracks is sending the track data.
|
|
||||||
|
|
||||||
%1$s: service name. 'Google Maps', 'Google Fusion Tables', or 'Google Docs'.">
|
|
||||||
Sending a track to %1$s...
|
|
||||||
</string>
|
|
||||||
<string name="send_google_recorded"
|
<string name="send_google_recorded"
|
||||||
translation_description="When sending a track to Google, in the track description, a tag
|
translation_description="When sending a track to Google, in the track description, a tag
|
||||||
for the track recorded date. E.g., 'Recorded: Tue Sep 06 15:08:30 PDT 2011'.">
|
for the track recorded date. E.g., 'Recorded: Tue Sep 06 15:08:30 PDT 2011'.">
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
package com.google.android.apps.mytracks;
|
|
||||||
|
|
||||||
import com.google.android.maps.mytracks.R;
|
|
||||||
|
|
||||||
import android.accounts.Account;
|
|
||||||
import android.accounts.AccountManager;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Choose which account to upload track information to.
|
|
||||||
*
|
|
||||||
* @author Sandor Dornbush
|
|
||||||
*
|
|
||||||
* @deprecated To be removed.
|
|
||||||
*/
|
|
||||||
public class AccountChooser {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The last selected account.
|
|
||||||
*/
|
|
||||||
private int selectedAccountIndex = -1;
|
|
||||||
private Account selectedAccount = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An interface for receiving updates once the user has selected the account.
|
|
||||||
*/
|
|
||||||
public interface AccountHandler {
|
|
||||||
/**
|
|
||||||
* Handle the account being selected.
|
|
||||||
* @param account The selected account or null if none could be found
|
|
||||||
*/
|
|
||||||
public void onAccountSelected(Account account);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Chooses the best account to upload to.
|
|
||||||
* If no account is found the user will be alerted.
|
|
||||||
* If only one account is found that will be used.
|
|
||||||
* If multiple accounts are found the user will be allowed to choose.
|
|
||||||
*
|
|
||||||
* @param activity The parent activity
|
|
||||||
* @param handler The handler to be notified when an account has been selected
|
|
||||||
*/
|
|
||||||
public void chooseAccount(final Activity activity,
|
|
||||||
final AccountHandler handler) {
|
|
||||||
final Account[] accounts = AccountManager.get(activity)
|
|
||||||
.getAccountsByType(Constants.ACCOUNT_TYPE);
|
|
||||||
if (accounts.length < 1) {
|
|
||||||
alertNoAccounts(activity, handler);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (accounts.length == 1) {
|
|
||||||
handler.onAccountSelected(accounts[0]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO This should be read out of a preference.
|
|
||||||
if (selectedAccount != null) {
|
|
||||||
handler.onAccountSelected(selectedAccount);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let the user choose.
|
|
||||||
Log.e(Constants.TAG, "Multiple matching accounts found.");
|
|
||||||
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
|
||||||
builder.setTitle(R.string.send_google_choose_account_title);
|
|
||||||
builder.setCancelable(false);
|
|
||||||
builder.setPositiveButton(R.string.generic_ok,
|
|
||||||
new DialogInterface.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
dialog.dismiss();
|
|
||||||
|
|
||||||
if (selectedAccountIndex >= 0) {
|
|
||||||
selectedAccount = accounts[selectedAccountIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.onAccountSelected(selectedAccount);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
builder.setNegativeButton(R.string.generic_cancel,
|
|
||||||
new DialogInterface.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
dialog.dismiss();
|
|
||||||
handler.onAccountSelected(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
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.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setChosenAccount(String accountName, String accountType) {
|
|
||||||
selectedAccount = new Account(accountName, accountType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Puts up a dialog alerting the user that no suitable account was found.
|
|
||||||
*/
|
|
||||||
private void alertNoAccounts(final Activity activity,
|
|
||||||
final AccountHandler handler) {
|
|
||||||
Log.e(Constants.TAG, "No matching accounts found.");
|
|
||||||
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
|
||||||
builder.setTitle(R.string.send_google_no_account_title);
|
|
||||||
builder.setMessage(R.string.send_google_no_account_message);
|
|
||||||
builder.setCancelable(true);
|
|
||||||
builder.setPositiveButton(R.string.generic_ok,
|
|
||||||
new DialogInterface.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
dialog.dismiss();
|
|
||||||
handler.onAccountSelected(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
builder.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -40,23 +40,22 @@ public abstract class Constants {
|
|||||||
* activities that call startActivityForResult. Thus do not need to keep a
|
* activities that call startActivityForResult. Thus do not need to keep a
|
||||||
* global order across all activities.
|
* global order across all activities.
|
||||||
*/
|
*/
|
||||||
public static final int GET_LOGIN = 0;
|
public static final int SHOW_TRACK = 0;
|
||||||
public static final int CHOOSE_MAP = 1;
|
public static final int SHARE_GPX_FILE = 1;
|
||||||
public static final int SHOW_TRACK = 2;
|
public static final int SHARE_KML_FILE = 2;
|
||||||
public static final int AUTHENTICATE_TO_MAPS = 3;
|
public static final int SHARE_CSV_FILE = 3;
|
||||||
public static final int SHARE_GPX_FILE = 4;
|
public static final int SHARE_TCX_FILE = 4;
|
||||||
public static final int SHARE_KML_FILE = 5;
|
public static final int SAVE_GPX_FILE = 5;
|
||||||
public static final int SHARE_CSV_FILE = 6;
|
public static final int SAVE_KML_FILE = 6;
|
||||||
public static final int SHARE_TCX_FILE = 7;
|
public static final int SAVE_CSV_FILE = 7;
|
||||||
public static final int SAVE_GPX_FILE = 8;
|
public static final int SAVE_TCX_FILE = 8;
|
||||||
public static final int SAVE_KML_FILE = 9;
|
public static final int SHOW_WAYPOINT = 9;
|
||||||
public static final int SAVE_CSV_FILE = 10;
|
public static final int WELCOME = 10;
|
||||||
public static final int SAVE_TCX_FILE = 11;
|
public static final int CHOOSE_ACCOUNT = 11;
|
||||||
public static final int SHOW_WAYPOINT = 12;
|
public static final int CHOOSE_MAP = 12;
|
||||||
public static final int WELCOME = 13;
|
public static final int SEND_MAPS = 13;
|
||||||
public static final int CHOOSE_ACCOUNT = 14;
|
public static final int SEND_FUSION_TABLES = 14;
|
||||||
public static final int SEND_DOCS = 15;
|
public static final int SEND_DOCS = 15;
|
||||||
public static final int SEND_FUSION_TABLES = 16;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Menu ids:
|
* Menu ids:
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
package com.google.android.apps.mytracks;
|
|
||||||
|
|
||||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.WindowManager.BadTokenException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A class to handle all dialog related events for My Tracks.
|
|
||||||
*
|
|
||||||
* @author Sandor Dornbush
|
|
||||||
*/
|
|
||||||
public class DialogManager {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The equivalent of {@link Dialog#show()}, but for a specific dialog
|
|
||||||
* instance.
|
|
||||||
*/
|
|
||||||
public static void showDialogSafely(Activity activity, final Dialog dialog) {
|
|
||||||
if (activity.isFinishing()) {
|
|
||||||
Log.w(TAG, "Activity finishing - not showing dialog");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
dialog.show();
|
|
||||||
} catch (BadTokenException e) {
|
|
||||||
Log.w(TAG, "Could not display dialog", e);
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
Log.w(TAG, "Could not display dialog", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The equivalent of {@link Dialog#dismiss()}, but for a specific dialog
|
|
||||||
* instance.
|
|
||||||
*/
|
|
||||||
public static void dismissDialogSafely(Activity activity, final Dialog dialog) {
|
|
||||||
if (activity.isFinishing()) {
|
|
||||||
Log.w(TAG, "Activity finishing - not dismissing dialog");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
dialog.dismiss();
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// This will be thrown if this dialog was not shown before.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private DialogManager() {}
|
|
||||||
}
|
|
||||||
@@ -1,31 +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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface that allows to set a progress dialog message and progress value
|
|
||||||
* in percent.
|
|
||||||
*
|
|
||||||
* @author Leif Hendrik Wilden
|
|
||||||
*/
|
|
||||||
public interface ProgressIndicator {
|
|
||||||
|
|
||||||
public void setProgressMessage(String message);
|
|
||||||
public void clearProgressMessage();
|
|
||||||
|
|
||||||
public void setProgressValue(int percent);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,93 +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 android.content.Intent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This interface describes a class that will fetch and maintain a Google
|
|
||||||
* authentication token.
|
|
||||||
*
|
|
||||||
* @author Sandor Dornbush
|
|
||||||
*/
|
|
||||||
public interface AuthManager {
|
|
||||||
/**
|
|
||||||
* Callback for authentication token retrieval operations.
|
|
||||||
*/
|
|
||||||
public interface AuthCallback {
|
|
||||||
/**
|
|
||||||
* Indicates that we're done fetching an auth token.
|
|
||||||
*
|
|
||||||
* @param success if true, indicates we have the requested auth token available
|
|
||||||
* to be retrieved using {@link AuthManager#getAuthToken}
|
|
||||||
*/
|
|
||||||
void onAuthResult(boolean success);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the login process. The user should be asked to login if they
|
|
||||||
* haven't already. The {@link AuthCallback} provided will be executed when the
|
|
||||||
* auth token fetching is done (successfully or not).
|
|
||||||
*
|
|
||||||
* @param whenFinished A {@link AuthCallback} to execute when the auth token
|
|
||||||
* fetching is done
|
|
||||||
*/
|
|
||||||
void doLogin(AuthCallback whenFinished, Object o);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link android.app.Activity} owner of this class should call this
|
|
||||||
* function when it gets {@link android.app.Activity#onActivityResult} with
|
|
||||||
* the request code passed into the constructor. The resultCode and results
|
|
||||||
* should come directly from the {@link android.app.Activity#onActivityResult}
|
|
||||||
* function. This function will return true if an auth token was successfully
|
|
||||||
* fetched or the process is not finished.
|
|
||||||
*
|
|
||||||
* @param resultCode The result code passed in to the
|
|
||||||
* {@link android.app.Activity}'s
|
|
||||||
* {@link android.app.Activity#onActivityResult} function
|
|
||||||
* @param results The data passed in to the {@link android.app.Activity}'s
|
|
||||||
* {@link android.app.Activity#onActivityResult} function
|
|
||||||
*/
|
|
||||||
void authResult(int resultCode, Intent results);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current auth token. Response may be null if no valid auth
|
|
||||||
* token has been fetched.
|
|
||||||
*
|
|
||||||
* @return The current auth token or null if no auth token has been
|
|
||||||
* fetched
|
|
||||||
*/
|
|
||||||
String getAuthToken();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invalidates the existing auth token and request a new one. The
|
|
||||||
* {@link Runnable} provided will be executed when the new auth token is
|
|
||||||
* successfully fetched.
|
|
||||||
*
|
|
||||||
* @param whenFinished A {@link Runnable} to execute when a new auth token
|
|
||||||
* is successfully fetched
|
|
||||||
*/
|
|
||||||
void invalidateAndRefresh(AuthCallback whenFinished);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an object that represents the given account, if possible.
|
|
||||||
*
|
|
||||||
* @param accountName the name of the account
|
|
||||||
* @param accountType the type of the account
|
|
||||||
* @return the account object
|
|
||||||
*/
|
|
||||||
Object getAccountObject(String accountName, String accountType);
|
|
||||||
}
|
|
||||||
@@ -1,57 +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 android.app.Activity;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A factory for getting the platform specific AuthManager.
|
|
||||||
*
|
|
||||||
* @author Sandor Dornbush
|
|
||||||
*/
|
|
||||||
public class AuthManagerFactory {
|
|
||||||
|
|
||||||
private AuthManagerFactory() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the modern AuthManager should be used
|
|
||||||
*/
|
|
||||||
public static boolean useModernAuthManager() {
|
|
||||||
return Integer.parseInt(Build.VERSION.SDK) >= 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a right {@link AuthManager} for the platform.
|
|
||||||
* @return A new AuthManager
|
|
||||||
*/
|
|
||||||
public static AuthManager getAuthManager(Activity activity, int code,
|
|
||||||
Bundle extras, boolean requireGoogle, String service) {
|
|
||||||
if (useModernAuthManager()) {
|
|
||||||
Log.i(TAG, "Creating modern auth manager: " + service);
|
|
||||||
return new ModernAuthManager(activity, service);
|
|
||||||
} else {
|
|
||||||
Log.i(TAG, "Creating legacy auth manager: " + service);
|
|
||||||
return new AuthManagerOld(activity, code, extras, requireGoogle, service);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2009 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
package com.google.android.apps.mytracks.io;
|
|
||||||
|
|
||||||
import com.google.android.googlelogindist.GoogleLoginServiceConstants;
|
|
||||||
import com.google.android.googlelogindist.GoogleLoginServiceHelper;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AuthManager keeps track of the current auth token for a user. The advantage
|
|
||||||
* over just passing around a String is that this class can renew the auth
|
|
||||||
* token if necessary, and it will change for all classes using this
|
|
||||||
* AuthManager.
|
|
||||||
*/
|
|
||||||
public class AuthManagerOld implements AuthManager {
|
|
||||||
/** The activity that will handle auth result callbacks. */
|
|
||||||
private final Activity activity;
|
|
||||||
|
|
||||||
/** The code used to tell the activity that it is an auth result. */
|
|
||||||
private final int code;
|
|
||||||
|
|
||||||
/** Extras to pass into the getCredentials function. */
|
|
||||||
private final Bundle extras;
|
|
||||||
|
|
||||||
/** True if the account must be a Google account (not a domain account). */
|
|
||||||
private final boolean requireGoogle;
|
|
||||||
|
|
||||||
/** The name of the service to authorize for. */
|
|
||||||
private final String service;
|
|
||||||
|
|
||||||
/** The handler to call when a new auth token is fetched. */
|
|
||||||
private AuthCallback authCallback;
|
|
||||||
|
|
||||||
/** The most recently fetched auth token or null if none is available. */
|
|
||||||
private String authToken;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AuthManager requires many of the same parameters as
|
|
||||||
* {@link GoogleLoginServiceHelper#getCredentials(Activity, int, Bundle,
|
|
||||||
* boolean, String, boolean)}. The activity must have
|
|
||||||
* a handler in {@link Activity#onActivityResult} that calls
|
|
||||||
* {@link #authResult(int, Intent)} if the request code is the code given
|
|
||||||
* here.
|
|
||||||
*
|
|
||||||
* @param activity An activity with a handler in
|
|
||||||
* {@link Activity#onActivityResult} that calls
|
|
||||||
* {@link #authResult(int, Intent)} when {@literal code} is the request
|
|
||||||
* code
|
|
||||||
* @param code The request code to pass to
|
|
||||||
* {@link Activity#onActivityResult} when
|
|
||||||
* {@link #authResult(int, Intent)} should be called
|
|
||||||
* @param extras A {@link Bundle} of extras for
|
|
||||||
* {@link GoogleLoginServiceHelper}
|
|
||||||
* @param requireGoogle True if the account must be a Google account
|
|
||||||
* @param service The name of the service to authenticate as
|
|
||||||
*/
|
|
||||||
public AuthManagerOld(Activity activity, int code, Bundle extras,
|
|
||||||
boolean requireGoogle, String service) {
|
|
||||||
this.activity = activity;
|
|
||||||
this.code = code;
|
|
||||||
this.extras = extras;
|
|
||||||
this.requireGoogle = requireGoogle;
|
|
||||||
this.service = service;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doLogin(AuthCallback callback, Object o) {
|
|
||||||
authCallback = callback;
|
|
||||||
activity.runOnUiThread(new LoginRunnable());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runnable which actually gets login credentials.
|
|
||||||
*/
|
|
||||||
private class LoginRunnable implements Runnable {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
GoogleLoginServiceHelper.getCredentials(
|
|
||||||
activity, code, extras, requireGoogle, service, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void authResult(int resultCode, Intent results) {
|
|
||||||
if (resultCode != Activity.RESULT_OK) {
|
|
||||||
runAuthCallback(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
authToken = results.getStringExtra(GoogleLoginServiceConstants.AUTHTOKEN_KEY);
|
|
||||||
if (authToken == null) {
|
|
||||||
// Retry, without prompting the user.
|
|
||||||
GoogleLoginServiceHelper.getCredentials(
|
|
||||||
activity, code, extras, requireGoogle, service, false);
|
|
||||||
} else {
|
|
||||||
// Notify all active listeners that we have a new auth token.
|
|
||||||
runAuthCallback(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void runAuthCallback(boolean success) {
|
|
||||||
authCallback.onAuthResult(success);
|
|
||||||
authCallback = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAuthToken() {
|
|
||||||
return authToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void invalidateAndRefresh(AuthCallback callback) {
|
|
||||||
authCallback = callback;
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
GoogleLoginServiceHelper.invalidateAuthToken(activity, code, authToken);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getAccountObject(String accountName, String accountType) {
|
|
||||||
throw new UnsupportedOperationException("Legacy auth manager knows nothing about accounts");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,217 +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.ACCOUNT_TYPE;
|
|
||||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.AccountChooser;
|
|
||||||
|
|
||||||
import android.accounts.Account;
|
|
||||||
import android.accounts.AccountManager;
|
|
||||||
import android.accounts.AccountManagerCallback;
|
|
||||||
import android.accounts.AccountManagerFuture;
|
|
||||||
import android.accounts.AuthenticatorException;
|
|
||||||
import android.accounts.OperationCanceledException;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AuthManager keeps track of the current auth token for a user. The advantage
|
|
||||||
* over just passing around a String is that this class can renew the auth
|
|
||||||
* token if necessary, and it will change for all classes using this
|
|
||||||
* AuthManager.
|
|
||||||
*/
|
|
||||||
public class ModernAuthManager implements AuthManager {
|
|
||||||
/** The activity that will handle auth result callbacks. */
|
|
||||||
private final Activity activity;
|
|
||||||
|
|
||||||
/** The name of the service to authorize for. */
|
|
||||||
private final String service;
|
|
||||||
|
|
||||||
/** The most recently fetched auth token or null if none is available. */
|
|
||||||
private String authToken;
|
|
||||||
|
|
||||||
private final AccountManager accountManager;
|
|
||||||
|
|
||||||
private AuthCallback authCallback;
|
|
||||||
|
|
||||||
private Account lastAccount;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AuthManager requires many of the same parameters as
|
|
||||||
* {@link com.google.android.googlelogindist.GoogleLoginServiceHelper
|
|
||||||
* #getCredentials(Activity, int, Bundle, boolean, String, boolean)}.
|
|
||||||
* The activity must have a handler in {@link Activity#onActivityResult} that
|
|
||||||
* calls {@link #authResult(int, Intent)} if the request code is the code
|
|
||||||
* given here.
|
|
||||||
*
|
|
||||||
* @param activity An activity with a handler in
|
|
||||||
* {@link Activity#onActivityResult} that calls
|
|
||||||
* {@link #authResult(int, Intent)} when {@literal code} is the request
|
|
||||||
* code
|
|
||||||
* @param service The name of the service to authenticate as
|
|
||||||
*/
|
|
||||||
public ModernAuthManager(Activity activity, String service) {
|
|
||||||
this.activity = activity;
|
|
||||||
this.service = service;
|
|
||||||
this.accountManager = AccountManager.get(activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call this to do the initial login. The user will be asked to login if
|
|
||||||
* they haven't already. The {@link Runnable} provided will be executed
|
|
||||||
* when the auth token is successfully fetched.
|
|
||||||
*
|
|
||||||
* @param runnable A {@link Runnable} to execute when the auth token
|
|
||||||
* has been successfully fetched and is available via
|
|
||||||
* {@link #getAuthToken()}
|
|
||||||
*/
|
|
||||||
public void doLogin(AuthCallback runnable, Object o) {
|
|
||||||
this.authCallback = runnable;
|
|
||||||
if (!(o instanceof Account)) {
|
|
||||||
throw new IllegalArgumentException("ModernAuthManager requires an account.");
|
|
||||||
}
|
|
||||||
Account account = (Account) o;
|
|
||||||
doLogin(account);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doLogin(Account account) {
|
|
||||||
// Keep the account in case we need to retry.
|
|
||||||
this.lastAccount = account;
|
|
||||||
|
|
||||||
// NOTE: Many Samsung phones have a crashing bug in
|
|
||||||
// AccountManager#getAuthToken(Account, String, boolean, AccountManagerCallback<Bundle>)
|
|
||||||
// so we use the other version of the method.
|
|
||||||
// More details here:
|
|
||||||
// http://forum.xda-developers.com/showthread.php?p=15155487
|
|
||||||
// http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=core/java/android/accounts/AccountManagerService.java
|
|
||||||
accountManager.getAuthToken(account, service, null, activity,
|
|
||||||
new AccountManagerCallback<Bundle>() {
|
|
||||||
public void run(AccountManagerFuture<Bundle> future) {
|
|
||||||
try {
|
|
||||||
authToken = future.getResult().getString(
|
|
||||||
AccountManager.KEY_AUTHTOKEN);
|
|
||||||
Log.i(TAG, "Got auth token");
|
|
||||||
} catch (OperationCanceledException e) {
|
|
||||||
Log.e(TAG, "Auth token operation Canceled", e);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "Auth token IO exception", e);
|
|
||||||
} catch (AuthenticatorException e) {
|
|
||||||
Log.e(TAG, "Authentication Failed", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
runAuthCallback();
|
|
||||||
}
|
|
||||||
}, null /* handler */);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link Activity} passed into the constructor should call this
|
|
||||||
* function when it gets {@link Activity#onActivityResult} with the request
|
|
||||||
* code passed into the constructor. The resultCode and results should
|
|
||||||
* come directly from the {@link Activity#onActivityResult} function. This
|
|
||||||
* function will return true if an auth token was successfully fetched or
|
|
||||||
* the process is not finished.
|
|
||||||
*
|
|
||||||
* @param resultCode The result code passed in to the {@link Activity}'s
|
|
||||||
* {@link Activity#onActivityResult} function
|
|
||||||
* @param results The data passed in to the {@link Activity}'s
|
|
||||||
* {@link Activity#onActivityResult} function
|
|
||||||
*/
|
|
||||||
public void authResult(int resultCode, Intent results) {
|
|
||||||
boolean retry = false;
|
|
||||||
if (results == null) {
|
|
||||||
Log.e(TAG, "No auth token!!");
|
|
||||||
} else {
|
|
||||||
authToken = results.getStringExtra(AccountManager.KEY_AUTHTOKEN);
|
|
||||||
retry = results.getBooleanExtra("retry", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authToken == null && retry) {
|
|
||||||
Log.i(TAG, "Retrying to get auth result");
|
|
||||||
doLogin(lastAccount);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
runAuthCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current auth token. Response may be null if no valid auth
|
|
||||||
* token has been fetched.
|
|
||||||
*
|
|
||||||
* @return The current auth token or null if no auth token has been
|
|
||||||
* fetched
|
|
||||||
*/
|
|
||||||
public String getAuthToken() {
|
|
||||||
return authToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invalidates the existing auth token and request a new one. The callback
|
|
||||||
* provided will be executed when the new auth token is successfully fetched.
|
|
||||||
*
|
|
||||||
* @param callback A callback to execute when a new auth token is successfully
|
|
||||||
* fetched
|
|
||||||
*/
|
|
||||||
public void invalidateAndRefresh(final AuthCallback callback) {
|
|
||||||
this.authCallback = callback;
|
|
||||||
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
accountManager.invalidateAuthToken(ACCOUNT_TYPE, authToken);
|
|
||||||
authToken = null;
|
|
||||||
|
|
||||||
AccountChooser accountChooser = new AccountChooser();
|
|
||||||
accountChooser.chooseAccount(activity,
|
|
||||||
new AccountChooser.AccountHandler() {
|
|
||||||
@Override
|
|
||||||
public void onAccountSelected(Account account) {
|
|
||||||
if (account != null) {
|
|
||||||
doLogin(account);
|
|
||||||
} else {
|
|
||||||
runAuthCallback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void runAuthCallback() {
|
|
||||||
lastAccount = null;
|
|
||||||
|
|
||||||
if (authCallback != null) {
|
|
||||||
(new Thread() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
authCallback.onAuthResult(authToken != null);
|
|
||||||
authCallback = null;
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getAccountObject(String accountName, String accountType) {
|
|
||||||
return new Account(accountName, accountType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,457 +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 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.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;
|
|
||||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
|
||||||
import com.google.android.apps.mytracks.util.StringUtils;
|
|
||||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
|
||||||
import com.google.android.maps.mytracks.R;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.location.Location;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Vector;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
* the Google Maps servers through this API is only allowed for the My Tracks
|
|
||||||
* application. Other applications looking to upload maps data should look
|
|
||||||
* into using the Fusion Tables API.
|
|
||||||
*
|
|
||||||
* @author Leif Hendrik Wilden
|
|
||||||
*/
|
|
||||||
public class SendToMaps implements Runnable {
|
|
||||||
|
|
||||||
public static final String NEW_MAP_ID = "new";
|
|
||||||
private static final int MAX_POINTS_PER_UPLOAD = 2048;
|
|
||||||
|
|
||||||
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;
|
|
||||||
private String mapId;
|
|
||||||
|
|
||||||
private MapsFacade mapsClient;
|
|
||||||
|
|
||||||
// Progress status
|
|
||||||
private int totalLocationsRead;
|
|
||||||
private int totalLocationsPrepared;
|
|
||||||
private int totalLocationsUploaded;
|
|
||||||
private int totalLocations;
|
|
||||||
private int totalSegmentsUploaded;
|
|
||||||
|
|
||||||
public interface OnSendCompletedListener {
|
|
||||||
void onSendCompleted(String mapId, boolean success);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SendToMaps(Activity context, String mapId, AuthManager auth,
|
|
||||||
long trackId, ProgressIndicator progressIndicator,
|
|
||||||
OnSendCompletedListener onCompletion) {
|
|
||||||
this.context = context;
|
|
||||||
this.mapId = mapId;
|
|
||||||
this.auth = auth;
|
|
||||||
this.trackId = trackId;
|
|
||||||
this.progressIndicator = progressIndicator;
|
|
||||||
this.onCompletion = onCompletion;
|
|
||||||
this.stringUtils = new StringUtils(context);
|
|
||||||
this.providerUtils = MyTracksProviderUtils.Factory.get(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Log.d(TAG, "Sending to Maps: trackId = " + trackId);
|
|
||||||
doUpload();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doUpload() {
|
|
||||||
boolean success = true;
|
|
||||||
try {
|
|
||||||
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(Constants.TAG, "Cannot get track.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String originalDescription = track.getDescription();
|
|
||||||
track.setDescription("<p>" + track.getDescription() + "</p><p>"
|
|
||||||
+ stringUtils.generateTrackDescription(track, null, null) + "</p>");
|
|
||||||
|
|
||||||
mapsClient = new MapsFacade(context, auth);
|
|
||||||
|
|
||||||
// Create a new map if necessary:
|
|
||||||
boolean isNewMap = mapId.equals(NEW_MAP_ID);
|
|
||||||
if (isNewMap) {
|
|
||||||
SharedPreferences preferences = context.getSharedPreferences(
|
|
||||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
|
||||||
boolean mapPublic = true;
|
|
||||||
if (preferences != null) {
|
|
||||||
mapPublic = preferences.getBoolean(
|
|
||||||
context.getString(R.string.default_map_public_key), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
String creatingFormat = context.getString(R.string.send_google_progress_creating);
|
|
||||||
String serviceName = context.getString(SendType.MAPS.getServiceName());
|
|
||||||
progressIndicator.setProgressMessage(String.format(creatingFormat, serviceName));
|
|
||||||
|
|
||||||
StringBuilder mapIdBuilder = new StringBuilder();
|
|
||||||
success = mapsClient.createNewMap(
|
|
||||||
track.getName(), track.getCategory(), originalDescription, mapPublic, mapIdBuilder);
|
|
||||||
mapId = mapIdBuilder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload all of the segments of the track plus start/end markers
|
|
||||||
if (success) {
|
|
||||||
success = uploadAllTrackPoints(track, originalDescription);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put waypoints.
|
|
||||||
if (success) {
|
|
||||||
Cursor c = providerUtils.getWaypointsCursor(
|
|
||||||
track.getId(), 0,
|
|
||||||
Constants.MAX_LOADED_WAYPOINTS_POINTS);
|
|
||||||
if (c != null) {
|
|
||||||
try {
|
|
||||||
if (c.getCount() > 1 && c.moveToFirst()) {
|
|
||||||
// This will skip the 1st waypoint (it carries the stats for the
|
|
||||||
// last segment).
|
|
||||||
ArrayList<Waypoint> waypoints = new ArrayList<Waypoint>(c.getCount() - 1);
|
|
||||||
while (c.moveToNext()) {
|
|
||||||
Waypoint wpt = providerUtils.createWaypoint(c);
|
|
||||||
waypoints.add(wpt);
|
|
||||||
}
|
|
||||||
|
|
||||||
success = mapsClient.uploadWaypoints(mapId, waypoints);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
Log.w(TAG, "SendToMaps: upload waypoints failed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG, "SendToMaps: Done: " + success);
|
|
||||||
progressIndicator.setProgressValue(100);
|
|
||||||
} finally {
|
|
||||||
if (mapsClient != null) {
|
|
||||||
mapsClient.cleanUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
final boolean finalSuccess = success;
|
|
||||||
context.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
if (onCompletion != null) {
|
|
||||||
onCompletion.onSendCompleted(mapId, finalSuccess);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
progressIndicator.setProgressMessage(
|
|
||||||
context.getString(R.string.send_google_progress_reading_track));
|
|
||||||
Location lastLocation = null;
|
|
||||||
do {
|
|
||||||
if (totalLocationsRead % 100 == 0) {
|
|
||||||
updateProgress();
|
|
||||||
}
|
|
||||||
|
|
||||||
Location loc = providerUtils.createLocation(locationsCursor);
|
|
||||||
locations.add(loc);
|
|
||||||
|
|
||||||
if (totalLocationsRead == 0) {
|
|
||||||
// Put a marker at the first point of the first valid segment:
|
|
||||||
mapsClient.uploadMarker(mapId, track.getName(), track.getDescription(), loc, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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>");
|
|
||||||
return mapsClient.uploadMarker(mapId, track.getName(), track.getDescription(),
|
|
||||||
lastLocation, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} finally {
|
|
||||||
if (locationsCursor != null) {
|
|
||||||
locationsCursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private boolean prepareAndUploadPoints(Track track, List<Location> locations) {
|
|
||||||
progressIndicator.setProgressMessage(
|
|
||||||
context.getString(R.string.send_google_progress_preparing_track));
|
|
||||||
updateProgress();
|
|
||||||
|
|
||||||
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
|
|
||||||
String sendingFormat = context.getString(R.string.send_google_progress_sending);
|
|
||||||
String serviceName = context.getString(SendType.MAPS.getServiceName());
|
|
||||||
progressIndicator.setProgressMessage(String.format(sendingFormat, serviceName));
|
|
||||||
for (Track splitTrack : splitTracks) {
|
|
||||||
if (totalSegmentsUploaded > 1) {
|
|
||||||
splitTrack.setName(context.getString(
|
|
||||||
R.string.send_google_track_part_label, splitTrack.getName(), totalSegmentsUploaded));
|
|
||||||
}
|
|
||||||
totalSegmentsUploaded++;
|
|
||||||
Log.d(TAG,
|
|
||||||
"SendToMaps: Prepared feature for upload w/ "
|
|
||||||
+ splitTrack.getLocations().size() + " points.");
|
|
||||||
|
|
||||||
// Transmit tracks via GData feed:
|
|
||||||
// -------------------------------
|
|
||||||
Log.d(TAG,
|
|
||||||
"SendToMaps: Uploading to map " + mapId + " w/ auth " + auth);
|
|
||||||
if (!mapsClient.uploadTrackPoints(mapId, splitTrack.getName(), splitTrack.getLocations())) {
|
|
||||||
Log.e(TAG, "Uploading failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
locations.clear();
|
|
||||||
totalLocationsUploaded += numLocations;
|
|
||||||
updateProgress();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepares a buffer of locations for transmission to google maps.
|
|
||||||
*
|
|
||||||
* @param track the original track with meta data
|
|
||||||
* @param locations a buffer of locations on the track
|
|
||||||
* @return an array of tracks each with a sub section of the points in the
|
|
||||||
* original buffer
|
|
||||||
*/
|
|
||||||
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) {
|
|
||||||
updateProgress();
|
|
||||||
}
|
|
||||||
if (loc.getLatitude() > 90) {
|
|
||||||
startNewTrackSegment = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startNewTrackSegment) {
|
|
||||||
// Close up the last segment.
|
|
||||||
prepareTrackSegment(segment, splitTracks);
|
|
||||||
|
|
||||||
Log.d(TAG,
|
|
||||||
"MyTracksSendToMaps: 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 maps.
|
|
||||||
* 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. Mapshop doesn't like too many
|
|
||||||
* points:
|
|
||||||
*/
|
|
||||||
LocationUtils.decimate(segment, 2.0);
|
|
||||||
|
|
||||||
/* It the track still has > 500 points, split it in pieces: */
|
|
||||||
if (segment.getLocations().size() > 500) {
|
|
||||||
splitTracks.addAll(LocationUtils.split(segment, 500));
|
|
||||||
} else if (segment.getLocations().size() >= 2) {
|
|
||||||
splitTracks.add(segment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the current upload progress.
|
|
||||||
*/
|
|
||||||
private void updateProgress() {
|
|
||||||
// The percent of the total that represents the completed part of this
|
|
||||||
// segment.
|
|
||||||
int totalPercentage =
|
|
||||||
(totalLocationsRead + totalLocationsPrepared + totalLocationsUploaded) * 100
|
|
||||||
/ (totalLocations * 3);
|
|
||||||
totalPercentage = Math.min(99, totalPercentage);
|
|
||||||
progressIndicator.setProgressValue(totalPercentage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,309 +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.gdata;
|
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.Constants;
|
|
||||||
import com.google.android.apps.mytracks.io.AuthManager;
|
|
||||||
import com.google.android.apps.mytracks.io.AuthManager.AuthCallback;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.FutureTask;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GDataWrapper 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.
|
|
||||||
*
|
|
||||||
* @param <C> the GData service client
|
|
||||||
* @author Sandor Dornbush
|
|
||||||
*/
|
|
||||||
public class GDataWrapper<C> {
|
|
||||||
|
|
||||||
public static class AuthenticationException extends Exception {
|
|
||||||
private Exception exception;
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
public AuthenticationException(Exception caught) {
|
|
||||||
this.exception = caught;
|
|
||||||
}
|
|
||||||
public Exception getException() {
|
|
||||||
return exception;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public static class ParseException extends Exception {
|
|
||||||
private Exception exception;
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
public ParseException(Exception caught) {
|
|
||||||
this.exception = caught;
|
|
||||||
}
|
|
||||||
public Exception getException() {
|
|
||||||
return exception;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public static class ConflictDetectedException extends Exception {
|
|
||||||
private Exception exception;
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
public ConflictDetectedException(Exception caught) {
|
|
||||||
this.exception = caught;
|
|
||||||
}
|
|
||||||
public Exception getException() {
|
|
||||||
return exception;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public static class HttpException extends Exception {
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
private int statusCode;
|
|
||||||
private String statusMessage;
|
|
||||||
public HttpException(int statusCode, String statusMessage) {
|
|
||||||
super();
|
|
||||||
this.statusCode = statusCode;
|
|
||||||
this.statusMessage = statusMessage;
|
|
||||||
}
|
|
||||||
public int getStatusCode() {
|
|
||||||
return statusCode;
|
|
||||||
}
|
|
||||||
public String getStatusMessage() {
|
|
||||||
return statusMessage;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A QueryFunction is passed in when executing a query. The query function of
|
|
||||||
* the class is called with the GData client as a parameter. The function
|
|
||||||
* should execute whatever operations it desires on the client without concern
|
|
||||||
* for whether the client will throw an error.
|
|
||||||
*/
|
|
||||||
public interface QueryFunction<C> {
|
|
||||||
public abstract void query(C client)
|
|
||||||
throws AuthenticationException, IOException, ParseException,
|
|
||||||
ConflictDetectedException, HttpException;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A AuthenticatedFunction is passed in when executing the google
|
|
||||||
* authenticated service. The authenticated function of the class is called
|
|
||||||
* with the current authentication token for the service. The function should
|
|
||||||
* execute whatever operations with the google service without concern for
|
|
||||||
* whether the client will throw an error.
|
|
||||||
*/
|
|
||||||
public interface AuthenticatedFunction {
|
|
||||||
public abstract void run(String authenticationToken)
|
|
||||||
throws AuthenticationException, IOException;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The types of error that may be encountered
|
|
||||||
// No error occurred.
|
|
||||||
public static final int ERROR_NO_ERROR = 0;
|
|
||||||
// There was an authentication error, the auth token may be invalid.
|
|
||||||
public static final int ERROR_AUTH = 1;
|
|
||||||
// There was an internal error on the server side.
|
|
||||||
public static final int ERROR_INTERNAL = 2;
|
|
||||||
// There was an error connecting to the server.
|
|
||||||
public static final int ERROR_CONNECTION = 3;
|
|
||||||
// The item queried did not exit.
|
|
||||||
public static final int ERROR_NOT_FOUND = 4;
|
|
||||||
// There was an error parsing or serializing locally.
|
|
||||||
public static final int ERROR_LOCAL = 5;
|
|
||||||
// There was a conflict, update the entry and try again.
|
|
||||||
public static final int ERROR_CONFLICT = 6;
|
|
||||||
// A query was run after cleaning up the wrapper, so the client was invalid.
|
|
||||||
public static final int ERROR_CLEANED_UP = 7;
|
|
||||||
// An unknown error occurred.
|
|
||||||
public static final int ERROR_UNKNOWN = 100;
|
|
||||||
|
|
||||||
private static final int AUTH_TOKEN_INVALIDATE_REFRESH_NUM_RETRIES = 1;
|
|
||||||
private static final int AUTH_TOKEN_INVALIDATE_REFRESH_TIMEOUT = 5000;
|
|
||||||
|
|
||||||
private String errorMessage;
|
|
||||||
private int errorType;
|
|
||||||
private C gdataServiceClient;
|
|
||||||
private AuthManager auth;
|
|
||||||
private boolean retryOnAuthFailure;
|
|
||||||
|
|
||||||
public GDataWrapper() {
|
|
||||||
errorType = ERROR_NO_ERROR;
|
|
||||||
errorMessage = null;
|
|
||||||
auth = null;
|
|
||||||
retryOnAuthFailure = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setClient(C gdataServiceClient) {
|
|
||||||
this.gdataServiceClient = gdataServiceClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean runAuthenticatedFunction(
|
|
||||||
final AuthenticatedFunction function) {
|
|
||||||
return runCommon(function, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean runQuery(final QueryFunction<C> query) {
|
|
||||||
return runCommon(null, query);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs an arbitrary piece of code.
|
|
||||||
*/
|
|
||||||
private boolean runCommon(final AuthenticatedFunction function,
|
|
||||||
final QueryFunction<C> query) {
|
|
||||||
for (int i = 0; i <= AUTH_TOKEN_INVALIDATE_REFRESH_NUM_RETRIES; i++) {
|
|
||||||
runOne(function, query);
|
|
||||||
if (errorType == ERROR_NO_ERROR) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(Constants.TAG, "GData error encountered: " + errorMessage);
|
|
||||||
if (errorType == ERROR_AUTH && auth != null) {
|
|
||||||
if (!retryOnAuthFailure || !invalidateAndRefreshAuthToken()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(Constants.TAG, "retrying function/query");
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute a given function or query. If one is executed, errorType and
|
|
||||||
* errorMessage will contain the result/status of the function/query.
|
|
||||||
*/
|
|
||||||
private void runOne(final AuthenticatedFunction function,
|
|
||||||
final QueryFunction<C> query) {
|
|
||||||
try {
|
|
||||||
if (function != null) {
|
|
||||||
function.run(this.auth.getAuthToken());
|
|
||||||
} else if (query != null) {
|
|
||||||
query.query(gdataServiceClient);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"invalid invocation of runOne; one of function/query " +
|
|
||||||
"must be non-null");
|
|
||||||
}
|
|
||||||
|
|
||||||
errorType = ERROR_NO_ERROR;
|
|
||||||
errorMessage = null;
|
|
||||||
|
|
||||||
} catch (AuthenticationException e) {
|
|
||||||
Log.e(Constants.TAG, "AuthenticationException", e);
|
|
||||||
errorType = ERROR_AUTH;
|
|
||||||
errorMessage = e.getMessage();
|
|
||||||
} catch (HttpException e) {
|
|
||||||
Log.e(Constants.TAG,
|
|
||||||
"HttpException, code " + e.getStatusCode() + " message " + e.getMessage(), e);
|
|
||||||
errorMessage = e.getMessage();
|
|
||||||
if (e.getStatusCode() == 401) {
|
|
||||||
errorType = ERROR_AUTH;
|
|
||||||
} else {
|
|
||||||
errorType = ERROR_CONNECTION;
|
|
||||||
}
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
Log.e(Constants.TAG, "Exception", e);
|
|
||||||
errorType = ERROR_AUTH;
|
|
||||||
errorMessage = e.getMessage();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(Constants.TAG, "Exception", e);
|
|
||||||
errorMessage = e.getMessage();
|
|
||||||
if (errorMessage != null && errorMessage.contains("503")) {
|
|
||||||
errorType = ERROR_INTERNAL;
|
|
||||||
} else {
|
|
||||||
errorType = ERROR_CONNECTION;
|
|
||||||
}
|
|
||||||
} catch (ParseException e) {
|
|
||||||
Log.e(Constants.TAG, "Exception", e);
|
|
||||||
errorType = ERROR_LOCAL;
|
|
||||||
errorMessage = e.getMessage();
|
|
||||||
} catch (ConflictDetectedException e) {
|
|
||||||
Log.e(Constants.TAG, "Exception", e);
|
|
||||||
errorType = ERROR_CONFLICT;
|
|
||||||
errorMessage = e.getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invalidates and refreshes the auth token. Blocks until the refresh has
|
|
||||||
* completed or until we deem the refresh as having timed out.
|
|
||||||
*
|
|
||||||
* @return true If the invalidate/refresh succeeds, false if it fails or
|
|
||||||
* times out.
|
|
||||||
*/
|
|
||||||
private boolean invalidateAndRefreshAuthToken() {
|
|
||||||
Log.d(Constants.TAG, "Retrying due to auth failure");
|
|
||||||
// This FutureTask doesn't do anything -- it exists simply to be
|
|
||||||
// blocked upon using get().
|
|
||||||
final FutureTask<?> whenFinishedFuture = new FutureTask<Object>(new Runnable() {
|
|
||||||
public void run() {}
|
|
||||||
}, null);
|
|
||||||
|
|
||||||
final AtomicBoolean finalSuccess = new AtomicBoolean(false);
|
|
||||||
auth.invalidateAndRefresh(new AuthCallback() {
|
|
||||||
@Override
|
|
||||||
public void onAuthResult(boolean success) {
|
|
||||||
finalSuccess.set(success);
|
|
||||||
whenFinishedFuture.run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
Log.d(Constants.TAG, "waiting for invalidate");
|
|
||||||
whenFinishedFuture.get(AUTH_TOKEN_INVALIDATE_REFRESH_TIMEOUT,
|
|
||||||
TimeUnit.MILLISECONDS);
|
|
||||||
boolean success = finalSuccess.get();
|
|
||||||
Log.d(Constants.TAG, "invalidate finished, success = " + success);
|
|
||||||
return success;
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.e(Constants.TAG, "Failed to invalidate", e);
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
Log.e(Constants.TAG, "Failed to invalidate", e);
|
|
||||||
} catch (TimeoutException e) {
|
|
||||||
Log.e(Constants.TAG, "Invalidate didn't complete in time", e);
|
|
||||||
} finally {
|
|
||||||
whenFinishedFuture.cancel(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getErrorType() {
|
|
||||||
return errorType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getErrorMessage() {
|
|
||||||
return errorMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAuthManager(AuthManager auth) {
|
|
||||||
this.auth = auth;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthManager getAuthManager() {
|
|
||||||
return auth;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRetryOnAuthFailure(boolean retry) {
|
|
||||||
retryOnAuthFailure = retry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,339 +0,0 @@
|
|||||||
// Copyright 2011 Google Inc. All Rights Reserved.
|
|
||||||
package com.google.android.apps.mytracks.io.maps;
|
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
|
||||||
import com.google.android.apps.mytracks.io.AuthManager;
|
|
||||||
import com.google.android.apps.mytracks.util.StringUtils;
|
|
||||||
import com.google.android.maps.GeoPoint;
|
|
||||||
import com.google.android.maps.mytracks.R;
|
|
||||||
import com.google.wireless.gdata.client.HttpException;
|
|
||||||
import com.google.wireless.gdata.data.Entry;
|
|
||||||
import com.google.wireless.gdata.parser.ParseException;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.location.Location;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Single interface which abstracts all access to the Google Maps service.
|
|
||||||
*
|
|
||||||
* @author Rodrigo Damazio
|
|
||||||
*/
|
|
||||||
public class MapsFacade {
|
|
||||||
|
|
||||||
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 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 MapsGDataWrapper(context, auth);
|
|
||||||
wrapper.setRetryOnAuthFailure(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
gdataConverter = new MapsGDataConverter();
|
|
||||||
} catch (XmlPullParserException e) {
|
|
||||||
throw new IllegalStateException("Unable to create maps data converter", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String buildMapUrl(String mapId) {
|
|
||||||
return MapsClient.buildMapUrl(mapId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new map for the current user.
|
|
||||||
*
|
|
||||||
* @param title title of the map
|
|
||||||
* @param category category of the map
|
|
||||||
* @param description description for the map
|
|
||||||
* @param isPublic whether the map should be public
|
|
||||||
* @param mapIdBuilder builder to append the map ID to
|
|
||||||
* @return true on success, false otherwise
|
|
||||||
*/
|
|
||||||
public boolean createNewMap(
|
|
||||||
final String title, final String category, final String description,
|
|
||||||
final boolean isPublic, final StringBuilder mapIdBuilder) {
|
|
||||||
if (mapIdBuilder.length() > 0) {
|
|
||||||
throw new IllegalArgumentException("mapIdBuilder should be empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
return wrapper.runQuery(new MapsGDataWrapper.QueryFunction() {
|
|
||||||
@Override
|
|
||||||
public void query(MapsClient client) throws IOException, Exception {
|
|
||||||
Log.d(MapsConstants.TAG, "Creating a new map.");
|
|
||||||
String mapFeed = MapsClient.getMapsFeed();
|
|
||||||
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 = 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(MapsConstants.TAG, "New map id is: " + mapId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uploads a single start or end marker to the given map.
|
|
||||||
*
|
|
||||||
* @param mapId ID of the map to upload to
|
|
||||||
* @param trackName name of the track being started/ended
|
|
||||||
* @param trackDescription description of the track being started/ended
|
|
||||||
* @param loc the location of the marker
|
|
||||||
* @param isStart true to add a start marker, false to add an end marker
|
|
||||||
* @return true on success, false otherwise
|
|
||||||
*/
|
|
||||||
public boolean uploadMarker(final String mapId, final String trackName,
|
|
||||||
final String trackDescription, final Location loc, final boolean isStart) {
|
|
||||||
return wrapper.runQuery(new MapsGDataWrapper.QueryFunction() {
|
|
||||||
@Override
|
|
||||||
public void query(MapsClient client)
|
|
||||||
throws IOException, Exception {
|
|
||||||
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
|
||||||
GeoPoint geoPoint = getGeoPoint(loc);
|
|
||||||
insertMarker(client, featureFeed, trackName, trackDescription, geoPoint, isStart);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts a place mark. Second try if 1st try fails. Will throw exception on
|
|
||||||
* 2nd failure.
|
|
||||||
*/
|
|
||||||
private void insertMarker(MapsClient client,
|
|
||||||
String featureFeed,
|
|
||||||
String trackName, String trackDescription,
|
|
||||||
GeoPoint geoPoint,
|
|
||||||
boolean isStart) throws IOException, Exception {
|
|
||||||
MapsFeature feature =
|
|
||||||
buildMapsPlacemarkFeature(trackName, trackDescription, geoPoint, isStart);
|
|
||||||
Entry entry = gdataConverter.getEntryForFeature(feature);
|
|
||||||
Log.d(MapsConstants.TAG, "SendToMaps: Creating placemark "
|
|
||||||
+ entry.getTitle());
|
|
||||||
try {
|
|
||||||
client.createEntry(featureFeed, authToken, entry);
|
|
||||||
Log.d(MapsConstants.TAG, "SendToMaps: createEntry success!");
|
|
||||||
} catch (IOException e) {
|
|
||||||
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(MapsConstants.TAG,
|
|
||||||
"SendToMaps: createEntry success on 2nd try!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 MapsFeature
|
|
||||||
*/
|
|
||||||
private MapsFeature buildMapsPlacemarkFeature(
|
|
||||||
String trackName, String trackDescription,
|
|
||||||
GeoPoint geoPoint, boolean isStart) {
|
|
||||||
String iconUrl;
|
|
||||||
if (isStart) {
|
|
||||||
iconUrl = START_ICON_URL;
|
|
||||||
} else {
|
|
||||||
iconUrl = END_ICON_URL;
|
|
||||||
}
|
|
||||||
String title = isStart ? context.getString(R.string.marker_label_start, trackName) :
|
|
||||||
context.getString(R.string.marker_label_end, trackName);
|
|
||||||
String description = isStart ? "" : trackDescription;
|
|
||||||
return buildMapsPlacemarkFeature(title, description, iconUrl, geoPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 MapsFeature
|
|
||||||
*/
|
|
||||||
private static MapsFeature buildMapsPlacemarkFeature(
|
|
||||||
String title, String description, String iconUrl, GeoPoint 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):
|
|
||||||
mapsFeature.setTitle("-");
|
|
||||||
} else {
|
|
||||||
mapsFeature.setTitle(title);
|
|
||||||
}
|
|
||||||
mapsFeature.setDescription(description.replaceAll("\n", "<br>"));
|
|
||||||
return mapsFeature;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uploads a series of waypoints to the given map.
|
|
||||||
*
|
|
||||||
* @param mapId ID of the map to upload to
|
|
||||||
* @param waypoints the waypoints to upload
|
|
||||||
* @return true on success, false otherwise
|
|
||||||
*/
|
|
||||||
public boolean uploadWaypoints(
|
|
||||||
final String mapId, final Iterable<Waypoint> waypoints) {
|
|
||||||
return wrapper.runQuery(new 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
|
|
||||||
// problem because we don't try to load them into objects all at the
|
|
||||||
// same time.
|
|
||||||
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (Waypoint waypoint : waypoints) {
|
|
||||||
MapsFeature feature = buildMapsPlacemarkFeature(
|
|
||||||
waypoint.getName(), waypoint.getDescription(), waypoint.getIcon(),
|
|
||||||
getGeoPoint(waypoint.getLocation()));
|
|
||||||
Entry entry = gdataConverter.getEntryForFeature(feature);
|
|
||||||
|
|
||||||
Log.d(MapsConstants.TAG,
|
|
||||||
"SendToMaps: Creating waypoint.");
|
|
||||||
try {
|
|
||||||
client.createEntry(featureFeed, authToken, entry);
|
|
||||||
Log.d(MapsConstants.TAG,
|
|
||||||
"SendToMaps: createEntry success!");
|
|
||||||
} catch (IOException e) {
|
|
||||||
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(MapsConstants.TAG,
|
|
||||||
"SendToMaps: createEntry success on 2nd try!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (ParseException e) {
|
|
||||||
Log.w(MapsConstants.TAG, "ParseException caught.", e);
|
|
||||||
} catch (HttpException e) {
|
|
||||||
Log.w(MapsConstants.TAG, "HttpException caught.", e);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(MapsConstants.TAG, "IOException caught.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uploads a series of points to the given map.
|
|
||||||
*
|
|
||||||
* @param mapId ID of the map to upload to
|
|
||||||
* @param trackName the name of the track
|
|
||||||
* @param locations the locations to upload
|
|
||||||
* @return true on success, false otherwise
|
|
||||||
*/
|
|
||||||
public boolean uploadTrackPoints(
|
|
||||||
final String mapId, final String trackName, final Collection<Location> locations) {
|
|
||||||
return wrapper.runQuery(new MapsGDataWrapper.QueryFunction() {
|
|
||||||
@Override
|
|
||||||
public void query(MapsClient client)
|
|
||||||
throws IOException, Exception {
|
|
||||||
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
|
||||||
Log.d(MapsConstants.TAG, "Feature feed url: " + featureFeed);
|
|
||||||
uploadTrackPoints(client, featureFeed, trackName, locations);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean uploadTrackPoints(
|
|
||||||
MapsClient client,
|
|
||||||
String featureFeed,
|
|
||||||
String trackName, Collection<Location> locations)
|
|
||||||
throws IOException, Exception {
|
|
||||||
Entry entry = null;
|
|
||||||
int numLocations = locations.size();
|
|
||||||
if (numLocations < 2) {
|
|
||||||
// Need at least two points for a polyline:
|
|
||||||
Log.w(MapsConstants.TAG, "Not uploading too few points");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put the line:
|
|
||||||
entry = gdataConverter.getEntryForFeature(
|
|
||||||
buildMapsLineFeature(trackName, locations));
|
|
||||||
Log.d(MapsConstants.TAG,
|
|
||||||
"SendToMaps: Creating line " + entry.getTitle());
|
|
||||||
try {
|
|
||||||
client.createEntry(featureFeed, authToken, entry);
|
|
||||||
Log.d(MapsConstants.TAG, "SendToMaps: createEntry success!");
|
|
||||||
} catch (IOException e) {
|
|
||||||
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(MapsConstants.TAG,
|
|
||||||
"SendToMaps: createEntry success on 2nd try!");
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a MapsFeature from a track.
|
|
||||||
*
|
|
||||||
* @param trackName the track name
|
|
||||||
* @param locations locations on the track
|
|
||||||
* @return a MapsFeature
|
|
||||||
*/
|
|
||||||
private static MapsFeature buildMapsLineFeature(String trackName,
|
|
||||||
Iterable<Location> locations) {
|
|
||||||
MapsFeature mapsFeature = new MapsFeature();
|
|
||||||
mapsFeature.generateAndroidId();
|
|
||||||
mapsFeature.setType(MapsFeature.LINE);
|
|
||||||
if (TextUtils.isEmpty(trackName)) {
|
|
||||||
// Features must have a name (otherwise GData upload may fail):
|
|
||||||
mapsFeature.setTitle("-");
|
|
||||||
} else {
|
|
||||||
mapsFeature.setTitle(trackName);
|
|
||||||
}
|
|
||||||
mapsFeature.setColor(0x80FF0000);
|
|
||||||
for (Location loc : locations) {
|
|
||||||
mapsFeature.addPoint(getGeoPoint(loc));
|
|
||||||
}
|
|
||||||
return mapsFeature;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleans up after a series of uploads.
|
|
||||||
* This closes the connection to Maps and resets retry counters.
|
|
||||||
*/
|
|
||||||
public void cleanUp() {
|
|
||||||
wrapper.cleanUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GeoPoint getGeoPoint(Location location) {
|
|
||||||
return new GeoPoint((int) (location.getLatitude() * 1E6),
|
|
||||||
(int) (location.getLongitude() * 1E6));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -52,7 +52,7 @@ class MapsGDataConverter {
|
|||||||
MapFeatureEntry entry = new MapFeatureEntry();
|
MapFeatureEntry entry = new MapFeatureEntry();
|
||||||
entry.setEditUri(metadata.getGDataEditUri());
|
entry.setEditUri(metadata.getGDataEditUri());
|
||||||
entry.setTitle(metadata.getTitle());
|
entry.setTitle(metadata.getTitle());
|
||||||
entry.setContent(metadata.getDescription());
|
entry.setSummary(metadata.getDescription());
|
||||||
entry.setPrivacy(metadata.getSearchable() ? "public" : "unlisted");
|
entry.setPrivacy(metadata.getSearchable() ? "public" : "unlisted");
|
||||||
entry.setAuthor("android");
|
entry.setAuthor("android");
|
||||||
entry.setEmail("nobody@google.com");
|
entry.setEmail("nobody@google.com");
|
||||||
|
|||||||
@@ -1,171 +0,0 @@
|
|||||||
// Copyright 2010 Google Inc. All Rights Reserved.
|
|
||||||
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;
|
|
||||||
import com.google.android.apps.mytracks.io.gdata.GDataClientFactory;
|
|
||||||
import com.google.android.common.gdata.AndroidXmlParserFactory;
|
|
||||||
import com.google.wireless.gdata.client.GDataClient;
|
|
||||||
import com.google.wireless.gdata.client.HttpException;
|
|
||||||
import com.google.wireless.gdata.parser.xml.SimplePullParser.ParseException;
|
|
||||||
import com.google.wireless.gdata2.ConflictDetectedException;
|
|
||||||
import com.google.wireless.gdata2.client.AuthenticationException;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 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
|
|
||||||
* function should execute whatever operations it desires on the client
|
|
||||||
* without concern for whether the client will throw an error.
|
|
||||||
*/
|
|
||||||
interface QueryFunction {
|
|
||||||
public abstract void query(MapsClient client)
|
|
||||||
throws AuthenticationException, IOException, ParseException,
|
|
||||||
ConflictDetectedException, Exception;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The types of error that may be encountered
|
|
||||||
/** No error occurred. */
|
|
||||||
public static final int ERROR_NO_ERROR = 0;
|
|
||||||
/** There was an authentication error, the auth token may be invalid. */
|
|
||||||
public static final int ERROR_AUTH = 1;
|
|
||||||
/** There was an internal error on the server side. */
|
|
||||||
public static final int ERROR_INTERNAL = 2;
|
|
||||||
/** There was an error connecting to the server. */
|
|
||||||
public static final int ERROR_CONNECTION = 3;
|
|
||||||
/** The item queried did not exit. */
|
|
||||||
public static final int ERROR_NOT_FOUND = 4;
|
|
||||||
/** There was an error parsing or serializing locally. */
|
|
||||||
public static final int ERROR_LOCAL = 5;
|
|
||||||
/** There was a conflict, update the entry and try again. */
|
|
||||||
public static final int ERROR_CONFLICT = 6;
|
|
||||||
/**
|
|
||||||
* A query was run after cleaning up the wrapper, so the client was invalid.
|
|
||||||
*/
|
|
||||||
public static final int ERROR_CLEANED_UP = 7;
|
|
||||||
/** An unknown error occurred. */
|
|
||||||
public static final int ERROR_UNKNOWN = 100;
|
|
||||||
|
|
||||||
private final GDataClient androidGdataClient;
|
|
||||||
private final AuthManager auth;
|
|
||||||
private final MapsClient client;
|
|
||||||
|
|
||||||
private String errorMessage;
|
|
||||||
private int errorType;
|
|
||||||
private boolean retryOnAuthFailure;
|
|
||||||
private int retriesPending;
|
|
||||||
private boolean cleanupCalled;
|
|
||||||
|
|
||||||
public MapsGDataWrapper(Context context, AuthManager auth) {
|
|
||||||
androidGdataClient = GDataClientFactory.getGDataClient(context);
|
|
||||||
this.auth = auth;
|
|
||||||
client =
|
|
||||||
new MapsClient(androidGdataClient, new XmlMapsGDataParserFactory(
|
|
||||||
new AndroidXmlParserFactory()));
|
|
||||||
errorType = ERROR_NO_ERROR;
|
|
||||||
errorMessage = null;
|
|
||||||
retryOnAuthFailure = false;
|
|
||||||
retriesPending = 0;
|
|
||||||
cleanupCalled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean runQuery(final QueryFunction query) {
|
|
||||||
if (client == null) {
|
|
||||||
errorType = ERROR_CLEANED_UP;
|
|
||||||
errorMessage = "GData Wrapper has already been cleaned up!";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
query.query(client);
|
|
||||||
errorType = ERROR_NO_ERROR;
|
|
||||||
errorMessage = null;
|
|
||||||
return true;
|
|
||||||
} catch (AuthenticationException e) {
|
|
||||||
Log.e(MapsConstants.TAG, "Exception", e);
|
|
||||||
errorType = ERROR_AUTH;
|
|
||||||
errorMessage = e.getMessage();
|
|
||||||
} catch (HttpException e) {
|
|
||||||
Log.e(MapsConstants.TAG, "HttpException", e);
|
|
||||||
errorMessage = e.getMessage();
|
|
||||||
if (errorMessage != null && errorMessage.contains("401")) {
|
|
||||||
errorType = ERROR_AUTH;
|
|
||||||
} else {
|
|
||||||
errorType = ERROR_CONNECTION;
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(MapsConstants.TAG, "Exception", e);
|
|
||||||
errorMessage = e.getMessage();
|
|
||||||
if (errorMessage != null && errorMessage.contains("503")) {
|
|
||||||
errorType = ERROR_INTERNAL;
|
|
||||||
} else {
|
|
||||||
errorType = ERROR_CONNECTION;
|
|
||||||
}
|
|
||||||
} catch (ParseException e) {
|
|
||||||
Log.e(MapsConstants.TAG, "Exception", e);
|
|
||||||
errorType = ERROR_LOCAL;
|
|
||||||
errorMessage = e.getMessage();
|
|
||||||
} catch (ConflictDetectedException e) {
|
|
||||||
Log.e(MapsConstants.TAG, "Exception", e);
|
|
||||||
errorType = ERROR_CONFLICT;
|
|
||||||
errorMessage = e.getMessage();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(MapsConstants.TAG, "Exception", e);
|
|
||||||
errorType = ERROR_UNKNOWN;
|
|
||||||
errorMessage = e.getMessage();
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
Log.d(MapsConstants.TAG, "GData error encountered: " + errorMessage);
|
|
||||||
if (errorType == ERROR_AUTH && auth != null) {
|
|
||||||
AuthCallback whenFinished = null;
|
|
||||||
if (retryOnAuthFailure) {
|
|
||||||
retriesPending++;
|
|
||||||
whenFinished = new AuthCallback() {
|
|
||||||
@Override
|
|
||||||
public void onAuthResult(boolean success) {
|
|
||||||
retriesPending--;
|
|
||||||
retryOnAuthFailure = false;
|
|
||||||
runQuery(query);
|
|
||||||
if (cleanupCalled && retriesPending == 0) {
|
|
||||||
cleanUp();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
auth.invalidateAndRefresh(whenFinished);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getErrorType() {
|
|
||||||
return errorType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getErrorMessage() {
|
|
||||||
return errorMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanUp must be called when done using this wrapper to close the client.
|
|
||||||
// Note that the cleanup will be delayed if auth failure retries were
|
|
||||||
// requested and there is a pending retry.
|
|
||||||
public void cleanUp() {
|
|
||||||
if (retriesPending == 0 && !cleanupCalled) {
|
|
||||||
androidGdataClient.close();
|
|
||||||
}
|
|
||||||
cleanupCalled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRetryOnAuthFailure(boolean retry) {
|
|
||||||
retryOnAuthFailure = retry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,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.maps;
|
||||||
|
|
||||||
|
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 Maps.
|
||||||
|
*
|
||||||
|
* @author Jimmy Shih
|
||||||
|
*/
|
||||||
|
public class SendMapsActivity extends Activity {
|
||||||
|
|
||||||
|
// parameters in the input intent
|
||||||
|
public static final String ACCOUNT = "account";
|
||||||
|
public static final String TRACK_ID = "trackId";
|
||||||
|
public static final String MAP_ID = "mapId";
|
||||||
|
|
||||||
|
// parameters in the output intent
|
||||||
|
public static final String SUCCESS = "success";
|
||||||
|
|
||||||
|
private static final int PROGRESS_DIALOG = 1;
|
||||||
|
|
||||||
|
private SendMapsAsyncTask asyncTask;
|
||||||
|
private ProgressDialog progressDialog;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
Object retained = getLastNonConfigurationInstance();
|
||||||
|
if (retained instanceof SendMapsAsyncTask) {
|
||||||
|
asyncTask = (SendMapsAsyncTask) 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;
|
||||||
|
}
|
||||||
|
String mapId = intent.getStringExtra(MAP_ID);
|
||||||
|
|
||||||
|
asyncTask = new SendMapsAsyncTask(this, account, trackId, mapId);
|
||||||
|
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_maps)));
|
||||||
|
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) {
|
||||||
|
setResult(RESULT_OK, new Intent().putExtra(SUCCESS, success));
|
||||||
|
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,554 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.io.gdata.GDataClientFactory;
|
||||||
|
import com.google.android.apps.mytracks.io.sendtogoogle.SendToGoogleUtils;
|
||||||
|
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
||||||
|
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||||
|
import com.google.android.apps.mytracks.util.StringUtils;
|
||||||
|
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||||
|
import com.google.android.common.gdata.AndroidXmlParserFactory;
|
||||||
|
import com.google.android.maps.mytracks.R;
|
||||||
|
import com.google.wireless.gdata.client.GDataClient;
|
||||||
|
import com.google.wireless.gdata.client.HttpException;
|
||||||
|
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.content.SharedPreferences;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AsyncTask to send a track to Google Maps.
|
||||||
|
* <p>
|
||||||
|
* IMPORTANT: While this code is Apache-licensed, please notice that usage of
|
||||||
|
* the Google Maps servers through this API is only allowed for the My Tracks
|
||||||
|
* application. Other applications looking to upload maps data should look into
|
||||||
|
* using the Google Fusion Tables API.
|
||||||
|
*
|
||||||
|
* @author Jimmy Shih
|
||||||
|
*/
|
||||||
|
public class SendMapsAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||||
|
private static final String START_ICON_URL =
|
||||||
|
"http://maps.google.com/mapfiles/ms/micons/green-dot.png";
|
||||||
|
private static final String END_ICON_URL =
|
||||||
|
"http://maps.google.com/mapfiles/ms/micons/red-dot.png";
|
||||||
|
private static final int MAX_POINTS_PER_UPLOAD = 500;
|
||||||
|
|
||||||
|
private static final int PROGRESS_FETCH_MAP_ID = 5;
|
||||||
|
private static final int PROGRESS_UPLOAD_DATA_MIN = 10;
|
||||||
|
private static final int PROGRESS_UPLOAD_DATA_MAX = 90;
|
||||||
|
private static final int PROGRESS_UPLOAD_WAYPOINTS = 95;
|
||||||
|
private static final int PROGRESS_COMPLETE = 100;
|
||||||
|
|
||||||
|
private static final String TAG = SendMapsAsyncTask.class.getSimpleName();
|
||||||
|
|
||||||
|
private SendMapsActivity activity;
|
||||||
|
private final Account account;
|
||||||
|
private final long trackId;
|
||||||
|
private final String chooseMapId;
|
||||||
|
private final Context context;
|
||||||
|
private final MyTracksProviderUtils myTracksProviderUtils;
|
||||||
|
private final GDataClient gDataClient;
|
||||||
|
private final MapsClient mapsClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if can retry sending to Google Maps.
|
||||||
|
*/
|
||||||
|
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 MapsGDataConverter mapsGDataConverter;
|
||||||
|
private String authToken;
|
||||||
|
private String mapId;
|
||||||
|
int currentSegment;
|
||||||
|
|
||||||
|
public SendMapsAsyncTask(
|
||||||
|
SendMapsActivity activity, Account account, long trackId, String chooseMapId) {
|
||||||
|
this.activity = activity;
|
||||||
|
this.account = account;
|
||||||
|
this.trackId = trackId;
|
||||||
|
this.chooseMapId = chooseMapId;
|
||||||
|
|
||||||
|
context = activity.getApplicationContext();
|
||||||
|
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
|
||||||
|
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(SendMapsActivity 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 (success) {
|
||||||
|
Track track = myTracksProviderUtils.getTrack(trackId);
|
||||||
|
if (track != null) {
|
||||||
|
track.setMapId(mapId);
|
||||||
|
myTracksProviderUtils.updateTrack(track);
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "No track");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (activity != null) {
|
||||||
|
activity.onAsyncTaskCompleted(success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCancelled() {
|
||||||
|
closeClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the gdata client.
|
||||||
|
*/
|
||||||
|
private void closeClient() {
|
||||||
|
if (gDataClient != null) {
|
||||||
|
gDataClient.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads a track to Google Maps.
|
||||||
|
*
|
||||||
|
* @return true if success.
|
||||||
|
*/
|
||||||
|
private boolean doUpload() {
|
||||||
|
// Reset the per upload states
|
||||||
|
mapsGDataConverter = null;
|
||||||
|
authToken = null;
|
||||||
|
mapId = null;
|
||||||
|
currentSegment = 1;
|
||||||
|
|
||||||
|
// Create a maps gdata converter
|
||||||
|
try {
|
||||||
|
mapsGDataConverter = new MapsGDataConverter();
|
||||||
|
} catch (XmlPullParserException e) {
|
||||||
|
Log.d(TAG, "Unable to create a maps gdata converter", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get auth token
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the track
|
||||||
|
Track track = myTracksProviderUtils.getTrack(trackId);
|
||||||
|
if (track == null) {
|
||||||
|
Log.d(TAG, "Track is null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the mapId, create a new map if necessary
|
||||||
|
publishProgress(PROGRESS_FETCH_MAP_ID);
|
||||||
|
if (!fetchSendMapId(track)) {
|
||||||
|
Log.d("TAG", "Unable to upload all track points");
|
||||||
|
return retryUpload();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload all the track points plus the start and end markers
|
||||||
|
publishProgress(PROGRESS_UPLOAD_DATA_MIN);
|
||||||
|
if (!uploadAllTrackPoints(track)) {
|
||||||
|
Log.d("TAG", "Unable to upload all track points");
|
||||||
|
return retryUpload();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload all the waypoints
|
||||||
|
publishProgress(PROGRESS_UPLOAD_WAYPOINTS);
|
||||||
|
if (!uploadWaypoints()) {
|
||||||
|
Log.d("TAG", "Unable to upload waypoints");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
publishProgress(PROGRESS_COMPLETE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retries upload. Invalidates the authToken. If can retry, invokes
|
||||||
|
* {@link SendMapsAsyncTask#doUpload()}. 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 doUpload();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the {@link SendMapsAsyncTask#mapId} instance variable for
|
||||||
|
* sending a track to Google Maps.
|
||||||
|
*
|
||||||
|
* @param track the Track
|
||||||
|
* @return true if able to fetch the mapId variable.
|
||||||
|
*/
|
||||||
|
private boolean fetchSendMapId(Track track) {
|
||||||
|
if (isCancelled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chooseMapId != null) {
|
||||||
|
mapId = chooseMapId;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
SharedPreferences sharedPreferences = context.getSharedPreferences(
|
||||||
|
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||||
|
boolean mapPublic = sharedPreferences.getBoolean(
|
||||||
|
context.getString(R.string.default_map_public_key), true);
|
||||||
|
try {
|
||||||
|
String description = track.getCategory() + "\n" + track.getDescription() + "\n"
|
||||||
|
+ StringUtils.getCreatedByMyTracks(context, false);
|
||||||
|
mapId = SendMapsUtils.createNewMap(
|
||||||
|
track.getName(), description, mapPublic, mapsClient, authToken);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
Log.d(TAG, "Unable to create a new map", e);
|
||||||
|
return false;
|
||||||
|
} catch (HttpException e) {
|
||||||
|
Log.d(TAG, "Unable to create a new map", e);
|
||||||
|
return false;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.d(TAG, "Unable to create a new map", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return mapId != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 to 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
|
||||||
|
if (!uploadMarker(context.getString(R.string.marker_label_start, track.getName()), "",
|
||||||
|
START_ICON_URL, location)) {
|
||||||
|
Log.d(TAG, "Unable to create a 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 == 0) {
|
||||||
|
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>");
|
||||||
|
if (!uploadMarker(context.getString(R.string.marker_label_end, track.getName()),
|
||||||
|
track.getDescription(), END_ICON_URL, lastLocation)) {
|
||||||
|
Log.d(TAG, "Unable to create an end marker");
|
||||||
|
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
|
||||||
|
* @return true if success.
|
||||||
|
*/
|
||||||
|
private boolean prepareAndUploadPoints(Track track, List<Location> locations, boolean lastBatch) {
|
||||||
|
// Prepare locations
|
||||||
|
ArrayList<Track> splitTracks = SendToGoogleUtils.prepareLocations(track, locations);
|
||||||
|
|
||||||
|
// Upload segments
|
||||||
|
boolean onlyOneSegment = lastBatch && currentSegment == 1 && splitTracks.size() == 1;
|
||||||
|
for (Track segment : splitTracks) {
|
||||||
|
if (!onlyOneSegment) {
|
||||||
|
segment.setName(context.getString(
|
||||||
|
R.string.send_google_track_part_label, segment.getName(), currentSegment));
|
||||||
|
}
|
||||||
|
if (!uploadSegment(segment.getName(), segment.getLocations())) {
|
||||||
|
Log.d(TAG, "Unable to upload segment");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
currentSegment++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads a marker.
|
||||||
|
*
|
||||||
|
* @param title marker title
|
||||||
|
* @param description marker description
|
||||||
|
* @param iconUrl marker marker icon
|
||||||
|
* @param location marker location
|
||||||
|
* @return true if success.
|
||||||
|
*/
|
||||||
|
private boolean uploadMarker(
|
||||||
|
String title, String description, String iconUrl, Location location) {
|
||||||
|
if (isCancelled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (!SendMapsUtils.uploadMarker(mapId, title, description, iconUrl, location, mapsClient,
|
||||||
|
authToken, mapsGDataConverter)) {
|
||||||
|
Log.d(TAG, "Unable to upload marker");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (ParseException e) {
|
||||||
|
Log.d(TAG, "Unable to upload marker", e);
|
||||||
|
return false;
|
||||||
|
} catch (HttpException e) {
|
||||||
|
Log.d(TAG, "Unable to upload marker", e);
|
||||||
|
return false;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.d(TAG, "Unable to upload marker", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads a segment
|
||||||
|
*
|
||||||
|
* @param title segment title
|
||||||
|
* @param locations segment locations
|
||||||
|
* @return true if success
|
||||||
|
*/
|
||||||
|
private boolean uploadSegment(String title, ArrayList<Location> locations) {
|
||||||
|
if (isCancelled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (!SendMapsUtils.uploadSegment(
|
||||||
|
mapId, title, locations, mapsClient, authToken, mapsGDataConverter)) {
|
||||||
|
Log.d(TAG, "Unable to upload track points");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (ParseException e) {
|
||||||
|
Log.d(TAG, "Unable to upload track points", e);
|
||||||
|
return false;
|
||||||
|
} catch (HttpException e) {
|
||||||
|
Log.d(TAG, "Unable to upload track points", e);
|
||||||
|
return false;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.d(TAG, "Unable to upload track points", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()) {
|
||||||
|
if (isCancelled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Waypoint waypoint = myTracksProviderUtils.createWaypoint(cursor);
|
||||||
|
try {
|
||||||
|
if (!SendMapsUtils.uploadWaypoint(
|
||||||
|
mapId, waypoint, mapsClient, authToken, mapsGDataConverter)) {
|
||||||
|
Log.d(TAG, "Unable to upload waypoint");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (ParseException e) {
|
||||||
|
Log.d(TAG, "Unable to upload waypoint", e);
|
||||||
|
return false;
|
||||||
|
} catch (HttpException e) {
|
||||||
|
Log.d(TAG, "Unable to upload waypoint", e);
|
||||||
|
return false;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.d(TAG, "Unable to upload waypoint", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
/*
|
||||||
|
* 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.content.Track;
|
||||||
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
|
import com.google.android.maps.GeoPoint;
|
||||||
|
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.parser.ParseException;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities for sending a track to Google Maps.
|
||||||
|
*
|
||||||
|
* @author jshih@google.com (Jimmy Shih)
|
||||||
|
*/
|
||||||
|
public class SendMapsUtils {
|
||||||
|
|
||||||
|
private static final String EMPTY_TITLE = "-";
|
||||||
|
private static final int LINE_COLOR = 0x80FF0000;
|
||||||
|
private static final String TAG = SendMapsUtils.class.getSimpleName();
|
||||||
|
|
||||||
|
private SendMapsUtils() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the Google Maps url for a track.
|
||||||
|
*
|
||||||
|
* @param track the track
|
||||||
|
* @return the url if available.
|
||||||
|
*/
|
||||||
|
public static String getMapUrl(Track track) {
|
||||||
|
if (track == null || track.getMapId() == null) {
|
||||||
|
Log.e(TAG, "Invalid track");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MapsClient.buildMapUrl(track.getMapId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Google Map.
|
||||||
|
*
|
||||||
|
* @param title title of the map
|
||||||
|
* @param description description of the map
|
||||||
|
* @param isPublic true if the map can be public
|
||||||
|
* @param mapsClient the maps client
|
||||||
|
* @param authToken the auth token
|
||||||
|
* @return map id of the created map if successful.
|
||||||
|
*/
|
||||||
|
public static String createNewMap(
|
||||||
|
String title, String description, boolean isPublic, MapsClient mapsClient, String authToken)
|
||||||
|
throws ParseException, HttpException, IOException {
|
||||||
|
String mapFeed = MapsClient.getMapsFeed();
|
||||||
|
MapsMapMetadata metaData = new MapsMapMetadata();
|
||||||
|
metaData.setTitle(title);
|
||||||
|
metaData.setDescription(description);
|
||||||
|
metaData.setSearchable(isPublic);
|
||||||
|
Entry entry = MapsGDataConverter.getMapEntryForMetadata(metaData);
|
||||||
|
Entry result = mapsClient.createEntry(mapFeed, authToken, entry);
|
||||||
|
if (result == null) {
|
||||||
|
Log.d(TAG, "No result when creating a new map");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return MapsClient.getMapIdFromMapEntryId(result.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads a start/end marker to Google Maps.
|
||||||
|
*
|
||||||
|
* @param mapId the map id
|
||||||
|
* @param title the marker title
|
||||||
|
* @param description the marker description
|
||||||
|
* @param iconUrl the marker icon URL
|
||||||
|
* @param location the marker location
|
||||||
|
* @param mapsClient the maps client
|
||||||
|
* @param authToken the auth token
|
||||||
|
* @param mapsGDataConverter the maps gdata converter
|
||||||
|
* @return true if success.
|
||||||
|
*/
|
||||||
|
public static boolean uploadMarker(String mapId, String title, String description, String iconUrl,
|
||||||
|
Location location, MapsClient mapsClient, String authToken,
|
||||||
|
MapsGDataConverter mapsGDataConverter) throws ParseException, HttpException, IOException {
|
||||||
|
String featuresFeed = MapsClient.getFeaturesFeed(mapId);
|
||||||
|
MapsFeature mapsFeature = buildMapsMarkerFeature(
|
||||||
|
title, description, iconUrl, getGeoPoint(location));
|
||||||
|
Entry entry = mapsGDataConverter.getEntryForFeature(mapsFeature);
|
||||||
|
try {
|
||||||
|
mapsClient.createEntry(featuresFeed, authToken, entry);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Retry once (often IOException is thrown on a timeout)
|
||||||
|
Log.d(TAG, "Retry upload marker", e);
|
||||||
|
mapsClient.createEntry(featuresFeed, authToken, entry);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads a waypoint as a marker feature to Google Maps.
|
||||||
|
*
|
||||||
|
* @param mapId the map id
|
||||||
|
* @param waypoint the waypoint
|
||||||
|
* @param mapsClient the maps client
|
||||||
|
* @param authToken the auth token
|
||||||
|
* @param mapsGDataConverter the maps gdata converter
|
||||||
|
* @return true if success.
|
||||||
|
*/
|
||||||
|
public static boolean uploadWaypoint(String mapId, Waypoint waypoint, MapsClient mapsClient,
|
||||||
|
String authToken, MapsGDataConverter mapsGDataConverter)
|
||||||
|
throws ParseException, HttpException, IOException {
|
||||||
|
String featuresFeed = MapsClient.getFeaturesFeed(mapId);
|
||||||
|
MapsFeature feature = buildMapsMarkerFeature(waypoint.getName(), waypoint.getDescription(),
|
||||||
|
waypoint.getIcon(), getGeoPoint(waypoint.getLocation()));
|
||||||
|
Entry entry = mapsGDataConverter.getEntryForFeature(feature);
|
||||||
|
try {
|
||||||
|
mapsClient.createEntry(featuresFeed, authToken, entry);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Retry once (often IOException is thrown on a timeout)
|
||||||
|
Log.d(TAG, "Retry upload waypoint", e);
|
||||||
|
mapsClient.createEntry(featuresFeed, authToken, entry);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads a segment as a line feature to Google Maps.
|
||||||
|
*
|
||||||
|
* @param mapId the map id
|
||||||
|
* @param title the segment title
|
||||||
|
* @param locations the segment locations
|
||||||
|
* @param mapsClient the maps client
|
||||||
|
* @param authToken the auth token
|
||||||
|
* @param mapsGDataConverter the maps gdata converter
|
||||||
|
* @return true if success.
|
||||||
|
*/
|
||||||
|
public static boolean uploadSegment(String mapId, String title, ArrayList<Location> locations,
|
||||||
|
MapsClient mapsClient, String authToken, MapsGDataConverter mapsGDataConverter)
|
||||||
|
throws ParseException, HttpException, IOException {
|
||||||
|
String featuresFeed = MapsClient.getFeaturesFeed(mapId);
|
||||||
|
Entry entry = mapsGDataConverter.getEntryForFeature(buildMapsLineFeature(title, locations));
|
||||||
|
try {
|
||||||
|
mapsClient.createEntry(featuresFeed, authToken, entry);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Retry once (often IOException is thrown on a timeout)
|
||||||
|
Log.d(TAG, "Retry upload track points", e);
|
||||||
|
mapsClient.createEntry(featuresFeed, authToken, entry);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a map marker feature.
|
||||||
|
*
|
||||||
|
* @param title feature title
|
||||||
|
* @param description the feature description
|
||||||
|
* @param iconUrl the feature icon URL
|
||||||
|
* @param geoPoint the marker
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
static MapsFeature buildMapsMarkerFeature(
|
||||||
|
String title, String description, String iconUrl, GeoPoint geoPoint) {
|
||||||
|
MapsFeature mapsFeature = new MapsFeature();
|
||||||
|
mapsFeature.setType(MapsFeature.MARKER);
|
||||||
|
mapsFeature.generateAndroidId();
|
||||||
|
// Feature must have a name (otherwise GData upload may fail)
|
||||||
|
mapsFeature.setTitle(TextUtils.isEmpty(title) ? EMPTY_TITLE : title);
|
||||||
|
mapsFeature.setDescription(description.replaceAll("\n", "<br>"));
|
||||||
|
mapsFeature.setIconUrl(iconUrl);
|
||||||
|
mapsFeature.addPoint(geoPoint);
|
||||||
|
return mapsFeature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a maps line feature from a set of locations.
|
||||||
|
*
|
||||||
|
* @param title the feature title
|
||||||
|
* @param locations set of locations
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
static MapsFeature buildMapsLineFeature(String title, ArrayList<Location> locations) {
|
||||||
|
MapsFeature mapsFeature = new MapsFeature();
|
||||||
|
mapsFeature.setType(MapsFeature.LINE);
|
||||||
|
mapsFeature.generateAndroidId();
|
||||||
|
// Feature must have a name (otherwise GData upload may fail)
|
||||||
|
mapsFeature.setTitle(TextUtils.isEmpty(title) ? EMPTY_TITLE : title);
|
||||||
|
mapsFeature.setColor(LINE_COLOR);
|
||||||
|
for (Location location : locations) {
|
||||||
|
mapsFeature.addPoint(getGeoPoint(location));
|
||||||
|
}
|
||||||
|
return mapsFeature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a {@link GeoPoint} from a {@link Location}.
|
||||||
|
*
|
||||||
|
* @param location the location
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
static GeoPoint getGeoPoint(Location location) {
|
||||||
|
return new GeoPoint(
|
||||||
|
(int) (location.getLatitude() * 1E6), (int) (location.getLongitude() * 1E6));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,36 +19,27 @@ import static com.google.android.apps.mytracks.Constants.TAG;
|
|||||||
|
|
||||||
import com.google.android.apps.analytics.GoogleAnalyticsTracker;
|
import com.google.android.apps.analytics.GoogleAnalyticsTracker;
|
||||||
import com.google.android.apps.mytracks.Constants;
|
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.MyTracksProviderUtils;
|
||||||
import com.google.android.apps.mytracks.content.Track;
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
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.SendToMaps;
|
|
||||||
import com.google.android.apps.mytracks.io.docs.SendDocsActivity;
|
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.ChooseMapActivity;
|
||||||
import com.google.android.apps.mytracks.io.maps.MapsConstants;
|
import com.google.android.apps.mytracks.io.maps.SendMapsActivity;
|
||||||
import com.google.android.apps.mytracks.io.maps.MapsFacade;
|
import com.google.android.apps.mytracks.io.maps.SendMapsUtils;
|
||||||
import com.google.android.apps.mytracks.util.SystemUtils;
|
import com.google.android.apps.mytracks.util.SystemUtils;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.accounts.Account;
|
import android.accounts.Account;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper activity for managing the sending of tracks to Google services.
|
* Helper activity for managing the sending of tracks to Google services.
|
||||||
*
|
*
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public class SendActivity extends Activity implements ProgressIndicator {
|
public class SendActivity extends Activity {
|
||||||
|
|
||||||
// Items in the intent that starts the activity.
|
// Items in the intent that starts the activity.
|
||||||
public static final String TRACK_ID = "trackId";
|
public static final String TRACK_ID = "trackId";
|
||||||
@@ -59,20 +50,18 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
public static final String CREATE_MAP = "createMap";
|
public static final String CREATE_MAP = "createMap";
|
||||||
|
|
||||||
// Keys for saved state variables.
|
// Keys for saved state variables.
|
||||||
private static final String STATE_ACCOUNT = "account";
|
|
||||||
private static final String STATE_STATE = "state";
|
private static final String STATE_STATE = "state";
|
||||||
private static final String STATE_DOCS_SUCCESS = "docsSuccess";
|
private static final String STATE_ACCOUNT = "account";
|
||||||
private static final String STATE_FUSION_SUCCESS = "fusionSuccess";
|
|
||||||
private static final String STATE_MAPS_SUCCESS = "mapsSuccess";
|
|
||||||
private static final String STATE_TABLE_ID = "tableId";
|
|
||||||
private static final String STATE_MAP_ID = "mapId";
|
private static final String STATE_MAP_ID = "mapId";
|
||||||
|
private static final String STATE_DOCS_SUCCESS = "docsSuccess";
|
||||||
|
private static final String STATE_FUSION_TABLES_SUCCESS = "fusionTablesSuccess";
|
||||||
|
private static final String STATE_MAPS_SUCCESS = "mapsSuccess";
|
||||||
|
|
||||||
/** States for the state machine that defines the upload process. */
|
/** States for the state machine that defines the upload process. */
|
||||||
private enum SendState {
|
private enum SendState {
|
||||||
CHOOSE_ACCOUNT,
|
CHOOSE_ACCOUNT,
|
||||||
|
CHOOSE_MAP,
|
||||||
START,
|
START,
|
||||||
AUTHENTICATE_MAPS,
|
|
||||||
PICK_MAP,
|
|
||||||
SEND_TO_MAPS,
|
SEND_TO_MAPS,
|
||||||
SEND_TO_MAPS_DONE,
|
SEND_TO_MAPS_DONE,
|
||||||
SEND_TO_FUSION_TABLES,
|
SEND_TO_FUSION_TABLES,
|
||||||
@@ -85,9 +74,6 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
NOT_READY
|
NOT_READY
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final int PROGRESS_DIALOG = 1;
|
|
||||||
private ProgressDialog progressDialog;
|
|
||||||
|
|
||||||
// Set in Activity.onCreate
|
// Set in Activity.onCreate
|
||||||
private MyTracksProviderUtils providerUtils;
|
private MyTracksProviderUtils providerUtils;
|
||||||
private GoogleAnalyticsTracker tracker;
|
private GoogleAnalyticsTracker tracker;
|
||||||
@@ -100,19 +86,19 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
private boolean sendToFusionTables;
|
private boolean sendToFusionTables;
|
||||||
private boolean sendToDocs;
|
private boolean sendToDocs;
|
||||||
|
|
||||||
// Authentication information.
|
|
||||||
private Account account;
|
|
||||||
private HashMap<String, AuthManager> authMap = new HashMap<String, AuthManager>();
|
|
||||||
|
|
||||||
// Current sending state.
|
// Current sending state.
|
||||||
private SendState currentState;
|
private SendState currentState;
|
||||||
|
|
||||||
|
// Authentication information.
|
||||||
|
private Account account;
|
||||||
|
|
||||||
|
// Map id from choosing a Google Map
|
||||||
|
private String mapId;
|
||||||
|
|
||||||
// Send result information. Used by the results dialog.
|
// Send result information. Used by the results dialog.
|
||||||
private boolean sendToMapsSuccess;
|
private boolean sendToMapsSuccess;
|
||||||
private boolean sendToFusionTablesSuccess;
|
private boolean sendToFusionTablesSuccess;
|
||||||
private boolean sendToDocsSuccess;
|
private boolean sendToDocsSuccess;
|
||||||
private String sendToMapsMapId;
|
|
||||||
private String sendToFusionTablesTableId;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -141,7 +127,8 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
|
|
||||||
// If we had the instance restored after it was done, reset it.
|
// If we had the instance restored after it was done, reset it.
|
||||||
if (currentState == SendState.DONE) {
|
if (currentState == SendState.DONE) {
|
||||||
resetState();
|
finish();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the state machine, at the start or restored state.
|
// Execute the state machine, at the start or restored state.
|
||||||
@@ -165,40 +152,24 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Dialog onCreateDialog(int id) {
|
|
||||||
switch (id) {
|
|
||||||
case PROGRESS_DIALOG:
|
|
||||||
return createProgressDialog();
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void restoreInstanceState(Bundle savedInstanceState) {
|
private void restoreInstanceState(Bundle savedInstanceState) {
|
||||||
account = savedInstanceState.getParcelable(STATE_ACCOUNT);
|
|
||||||
|
|
||||||
currentState = SendState.values()[savedInstanceState.getInt(STATE_STATE)];
|
currentState = SendState.values()[savedInstanceState.getInt(STATE_STATE)];
|
||||||
|
account = savedInstanceState.getParcelable(STATE_ACCOUNT);
|
||||||
|
mapId = savedInstanceState.getString(STATE_MAP_ID);
|
||||||
sendToMapsSuccess = savedInstanceState.getBoolean(STATE_MAPS_SUCCESS);
|
sendToMapsSuccess = savedInstanceState.getBoolean(STATE_MAPS_SUCCESS);
|
||||||
sendToFusionTablesSuccess = savedInstanceState.getBoolean(STATE_FUSION_SUCCESS);
|
sendToFusionTablesSuccess = savedInstanceState.getBoolean(STATE_FUSION_TABLES_SUCCESS);
|
||||||
sendToDocsSuccess = savedInstanceState.getBoolean(STATE_DOCS_SUCCESS);
|
sendToDocsSuccess = savedInstanceState.getBoolean(STATE_DOCS_SUCCESS);
|
||||||
sendToMapsMapId = savedInstanceState.getString(STATE_MAP_ID);
|
|
||||||
sendToFusionTablesTableId = savedInstanceState.getString(STATE_TABLE_ID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSaveInstanceState(Bundle outState) {
|
protected void onSaveInstanceState(Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
outState.putParcelable(STATE_ACCOUNT, account);
|
|
||||||
|
|
||||||
outState.putInt(STATE_STATE, currentState.ordinal());
|
outState.putInt(STATE_STATE, currentState.ordinal());
|
||||||
|
outState.putParcelable(STATE_ACCOUNT, account);
|
||||||
|
outState.putString(STATE_MAP_ID, mapId);
|
||||||
outState.putBoolean(STATE_MAPS_SUCCESS, sendToMapsSuccess);
|
outState.putBoolean(STATE_MAPS_SUCCESS, sendToMapsSuccess);
|
||||||
outState.putBoolean(STATE_FUSION_SUCCESS, sendToFusionTablesSuccess);
|
outState.putBoolean(STATE_FUSION_TABLES_SUCCESS, sendToFusionTablesSuccess);
|
||||||
outState.putBoolean(STATE_DOCS_SUCCESS, sendToDocsSuccess);
|
outState.putBoolean(STATE_DOCS_SUCCESS, sendToDocsSuccess);
|
||||||
outState.putString(STATE_MAP_ID, sendToMapsMapId);
|
|
||||||
outState.putString(STATE_TABLE_ID, sendToFusionTablesTableId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -226,12 +197,10 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
switch (state) {
|
switch (state) {
|
||||||
case CHOOSE_ACCOUNT:
|
case CHOOSE_ACCOUNT:
|
||||||
return chooseAccount();
|
return chooseAccount();
|
||||||
|
case CHOOSE_MAP:
|
||||||
|
return chooseMap();
|
||||||
case START:
|
case START:
|
||||||
return startSend();
|
return startSend();
|
||||||
case AUTHENTICATE_MAPS:
|
|
||||||
return authenticateToGoogleMaps();
|
|
||||||
case PICK_MAP:
|
|
||||||
return pickMap();
|
|
||||||
case SEND_TO_MAPS:
|
case SEND_TO_MAPS:
|
||||||
return sendToGoogleMaps();
|
return sendToGoogleMaps();
|
||||||
case SEND_TO_MAPS_DONE:
|
case SEND_TO_MAPS_DONE:
|
||||||
@@ -267,7 +236,7 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
*/
|
*/
|
||||||
private SendState startSend() {
|
private SendState startSend() {
|
||||||
if (sendToMaps) {
|
if (sendToMaps) {
|
||||||
return SendState.AUTHENTICATE_MAPS;
|
return SendState.SEND_TO_MAPS;
|
||||||
} else if (sendToFusionTables) {
|
} else if (sendToFusionTables) {
|
||||||
return SendState.SEND_TO_FUSION_TABLES;
|
return SendState.SEND_TO_FUSION_TABLES;
|
||||||
} else if (sendToDocs) {
|
} else if (sendToDocs) {
|
||||||
@@ -278,78 +247,24 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dialog createProgressDialog() {
|
private SendState chooseMap() {
|
||||||
progressDialog = new ProgressDialog(this);
|
|
||||||
progressDialog.setCancelable(false);
|
|
||||||
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
|
|
||||||
progressDialog.setTitle(R.string.generic_progress_title);
|
|
||||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
|
||||||
progressDialog.setMax(100);
|
|
||||||
progressDialog.setProgress(0);
|
|
||||||
|
|
||||||
return progressDialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SendState authenticateToGoogleMaps() {
|
|
||||||
Log.d(TAG, "SendActivity.authenticateToGoogleMaps");
|
|
||||||
showDialog(PROGRESS_DIALOG);
|
|
||||||
progressDialog.setProgress(0);
|
|
||||||
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 (!sendToMapsNewMap) {
|
if (!sendToMapsNewMap) {
|
||||||
Intent intent = new Intent(this, ChooseMapActivity.class);
|
Intent intent = new Intent(this, ChooseMapActivity.class)
|
||||||
intent.putExtra(ChooseMapActivity.ACCOUNT, account);
|
.putExtra(ChooseMapActivity.ACCOUNT, account);
|
||||||
startActivityForResult(intent, Constants.CHOOSE_MAP);
|
startActivityForResult(intent, Constants.CHOOSE_MAP);
|
||||||
return SendState.NOT_READY;
|
return SendState.NOT_READY;
|
||||||
} else {
|
} else {
|
||||||
return SendState.SEND_TO_MAPS;
|
return SendState.START;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SendState sendToGoogleMaps() {
|
private SendState sendToGoogleMaps() {
|
||||||
tracker.trackPageView("/send/maps");
|
tracker.trackPageView("/send/maps");
|
||||||
|
Intent intent = new Intent(this, SendMapsActivity.class)
|
||||||
SendToMaps.OnSendCompletedListener onCompletion = new SendToMaps.OnSendCompletedListener() {
|
.putExtra(SendMapsActivity.ACCOUNT, account)
|
||||||
@Override
|
.putExtra(SendMapsActivity.TRACK_ID, sendTrackId)
|
||||||
public void onSendCompleted(String mapId, boolean success) {
|
.putExtra(SendMapsActivity.MAP_ID, mapId);
|
||||||
sendToMapsSuccess = success;
|
startActivityForResult(intent, Constants.SEND_MAPS);
|
||||||
if (sendToMapsSuccess) {
|
|
||||||
sendToMapsMapId = mapId;
|
|
||||||
// Update the map id for this track:
|
|
||||||
try {
|
|
||||||
Track track = providerUtils.getTrack(sendTrackId);
|
|
||||||
if (track != null) {
|
|
||||||
track.setMapId(mapId);
|
|
||||||
providerUtils.updateTrack(track);
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "Updating map id failed.");
|
|
||||||
}
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
// If that fails whatever reasons we'll just log an error, but
|
|
||||||
// continue.
|
|
||||||
Log.w(TAG, "Updating map id failed.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
executeStateMachine(SendState.SEND_TO_MAPS_DONE);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (sendToMapsMapId == null) {
|
|
||||||
sendToMapsMapId = SendToMaps.NEW_MAP_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
final SendToMaps sender = new SendToMaps(this, sendToMapsMapId,
|
|
||||||
getAuthManager(MapsConstants.SERVICE_NAME), sendTrackId, this /*progressIndicator*/,
|
|
||||||
onCompletion);
|
|
||||||
|
|
||||||
new Thread(sender, "SendToMaps").start();
|
|
||||||
|
|
||||||
return SendState.NOT_READY;
|
return SendState.NOT_READY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,12 +310,10 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
|
|
||||||
private SendState onSendToGoogleDone() {
|
private SendState onSendToGoogleDone() {
|
||||||
tracker.dispatch();
|
tracker.dispatch();
|
||||||
String mapsUrl = sendToMaps && sendToMapsSuccess
|
Track track = providerUtils.getTrack(sendTrackId);
|
||||||
? MapsFacade.buildMapUrl(sendToMapsMapId)
|
String mapsUrl = sendToMaps && sendToMapsSuccess ? SendMapsUtils.getMapUrl(track) : null;
|
||||||
: null;
|
|
||||||
String fusionTablesUrl = sendToFusionTables && sendToFusionTablesSuccess
|
String fusionTablesUrl = sendToFusionTables && sendToFusionTablesSuccess
|
||||||
? SendFusionTablesUtils.getMapUrl(providerUtils.getTrack(sendTrackId))
|
? SendFusionTablesUtils.getMapUrl(track) : null;
|
||||||
: null;
|
|
||||||
Intent intent = new Intent(this, UploadResultActivity.class)
|
Intent intent = new Intent(this, UploadResultActivity.class)
|
||||||
.putExtra(UploadResultActivity.HAS_MAPS_RESULT, sendToMaps)
|
.putExtra(UploadResultActivity.HAS_MAPS_RESULT, sendToMaps)
|
||||||
.putExtra(UploadResultActivity.HAS_FUSION_TABLES_RESULT, sendToFusionTables)
|
.putExtra(UploadResultActivity.HAS_FUSION_TABLES_RESULT, sendToFusionTables)
|
||||||
@@ -420,44 +333,11 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
return SendState.DONE;
|
return SendState.DONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
|
||||||
AuthManager authManager = getAuthManager(service);
|
|
||||||
authManager.doLogin(new AuthCallback() {
|
|
||||||
@Override
|
|
||||||
public void onAuthResult(boolean success) {
|
|
||||||
Log.i(TAG, "Login success for " + service + ": " + success);
|
|
||||||
if (!success) {
|
|
||||||
executeStateMachine(SendState.SHOW_RESULTS);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoginSuccess(requestCode);
|
|
||||||
}
|
|
||||||
}, account);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode,
|
public void onActivityResult(int requestCode, int resultCode,
|
||||||
final Intent results) {
|
final Intent results) {
|
||||||
SendState nextState = null;
|
SendState nextState = null;
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
case Constants.CHOOSE_MAP: {
|
|
||||||
if (resultCode == RESULT_CANCELED) {
|
|
||||||
nextState = SendState.FINISH;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
sendToMapsMapId = results.getStringExtra(ChooseMapActivity.MAP_ID);
|
|
||||||
if (sendToMapsMapId == null) {
|
|
||||||
nextState = SendState.FINISH;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
nextState = SendState.SEND_TO_MAPS;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Constants.CHOOSE_ACCOUNT: {
|
case Constants.CHOOSE_ACCOUNT: {
|
||||||
if (resultCode == RESULT_CANCELED) {
|
if (resultCode == RESULT_CANCELED) {
|
||||||
nextState = SendState.FINISH;
|
nextState = SendState.FINISH;
|
||||||
@@ -468,16 +348,29 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
nextState = SendState.FINISH;
|
nextState = SendState.FINISH;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
nextState = SendState.START;
|
nextState = SendState.CHOOSE_MAP;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Constants.SEND_DOCS: {
|
case Constants.CHOOSE_MAP: {
|
||||||
if (resultCode == RESULT_CANCELED) {
|
if (resultCode == RESULT_CANCELED) {
|
||||||
nextState = SendState.FINISH;
|
nextState = SendState.FINISH;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
sendToDocsSuccess = results.getBooleanExtra(SendDocsActivity.SUCCESS, false);
|
mapId = results.getStringExtra(ChooseMapActivity.MAP_ID);
|
||||||
nextState = SendState.SEND_TO_DOCS_DONE;
|
if (mapId == null) {
|
||||||
|
nextState = SendState.FINISH;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nextState = SendState.START;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Constants.SEND_MAPS: {
|
||||||
|
if (resultCode == RESULT_CANCELED) {
|
||||||
|
nextState = SendState.FINISH;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sendToMapsSuccess = results.getBooleanExtra(SendMapsActivity.SUCCESS, false);
|
||||||
|
nextState = SendState.SEND_TO_MAPS_DONE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Constants.SEND_FUSION_TABLES: {
|
case Constants.SEND_FUSION_TABLES: {
|
||||||
@@ -487,10 +380,18 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
}
|
}
|
||||||
sendToFusionTablesSuccess = results.getBooleanExtra(
|
sendToFusionTablesSuccess = results.getBooleanExtra(
|
||||||
SendFusionTablesActivity.SUCCESS, false);
|
SendFusionTablesActivity.SUCCESS, false);
|
||||||
sendToFusionTablesTableId = results.getStringExtra(SendFusionTablesActivity.TABLE_ID);
|
|
||||||
nextState = SendState.SEND_TO_FUSION_TABLES_DONE;
|
nextState = SendState.SEND_TO_FUSION_TABLES_DONE;
|
||||||
break;
|
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;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
Log.e(TAG, "Unrequested result: " + requestCode);
|
Log.e(TAG, "Unrequested result: " + requestCode);
|
||||||
return;
|
return;
|
||||||
@@ -502,85 +403,15 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onLoginSuccess(int requestCode) {
|
|
||||||
SendState nextState;
|
|
||||||
switch (requestCode) {
|
|
||||||
case Constants.AUTHENTICATE_TO_MAPS:
|
|
||||||
// Authenticated with Google Maps
|
|
||||||
nextState = SendState.PICK_MAP;
|
|
||||||
break;
|
|
||||||
default: {
|
|
||||||
Log.e(TAG, "Unrequested login code: " + requestCode);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 Maps/Fusion Tables/Docs.
|
* Resets status information for sending to Maps/Fusion Tables/Docs.
|
||||||
*/
|
*/
|
||||||
private void resetState() {
|
private void resetState() {
|
||||||
account = null;
|
|
||||||
authMap.clear();
|
|
||||||
currentState = SendState.CHOOSE_ACCOUNT;
|
currentState = SendState.CHOOSE_ACCOUNT;
|
||||||
|
account = null;
|
||||||
|
mapId = null;
|
||||||
sendToMapsSuccess = false;
|
sendToMapsSuccess = false;
|
||||||
sendToFusionTablesSuccess = false;
|
sendToFusionTablesSuccess = false;
|
||||||
sendToDocsSuccess = false;
|
sendToDocsSuccess = false;
|
||||||
sendToMapsMapId = null;
|
|
||||||
sendToFusionTablesTableId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a progress message indicating My Tracks is authenticating to a
|
|
||||||
* service.
|
|
||||||
*
|
|
||||||
* @param type the type of service
|
|
||||||
*/
|
|
||||||
private String getAuthenticatingProgressMessage(SendType type) {
|
|
||||||
String format = getString(R.string.send_google_progress_authenticating);
|
|
||||||
String serviceName = getString(type.getServiceName());
|
|
||||||
return String.format(format, serviceName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setProgressMessage(final String message) {
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
if (progressDialog != null) {
|
|
||||||
progressDialog.setMessage(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setProgressValue(final int percent) {
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
if (progressDialog != null) {
|
|
||||||
progressDialog.setProgress(percent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clearProgressMessage() {
|
|
||||||
progressDialog.setMessage("");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-8
@@ -48,7 +48,6 @@ public class SendFusionTablesActivity extends Activity {
|
|||||||
|
|
||||||
// parameters in the output intent
|
// parameters in the output intent
|
||||||
public static final String SUCCESS = "success";
|
public static final String SUCCESS = "success";
|
||||||
public static final String TABLE_ID = "tableId";
|
|
||||||
|
|
||||||
private static final int PROGRESS_DIALOG = 1;
|
private static final int PROGRESS_DIALOG = 1;
|
||||||
|
|
||||||
@@ -67,15 +66,13 @@ public class SendFusionTablesActivity extends Activity {
|
|||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
Account account = intent.getParcelableExtra(ACCOUNT);
|
Account account = intent.getParcelableExtra(ACCOUNT);
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
setResult(
|
setResult(RESULT_OK, new Intent().putExtra(SUCCESS, false));
|
||||||
RESULT_OK, new Intent().putExtra(SUCCESS, false).putExtra(TABLE_ID, (String) null));
|
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long trackId = intent.getLongExtra(TRACK_ID, -1L);
|
long trackId = intent.getLongExtra(TRACK_ID, -1L);
|
||||||
if (trackId == -1L) {
|
if (trackId == -1L) {
|
||||||
setResult(
|
setResult(RESULT_OK, new Intent().putExtra(SUCCESS, false));
|
||||||
RESULT_OK, new Intent().putExtra(SUCCESS, false).putExtra(TABLE_ID, (String) null));
|
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -121,10 +118,9 @@ public class SendFusionTablesActivity extends Activity {
|
|||||||
* Invokes when the associated AsyncTask completes.
|
* Invokes when the associated AsyncTask completes.
|
||||||
*
|
*
|
||||||
* @param success true if success
|
* @param success true if success
|
||||||
* @param tableId tableId if available
|
|
||||||
*/
|
*/
|
||||||
public void onAsyncTaskCompleted(boolean success, String tableId) {
|
public void onAsyncTaskCompleted(boolean success) {
|
||||||
setResult(RESULT_OK, new Intent().putExtra(SUCCESS, success).putExtra(TABLE_ID, tableId));
|
setResult(RESULT_OK, new Intent().putExtra(SUCCESS, success));
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-98
@@ -7,7 +7,6 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
|||||||
import com.google.android.apps.mytracks.content.Track;
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
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.ApiFeatures;
|
||||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||||
import com.google.android.apps.mytracks.util.StringUtils;
|
import com.google.android.apps.mytracks.util.StringUtils;
|
||||||
@@ -125,7 +124,7 @@ public class SendFusionTablesAsyncTask extends AsyncTask<Void, Integer, Boolean>
|
|||||||
public void setActivity(SendFusionTablesActivity activity) {
|
public void setActivity(SendFusionTablesActivity activity) {
|
||||||
this.activity = activity;
|
this.activity = activity;
|
||||||
if (completed && activity != null) {
|
if (completed && activity != null) {
|
||||||
activity.onAsyncTaskCompleted(success, tableId);
|
activity.onAsyncTaskCompleted(success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +159,7 @@ public class SendFusionTablesAsyncTask extends AsyncTask<Void, Integer, Boolean>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (activity != null) {
|
if (activity != null) {
|
||||||
activity.onAsyncTaskCompleted(success, tableId);
|
activity.onAsyncTaskCompleted(success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,7 +324,7 @@ public class SendFusionTablesAsyncTask extends AsyncTask<Void, Integer, Boolean>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Upload periodically
|
// Upload periodically
|
||||||
if (readCount % MAX_POINTS_PER_UPLOAD == MAX_POINTS_PER_UPLOAD) {
|
if (readCount % MAX_POINTS_PER_UPLOAD == 0) {
|
||||||
if (!prepareAndUploadPoints(track, locations, false)) {
|
if (!prepareAndUploadPoints(track, locations, false)) {
|
||||||
Log.d(TAG, "Unable to upload points");
|
Log.d(TAG, "Unable to upload points");
|
||||||
return false;
|
return false;
|
||||||
@@ -371,7 +370,7 @@ public class SendFusionTablesAsyncTask extends AsyncTask<Void, Integer, Boolean>
|
|||||||
*/
|
*/
|
||||||
private boolean prepareAndUploadPoints(Track track, List<Location> locations, boolean lastBatch) {
|
private boolean prepareAndUploadPoints(Track track, List<Location> locations, boolean lastBatch) {
|
||||||
// Prepare locations
|
// Prepare locations
|
||||||
ArrayList<Track> splitTracks = prepareLocations(track, locations);
|
ArrayList<Track> splitTracks = SendToGoogleUtils.prepareLocations(track, locations);
|
||||||
|
|
||||||
// Upload segments
|
// Upload segments
|
||||||
boolean onlyOneSegment = lastBatch && currentSegment == 1 && splitTracks.size() == 1;
|
boolean onlyOneSegment = lastBatch && currentSegment == 1 && splitTracks.size() == 1;
|
||||||
@@ -389,99 +388,6 @@ public class SendFusionTablesAsyncTask extends AsyncTask<Void, Integer, Boolean>
|
|||||||
return true;
|
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.
|
* Uploads all the waypoints.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.apps.mytracks.io.sendtogoogle;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
|
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||||
|
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commons utilities for sending a track to Google.
|
||||||
|
*
|
||||||
|
* @author Jimmy Shih
|
||||||
|
*/
|
||||||
|
public class SendToGoogleUtils {
|
||||||
|
|
||||||
|
private static final String TAG = SendToGoogleUtils.class.getSimpleName();
|
||||||
|
|
||||||
|
private SendToGoogleUtils() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares a list of locations to send to Google Maps or Google Fusion
|
||||||
|
* Tables. Splits the locations into segments if necessary.
|
||||||
|
*
|
||||||
|
* @param track the track
|
||||||
|
* @param locations the list of locations
|
||||||
|
* @return an array of split segments.
|
||||||
|
*/
|
||||||
|
public static ArrayList<Track> prepareLocations(Track track, List<Location> locations) {
|
||||||
|
ArrayList<Track> splitTracks = new ArrayList<Track>();
|
||||||
|
|
||||||
|
// Create a new segment
|
||||||
|
Track segment = 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 Maps or 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 static 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. Google Maps and Google Fusion Tables do
|
||||||
|
// not like the locations to be too precise.
|
||||||
|
LocationUtils.decimate(segment, 2.0);
|
||||||
|
|
||||||
|
splitTracks.add(segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ import android.test.RenamingDelegatingContext;
|
|||||||
import android.test.mock.MockContentResolver;
|
import android.test.mock.MockContentResolver;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
@@ -59,6 +60,10 @@ public class TrackWriterTest extends AndroidTestCase {
|
|||||||
|
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
this.canWrite = canWrite;
|
this.canWrite = canWrite;
|
||||||
|
|
||||||
|
// The directory is set in the canWriteFile. However, this class
|
||||||
|
// overwrites canWriteFile, thus needs to set it.
|
||||||
|
setDirectory(new File("/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
/*
|
||||||
|
* 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.content.Track;
|
||||||
|
import com.google.android.maps.GeoPoint;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendMapsUtils}.
|
||||||
|
*
|
||||||
|
* @author Jimmy Shih
|
||||||
|
*/
|
||||||
|
public class SendMapsUtilsTest extends TestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendMapsUtils#getMapUrl(Track)} with null track.
|
||||||
|
*/
|
||||||
|
public void testGetMapUrl_null_track() {
|
||||||
|
assertEquals(null, SendMapsUtils.getMapUrl(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendMapsUtils#getMapUrl(Track)} with null map id.
|
||||||
|
*/
|
||||||
|
public void testGetMapUrl_null_map_id() {
|
||||||
|
Track track = new Track();
|
||||||
|
track.setMapId(null);
|
||||||
|
assertEquals(null, SendMapsUtils.getMapUrl(track));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SendMapsUtils#getMapUrl(Track)} with a valid track.
|
||||||
|
*/
|
||||||
|
public void testGetMapUrl_valid_track() {
|
||||||
|
Track track = new Track();
|
||||||
|
track.setMapId("123");
|
||||||
|
assertEquals("https://maps.google.com/maps/ms?msa=0&msid=123", SendMapsUtils.getMapUrl(track));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test {@link SendMapsUtils#buildMapsMarkerFeature(String, String, String,
|
||||||
|
* GeoPoint)} with a title.
|
||||||
|
*/
|
||||||
|
public void testBuildMapsMarkerFeature_with_title() {
|
||||||
|
MapsFeature mapFeature = SendMapsUtils.buildMapsMarkerFeature(
|
||||||
|
"name", "this\nmap\ndescription", "url", new GeoPoint(123, 456));
|
||||||
|
assertEquals(MapsFeature.MARKER, mapFeature.getType());
|
||||||
|
assertNotNull(mapFeature.getAndroidId());
|
||||||
|
assertEquals("name", mapFeature.getTitle());
|
||||||
|
assertEquals("this<br>map<br>description", mapFeature.getDescription());
|
||||||
|
assertEquals("url", mapFeature.getIconUrl());
|
||||||
|
assertEquals(123, mapFeature.getPoint(0).getLatitudeE6());
|
||||||
|
assertEquals(456, mapFeature.getPoint(0).getLongitudeE6());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test {@link SendMapsUtils#buildMapsMarkerFeature(String, String, String,
|
||||||
|
* GeoPoint)} with an empty title.
|
||||||
|
*/
|
||||||
|
public void testBuildMapsMarkerFeature_empty_title() {
|
||||||
|
MapsFeature mapFeature = SendMapsUtils.buildMapsMarkerFeature(
|
||||||
|
"", "description", "url", new GeoPoint(123, 456));
|
||||||
|
assertEquals(MapsFeature.MARKER, mapFeature.getType());
|
||||||
|
assertNotNull(mapFeature.getAndroidId());
|
||||||
|
assertEquals("-", mapFeature.getTitle());
|
||||||
|
assertEquals("description", mapFeature.getDescription());
|
||||||
|
assertEquals("url", mapFeature.getIconUrl());
|
||||||
|
assertEquals(123, mapFeature.getPoint(0).getLatitudeE6());
|
||||||
|
assertEquals(456, mapFeature.getPoint(0).getLongitudeE6());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test {@link SendMapsUtils#buildMapsMarkerFeature(String, String, String,
|
||||||
|
* GeoPoint)} with a null title.
|
||||||
|
*/
|
||||||
|
public void testBuildMapsMarkerFeature_null_title() {
|
||||||
|
MapsFeature mapFeature = SendMapsUtils.buildMapsMarkerFeature(
|
||||||
|
null, "description", "url", new GeoPoint(123, 456));
|
||||||
|
assertEquals(MapsFeature.MARKER, mapFeature.getType());
|
||||||
|
assertNotNull(mapFeature.getAndroidId());
|
||||||
|
assertEquals("-", mapFeature.getTitle());
|
||||||
|
assertEquals("description", mapFeature.getDescription());
|
||||||
|
assertEquals("url", mapFeature.getIconUrl());
|
||||||
|
assertEquals(123, mapFeature.getPoint(0).getLatitudeE6());
|
||||||
|
assertEquals(456, mapFeature.getPoint(0).getLongitudeE6());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test {@link SendMapsUtils#buildMapsLineFeature(String, ArrayList)} with a
|
||||||
|
* title.
|
||||||
|
*/
|
||||||
|
public void testBuildMapsLineFeature_with_title() {
|
||||||
|
ArrayList<Location> locations = new ArrayList<Location>();
|
||||||
|
Location location = new Location("test");
|
||||||
|
location.setLatitude(50);
|
||||||
|
location.setLongitude(100);
|
||||||
|
locations.add(location);
|
||||||
|
MapsFeature mapFeature = SendMapsUtils.buildMapsLineFeature("name", locations);
|
||||||
|
|
||||||
|
assertEquals(MapsFeature.LINE, mapFeature.getType());
|
||||||
|
assertNotNull(mapFeature.getAndroidId());
|
||||||
|
assertEquals("name", mapFeature.getTitle());
|
||||||
|
assertEquals(0x80FF0000, mapFeature.getColor());
|
||||||
|
assertEquals(50000000, mapFeature.getPoint(0).getLatitudeE6());
|
||||||
|
assertEquals(100000000, mapFeature.getPoint(0).getLongitudeE6());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test {@link SendMapsUtils#buildMapsLineFeature(String, ArrayList)} with an
|
||||||
|
* empty title.
|
||||||
|
*/
|
||||||
|
public void testBuildMapsLineFeature_empty_title() {
|
||||||
|
ArrayList<Location> locations = new ArrayList<Location>();
|
||||||
|
Location location = new Location("test");
|
||||||
|
location.setLatitude(50);
|
||||||
|
location.setLongitude(100);
|
||||||
|
locations.add(location);
|
||||||
|
MapsFeature mapFeature = SendMapsUtils.buildMapsLineFeature("", locations);
|
||||||
|
|
||||||
|
assertEquals(MapsFeature.LINE, mapFeature.getType());
|
||||||
|
assertNotNull(mapFeature.getAndroidId());
|
||||||
|
assertEquals("-", mapFeature.getTitle());
|
||||||
|
assertEquals(0x80FF0000, mapFeature.getColor());
|
||||||
|
assertEquals(50000000, mapFeature.getPoint(0).getLatitudeE6());
|
||||||
|
assertEquals(100000000, mapFeature.getPoint(0).getLongitudeE6());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test {@link SendMapsUtils#buildMapsLineFeature(String, ArrayList)} with a
|
||||||
|
* null title.
|
||||||
|
*/
|
||||||
|
public void testBuildMapsLineFeature_null_title() {
|
||||||
|
ArrayList<Location> locations = new ArrayList<Location>();
|
||||||
|
Location location = new Location("test");
|
||||||
|
location.setLatitude(50);
|
||||||
|
location.setLongitude(100);
|
||||||
|
locations.add(location);
|
||||||
|
MapsFeature mapFeature = SendMapsUtils.buildMapsLineFeature(null, locations);
|
||||||
|
|
||||||
|
assertEquals(MapsFeature.LINE, mapFeature.getType());
|
||||||
|
assertNotNull(mapFeature.getAndroidId());
|
||||||
|
assertEquals("-", mapFeature.getTitle());
|
||||||
|
assertEquals(0x80FF0000, mapFeature.getColor());
|
||||||
|
assertEquals(50000000, mapFeature.getPoint(0).getLatitudeE6());
|
||||||
|
assertEquals(100000000, mapFeature.getPoint(0).getLongitudeE6());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test {@link SendMapsUtils#getGeoPoint(Location)}.
|
||||||
|
*/
|
||||||
|
public void testGeoPoint() {
|
||||||
|
Location location = new Location("test");
|
||||||
|
location.setLatitude(50);
|
||||||
|
location.setLongitude(100);
|
||||||
|
GeoPoint geoPoint = SendMapsUtils.getGeoPoint(location);
|
||||||
|
assertEquals(50000000, geoPoint.getLatitudeE6());
|
||||||
|
assertEquals(100000000, geoPoint.getLongitudeE6());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user