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.maps.ChooseMapActivity"
|
||||
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.SendFusionTablesActivity"/>
|
||||
<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.
|
||||
</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 -->
|
||||
<string name="marker_detail_marker_description"
|
||||
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
|
||||
</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 -->
|
||||
<string name="menu_aggregated_statistics"
|
||||
translation_description="In the menu, to go to the aggregated statistics page.
|
||||
@@ -1179,25 +1179,6 @@ limitations under the License.
|
||||
found.">
|
||||
No account found
|
||||
</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"
|
||||
translation_description="When sending a track to a Google service, the title on the progress
|
||||
dialog.
|
||||
@@ -1205,18 +1186,6 @@ limitations under the License.
|
||||
%1$s: service name. 'Google Maps', 'Google Fusion Tables', or 'Google Docs'.">
|
||||
Sending to %1$s. Please wait...
|
||||
</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"
|
||||
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'.">
|
||||
|
||||
@@ -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
|
||||
* global order across all activities.
|
||||
*/
|
||||
public static final int GET_LOGIN = 0;
|
||||
public static final int CHOOSE_MAP = 1;
|
||||
public static final int SHOW_TRACK = 2;
|
||||
public static final int AUTHENTICATE_TO_MAPS = 3;
|
||||
public static final int SHARE_GPX_FILE = 4;
|
||||
public static final int SHARE_KML_FILE = 5;
|
||||
public static final int SHARE_CSV_FILE = 6;
|
||||
public static final int SHARE_TCX_FILE = 7;
|
||||
public static final int SAVE_GPX_FILE = 8;
|
||||
public static final int SAVE_KML_FILE = 9;
|
||||
public static final int SAVE_CSV_FILE = 10;
|
||||
public static final int SAVE_TCX_FILE = 11;
|
||||
public static final int SHOW_WAYPOINT = 12;
|
||||
public static final int WELCOME = 13;
|
||||
public static final int CHOOSE_ACCOUNT = 14;
|
||||
public static final int SHOW_TRACK = 0;
|
||||
public static final int SHARE_GPX_FILE = 1;
|
||||
public static final int SHARE_KML_FILE = 2;
|
||||
public static final int SHARE_CSV_FILE = 3;
|
||||
public static final int SHARE_TCX_FILE = 4;
|
||||
public static final int SAVE_GPX_FILE = 5;
|
||||
public static final int SAVE_KML_FILE = 6;
|
||||
public static final int SAVE_CSV_FILE = 7;
|
||||
public static final int SAVE_TCX_FILE = 8;
|
||||
public static final int SHOW_WAYPOINT = 9;
|
||||
public static final int WELCOME = 10;
|
||||
public static final int CHOOSE_ACCOUNT = 11;
|
||||
public static final int CHOOSE_MAP = 12;
|
||||
public static final int SEND_MAPS = 13;
|
||||
public static final int SEND_FUSION_TABLES = 14;
|
||||
public static final int SEND_DOCS = 15;
|
||||
public static final int SEND_FUSION_TABLES = 16;
|
||||
|
||||
/*
|
||||
* 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();
|
||||
entry.setEditUri(metadata.getGDataEditUri());
|
||||
entry.setTitle(metadata.getTitle());
|
||||
entry.setContent(metadata.getDescription());
|
||||
entry.setSummary(metadata.getDescription());
|
||||
entry.setPrivacy(metadata.getSearchable() ? "public" : "unlisted");
|
||||
entry.setAuthor("android");
|
||||
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.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.ProgressIndicator;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.io.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.maps.ChooseMapActivity;
|
||||
import com.google.android.apps.mytracks.io.maps.MapsConstants;
|
||||
import com.google.android.apps.mytracks.io.maps.MapsFacade;
|
||||
import com.google.android.apps.mytracks.io.maps.SendMapsActivity;
|
||||
import com.google.android.apps.mytracks.io.maps.SendMapsUtils;
|
||||
import com.google.android.apps.mytracks.util.SystemUtils;
|
||||
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.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Helper activity for managing the sending of tracks to Google services.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class SendActivity extends Activity implements ProgressIndicator {
|
||||
public class SendActivity extends Activity {
|
||||
|
||||
// Items in the intent that starts the activity.
|
||||
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";
|
||||
|
||||
// Keys for saved state variables.
|
||||
private static final String STATE_ACCOUNT = "account";
|
||||
private static final String STATE_STATE = "state";
|
||||
private static final String STATE_DOCS_SUCCESS = "docsSuccess";
|
||||
private static final String STATE_FUSION_SUCCESS = "fusionSuccess";
|
||||
private static final String STATE_MAPS_SUCCESS = "mapsSuccess";
|
||||
private static final String STATE_TABLE_ID = "tableId";
|
||||
private static final String STATE_ACCOUNT = "account";
|
||||
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. */
|
||||
private enum SendState {
|
||||
CHOOSE_ACCOUNT,
|
||||
CHOOSE_MAP,
|
||||
START,
|
||||
AUTHENTICATE_MAPS,
|
||||
PICK_MAP,
|
||||
SEND_TO_MAPS,
|
||||
SEND_TO_MAPS_DONE,
|
||||
SEND_TO_FUSION_TABLES,
|
||||
@@ -85,9 +74,6 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
NOT_READY
|
||||
}
|
||||
|
||||
private static final int PROGRESS_DIALOG = 1;
|
||||
private ProgressDialog progressDialog;
|
||||
|
||||
// Set in Activity.onCreate
|
||||
private MyTracksProviderUtils providerUtils;
|
||||
private GoogleAnalyticsTracker tracker;
|
||||
@@ -100,19 +86,19 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
private boolean sendToFusionTables;
|
||||
private boolean sendToDocs;
|
||||
|
||||
// Authentication information.
|
||||
private Account account;
|
||||
private HashMap<String, AuthManager> authMap = new HashMap<String, AuthManager>();
|
||||
|
||||
// Current sending state.
|
||||
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.
|
||||
private boolean sendToMapsSuccess;
|
||||
private boolean sendToFusionTablesSuccess;
|
||||
private boolean sendToDocsSuccess;
|
||||
private String sendToMapsMapId;
|
||||
private String sendToFusionTablesTableId;
|
||||
|
||||
@Override
|
||||
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 (currentState == SendState.DONE) {
|
||||
resetState();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute the state machine, at the start or restored state.
|
||||
@@ -165,40 +152,24 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case PROGRESS_DIALOG:
|
||||
return createProgressDialog();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void restoreInstanceState(Bundle savedInstanceState) {
|
||||
account = savedInstanceState.getParcelable(STATE_ACCOUNT);
|
||||
|
||||
currentState = SendState.values()[savedInstanceState.getInt(STATE_STATE)];
|
||||
|
||||
account = savedInstanceState.getParcelable(STATE_ACCOUNT);
|
||||
mapId = savedInstanceState.getString(STATE_MAP_ID);
|
||||
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);
|
||||
sendToMapsMapId = savedInstanceState.getString(STATE_MAP_ID);
|
||||
sendToFusionTablesTableId = savedInstanceState.getString(STATE_TABLE_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putParcelable(STATE_ACCOUNT, account);
|
||||
|
||||
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_FUSION_SUCCESS, sendToFusionTablesSuccess);
|
||||
outState.putBoolean(STATE_FUSION_TABLES_SUCCESS, sendToFusionTablesSuccess);
|
||||
outState.putBoolean(STATE_DOCS_SUCCESS, sendToDocsSuccess);
|
||||
outState.putString(STATE_MAP_ID, sendToMapsMapId);
|
||||
outState.putString(STATE_TABLE_ID, sendToFusionTablesTableId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -226,12 +197,10 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
switch (state) {
|
||||
case CHOOSE_ACCOUNT:
|
||||
return chooseAccount();
|
||||
case CHOOSE_MAP:
|
||||
return chooseMap();
|
||||
case START:
|
||||
return startSend();
|
||||
case AUTHENTICATE_MAPS:
|
||||
return authenticateToGoogleMaps();
|
||||
case PICK_MAP:
|
||||
return pickMap();
|
||||
case SEND_TO_MAPS:
|
||||
return sendToGoogleMaps();
|
||||
case SEND_TO_MAPS_DONE:
|
||||
@@ -267,7 +236,7 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
*/
|
||||
private SendState startSend() {
|
||||
if (sendToMaps) {
|
||||
return SendState.AUTHENTICATE_MAPS;
|
||||
return SendState.SEND_TO_MAPS;
|
||||
} else if (sendToFusionTables) {
|
||||
return SendState.SEND_TO_FUSION_TABLES;
|
||||
} else if (sendToDocs) {
|
||||
@@ -278,78 +247,24 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
}
|
||||
}
|
||||
|
||||
private Dialog createProgressDialog() {
|
||||
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() {
|
||||
private SendState chooseMap() {
|
||||
if (!sendToMapsNewMap) {
|
||||
Intent intent = new Intent(this, ChooseMapActivity.class);
|
||||
intent.putExtra(ChooseMapActivity.ACCOUNT, account);
|
||||
Intent intent = new Intent(this, ChooseMapActivity.class)
|
||||
.putExtra(ChooseMapActivity.ACCOUNT, account);
|
||||
startActivityForResult(intent, Constants.CHOOSE_MAP);
|
||||
return SendState.NOT_READY;
|
||||
} else {
|
||||
return SendState.SEND_TO_MAPS;
|
||||
return SendState.START;
|
||||
}
|
||||
}
|
||||
|
||||
private SendState sendToGoogleMaps() {
|
||||
tracker.trackPageView("/send/maps");
|
||||
|
||||
SendToMaps.OnSendCompletedListener onCompletion = new SendToMaps.OnSendCompletedListener() {
|
||||
@Override
|
||||
public void onSendCompleted(String mapId, boolean success) {
|
||||
sendToMapsSuccess = success;
|
||||
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();
|
||||
|
||||
Intent intent = new Intent(this, SendMapsActivity.class)
|
||||
.putExtra(SendMapsActivity.ACCOUNT, account)
|
||||
.putExtra(SendMapsActivity.TRACK_ID, sendTrackId)
|
||||
.putExtra(SendMapsActivity.MAP_ID, mapId);
|
||||
startActivityForResult(intent, Constants.SEND_MAPS);
|
||||
return SendState.NOT_READY;
|
||||
}
|
||||
|
||||
@@ -395,12 +310,10 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
|
||||
private SendState onSendToGoogleDone() {
|
||||
tracker.dispatch();
|
||||
String mapsUrl = sendToMaps && sendToMapsSuccess
|
||||
? MapsFacade.buildMapUrl(sendToMapsMapId)
|
||||
: null;
|
||||
Track track = providerUtils.getTrack(sendTrackId);
|
||||
String mapsUrl = sendToMaps && sendToMapsSuccess ? SendMapsUtils.getMapUrl(track) : null;
|
||||
String fusionTablesUrl = sendToFusionTables && sendToFusionTablesSuccess
|
||||
? SendFusionTablesUtils.getMapUrl(providerUtils.getTrack(sendTrackId))
|
||||
: null;
|
||||
? SendFusionTablesUtils.getMapUrl(track) : null;
|
||||
Intent intent = new Intent(this, UploadResultActivity.class)
|
||||
.putExtra(UploadResultActivity.HAS_MAPS_RESULT, sendToMaps)
|
||||
.putExtra(UploadResultActivity.HAS_FUSION_TABLES_RESULT, sendToFusionTables)
|
||||
@@ -420,44 +333,11 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
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
|
||||
public void onActivityResult(int requestCode, int resultCode,
|
||||
final Intent results) {
|
||||
SendState nextState = null;
|
||||
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: {
|
||||
if (resultCode == RESULT_CANCELED) {
|
||||
nextState = SendState.FINISH;
|
||||
@@ -468,16 +348,29 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
nextState = SendState.FINISH;
|
||||
break;
|
||||
}
|
||||
nextState = SendState.START;
|
||||
nextState = SendState.CHOOSE_MAP;
|
||||
break;
|
||||
}
|
||||
case Constants.SEND_DOCS: {
|
||||
case Constants.CHOOSE_MAP: {
|
||||
if (resultCode == RESULT_CANCELED) {
|
||||
nextState = SendState.FINISH;
|
||||
break;
|
||||
}
|
||||
sendToDocsSuccess = results.getBooleanExtra(SendDocsActivity.SUCCESS, false);
|
||||
nextState = SendState.SEND_TO_DOCS_DONE;
|
||||
mapId = results.getStringExtra(ChooseMapActivity.MAP_ID);
|
||||
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;
|
||||
}
|
||||
case Constants.SEND_FUSION_TABLES: {
|
||||
@@ -487,10 +380,18 @@ public class SendActivity extends Activity implements ProgressIndicator {
|
||||
}
|
||||
sendToFusionTablesSuccess = results.getBooleanExtra(
|
||||
SendFusionTablesActivity.SUCCESS, false);
|
||||
sendToFusionTablesTableId = results.getStringExtra(SendFusionTablesActivity.TABLE_ID);
|
||||
nextState = SendState.SEND_TO_FUSION_TABLES_DONE;
|
||||
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: {
|
||||
Log.e(TAG, "Unrequested result: " + requestCode);
|
||||
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.
|
||||
*/
|
||||
private void resetState() {
|
||||
account = null;
|
||||
authMap.clear();
|
||||
currentState = SendState.CHOOSE_ACCOUNT;
|
||||
account = null;
|
||||
mapId = null;
|
||||
sendToMapsSuccess = false;
|
||||
sendToFusionTablesSuccess = 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
|
||||
public static final String SUCCESS = "success";
|
||||
public static final String TABLE_ID = "tableId";
|
||||
|
||||
private static final int PROGRESS_DIALOG = 1;
|
||||
|
||||
@@ -67,15 +66,13 @@ public class SendFusionTablesActivity extends Activity {
|
||||
Intent intent = getIntent();
|
||||
Account account = intent.getParcelableExtra(ACCOUNT);
|
||||
if (account == null) {
|
||||
setResult(
|
||||
RESULT_OK, new Intent().putExtra(SUCCESS, false).putExtra(TABLE_ID, (String) 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).putExtra(TABLE_ID, (String) null));
|
||||
setResult(RESULT_OK, new Intent().putExtra(SUCCESS, false));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
@@ -121,10 +118,9 @@ public class SendFusionTablesActivity extends Activity {
|
||||
* Invokes when the associated AsyncTask completes.
|
||||
*
|
||||
* @param success true if success
|
||||
* @param tableId tableId if available
|
||||
*/
|
||||
public void onAsyncTaskCompleted(boolean success, String tableId) {
|
||||
setResult(RESULT_OK, new Intent().putExtra(SUCCESS, success).putExtra(TABLE_ID, tableId));
|
||||
public void onAsyncTaskCompleted(boolean success) {
|
||||
setResult(RESULT_OK, new Intent().putExtra(SUCCESS, success));
|
||||
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.Waypoint;
|
||||
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
@@ -125,7 +124,7 @@ public class SendFusionTablesAsyncTask extends AsyncTask<Void, Integer, Boolean>
|
||||
public void setActivity(SendFusionTablesActivity activity) {
|
||||
this.activity = activity;
|
||||
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) {
|
||||
activity.onAsyncTaskCompleted(success, tableId);
|
||||
activity.onAsyncTaskCompleted(success);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,7 +324,7 @@ public class SendFusionTablesAsyncTask extends AsyncTask<Void, Integer, Boolean>
|
||||
}
|
||||
|
||||
// Upload periodically
|
||||
if (readCount % MAX_POINTS_PER_UPLOAD == MAX_POINTS_PER_UPLOAD) {
|
||||
if (readCount % MAX_POINTS_PER_UPLOAD == 0) {
|
||||
if (!prepareAndUploadPoints(track, locations, false)) {
|
||||
Log.d(TAG, "Unable to upload points");
|
||||
return false;
|
||||
@@ -371,7 +370,7 @@ public class SendFusionTablesAsyncTask extends AsyncTask<Void, Integer, Boolean>
|
||||
*/
|
||||
private boolean prepareAndUploadPoints(Track track, List<Location> locations, boolean lastBatch) {
|
||||
// Prepare locations
|
||||
ArrayList<Track> splitTracks = prepareLocations(track, locations);
|
||||
ArrayList<Track> splitTracks = SendToGoogleUtils.prepareLocations(track, locations);
|
||||
|
||||
// Upload segments
|
||||
boolean onlyOneSegment = lastBatch && currentSegment == 1 && splitTracks.size() == 1;
|
||||
@@ -389,99 +388,6 @@ public class SendFusionTablesAsyncTask extends AsyncTask<Void, Integer, Boolean>
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -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 java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
@@ -59,6 +60,10 @@ public class TrackWriterTest extends AndroidTestCase {
|
||||
|
||||
this.stream = stream;
|
||||
this.canWrite = canWrite;
|
||||
|
||||
// The directory is set in the canWriteFile. However, this class
|
||||
// overwrites canWriteFile, thus needs to set it.
|
||||
setDirectory(new File("/"));
|
||||
}
|
||||
|
||||
@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