Initialize camera position only once instead of

initializing it once per onCreateView.
This commit is contained in:
Jimmy Shih
2012-11-09 16:41:19 -08:00
commit 6c9d64900e
696 changed files with 66444 additions and 0 deletions
@@ -0,0 +1,56 @@
/*
* 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.util;
import com.google.android.apps.analytics.GoogleAnalyticsTracker;
import android.content.Context;
/**
* Utitlites for sending pageviews to Google Analytics.
*
* @author Jimmy Shih
*/
public class AnalyticsUtils {
private static final String UA = "UA-7222692-2";
private static final String PRODUCT_NAME = "android-mytracks";
private static GoogleAnalyticsTracker tracker;
private AnalyticsUtils() {}
/**
* Sends a page view.
*
* @param context the context
* @param page the page
*/
public static void sendPageViews(Context context, String page) {
if (tracker == null) {
tracker = GoogleAnalyticsTracker.getInstance();
tracker.startNewSession(UA, context);
tracker.setProductVersion(PRODUCT_NAME, SystemUtils.getMyTracksVersion(context));
}
tracker.trackPageView(page);
}
public static void dispatch() {
if (tracker != null) {
tracker.dispatch();
}
}
}
@@ -0,0 +1,46 @@
/*
* 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.util;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.services.sensors.BluetoothConnectionManager;
import android.annotation.TargetApi;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.util.Log;
import java.io.IOException;
/**
* API level 10 specific implementation of the {@link ApiAdapter}.
*
* @author Jimmy Shih
*/
@TargetApi(10)
public class Api10Adapter extends Api9Adapter {
@Override
public BluetoothSocket getBluetoothSocket(BluetoothDevice bluetoothDevice) throws IOException {
try {
return bluetoothDevice.createInsecureRfcommSocketToServiceRecord(
BluetoothConnectionManager.MY_TRACKS_UUID);
} catch (IOException e) {
Log.d(Constants.TAG, "Unable to create insecure connection", e);
}
return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.MY_TRACKS_UUID);
};
}
@@ -0,0 +1,122 @@
/*
* 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.util;
import com.google.android.apps.mytracks.ContextualActionModeCallback;
import com.google.android.maps.mytracks.R;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.SearchManager;
import android.content.Context;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.SearchView;
import android.widget.TextView;
import java.util.List;
/**
* API level 11 specific implementation of the {@link ApiAdapter}.
*
* @author Jimmy Shih
*/
@TargetApi(11)
public class Api11Adapter extends Api10Adapter {
@Override
public void hideTitle(Activity activity) {
// Do nothing
}
@Override
public void configureActionBarHomeAsUp(Activity activity) {
activity.getActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public void configureListViewContextualMenu(final Activity activity, ListView listView,
final ContextualActionModeCallback contextualActionModeCallback) {
listView.setOnItemLongClickListener(new OnItemLongClickListener() {
ActionMode actionMode;
@Override
public boolean onItemLongClick(
AdapterView<?> parent, View view, final int position, final long id) {
if (actionMode != null) {
return false;
}
actionMode = activity.startActionMode(new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.getMenuInflater().inflate(R.menu.list_context_menu, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// Return false to indicate no change.
return false;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
actionMode = null;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
mode.finish();
return contextualActionModeCallback.onClick(item.getItemId(), position, id);
}
});
TextView textView = (TextView) view.findViewById(R.id.list_item_name);
if (textView != null) {
actionMode.setTitle(textView.getText());
}
view.setSelected(true);
return true;
}
});
};
@Override
public void configureSearchWidget(Activity activity, final MenuItem menuItem) {
SearchManager searchManager = (SearchManager) activity.getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) menuItem.getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(activity.getComponentName()));
searchView.setQueryRefinementEnabled(true);
}
@Override
public boolean handleSearchMenuSelection(Activity activity) {
// Returns false to allow the platform to expand the search widget.
return false;
}
@Override
public <T> void addAllToArrayAdapter(ArrayAdapter<T> arrayAdapter, List<T> items) {
arrayAdapter.addAll(items);
}
@Override
public void invalidMenu(Activity activity) {
activity.invalidateOptionsMenu();
}
}
@@ -0,0 +1,73 @@
/*
* 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.util;
import android.annotation.TargetApi;
import android.app.ActionBar;
import android.app.Activity;
import android.view.MenuItem;
import android.widget.SearchView;
/**
* API level 14 specific implementation of the {@link ApiAdapter}.
*
* @author Jimmy Shih
*/
@TargetApi(14)
public class Api14Adapter extends Api11Adapter {
@Override
public void configureActionBarHomeAsUp(Activity activity) {
ActionBar actionBar = activity.getActionBar();
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
}
@Override
public void configureSearchWidget(Activity activity, final MenuItem menuItem) {
super.configureSearchWidget(activity, menuItem);
SearchView searchView = (SearchView) menuItem.getActionView();
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
menuItem.collapseActionView();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
return false;
}
});
searchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() {
@Override
public boolean onSuggestionSelect(int position) {
return false;
}
@Override
public boolean onSuggestionClick(int position) {
menuItem.collapseActionView();
return false;
}
});
}
@Override
public boolean handleSearchKey(MenuItem menuItem) {
menuItem.expandActionView();
return true;
}
}
@@ -0,0 +1,139 @@
/*
* 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.util;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.ContextualActionModeCallback;
import com.google.android.apps.mytracks.services.sensors.BluetoothConnectionManager;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.apache.ApacheHttpTransport;
import android.app.Activity;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.SharedPreferences.Editor;
import android.util.Log;
import android.view.MenuItem;
import android.view.Window;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
/**
* API level 8 specific implementation of the {@link ApiAdapter}.
*
* @author Bartlomiej Niechwiej
*/
public class Api8Adapter implements ApiAdapter {
@Override
public void applyPreferenceChanges(Editor editor) {
editor.commit();
}
@Override
public void enableStrictMode() {
// Not supported
}
@Override
public byte[] copyByteArray(byte[] input, int start, int end) {
int length = end - start;
byte[] output = new byte[length];
System.arraycopy(input, start, output, 0, length);
return output;
}
@Override
public HttpTransport getHttpTransport() {
return new ApacheHttpTransport();
}
@Override
public boolean isGeoCoderPresent() {
return true;
}
@Override
public BluetoothSocket getBluetoothSocket(BluetoothDevice bluetoothDevice) throws IOException {
try {
Class<? extends BluetoothDevice> c = bluetoothDevice.getClass();
Method insecure = c.getMethod("createInsecureRfcommSocket", Integer.class);
insecure.setAccessible(true);
return (BluetoothSocket) insecure.invoke(bluetoothDevice, 1);
} catch (SecurityException e) {
Log.d(Constants.TAG, "Unable to create insecure connection", e);
} catch (NoSuchMethodException e) {
Log.d(Constants.TAG, "Unable to create insecure connection", e);
} catch (IllegalArgumentException e) {
Log.d(Constants.TAG, "Unable to create insecure connection", e);
} catch (IllegalAccessException e) {
Log.d(Constants.TAG, "Unable to create insecure connection", e);
} catch (InvocationTargetException e) {
Log.d(Constants.TAG, "Unable to create insecure connection", e);
}
return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.MY_TRACKS_UUID);
}
@Override
public void hideTitle(Activity activity) {
activity.requestWindowFeature(Window.FEATURE_NO_TITLE);
}
@Override
public void configureActionBarHomeAsUp(Activity activity) {
// Do nothing
}
@Override
public void configureListViewContextualMenu(Activity activity, ListView listView,
ContextualActionModeCallback contextualActionModeCallback) {
activity.registerForContextMenu(listView);
}
@Override
public void configureSearchWidget(Activity activity, MenuItem menuItem) {
// Do nothing
}
@Override
public boolean handleSearchMenuSelection(Activity activity) {
activity.onSearchRequested();
return true;
}
@Override
public <T> void addAllToArrayAdapter(ArrayAdapter<T> arrayAdapter, List<T> items) {
for (T item : items) {
arrayAdapter.add(item);
}
}
@Override
public void invalidMenu(Activity activity) {
// Do nothing
}
@Override
public boolean handleSearchKey(MenuItem menuItem) {
// Return false and allow the framework to handle the search key.
return false;
}
}
@@ -0,0 +1,71 @@
/*
* 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.util;
import com.google.android.apps.mytracks.Constants;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import android.annotation.TargetApi;
import android.content.SharedPreferences.Editor;
import android.location.Geocoder;
import android.os.StrictMode;
import android.util.Log;
import java.util.Arrays;
/**
* API level 9 specific implementation of the {@link ApiAdapter}.
*
* @author Rodrigo Damazio
*/
@TargetApi(9)
public class Api9Adapter extends Api8Adapter {
@Override
public void applyPreferenceChanges(Editor editor) {
// Apply asynchronously
editor.apply();
}
@Override
public void enableStrictMode() {
Log.d(Constants.TAG, "Enabling strict mode");
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.build());
}
@Override
public byte[] copyByteArray(byte[] input, int start, int end) {
return Arrays.copyOfRange(input, start, end);
}
@Override
public HttpTransport getHttpTransport() {
return new NetHttpTransport();
}
@Override
public boolean isGeoCoderPresent() {
return Geocoder.isPresent();
}
}
@@ -0,0 +1,173 @@
/*
* 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.util;
import com.google.android.apps.mytracks.ContextualActionModeCallback;
import com.google.api.client.http.HttpTransport;
import android.app.Activity;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.SharedPreferences;
import android.view.MenuItem;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import java.io.IOException;
import java.util.List;
/**
* A set of methods that may be implemented differently depending on the Android
* API level.
*
* @author Bartlomiej Niechwiej
*/
public interface ApiAdapter {
/**
* Applies all the changes done to a given preferences editor. Changes may or
* may not be applied immediately.
* <p>
* Due to changes in API level 9.
*
* @param editor the editor
*/
public void applyPreferenceChanges(SharedPreferences.Editor editor);
/**
* Enables strict mode where supported, only if this is a development build.
* <p>
* Due to changes in API level 9.
*/
public void enableStrictMode();
/**
* Copies elements from an input byte array into a new byte array, from
* indexes start (inclusive) to end (exclusive). The end index must be less
* than or equal to the input length.
* <p>
* Due to changes in API level 9.
*
* @param input the input byte array
* @param start the start index
* @param end the end index
* @return a new array containing elements from the input byte array.
*/
public byte[] copyByteArray(byte[] input, int start, int end);
/**
* Gets a {@link HttpTransport}.
* <p>
* Due to changes in API level 9.
*/
public HttpTransport getHttpTransport();
/**
* Returns true if GeoCoder is present.
* <p>
* Due to changes in API level 9.
*/
public boolean isGeoCoderPresent();
/**
* Gets a {@link BluetoothSocket}.
* <p>
* Due to changes in API level 10.
*
* @param bluetoothDevice
*/
public BluetoothSocket getBluetoothSocket(BluetoothDevice bluetoothDevice) throws IOException;
/**
* Hides the title. If the platform supports the action bar, do nothing.
* Ideally, with the action bar, we would like to collapse the navigation tabs
* into the action bar. However, collapsing is not supported by the
* compatibility library.
* <p>
* Due to changes in API level 11.
*
* @param activity the activity
*/
public void hideTitle(Activity activity);
/**
* Configures the action bar with the Home button as an Up button. If the
* platform doesn't support the action bar, do nothing.
* <p>
* Due to changes in API level 11.
*
* @param activity the activity
*/
public void configureActionBarHomeAsUp(Activity activity);
/**
* Configures the list view context menu.
* <p>
* Due to changes in API level 11.
*
* @param activity the activity
* @param listView the list view
* @param contextualActionModeCallback the callback when an item is selected
* in the contextual action mode
*/
public void configureListViewContextualMenu(Activity activity, ListView listView,
ContextualActionModeCallback contextualActionModeCallback);
/**
* Configures the search widget.
* <p>
* Due to changes in API level 11.
*
* @param activity the activity
* @param menuItem the search menu item
*/
public void configureSearchWidget(Activity activity, MenuItem menuItem);
/**
* Handles the search menu selection. Returns true if handled.
* <p>
* Due to changes in API level 11.
*
* @param activity the activity
*/
public boolean handleSearchMenuSelection(Activity activity);
/**
* Adds all items to an array adapter.
* <p>
* Due to changes in API level 11.
*
* @param arrayAdapter the array adapter
* @param items list of items
*/
public <T> void addAllToArrayAdapter(ArrayAdapter<T> arrayAdapter, List<T> items);
/**
* Invalidates the menu.
* <p>
* Due to changes in API level 11.
*/
public void invalidMenu(Activity activity);
/**
* Handles the search key press. Returns true if handled.
* <p>
* Due to changes in API level 14.
*
* @param menu the search menu
*/
public boolean handleSearchKey(MenuItem menu);
}
@@ -0,0 +1,53 @@
/*
* 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.util;
import android.os.Build;
/**
* A factory to get the {@link ApiAdapter} for the current device.
*
* @author Rodrigo Damazio
*/
public class ApiAdapterFactory {
private static ApiAdapter apiAdapter;
/**
* Gets the {@link ApiAdapter} for the current device.
*/
public static ApiAdapter getApiAdapter() {
if (apiAdapter == null) {
if (Build.VERSION.SDK_INT >= 14) {
apiAdapter = new Api14Adapter();
return apiAdapter;
} else if (Build.VERSION.SDK_INT >= 11) {
apiAdapter = new Api11Adapter();
return apiAdapter;
} else if (Build.VERSION.SDK_INT >= 10) {
apiAdapter = new Api10Adapter();
return apiAdapter;
} else if (Build.VERSION.SDK_INT >= 9) {
apiAdapter = new Api9Adapter();
return apiAdapter;
} else {
apiAdapter = new Api8Adapter();
return apiAdapter;
}
}
return apiAdapter;
}
}
@@ -0,0 +1,63 @@
/*
* 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.util;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import java.util.List;
import java.util.Set;
/**
* Utilities for dealing with bluetooth devices.
*
* @author Rodrigo Damazio
*/
public class BluetoothDeviceUtils {
private BluetoothDeviceUtils() {}
/**
* Populates the device names and the device addresses with all the suitable
* bluetooth devices.
*
* @param bluetoothAdapter the bluetooth adapter
* @param deviceNames list of device names
* @param deviceAddresses list of device addresses
*/
public static void populateDeviceLists(
BluetoothAdapter bluetoothAdapter, List<String> deviceNames, List<String> deviceAddresses) {
// Ensure the bluetooth adapter is not in discovery mode.
bluetoothAdapter.cancelDiscovery();
Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
for (BluetoothDevice device : pairedDevices) {
BluetoothClass bluetoothClass = device.getBluetoothClass();
if (bluetoothClass != null) {
// Not really sure what we want, but I know what we don't want.
switch (bluetoothClass.getMajorDeviceClass()) {
case BluetoothClass.Device.Major.COMPUTER:
case BluetoothClass.Device.Major.PHONE:
break;
default:
deviceAddresses.add(device.getAddress());
deviceNames.add(device.getName());
}
}
}
}
}
@@ -0,0 +1,165 @@
/*
* 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.util;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.maps.mytracks.R;
import com.google.common.annotations.VisibleForTesting;
import android.content.Context;
import java.util.Vector;
/**
* This class will generate google chart server url's.
*
* @author Sandor Dornbush
*/
public class ChartURLGenerator {
private static final String CHARTS_BASE_URL = "http://chart.apis.google.com/chart?";
private ChartURLGenerator() {}
/**
* Gets a chart of a track.
*
* @param distances An array of distance measurements
* @param elevations A matching array of elevation measurements
* @param track The track for this chart
* @param context The current appplication context
*/
public static String getChartUrl(
Vector<Double> distances, Vector<Double> elevations, Track track, Context context) {
boolean metricUnits = PreferencesUtils.getBoolean(
context, R.string.metric_units_key, PreferencesUtils.METRIC_UNITS_DEFAULT);
return getChartUrl(
distances, elevations, track, context.getString(R.string.stats_elevation), metricUnits);
}
/**
* Gets a chart of a track. This form is for testing without contexts.
*
* @param distances An array of distance measurements
* @param elevations A matching array of elevation measurements
* @param track The track for this chart
* @param title The title for the chart
* @param metricUnits Should the data be displayed in metric units
*/
@VisibleForTesting
static String getChartUrl(Vector<Double> distances, Vector<Double> elevations, Track track,
String title, boolean metricUnits) {
if (distances == null || elevations == null || track == null) {
return null;
}
if (distances.size() != elevations.size()) {
return null;
}
// Round it up.
TripStatistics tripStatistics = track.getTripStatistics();
double effectiveMaxY = tripStatistics.getMaxElevation();
if (!metricUnits) {
effectiveMaxY *= UnitConversions.M_TO_FT;
}
effectiveMaxY = ((int) (effectiveMaxY / 100)) * 100 + 100;
// Round it down.
double effectiveMinY = tripStatistics.getMinElevation();
if (!metricUnits) {
effectiveMinY *= UnitConversions.M_TO_FT;
}
effectiveMinY = ((int) (effectiveMinY / 100)) * 100;
if (tripStatistics.getMinElevation() < 0) {
effectiveMinY -= 100;
}
double ySpread = effectiveMaxY - effectiveMinY;
StringBuilder sb = new StringBuilder(CHARTS_BASE_URL);
sb.append("&chs=600x350");
sb.append("&cht=lxy");
// Title
sb.append("&chtt=");
sb.append(title);
// Labels
sb.append("&chxt=x,y");
double totalDistance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
if (!metricUnits) {
totalDistance *= UnitConversions.KM_TO_MI;
}
int xInterval = ((int) (totalDistance / 6));
int yInterval = ((int) (ySpread / 600)) * 100;
if (yInterval < 100) {
yInterval = 25;
}
// Range
sb.append("&chxr=0,0,");
sb.append((int) totalDistance);
sb.append(',');
sb.append(xInterval);
sb.append("|1,");
sb.append(effectiveMinY);
sb.append(',');
sb.append(effectiveMaxY);
sb.append(',');
sb.append(yInterval);
// Line color
sb.append("&chco=009A00");
// Fill
sb.append("&chm=B,00AA00,0,0,0");
// Grid lines
double desiredGrids = ySpread / yInterval;
sb.append("&chg=100000,");
sb.append(100.0 / desiredGrids);
sb.append(",1,0");
// Data
sb.append("&chd=e:");
for (int i = 0; i < distances.size(); i++) {
int normalized = (int) (getNormalizedDistance(distances.elementAt(i), track) * 4095);
sb.append(ChartsExtendedEncoder.getEncodedValue(normalized));
}
sb.append(ChartsExtendedEncoder.getSeparator());
for (int i = 0; i < elevations.size(); i++) {
double value = elevations.elementAt(i);
if (!metricUnits) {
value *= UnitConversions.M_TO_FT;
}
int normalized = (int) (getNormalizedElevation(value, effectiveMinY, ySpread) * 4095);
sb.append(ChartsExtendedEncoder.getEncodedValue(normalized));
}
return sb.toString();
}
private static double getNormalizedDistance(double value, Track track) {
return value / track.getTripStatistics().getTotalDistance();
}
private static double getNormalizedElevation(double value, double effectiveMinY, double ySpread) {
return (value - effectiveMinY) / ySpread;
}
}
@@ -0,0 +1,51 @@
/*
* 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.util;
/**
* The singleton class representing the extended encoding of chart data.
*/
public class ChartsExtendedEncoder {
// ChartServer data encoding in extended mode
private static final String CHARTSERVER_EXTENDED_ENCODING_SEPARATOR = ",";
private static final String CHARTSERVER_EXTENDED_ENCODING =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.";
private static final int CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES =
CHARTSERVER_EXTENDED_ENCODING.length();
private static final String MISSING_POINT_EXTENDED_ENCODING = "__";
private ChartsExtendedEncoder() { }
public static String getEncodedValue(int scaled) {
int index1 = scaled / CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES;
if (index1 < 0 || index1 >= CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES) {
return MISSING_POINT_EXTENDED_ENCODING;
}
int index2 = scaled % CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES;
if (index2 < 0 || index2 >= CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES) {
return MISSING_POINT_EXTENDED_ENCODING;
}
return String.valueOf(CHARTSERVER_EXTENDED_ENCODING.charAt(index1))
+ String.valueOf(CHARTSERVER_EXTENDED_ENCODING.charAt(index2));
}
public static String getSeparator() {
return CHARTSERVER_EXTENDED_ENCODING_SEPARATOR;
}
}
@@ -0,0 +1,125 @@
/*
* 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.util;
import com.google.android.maps.mytracks.R;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.view.View;
/**
* Utilities for creating dialogs.
*
* @author Jimmy Shih
*/
public class DialogUtils {
private DialogUtils() {}
/**
* Creates a confirmation dialog.
*
* @param context the context
* @param messageId the message id
* @param okListener the listener when OK is clicked
*/
public static Dialog createConfirmationDialog(
Context context, int messageId, DialogInterface.OnClickListener okListener) {
return createConfirmationDialog(context, messageId, null, okListener, null);
}
/**
* Creates a confirmation dialog.
*
* @param context the context
* @param messageId the messageId
* @param view the view
* @param okListener the listener when OK is clicked
* @param cancelListener the listener when cancel is clicked
*/
public static Dialog createConfirmationDialog(
Context context, int messageId, View view, DialogInterface.OnClickListener okListener,
DialogInterface.OnClickListener cancelListener) {
AlertDialog.Builder builder = new AlertDialog.Builder(context)
.setCancelable(true)
.setIcon(android.R.drawable.ic_dialog_alert)
.setNegativeButton(android.R.string.cancel, cancelListener)
.setPositiveButton(android.R.string.ok, okListener)
.setTitle(R.string.generic_confirm_title);
if (messageId != -1) {
builder.setMessage(messageId);
}
if (view != null) {
builder.setView(view);
}
return builder.create();
}
/**
* Creates a spinner progress dialog.
*
* @param context the context
* @param messageId the progress message id
* @param onCancelListener the cancel listener
*/
public static ProgressDialog createSpinnerProgressDialog(
Context context, int messageId, DialogInterface.OnCancelListener onCancelListener) {
return createProgressDialog(true, context, messageId, onCancelListener);
}
/**
* Creates a horizontal progress dialog.
*
* @param context the context
* @param messageId the progress message id
* @param onCancelListener the cancel listener
* @param formatArgs the format arguments for the messageId
*/
public static ProgressDialog createHorizontalProgressDialog(Context context, int messageId,
DialogInterface.OnCancelListener onCancelListener, Object... formatArgs) {
return createProgressDialog(false, context, messageId, onCancelListener, formatArgs);
}
/**
* Creates a progress dialog.
*
* @param spinner true to use the spinner style
* @param context the context
* @param messageId the progress message id
* @param onCancelListener the cancel listener
* @param formatArgs the format arguments for the message id
*/
private static ProgressDialog createProgressDialog(boolean spinner, Context context,
int messageId, DialogInterface.OnCancelListener onCancelListener, Object... formatArgs) {
ProgressDialog progressDialog = new ProgressDialog(context);
progressDialog.setCancelable(true);
progressDialog.setCanceledOnTouchOutside(false);
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
progressDialog.setIndeterminate(true);
progressDialog.setMessage(context.getString(messageId, formatArgs));
progressDialog.setOnCancelListener(onCancelListener);
progressDialog.setProgressStyle(spinner ? ProgressDialog.STYLE_SPINNER
: ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setTitle(R.string.generic_progress_title);
return progressDialog;
}
}
@@ -0,0 +1,66 @@
/*
* 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.util;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
/**
* Utilities for EULA.
*
* @author Jimmy Shih
*/
public class EulaUtils {
private static final String EULA_PREFERENCE_FILE = "eula";
// Accepting Google mobile terms of service
private static final String ACCEPT_EULA_PREFERENCE_KEY = "eula.google_mobile_tos_accepted";
private static final String SHOW_WELCOME_PREFERENCE_KEY = "showWelcome";
private EulaUtils() {}
public static boolean getAcceptEula(Context context) {
return getValue(context, ACCEPT_EULA_PREFERENCE_KEY, false);
}
public static void setAcceptEula(Context context) {
setValue(context, ACCEPT_EULA_PREFERENCE_KEY, true);
}
public static boolean getShowWelcome(Context context) {
return getValue(context, SHOW_WELCOME_PREFERENCE_KEY, true);
}
public static void setShowWelcome(Context context) {
setValue(context, SHOW_WELCOME_PREFERENCE_KEY, false);
}
private static boolean getValue(Context context, String key, boolean defaultValue) {
SharedPreferences sharedPreferences = context.getSharedPreferences(
EULA_PREFERENCE_FILE, Context.MODE_PRIVATE);
return sharedPreferences.getBoolean(key, defaultValue);
}
private static void setValue(Context context, String key, boolean value) {
SharedPreferences sharedPreferences = context.getSharedPreferences(
EULA_PREFERENCE_FILE, Context.MODE_PRIVATE);
Editor editor = sharedPreferences.edit();
editor.putBoolean(key, value);
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
}
}
@@ -0,0 +1,204 @@
/*
* 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.util;
import com.google.android.apps.mytracks.Constants;
import com.google.common.annotations.VisibleForTesting;
import android.os.Environment;
import java.io.File;
/**
* Utilities for dealing with files.
*
* @author Rodrigo Damazio
*/
public class FileUtils {
private FileUtils() {}
/**
* The maximum FAT32 path length. See the FAT32 spec at
* http://msdn.microsoft.com/en-us/windows/hardware/gg463080
*/
@VisibleForTesting
static final int MAX_FAT32_PATH_LENGTH = 260;
/**
* Returns whether the SD card is available.
*/
public static boolean isSdCardAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
/**
* Ensures the given directory exists by creating it and its parents if
* necessary.
*
* @return whether the directory exists (either already existed or was
* successfully created)
*/
public static boolean ensureDirectoryExists(File dir) {
if (dir.exists() && dir.isDirectory()) {
return true;
}
if (dir.mkdirs()) {
return true;
}
return false;
}
/**
* Builds a path inside the My Tracks directory in the SD card.
*
* @param components the path components inside the mytracks directory
* @return the full path to the destination
*/
public static String buildExternalDirectoryPath(String... components) {
StringBuilder dirNameBuilder = new StringBuilder();
dirNameBuilder.append(Environment.getExternalStorageDirectory());
dirNameBuilder.append(File.separatorChar);
dirNameBuilder.append(Constants.SDCARD_TOP_DIR);
for (String component : components) {
dirNameBuilder.append(File.separatorChar);
dirNameBuilder.append(component);
}
return dirNameBuilder.toString();
}
/**
* Builds a filename with the given base name (prefix) and the given
* extension, possibly adding a suffix to ensure the file doesn't exist.
*
* @param directory the directory the file will live in
* @param fileBaseName the prefix for the file name
* @param extension the file's extension
* @return the complete file name, without the directory
*/
public static synchronized String buildUniqueFileName(
File directory, String fileBaseName, String extension) {
return buildUniqueFileName(directory, fileBaseName, extension, 0);
}
/**
* Builds a filename with the given base and the given extension, possibly
* adding a suffix to ensure the file doesn't exist.
*
* @param directory the directory the filename will be located in
* @param base the base for the filename
* @param extension the extension for the filename
* @param suffix the first numeric suffix to try to use, or 0 for none
* @return the complete filename, without the directory
*/
private static String buildUniqueFileName(
File directory, String base, String extension, int suffix) {
String suffixName = "";
if (suffix > 0) {
suffixName += "(" + Integer.toString(suffix) + ")";
}
suffixName += "." + extension;
String baseName = sanitizeFileName(base);
baseName = truncateFileName(directory, baseName, suffixName);
String fullName = baseName + suffixName;
if (!new File(directory, fullName).exists()) {
return fullName;
}
return buildUniqueFileName(directory, base, extension, suffix + 1);
}
/**
* Sanitizes the name as a valid fat32 filename. For simplicity, fat32
* filename characters may be any combination of letters, digits, or
* characters with code point values greater than 127. Replaces the invalid
* characters with "_" and collapses multiple "_" together.
*
* @param name name
*/
@VisibleForTesting
static String sanitizeFileName(String name) {
StringBuffer buffer = new StringBuffer(name.length());
for (int i = 0; i < name.length(); i++) {
int codePoint = name.codePointAt(i);
char character = name.charAt(i);
if (Character.isLetterOrDigit(character) || codePoint > 127 || isSpecialFat32(character)) {
buffer.appendCodePoint(codePoint);
} else {
buffer.append("_");
}
}
String result = buffer.toString();
return result.replaceAll("_+", "_");
}
/**
* Returns true if it is a special FAT32 character.
*
* @param character the character
*/
private static boolean isSpecialFat32(char character) {
switch (character) {
case '$':
case '%':
case '\'':
case '-':
case '_':
case '@':
case '~':
case '`':
case '!':
case '(':
case ')':
case '{':
case '}':
case '^':
case '#':
case '&':
case '+':
case ',':
case ';':
case '=':
case '[':
case ']':
case ' ':
return true;
default:
return false;
}
}
/**
* Truncates the name if necessary so the filename path length (directory +
* name + suffix) meets the Fat32 path limit.
*
* @param directory directory
* @param name name
* @param suffix suffix
*/
@VisibleForTesting
static String truncateFileName(File directory, String name, String suffix) {
// 1 at the end accounts for the FAT32 filename trailing NUL character
int requiredLength = directory.getPath().length() + suffix.length() + 1;
if (name.length() + requiredLength > MAX_FAT32_PATH_LENGTH) {
int limit = MAX_FAT32_PATH_LENGTH - requiredLength;
return name.substring(0, limit);
} else {
return name;
}
}
}
@@ -0,0 +1,45 @@
/*
* 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.util;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
/**
* Utilities for Google location settings
*
* @author Jimmy Shih
*/
public class GoogleLocationUtils {
public static final String
ACTION_GOOGLE_LOCATION_SETTINGS = "com.google.android.gsf.GOOGLE_LOCATION_SETTINGS";
private static final String
ACTION_GOOGLE_APPS_LOCATION_SETTINGS = "com.google.android.gsf.GOOGLE_APPS_LOCATION_SETTINGS";
/**
* Returns true if the Google location settings is available.
*/
public static boolean isAvailable(Context context) {
Intent intent = new Intent(ACTION_GOOGLE_APPS_LOCATION_SETTINGS);
ResolveInfo resolveInfo = context.getPackageManager()
.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
return resolveInfo != null;
}
}
@@ -0,0 +1,106 @@
/*
* 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.util;
import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
import com.google.android.maps.mytracks.R;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import java.io.File;
/**
* Utilities for creating intents.
*
* @author Jimmy Shih
*/
public class IntentUtils {
public static final String TEXT_PLAIN_TYPE = "text/plain";
private static final String BLUETOOTH_PACKAGE_NAME = "com.android.bluetooth";
private static final String TWITTER_PACKAGE_NAME = "com.twitter.android";
private IntentUtils() {}
/**
* Creates an intent with {@link Intent#FLAG_ACTIVITY_CLEAR_TOP} and
* {@link Intent#FLAG_ACTIVITY_NEW_TASK}.
*
* @param context the context
* @param cls the class
*/
public static final Intent newIntent(Context context, Class<?> cls) {
return new Intent(context, cls).addFlags(
Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
}
/**
* Creates an intent to share a track url with an app.
*
* @param context the context
* @param trackId the track id
* @param trackUrl the track url
* @param packageName the sharing app package name
* @param className the sharing app class name
*/
public static final Intent newShareUrlIntent(
Context context, long trackId, String trackUrl, String packageName, String className) {
Track track = MyTracksProviderUtils.Factory.get(context).getTrack(trackId);
String trackDescription = track == null ? ""
: new DescriptionGeneratorImpl(context).generateTrackDescription(track, null, null, false);
boolean urlOnly = TWITTER_PACKAGE_NAME.equals(packageName)
|| BLUETOOTH_PACKAGE_NAME.equals(packageName);
return new Intent(Intent.ACTION_SEND)
.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP)
.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.share_track_subject))
.putExtra(Intent.EXTRA_TEXT, urlOnly
? trackUrl
: context.getString(R.string.share_track_share_url_body, trackUrl, trackDescription))
.setComponent(new ComponentName(packageName, className))
.setType(TEXT_PLAIN_TYPE);
}
/**
* Creates an intent to share a track file with an app.
*
* @param context the context
* @param trackId the track id
* @param filePath the file path
* @param trackFileFormat the track file format
*/
public static final Intent newShareFileIntent(
Context context, long trackId, String filePath, TrackFileFormat trackFileFormat) {
Track track = MyTracksProviderUtils.Factory.get(context).getTrack(trackId);
String trackDescription = track == null ? ""
: new DescriptionGeneratorImpl(context).generateTrackDescription(track, null, null, false);
return new Intent(Intent.ACTION_SEND)
.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(filePath)))
.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.share_track_subject))
.putExtra(Intent.EXTRA_TEXT,
context.getString(R.string.share_track_share_file_body, trackDescription))
.putExtra(context.getString(R.string.track_id_broadcast_extra), trackId)
.setType(trackFileFormat.getMimeType());
}
}
@@ -0,0 +1,110 @@
/*
* 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.util;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
/**
* Utilities to display a list item.
*
* @author Jimmy Shih
*/
public class ListItemUtils {
private ListItemUtils() {}
/**
* Sets a list item.
*
* @param context the context
* @param view the list item view
* @param isRecording true if recording
* @param isPaused true if paused
* @param name the name value
* @param iconId the icon id
* @param iconContentDescriptionId the icon content description id
* @param category the category value
* @param totalTime the total time value
* @param totalDistance the total distance value
* @param startTime the start time value
* @param description the description value
*/
public static void setListItem(Context context, View view, boolean isRecording, boolean isPaused,
int iconId, int iconContentDescriptionId, String name, String category, String totalTime,
String totalDistance, String startTime, String description) {
if (isRecording) {
iconId = isPaused ? R.drawable.track_paused : R.drawable.track_recording;
iconContentDescriptionId = isPaused ? R.string.icon_pause_recording
: R.string.icon_record_track;
}
ImageView iconImageView = (ImageView) view.findViewById(R.id.list_item_icon);
iconImageView.setImageResource(iconId);
iconImageView.setContentDescription(context.getString(iconContentDescriptionId));
TextView nameTextView = (TextView) view.findViewById(R.id.list_item_name);
nameTextView.setText(name);
TextView categoryTextView = (TextView) view.findViewById(R.id.list_item_category);
setTextView(categoryTextView, category, 0);
TextView totalTimeTextView = (TextView) view.findViewById(R.id.list_item_total_time);
setTextView(totalTimeTextView, isRecording ? null : totalTime, 0);
TextView totalDistanceTextView = (TextView) view.findViewById(R.id.list_item_total_distance);
setTextView(totalDistanceTextView, isRecording ? null : totalDistance, 0);
TextView startTimeTextView = (TextView) view.findViewById(R.id.list_item_start_time);
setTextView(startTimeTextView, isRecording ? null : startTime, 0);
TextView recordingTextView = (TextView) view.findViewById(R.id.list_item_recording);
String value = isRecording ? context.getString(
isPaused ? R.string.generic_paused : R.string.generic_recording)
: null;
int color = isRecording ? context.getResources()
.getColor(isPaused ? android.R.color.white : R.color.red)
: 0;
setTextView(recordingTextView, value, color);
TextView descriptionTextView = (TextView) view.findViewById(R.id.list_item_description);
setTextView(descriptionTextView, isRecording ? null : description, 0);
}
/**
* Sets a text view.
*
* @param textView the text view
* @param value the value for the text view
*/
private static void setTextView(TextView textView, String value, int color) {
if (value == null || value.length() == 0) {
textView.setVisibility(View.GONE);
} else {
textView.setVisibility(View.VISIBLE);
textView.setText(value);
if (color != 0) {
textView.setTextColor(color);
}
}
}
}
@@ -0,0 +1,162 @@
/*
* 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.util;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.Track;
import android.location.Location;
import android.util.Log;
import java.util.ArrayList;
import java.util.Stack;
/**
* Utility class for decimating tracks at a given level of precision.
*
* @author Leif Hendrik Wilden
*/
public class LocationUtils {
private LocationUtils() {}
/**
* Computes the distance on the two sphere between the point c0 and the line
* segment c1 to c2.
*
* @param c0 the first coordinate
* @param c1 the beginning of the line segment
* @param c2 the end of the lone segment
* @return the distance in m (assuming spherical earth)
*/
private static double distance(final Location c0, final Location c1, final Location c2) {
if (c1.equals(c2)) {
return c2.distanceTo(c0);
}
final double s0lat = c0.getLatitude() * UnitConversions.DEG_TO_RAD;
final double s0lng = c0.getLongitude() * UnitConversions.DEG_TO_RAD;
final double s1lat = c1.getLatitude() * UnitConversions.DEG_TO_RAD;
final double s1lng = c1.getLongitude() * UnitConversions.DEG_TO_RAD;
final double s2lat = c2.getLatitude() * UnitConversions.DEG_TO_RAD;
final double s2lng = c2.getLongitude() * UnitConversions.DEG_TO_RAD;
double s2s1lat = s2lat - s1lat;
double s2s1lng = s2lng - s1lng;
final double u = ((s0lat - s1lat) * s2s1lat + (s0lng - s1lng) * s2s1lng)
/ (s2s1lat * s2s1lat + s2s1lng * s2s1lng);
if (u <= 0) {
return c0.distanceTo(c1);
}
if (u >= 1) {
return c0.distanceTo(c2);
}
Location sa = new Location("");
sa.setLatitude(c0.getLatitude() - c1.getLatitude());
sa.setLongitude(c0.getLongitude() - c1.getLongitude());
Location sb = new Location("");
sb.setLatitude(u * (c2.getLatitude() - c1.getLatitude()));
sb.setLongitude(u * (c2.getLongitude() - c1.getLongitude()));
return sa.distanceTo(sb);
}
/**
* Decimates the given locations for a given zoom level. This uses a
* Douglas-Peucker decimation algorithm.
*
* @param tolerance in meters
* @param locations input
* @param decimated output
*/
private static void decimate(
double tolerance, ArrayList<Location> locations, ArrayList<Location> decimated) {
final int n = locations.size();
if (n < 1) {
return;
}
int idx;
int maxIdx = 0;
Stack<int[]> stack = new Stack<int[]>();
double[] dists = new double[n];
dists[0] = 1;
dists[n - 1] = 1;
double maxDist;
double dist = 0.0;
int[] current;
if (n > 2) {
int[] stackVal = new int[] { 0, (n - 1) };
stack.push(stackVal);
while (stack.size() > 0) {
current = stack.pop();
maxDist = 0;
for (idx = current[0] + 1; idx < current[1]; ++idx) {
dist = LocationUtils.distance(
locations.get(idx), locations.get(current[0]), locations.get(current[1]));
if (dist > maxDist) {
maxDist = dist;
maxIdx = idx;
}
}
if (maxDist > tolerance) {
dists[maxIdx] = maxDist;
int[] stackValCurMax = { current[0], maxIdx };
stack.push(stackValCurMax);
int[] stackValMaxCur = { maxIdx, current[1] };
stack.push(stackValMaxCur);
}
}
}
int i = 0;
idx = 0;
decimated.clear();
for (Location l : locations) {
if (dists[idx] != 0) {
decimated.add(l);
i++;
}
idx++;
}
Log.d(Constants.TAG, "Decimating " + n + " points to " + i + " w/ tolerance = " + tolerance);
}
/**
* Decimates the given track for the given precision.
*
* @param track a track
* @param precision desired precision in meters
*/
public static void decimate(Track track, double precision) {
ArrayList<Location> decimated = new ArrayList<Location>();
decimate(precision, track.getLocations(), decimated);
track.setLocations(decimated);
}
/**
* Checks if a given location is a valid (i.e. physically possible) location
* on Earth. Note: The special separator locations (which have latitude = 100)
* will not qualify as valid. Neither will locations with lat=0 and lng=0 as
* these are most likely "bad" measurements which often cause trouble.
*
* @param location the location to test
* @return true if the location is a valid location.
*/
public static boolean isValidLocation(Location location) {
return location != null && Math.abs(location.getLatitude()) <= 90
&& Math.abs(location.getLongitude()) <= 180;
}
}
@@ -0,0 +1,224 @@
/*
* 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.util;
import com.google.android.apps.mytracks.Constants;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
/**
* Utilities to access preferences stored in {@link SharedPreferences}.
*
* @author Jimmy Shih
*/
public class PreferencesUtils {
/*
* Preferences values. The defaults need to match the defaults in the xml
* files.
*/
public static final boolean ALLOW_ACCESS_DEFAULT = false;
public static final int AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT = 0;
public static final int AUTO_RESUME_TRACK_TIMEOUT_ALWAYS = -1;
public static final int AUTO_RESUME_TRACK_TIMEOUT_DEFAULT = 10;
public static final int AUTO_RESUME_TRACK_TIMEOUT_NEVER = 0;
public static final String BLUETOOTH_SENSOR_DEFAULT = "";
public static final boolean CHART_BY_DISTANCE_DEFAULT = true;
public static final boolean CHART_SHOW_CADENCE_DEFAULT = true;
public static final boolean CHART_SHOW_ELEVATION_DEFAULT = true;
public static final boolean CHART_SHOW_HEART_RATE_DEFAULT = true;
public static final boolean CHART_SHOW_POWER_DEFAULT = true;
public static final boolean CHART_SHOW_SPEED_DEFAULT = true;
public static final String DEFAULT_ACTIVITY_DEFAULT = "";
public static final boolean DEFAULT_MAP_PUBLIC_DEFAULT = false;
public static final int FREQUENCY_OFF = 0;
public static final String GOOGLE_ACCOUNT_DEFAULT = "";
public static final int MAX_RECORDING_DISTANCE_DEFAULT = 200;
public static final boolean METRIC_UNITS_DEFAULT = true;
public static final int MIN_RECORDING_DISTANCE_DEFAULT = 5;
public static final int MIN_RECORDING_INTERVAL_ADAPT_ACCURACY = -1;
public static final int MIN_RECORDING_INTERVAL_ADAPT_BATTERY_LIFE = -2;
public static final int MIN_RECORDING_INTERVAL_DEFAULT = 0;
public static final int MIN_REQUIRED_ACCURACY_DEFAULT = 200;
public static final int MIN_REQUIRED_ACCURACY_EXCELLENT = 10;
public static final int MIN_REQUIRED_ACCURACY_POOR = 5000;
public static final boolean PICK_EXISTING_MAP_DEFAULT = false;
public static final long RECORDING_TRACK_ID_DEFAULT = -1L;
public static final boolean RECORDING_TRACK_PAUSED_DEFAULT = true;
public static final boolean REPORT_SPEED_DEFAULT = true;
public static final long SELECTED_TRACK_ID_DEFAULT = -1L;
public static final boolean SEND_TO_DOCS_DEFAULT = true;
public static final boolean SEND_TO_FUSION_TABLES_DEFAULT = true;
public static final boolean SEND_TO_MAPS_DEFAULT = true;
public static final String SENSOR_TYPE_DEFAULT = "NONE";
public static final boolean SHOW_CONFIRM_PLAY_DIALOG_DEFAULT = true;
public static final boolean SHOW_CONFIRM_SHARING_DIALOG_DEFAULT = true;
public static final int SPLIT_FREQUENCY_DEFAULT = 0;
public static final boolean STATS_SHOW_COORDINATE_DEFAULT = false;
public static final boolean STATS_SHOW_ELEVATION_DEFAULT = false;
public static final boolean STATS_SHOW_GRADE_DEFAULT = false;
public static final boolean STATS_SHOW_MOVING_TIME_DEFAULT = false;
public static final String TRACK_COLOR_MODE_DEFAULT = "SINGLE";
public static final int TRACK_COLOR_MODE_MEDIUM_DEFAULT = 15;
public static final int TRACK_COLOR_MODE_PERCENTAGE_DEFAULT = 25;
public static final int TRACK_COLOR_MODE_SLOW_DEFAULT = 9;
public static final String TRACK_NAME_DEFAULT = "LOCATION";
public static final int TRACK_WIDGET_ITEM1_DEFAULT = 3; // moving time
public static final int TRACK_WIDGET_ITEM2_DEFAULT = 0; // distance
public static final int TRACK_WIDGET_ITEM3_DEFAULT = 1; // total time
public static final int TRACK_WIDGET_ITEM4_DEFAULT = 2; // average speed
public static final int VOICE_FREQUENCY_DEFAULT = 0;
private PreferencesUtils() {}
/**
* Gets a preference key
*
* @param context the context
* @param keyId the key id
*/
public static String getKey(Context context, int keyId) {
return context.getString(keyId);
}
/**
* Gets a boolean preference value.
*
* @param context the context
* @param keyId the key id
* @param defaultValue the default value
*/
public static boolean getBoolean(Context context, int keyId, boolean defaultValue) {
SharedPreferences sharedPreferences = context.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
return sharedPreferences.getBoolean(getKey(context, keyId), defaultValue);
}
/**
* Sets a boolean preference value.
*
* @param context the context
* @param keyId the key id
* @param value the value
*/
public static void setBoolean(Context context, int keyId, boolean value) {
SharedPreferences sharedPreferences = context.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
Editor editor = sharedPreferences.edit();
editor.putBoolean(getKey(context, keyId), value);
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
}
/**
* Gets an integer preference value.
*
* @param context the context
* @param keyId the key id
* @param defaultValue the default value
*/
public static int getInt(Context context, int keyId, int defaultValue) {
SharedPreferences sharedPreferences = context.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
return sharedPreferences.getInt(getKey(context, keyId), defaultValue);
}
/**
* Sets an integer preference value.
*
* @param context the context
* @param keyId the key id
* @param value the value
*/
public static void setInt(Context context, int keyId, int value) {
SharedPreferences sharedPreferences = context.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
Editor editor = sharedPreferences.edit();
editor.putInt(getKey(context, keyId), value);
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
}
/**
* Gets a long preference value.
*
* @param context the context
* @param keyId the key id
*/
public static long getLong(Context context, int keyId) {
SharedPreferences sharedPreferences = context.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
return sharedPreferences.getLong(getKey(context, keyId), -1L);
}
/**
* Sets a long preference value.
*
* @param context the context
* @param keyId the key id
* @param value the value
*/
public static void setLong(Context context, int keyId, long value) {
SharedPreferences sharedPreferences = context.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
Editor editor = sharedPreferences.edit();
editor.putLong(getKey(context, keyId), value);
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
}
/**
* Gets a string preference value.
*
* @param context the context
* @param keyId the key id
* @param defaultValue default value
*/
public static String getString(Context context, int keyId, String defaultValue) {
SharedPreferences sharedPreferences = context.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
return sharedPreferences.getString(getKey(context, keyId), defaultValue);
}
/**
* Sets a string preference value.
*
* @param context the context
* @param keyId the key id
* @param value the value
*/
public static void setString(Context context, int keyId, String value) {
SharedPreferences sharedPreferences = context.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
Editor editor = sharedPreferences.edit();
editor.putString(getKey(context, keyId), value);
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
}
}
@@ -0,0 +1,93 @@
/*
* 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.util;
import android.content.Context;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
/**
* Utility functions for android resources.
*
* @author Sandor Dornbush
*/
public class ResourceUtils {
public static CharSequence readFile(Context activity, int id) {
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(
activity.getResources().openRawResource(id)));
String line;
StringBuilder buffer = new StringBuilder();
while ((line = in.readLine()) != null) {
buffer.append(line).append('\n');
}
return buffer;
} catch (IOException e) {
return "";
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// Ignore
}
}
}
}
public static void readBinaryFileToOutputStream(
Context activity, int id, OutputStream os) {
BufferedInputStream in = null;
BufferedOutputStream out = null;
try {
in = new BufferedInputStream(
activity.getResources().openRawResource(id));
out = new BufferedOutputStream(os);
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
out.flush();
} catch (IOException e) {
return;
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// Ignore
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
// Ignore
}
}
}
}
private ResourceUtils() {
}
}
@@ -0,0 +1,352 @@
/*
* 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.util;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.location.Location;
import android.view.View;
import android.widget.TextView;
/**
* Utilities for updating the statistics UI labels and values.
*
* @author Jimmy Shih
*/
public class StatsUtils {
private StatsUtils() {}
/**
* Sets the location values.
*
* @param activity the activity
* @param location the location
* @param showAll true to show all the fields, false to show only the
* elevation field
*/
public static void setLocationValues(Activity activity, Location location, boolean showAll) {
boolean metricUnits = PreferencesUtils.getBoolean(
activity, R.string.metric_units_key, PreferencesUtils.METRIC_UNITS_DEFAULT);
boolean reportSpeed = PreferencesUtils.getBoolean(
activity, R.string.report_speed_key, PreferencesUtils.REPORT_SPEED_DEFAULT);
// Set elevation
boolean showElevation = PreferencesUtils.getBoolean(
activity, R.string.stats_show_elevation_key, PreferencesUtils.STATS_SHOW_ELEVATION_DEFAULT);
if (showElevation) {
double altitude = location == null ? Double.NaN : location.getAltitude();
setElevationValue(activity, R.id.stats_elevation_value, altitude, metricUnits);
}
if (!showAll) {
return;
}
// Set speed/pace
setSpeedLabel(
activity, R.id.stats_speed_label, R.string.stats_speed, R.string.stats_pace, reportSpeed);
double speed = location == null ? Double.NaN : location.getSpeed();
setSpeedValue(activity, R.id.stats_speed_value, speed, metricUnits, reportSpeed);
// Set coordinate
boolean showCoordinate = PreferencesUtils.getBoolean(activity,
R.string.stats_show_coordinate_key, PreferencesUtils.STATS_SHOW_COORDINATE_DEFAULT);
View coordinateLabelTableRow = activity.findViewById(R.id.stats_coordinate_label_table_row);
View coordinateValueTableRow = activity.findViewById(R.id.stats_coordinate_value_table_row);
if (coordinateLabelTableRow == null || coordinateValueTableRow == null) {
return;
}
coordinateLabelTableRow.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
coordinateValueTableRow.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
if (showCoordinate) {
double latitude = location == null ? Double.NaN : location.getLatitude();
double longitude = location == null ? Double.NaN : location.getLongitude();
setCoordinateValue(activity, R.id.stats_latitude_value, latitude);
setCoordinateValue(activity, R.id.stats_longitude_value, longitude);
}
}
/**
* Sets the total time value.
*
* @param activity the activity
* @param totalTime the total time
*/
public static void setTotalTimeValue(Activity activity, long totalTime) {
setTimeValue(activity, R.id.stats_total_time_value, totalTime);
}
/**
* Sets the trip statistics values.
*
* @param activity the activity
* @param tripStatistics the trip statistics
*/
public static void setTripStatisticsValues(Activity activity, TripStatistics tripStatistics) {
boolean metricUnits = PreferencesUtils.getBoolean(
activity, R.string.metric_units_key, PreferencesUtils.METRIC_UNITS_DEFAULT);
boolean reportSpeed = PreferencesUtils.getBoolean(
activity, R.string.report_speed_key, PreferencesUtils.REPORT_SPEED_DEFAULT);
boolean showMovingTime = PreferencesUtils.getBoolean(activity,
R.string.stats_show_moving_time_key, PreferencesUtils.STATS_SHOW_MOVING_TIME_DEFAULT);
// Set total distance
double totalDistance = tripStatistics == null ? Double.NaN : tripStatistics.getTotalDistance();
setDistanceValue(activity, R.id.stats_distance_value, totalDistance, metricUnits);
// Set total time
setTimeValue(activity, R.id.stats_total_time_value,
tripStatistics != null ? tripStatistics.getTotalTime() : -1L);
// Set moving time
setItemVisibility(activity, R.id.stats_moving_time_label, R.id.stats_moving_time_spacer,
R.id.stats_moving_time_value, showMovingTime);
if (showMovingTime) {
setTimeValue(activity, R.id.stats_moving_time_value,
tripStatistics != null ? tripStatistics.getMovingTime() : -1L);
}
// Set average speed
setSpeedLabel(activity, R.id.stats_average_speed_label, R.string.stats_average_speed,
R.string.stats_average_pace, reportSpeed);
double averageSpeed = tripStatistics != null ? tripStatistics.getAverageSpeed() : Double.NaN;
setSpeedValue(activity, R.id.stats_average_speed_value, averageSpeed, metricUnits, reportSpeed);
// Set average moving speed
setItemVisibility(activity, R.id.stats_average_moving_speed_label,
R.id.stats_average_moving_speed_spacer, R.id.stats_average_moving_speed_value,
showMovingTime);
if (showMovingTime) {
setSpeedLabel(activity, R.id.stats_average_moving_speed_label,
R.string.stats_average_moving_speed, R.string.stats_average_moving_pace, reportSpeed);
double averageMovingSpeed = tripStatistics != null ? tripStatistics.getAverageMovingSpeed()
: Double.NaN;
setSpeedValue(activity, R.id.stats_average_moving_speed_value, averageMovingSpeed,
metricUnits, reportSpeed);
}
// Set max speed
setSpeedLabel(activity, R.id.stats_max_speed_label, R.string.stats_max_speed,
R.string.stats_fastest_pace, reportSpeed);
double maxSpeed = tripStatistics == null ? Double.NaN : tripStatistics.getMaxSpeed();
setSpeedValue(activity, R.id.stats_max_speed_value, maxSpeed, metricUnits, reportSpeed);
// Set elevation
boolean showElevation = PreferencesUtils.getBoolean(
activity, R.string.stats_show_elevation_key, PreferencesUtils.STATS_SHOW_ELEVATION_DEFAULT);
View elevationLabelTableRow1 = activity.findViewById(R.id.stats_elevation_label_table_row1);
View elevationValueTableRow1 = activity.findViewById(R.id.stats_elevation_value_table_row1);
View elevationLabelTableRow2 = activity.findViewById(R.id.stats_elevation_label_table_row2);
View elevationValueTableRow2 = activity.findViewById(R.id.stats_elevation_value_table_row2);
if (elevationLabelTableRow1 == null
|| elevationValueTableRow1 == null
|| elevationLabelTableRow2 == null
|| elevationValueTableRow2 == null) {
return;
}
elevationLabelTableRow1.setVisibility(showElevation ? View.VISIBLE : View.GONE);
elevationValueTableRow1.setVisibility(showElevation ? View.VISIBLE : View.GONE);
elevationLabelTableRow2.setVisibility(showElevation ? View.VISIBLE : View.GONE);
elevationValueTableRow2.setVisibility(showElevation ? View.VISIBLE : View.GONE);
if (showElevation) {
double elevationGain = tripStatistics == null ? Double.NaN
: tripStatistics.getTotalElevationGain();
double minElevation = tripStatistics == null ? Double.NaN : tripStatistics.getMinElevation();
double maxElevation = tripStatistics == null ? Double.NaN : tripStatistics.getMaxElevation();
setElevationValue(activity, R.id.stats_elevation_gain_value, elevationGain, metricUnits);
setElevationValue(activity, R.id.stats_min_elevation_value, minElevation, metricUnits);
setElevationValue(activity, R.id.stats_max_elevation_value, maxElevation, metricUnits);
}
// Set grade
boolean showGrade = PreferencesUtils.getBoolean(
activity, R.string.stats_show_grade_key, PreferencesUtils.STATS_SHOW_GRADE_DEFAULT);
View gradeLabelTableRow = activity.findViewById(R.id.stats_grade_label_table_row);
View gradeValueTableRow = activity.findViewById(R.id.stats_grade_value_table_row);
if (gradeLabelTableRow == null || gradeValueTableRow == null) {
return;
}
gradeLabelTableRow.setVisibility(showGrade ? View.VISIBLE : View.GONE);
gradeValueTableRow.setVisibility(showGrade ? View.VISIBLE : View.GONE);
if (showGrade) {
double minGrade = tripStatistics == null ? Double.NaN : tripStatistics.getMinGrade();
double maxGrade = tripStatistics == null ? Double.NaN : tripStatistics.getMaxGrade();
setGradeValue(activity, R.id.stats_min_grade_value, minGrade);
setGradeValue(activity, R.id.stats_max_grade_value, maxGrade);
}
}
private static void setItemVisibility(
Activity activity, int labelId, int spacerId, int valueId, boolean show) {
View label = activity.findViewById(labelId);
View spacer = activity.findViewById(spacerId);
View value = activity.findViewById(valueId);
if (label != null) {
label.setVisibility(show ? View.VISIBLE : View.GONE);
}
if (spacer != null) {
spacer.setVisibility(show ? View.VISIBLE : View.GONE);
}
if (value != null) {
value.setVisibility(show ? View.VISIBLE : View.GONE);
}
}
/**
* Sets a speed label.
*
* @param activity the activity
* @param id the speed label resource id
* @param speedId the speed string id
* @param paceId the pace string id
* @param reportSpeed true to report speed
*/
private static void setSpeedLabel(
Activity activity, int id, int speedId, int paceId, boolean reportSpeed) {
TextView textView = (TextView) activity.findViewById(id);
if (textView == null) {
return;
}
textView.setText(reportSpeed ? speedId : paceId);
}
/**
* Sets a speed value.
*
* @param activity the activity
* @param id the speed value resource id
* @param speed the speed in meters per second
* @param metricUnits true to display in metric units
* @param reportSpeed true to report speed
*/
private static void setSpeedValue(
Activity activity, int id, double speed, boolean metricUnits, boolean reportSpeed) {
TextView textView = (TextView) activity.findViewById(id);
if (textView == null) {
return;
}
textView.setText(StringUtils.formatSpeed(activity, speed, metricUnits, reportSpeed));
}
/**
* Sets a distance value.
*
* @param activity the activity
* @param id the distance value resource id
* @param distance the distance in meters
* @param metricUnits true to display in metric units
*/
private static void setDistanceValue(
Activity activity, int id, double distance, boolean metricUnits) {
TextView textView = (TextView) activity.findViewById(id);
if (textView == null) {
return;
}
textView.setText(StringUtils.formatDistance(activity, distance, metricUnits));
}
/**
* Sets a time value.
*
* @param activity the activity
* @param id the time value resource id
* @param time the time
*/
private static void setTimeValue(Activity activity, int id, long time) {
TextView textView = (TextView) activity.findViewById(id);
if (textView == null) {
return;
}
String value = time == -1L ? activity.getString(R.string.value_unknown)
: StringUtils.formatElapsedTime(time);
textView.setText(value);
}
/**
* Sets an elevation value.
*
* @param activity the activity
* @param id the elevation value resource id
* @param elevation the elevation in meters
* @param metricUnits true to display in metric units
*/
private static void setElevationValue(
Activity activity, int id, double elevation, boolean metricUnits) {
TextView textView = (TextView) activity.findViewById(id);
if (textView == null) {
return;
}
String value;
if (Double.isNaN(elevation) || Double.isInfinite(elevation)) {
value = activity.getString(R.string.value_unknown);
} else {
if (metricUnits) {
value = activity.getString(R.string.value_float_meter, elevation);
} else {
elevation *= UnitConversions.M_TO_FT;
value = activity.getString(R.string.value_float_feet, elevation);
}
}
textView.setText(value);
}
/**
* Sets a grade value.
*
* @param activity the activity
* @param id the grade value resource id
* @param grade the grade in fraction between 0 and 1
*/
private static void setGradeValue(Activity activity, int id, double grade) {
TextView textView = (TextView) activity.findViewById(id);
if (textView == null) {
return;
}
String value;
if (Double.isNaN(grade) || Double.isInfinite(grade)) {
value = activity.getString(R.string.value_unknown);
} else {
value = activity.getString(R.string.value_integer_percent, Math.round(grade * 100));
}
textView.setText(value);
}
/**
* Sets a coordinate value.
*
* @param activity the activity
* @param id the coordinate value resource id
* @param coordinate the coordinate in degrees
*/
private static void setCoordinateValue(Activity activity, int id, double coordinate) {
TextView textView = (TextView) activity.findViewById(id);
if (textView == null) {
return;
}
String value;
if (Double.isNaN(coordinate) || Double.isInfinite(coordinate)) {
value = activity.getString(R.string.value_unknown);
} else {
value = activity.getString(
R.string.value_coordinate_degree, Location.convert(coordinate, Location.FORMAT_DEGREES));
}
textView.setText(value);
}
}
@@ -0,0 +1,390 @@
/*
* 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.util;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.text.Html;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.format.DateUtils;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Various string manipulation methods.
*
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public class StringUtils {
private static final SimpleDateFormat ISO_8601_DATE_TIME_FORMAT = new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
private static final SimpleDateFormat ISO_8601_BASE = new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss");
private static final Pattern ISO_8601_EXTRAS = Pattern.compile(
"^(\\.\\d+)?(?:Z|([+-])(\\d{2}):(\\d{2}))?$");
static {
ISO_8601_DATE_TIME_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
ISO_8601_BASE.setTimeZone(TimeZone.getTimeZone("UTC"));
}
private StringUtils() {}
/**
* Formats the date and time based on user's phone date/time preferences.
*
* @param context the context
* @param time the time in milliseconds
*/
public static String formatDateTime(Context context, long time) {
return android.text.format.DateFormat.getDateFormat(context).format(time) + " "
+ DateUtils.formatDateTime(context, time, DateUtils.FORMAT_SHOW_TIME).toString();
}
/**
* Formats the relative date and time based on user's phone date/time
* preferences.
*
* @param context the context
* @param time the time in milliseconds
*/
public static String formatRelativeDateTime(Context context, long time) {
long now = Calendar.getInstance().getTimeInMillis();
if (now - time > DateUtils.WEEK_IN_MILLIS) {
return formatDateTime(context, time);
} else {
return DateUtils.getRelativeTimeSpanString(
time, now, DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString();
}
}
/**
* Formats the time using the ISO 8601 date time format with fractional
* seconds in UTC time zone.
*
* @param time the time in milliseconds
*/
public static String formatDateTimeIso8601(long time) {
return ISO_8601_DATE_TIME_FORMAT.format(time);
}
/**
* Formats the elapsed timed in the form "MM:SS" or "H:MM:SS".
*
* @param time the time in milliseconds
*/
public static String formatElapsedTime(long time) {
return DateUtils.formatElapsedTime(time / 1000);
}
/**
* Formats the elapsed time in the form "H:MM:SS".
*
* @param time the time in milliseconds
*/
public static String formatElapsedTimeWithHour(long time) {
String value = formatElapsedTime(time);
return TextUtils.split(value, ":").length == 2 ? "0:" + value : value;
}
/**
* Formats the distance.
*
* @param context the context
* @param distance the distance in meters
* @param metricUnits true to use metric units. False to use imperial units
*/
public static String formatDistance(Context context, double distance, boolean metricUnits) {
if (Double.isNaN(distance) || Double.isInfinite(distance)) {
return context.getString(R.string.value_unknown);
}
if (metricUnits) {
if (distance > 500.0) {
distance *= UnitConversions.M_TO_KM;
return context.getString(R.string.value_float_kilometer, distance);
} else {
return context.getString(R.string.value_float_meter, distance);
}
} else {
if (distance * UnitConversions.M_TO_MI > 0.5) {
distance *= UnitConversions.M_TO_MI;
return context.getString(R.string.value_float_mile, distance);
} else {
distance *= UnitConversions.M_TO_FT;
return context.getString(R.string.value_float_feet, distance);
}
}
}
/**
* Gets the distance in an array of two strings. The first string is the
* distance. The second string is the unit.
*
* @param context the context
* @param distance the distance
* @param metricUnits true to use metric unit
*/
public static String[] getDistanceParts(Context context, double distance, boolean metricUnits) {
String[] result = new String[2];
if (Double.isNaN(distance) || Double.isInfinite(distance)) {
result[0] = context.getString(R.string.value_unknown);
result[1] = context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet);
return result;
}
int unitId;
if (metricUnits) {
if (distance > 500.0) {
distance *= UnitConversions.M_TO_KM;
unitId = R.string.unit_kilometer;
} else {
unitId = R.string.unit_meter;
}
} else {
if (distance * UnitConversions.M_TO_MI > 0.5) {
distance *= UnitConversions.M_TO_MI;
unitId = R.string.unit_mile;
} else {
distance *= UnitConversions.M_TO_FT;
unitId = R.string.unit_feet;
}
}
result[0] = String.format("%.2f", distance);
result[1] = context.getString(unitId);
return result;
}
/**
* Formats the speed.
*
* @param context the context
* @param speed the speed in meters per second
* @param metricUnits true to use metric units. False to use imperial units
* @param reportSpeed true to report as speed. False to report as pace
*/
public static String formatSpeed(
Context context, double speed, boolean metricUnits, boolean reportSpeed) {
if (Double.isNaN(speed) || Double.isInfinite(speed)) {
return context.getString(R.string.value_unknown);
}
speed *= UnitConversions.MS_TO_KMH;
if (metricUnits) {
if (reportSpeed) {
return context.getString(R.string.value_float_kilometer_hour, speed);
} else {
// convert from hours to minutes
double pace = speed == 0 ? 0.0 : 60.0 / speed;
return context.getString(R.string.value_float_minute_kilometer, pace);
}
} else {
speed *= UnitConversions.KM_TO_MI;
if (reportSpeed) {
return context.getString(R.string.value_float_mile_hour, speed);
} else {
// convert from hours to minutes
double pace = speed == 0 ? 0.0 : 60.0 / speed;
return context.getString(R.string.value_float_minute_mile, pace);
}
}
}
/**
* Gets the speed in an array of two strings. The first string is the
* speed. The second string is the unit.
*
* @param context the context
* @param speed the speed
* @param metricUnits true to use metric unit
* @param reportSpeed true to report speed
*/
public static String[] getSpeedParts(
Context context, double speed, boolean metricUnits, boolean reportSpeed) {
String[] result = new String[2];
int unitId;
if (metricUnits) {
unitId = reportSpeed ? R.string.unit_kilometer_per_hour : R.string.unit_minute_per_kilometer;
} else {
unitId = reportSpeed ? R.string.unit_mile_per_hour : R.string.unit_minute_per_mile;
}
result[1] = context.getString(unitId);
if (Double.isNaN(speed) || Double.isInfinite(speed)) {
result[0] = context.getString(R.string.value_unknown);
return result;
}
speed *= UnitConversions.MS_TO_KMH;
if (metricUnits) {
if (!reportSpeed) {
// convert from hours to minutes
speed = speed == 0 ? 0.0 : 60.0 / speed;
}
} else {
speed *= UnitConversions.KM_TO_MI;
if (!reportSpeed) {
// convert from hours to minutes
speed = speed == 0 ? 0.0 : 60.0 / speed;
}
}
result[0] = String.format("%.2f", speed);
return result;
}
/**
* Formats the given text as a XML CDATA element. This includes adding the
* starting and ending CDATA tags. Please notice that this may result in
* multiple consecutive CDATA tags.
*
* @param text the given text
*/
public static String formatCData(String text) {
return "<![CDATA[" + text.replaceAll("]]>", "]]]]><![CDATA[>") + "]]>";
}
/**
* Gets the time, in milliseconds, from an XML date time string as defined at
* http://www.w3.org/TR/xmlschema-2/#dateTime
*
* @param xmlDateTime the XML date time string
*/
public static long getTime(String xmlDateTime) {
// Parse the date time base
ParsePosition position = new ParsePosition(0);
Date date = ISO_8601_BASE.parse(xmlDateTime, position);
if (date == null) {
throw new IllegalArgumentException("Invalid XML dateTime value: " + xmlDateTime
+ " (at position " + position.getErrorIndex() + ")");
}
// Parse the date time extras
Matcher matcher = ISO_8601_EXTRAS.matcher(xmlDateTime.substring(position.getIndex()));
if (!matcher.matches()) {
// This will match even an empty string as all groups are optional. Thus a
// non-match means invalid content.
throw new IllegalArgumentException("Invalid XML dateTime value: " + xmlDateTime);
}
long time = date.getTime();
// Account for fractional seconds
String fractional = matcher.group(1);
if (fractional != null) {
// Regex ensures fractional part is in (0,1)
float fractionalSeconds = Float.parseFloat(fractional);
long fractionalMillis = (long) (fractionalSeconds * 1000.0f);
time += fractionalMillis;
}
// Account for timezones
String sign = matcher.group(2);
String offsetHoursStr = matcher.group(3);
String offsetMinsStr = matcher.group(4);
if (sign != null && offsetHoursStr != null && offsetMinsStr != null) {
// Regex ensures sign is + or -
boolean plusSign = sign.equals("+");
int offsetHours = Integer.parseInt(offsetHoursStr);
int offsetMins = Integer.parseInt(offsetMinsStr);
// Regex ensures values are >= 0
if (offsetHours > 14 || offsetMins > 59) {
throw new IllegalArgumentException("Bad timezone: " + xmlDateTime);
}
long totalOffsetMillis = (offsetMins + offsetHours * 60L) * 60000L;
// Convert to UTC
if (plusSign) {
time -= totalOffsetMillis;
} else {
time += totalOffsetMillis;
}
}
return time;
}
/**
* Gets the time as an array of three integers. Index 0 contains the number of
* seconds, index 1 contains the number of minutes, and index 2 contains the
* number of hours.
*
* @param time the time in milliseconds
* @return an array of 3 elements.
*/
public static int[] getTimeParts(long time) {
if (time < 0) {
int[] parts = getTimeParts(time * -1);
parts[0] *= -1;
parts[1] *= -1;
parts[2] *= -1;
return parts;
}
int[] parts = new int[3];
long seconds = time / 1000;
parts[0] = (int) (seconds % 60);
int minutes = (int) (seconds / 60);
parts[1] = minutes % 60;
parts[2] = minutes / 60;
return parts;
}
/**
* Gets the html.
*
* @param context the context
* @param resId the string resource id
* @param formatArgs the string resource ids of the format arguments
*/
public static Spanned getHtml(Context context, int resId, Object... formatArgs) {
Object[] args = new Object[formatArgs.length];
for (int i = 0; i < formatArgs.length; i++) {
String url = context.getString((Integer) formatArgs[i]);
args[i] = " <a href='" + url + "'>" + url + "</a> ";
}
return Html.fromHtml(context.getString(resId, args));
}
/**
* Gets the frequency display options.
*
* @param context the context
* @param metricUnits true to display in metric units
*/
public static String[] getFrequencyOptions(Context context, boolean metricUnits) {
String[] values = context.getResources().getStringArray(R.array.frequency_values);
String[] options = new String[values.length];
for (int i = 0; i < values.length; i++) {
int value = Integer.parseInt(values[i]);
if (value == PreferencesUtils.FREQUENCY_OFF) {
options[i] = context.getString(R.string.value_off);
} else if (value < 0) {
options[i] = context.getString(
metricUnits ? R.string.value_integer_kilometer : R.string.value_integer_mile,
Math.abs(value));
} else {
options[i] = context.getString(R.string.value_integer_minute, value);
}
}
return options;
}
}
@@ -0,0 +1,90 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.util;
import com.google.android.apps.mytracks.Constants;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
/**
* Utility class for acessing basic Android functionality.
*
* @author Rodrigo Damazio
*/
public class SystemUtils {
/**
* Get the My Tracks version from the manifest.
*
* @return the version, or an empty string in case of failure.
*/
public static String getMyTracksVersion(Context context) {
try {
PackageInfo pi = context.getPackageManager().getPackageInfo(
"com.google.android.maps.mytracks",
PackageManager.GET_META_DATA);
return pi.versionName;
} catch (NameNotFoundException e) {
Log.w(Constants.TAG, "Failed to get version info.", e);
return "";
}
}
/**
* Tries to acquire a partial wake lock if not already acquired. Logs errors
* and gives up trying in case the wake lock cannot be acquired.
*/
public static WakeLock acquireWakeLock(Activity activity, WakeLock wakeLock) {
Log.i(Constants.TAG, "LocationUtils: Acquiring wake lock.");
try {
PowerManager pm = (PowerManager) activity
.getSystemService(Context.POWER_SERVICE);
if (pm == null) {
Log.e(Constants.TAG, "LocationUtils: Power manager not found!");
return wakeLock;
}
if (wakeLock == null) {
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
Constants.TAG);
if (wakeLock == null) {
Log.e(Constants.TAG,
"LocationUtils: Could not create wake lock (null).");
}
return wakeLock;
}
if (!wakeLock.isHeld()) {
wakeLock.acquire();
if (!wakeLock.isHeld()) {
Log.e(Constants.TAG,
"LocationUtils: Could not acquire wake lock.");
}
}
} catch (RuntimeException e) {
Log.e(Constants.TAG,
"LocationUtils: Caught unexpected exception: " + e.getMessage(), e);
}
return wakeLock;
}
private SystemUtils() {}
}
@@ -0,0 +1,160 @@
/*
* 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.util;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import java.util.HashMap;
/**
* Utilities for track icon.
*
* @author Jimmy Shih
*/
public class TrackIconUtils {
private static final String AIRPLANE = "AIRPLANE";
private static final String BIKE = "BIKE";
private static final String BOAT = "BOAT";
private static final String DRIVE = "DRIVE";
private static final String RUN = "RUN";
private static final String SKI = "SKI";
private static final String SNOW_BOARDING = "SNOW_BOARDING";
private static final String WALK = "WALK";
private TrackIconUtils() {}
private static HashMap<String, Integer> map = new HashMap<String, Integer>();
static {
map.put(AIRPLANE, R.drawable.track_airplane);
map.put(BIKE, R.drawable.track_bike);
map.put(BOAT, R.drawable.track_boat);
map.put(DRIVE, R.drawable.track_drive);
map.put(RUN, R.drawable.track_run);
map.put(SKI, R.drawable.track_ski);
map.put(SNOW_BOARDING, R.drawable.track_snow_boarding);
map.put(WALK, R.drawable.track_walk);
}
private static int[] airplane = new int[] {
R.string.activity_type_airplane,
R.string.activity_type_commercial_airplane,
R.string.activity_type_rc_airplane
};
private static int[] bike = new int[] {
R.string.activity_type_biking,
R.string.activity_type_cycling,
R.string.activity_type_dirt_bike,
R.string.activity_type_motor_bike,
R.string.activity_type_mountain_biking,
R.string.activity_type_road_biking,
R.string.activity_type_track_cycling };
private static int[] boat = new int[] {
R.string.activity_type_boat,
R.string.activity_type_ferry,
R.string.activity_type_motor_boating,
R.string.activity_type_rc_boat
};
private static int[] drive = new int[] {
R.string.activity_type_atv,
R.string.activity_type_driving,
R.string.activity_type_driving_bus,
R.string.activity_type_driving_car };
private static int[] run = new int[] {
R.string.activity_type_running,
R.string.activity_type_street_running,
R.string.activity_type_track_running,
R.string.activity_type_trail_running };
private static int[] ski = new int[] {
R.string.activity_type_cross_country_skiing,
R.string.activity_type_skiing };
private static int[] snowBoarding = new int[] {
R.string.activity_type_snow_boarding
};
private static int[] walk = new int[] {
R.string.activity_type_hiking,
R.string.activity_type_off_trail_hiking,
R.string.activity_type_speed_walking,
R.string.activity_type_trail_hiking,
R.string.activity_type_walking };
/**
* Gets the icon drawable.
*
* @param iconValue the icon value
*/
public static int getIconDrawable(String iconValue) {
if (iconValue == null || iconValue.equals("")) {
return R.drawable.track_walk;
}
Integer drawable = map.get(iconValue);
return drawable == null ? R.drawable.track_walk : drawable;
}
/**
* Gets the icon value.
*
* @param context the context
* @param activityType the activity type
*/
public static String getIconValue(Context context, String activityType) {
if (inList(context, activityType, airplane)) {
return AIRPLANE;
}
if (inList(context, activityType, bike)) {
return BIKE;
}
if (inList(context, activityType, boat)) {
return BOAT;
}
if (inList(context, activityType, drive)) {
return DRIVE;
}
if (inList(context, activityType, run)) {
return RUN;
}
if (inList(context, activityType, ski)) {
return SKI;
}
if (inList(context, activityType, snowBoarding)) {
return SNOW_BOARDING;
}
if (inList(context, activityType, walk)) {
return WALK;
}
return "";
}
/**
* Returns true if the activity type is in the list.
*
* @param context the context
* @param activityType the activity type
* @param list the list
*/
private static boolean inList(Context context, String activityType, int[] list) {
for (int i : list) {
if (context.getString(i).equals(activityType)) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,129 @@
/*
* 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.util;
import com.google.android.maps.mytracks.R;
import com.google.common.annotations.VisibleForTesting;
import android.content.Context;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.List;
/**
* Utilities for track name.
*
* @author Matthew Simmons
*/
public class TrackNameUtils {
@VisibleForTesting
static final String ISO_8601_FORMAT = "yyyy-MM-dd HH:mm";
private TrackNameUtils() {
}
/**
* Gets the track name.
*
* @param context the context
* @param trackId the track id
* @param startTime the track start time
* @param location the track location
*/
public static String getTrackName(
Context context, long trackId, long startTime, Location location) {
String trackName = PreferencesUtils.getString(context, R.string.track_name_key,
PreferencesUtils.TRACK_NAME_DEFAULT);
if (trackName.equals(
context.getString(R.string.settings_recording_track_name_date_local_value))) {
if (startTime == -1L) {
return null;
}
return StringUtils.formatDateTime(context, startTime);
} else if (trackName.equals(
context.getString(R.string.settings_recording_track_name_date_iso_8601_value))) {
if (startTime == -1L) {
return null;
}
SimpleDateFormat dateFormat = new SimpleDateFormat(ISO_8601_FORMAT);
return dateFormat.format(startTime);
} else if (trackName.equals(
context.getString(R.string.settings_recording_track_name_number_value))) {
if (trackId == -1L) {
return null;
}
return context.getString(R.string.track_name_format, trackId);
} else {
// trackName equals settings_recording_track_name_location_value or any
// value
if (location != null) {
return getReverseGeoCoding(context, location);
} else {
// Use the startTime if available
if (startTime != -1L) {
return StringUtils.formatDateTime(context, startTime);
}
return null;
}
}
}
/**
* Gets the reverse geo coding string for a location.
*
* @param context the context
* @param location the location
*/
private static String getReverseGeoCoding(Context context, Location location) {
if (location == null || !ApiAdapterFactory.getApiAdapter().isGeoCoderPresent()) {
return null;
}
Geocoder geocoder = new Geocoder(context);
try {
List<Address> addresses = geocoder.getFromLocation(
location.getLatitude(), location.getLongitude(), 1);
if (addresses.size() > 0) {
Address address = addresses.get(0);
int lines = address.getMaxAddressLineIndex();
if (lines > 0) {
return address.getAddressLine(0);
}
String featureName = address.getFeatureName();
if (featureName != null) {
return featureName;
}
String thoroughfare = address.getThoroughfare();
if (thoroughfare != null) {
return thoroughfare;
}
String locality = address.getLocality();
if (locality != null) {
return locality;
}
}
} catch (IOException e) {
// Can safely ignore
}
return null;
}
}
@@ -0,0 +1,194 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.util;
import com.google.android.apps.mytracks.TrackEditActivity;
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
import com.google.android.apps.mytracks.services.TrackRecordingService;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.maps.mytracks.R;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.RemoteException;
import android.util.Log;
import android.widget.Toast;
import java.util.List;
/**
* Utilities for {@link TrackRecordingServiceConnection}.
*
* @author Rodrigo Damazio
*/
public class TrackRecordingServiceConnectionUtils {
private static final String TAG = TrackRecordingServiceConnectionUtils.class.getSimpleName();
private TrackRecordingServiceConnectionUtils() {}
/**
* Returns true if the recording service is running.
*
* @param context the current context
*/
public static boolean isRecordingServiceRunning(Context context) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(
Context.ACTIVITY_SERVICE);
List<RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE);
for (RunningServiceInfo serviceInfo : services) {
ComponentName componentName = serviceInfo.service;
String serviceName = componentName.getClassName();
if (TrackRecordingService.class.getName().equals(serviceName)) {
return true;
}
}
return false;
}
/**
* Resumes the recording track.
*
* @param trackRecordingServiceConnection the track recording service
*/
public static void resumeTrack(TrackRecordingServiceConnection trackRecordingServiceConnection) {
try {
ITrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service != null) {
service.resumeCurrentTrack();
}
} catch (RemoteException e) {
Log.e(TAG, "Unable to resume track.", e);
}
}
/**
* Pauses the recording track.
*
* @param trackRecordingServiceConnection the track recording service
* connection
*/
public static void pauseTrack(TrackRecordingServiceConnection trackRecordingServiceConnection) {
try {
ITrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service != null) {
service.pauseCurrentTrack();
}
} catch (RemoteException e) {
Log.e(TAG, "Unable to resume track.", e);
}
}
/**
* Stops the recording.
*
* @param context the context
* @param trackRecordingServiceConnection the track recording service
* connection
* @param showEditor true to show the editor
*/
public static void stopRecording(Context context,
TrackRecordingServiceConnection trackRecordingServiceConnection, boolean showEditor) {
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection
.getServiceIfBound();
if (trackRecordingService != null) {
try {
if (showEditor) {
/*
* Need to remember the recordingTrackId before calling
* endCurrentTrack. endCurrentTrack sets the value to -1L.
*/
long recordingTrackId = PreferencesUtils.getLong(
context, R.string.recording_track_id_key);
trackRecordingService.endCurrentTrack();
if (recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
Intent intent = IntentUtils.newIntent(context, TrackEditActivity.class)
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, recordingTrackId)
.putExtra(TrackEditActivity.EXTRA_NEW_TRACK, true);
context.startActivity(intent);
}
} else {
trackRecordingService.endCurrentTrack();
}
} catch (Exception e) {
Log.e(TAG, "Unable to stop recording.", e);
}
} else {
resetRecordingState(context);
}
trackRecordingServiceConnection.unbindAndStop();
}
/**
* Resumes the track recording service connection.
*
* @param context the context
* @param trackRecordingServiceConnection the track recording service
* connection
*/
public static void startConnection(
Context context, TrackRecordingServiceConnection trackRecordingServiceConnection) {
trackRecordingServiceConnection.bindIfStarted();
if (!isRecordingServiceRunning(context)) {
resetRecordingState(context);
}
}
private static void resetRecordingState(Context context) {
long recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key);
if (recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
PreferencesUtils.setLong(
context, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
}
boolean recordingTrackPaused = PreferencesUtils.getBoolean(context,
R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
if (!recordingTrackPaused) {
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key,
PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
}
}
/**
* Adds a marker.
*/
public static void addMarker(Context context,
TrackRecordingServiceConnection trackRecordingServiceConnection,
WaypointCreationRequest waypointCreationRequest) {
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection
.getServiceIfBound();
if (trackRecordingService == null) {
Log.d(TAG, "Unable to add marker, no track recording service");
} else {
try {
if (trackRecordingService.insertWaypoint(waypointCreationRequest) != -1L) {
Toast.makeText(context, R.string.marker_add_success, Toast.LENGTH_SHORT).show();
return;
}
} catch (RemoteException e) {
Log.e(TAG, "Unable to add marker", e);
} catch (IllegalStateException e) {
Log.e(TAG, "Unable to add marker.", e);
}
}
Toast.makeText(context, R.string.marker_add_error, Toast.LENGTH_LONG).show();
}
}
@@ -0,0 +1,53 @@
/*
* 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.util;
/**
* Unit conversion constants.
*
* @author Sandor Dornbush
*/
public class UnitConversions {
private UnitConversions() {}
// multiplication factor to convert kilometers to miles
public static final double KM_TO_MI = 0.621371192;
// multiplication factor to convert miles to kilometers
public static final double MI_TO_KM = 1 / KM_TO_MI;
// multiplication factor to convert miles to feet
public static final double MI_TO_FT = 5280.0;
// multiplication factor to convert feet to miles
public static final double FT_TO_MI = 1 / MI_TO_FT;
// multiplication factor to convert meters to kilometers
public static final double M_TO_KM = 1 / 1000.0;
// multiplication factor to convert meters per second to kilometers per hour
public static final double MS_TO_KMH = 3.6;
// multiplication factor to convert meters to miles
public static final double M_TO_MI = M_TO_KM * KM_TO_MI;
// multiplication factor to convert meters to feet
public static final double M_TO_FT = M_TO_MI * MI_TO_FT;
// multiplication factor to convert degrees to radians
public static final double DEG_TO_RAD = Math.PI / 180.0;
}
@@ -0,0 +1,60 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.util;
import android.net.Uri;
import java.util.List;
/**
* Utilities for dealing with content and other types of URIs.
*
* @author Rodrigo Damazio
*/
public class UriUtils {
public static boolean matchesContentUri(Uri uri, Uri baseContentUri) {
if (uri == null) {
return false;
}
// Check that scheme and authority are the same.
if (!uri.getScheme().equals(baseContentUri.getScheme()) ||
!uri.getAuthority().equals(baseContentUri.getAuthority())) {
return false;
}
// Checks that all the base path components are in the URI.
List<String> uriPathSegments = uri.getPathSegments();
List<String> basePathSegments = baseContentUri.getPathSegments();
if (basePathSegments.size() > uriPathSegments.size()) {
return false;
}
for (int i = 0; i < basePathSegments.size(); i++) {
if (!uriPathSegments.get(i).equals(basePathSegments.get(i))) {
return false;
}
}
return true;
}
public static boolean isFileUri(Uri uri) {
return "file".equals(uri.getScheme());
}
private UriUtils() {}
}