An end-to-end test case of MyTracks.

This commit is contained in:
youtaol
2012-04-24 19:38:32 +08:00
commit b9a9cc6cd9
591 changed files with 63728 additions and 0 deletions
@@ -0,0 +1,43 @@
/*
* 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 com.google.android.maps.mytracks.R;
import android.content.Context;
/**
* Utitlites for sending pageviews to Google Analytics.
*
* @author Jimmy Shih
*/
public class AnalyticsUtils {
private AnalyticsUtils() {}
public static void sendPageViews(Context context, String ... pages) {
GoogleAnalyticsTracker tracker = GoogleAnalyticsTracker.getInstance();
tracker.start(context.getString(R.string.my_tracks_analytics_id), context);
tracker.setProductVersion("android-mytracks", SystemUtils.getMyTracksVersion(context));
for (String page : pages) {
tracker.trackPageView(page);
}
tracker.dispatch();
tracker.stop();
}
}
@@ -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.SPP_UUID);
} catch (IOException e) {
Log.d(Constants.TAG, "Unable to create insecure connection", e);
}
return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.SPP_UUID);
};
}
@@ -0,0 +1,111 @@
/*
* 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 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.ListView;
import android.widget.SearchView;
import android.widget.TextView;
/**
* 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 int menuId,
final int actionModeTitleId,
final ContextualActionModeCallback contextualActionModeCallback) {
listView.setOnItemLongClickListener(new OnItemLongClickListener() {
ActionMode actionMode;
@Override
public boolean onItemLongClick(
AdapterView<?> parent, View view, 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(menuId, 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) {
return contextualActionModeCallback.onClick(item.getItemId(), id);
}
});
TextView textView = (TextView) view.findViewById(actionModeTitleId);
if (textView != null) {
actionMode.setTitle(textView.getText());
}
view.setSelected(true);
return true;
}
});
};
@Override
public void configureSearchWidget(Activity activity, MenuItem menuItem) {
SearchManager searchManager = (SearchManager) activity.getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) menuItem.getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(activity.getComponentName()));
}
@Override
public boolean handleSearchMenuSelection(Activity activity) {
// Returns false to allow the platform to expand the search widget.
return false;
}
}
@@ -0,0 +1,44 @@
/*
* 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;
/**
* 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 boolean handleSearchKey(MenuItem menuItem) {
menuItem.expandActionView();
return true;
}
}
@@ -0,0 +1,140 @@
/*
* 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.io.backup.BackupPreferencesListener;
import com.google.android.apps.mytracks.services.sensors.BluetoothConnectionManager;
import com.google.android.apps.mytracks.services.tasks.PeriodicTask;
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerTask;
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.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.util.Log;
import android.view.MenuItem;
import android.view.Window;
import android.widget.ListView;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* API level 7 specific implementation of the {@link ApiAdapter}.
*
* @author Bartlomiej Niechwiej
*/
public class Api7Adapter implements ApiAdapter {
@Override
public PeriodicTask getStatusAnnouncerTask(Context context) {
return new StatusAnnouncerTask(context);
}
@Override
public BackupPreferencesListener getBackupPreferencesListener(Context context) {
return new BackupPreferencesListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
// Do nothing
}
};
}
@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 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.SPP_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, int menuId,
int actionModeTitleId, 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 boolean handleSearchKey(MenuItem menuItem) {
// Return false and allow the framework to handle the search key.
return false;
}
}
@@ -0,0 +1,41 @@
/*
* 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.io.backup.BackupPreferencesListener;
import com.google.android.apps.mytracks.io.backup.Api8BackupPreferencesListener;
import com.google.android.apps.mytracks.services.tasks.Api8StatusAnnouncerTask;
import com.google.android.apps.mytracks.services.tasks.PeriodicTask;
import android.content.Context;
/**
* API level 8 specific implementation of the {@link ApiAdapter}.
*
* @author Jimmy Shih
*/
public class Api8Adapter extends Api7Adapter {
@Override
public PeriodicTask getStatusAnnouncerTask(Context context) {
return new Api8StatusAnnouncerTask(context);
}
@Override
public BackupPreferencesListener getBackupPreferencesListener(Context context) {
return new Api8BackupPreferencesListener(context);
}
}
@@ -0,0 +1,66 @@
/*
* 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.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()
.detectDiskWrites()
.detectNetwork()
.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();
}
}
@@ -0,0 +1,171 @@
/*
* 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.android.apps.mytracks.io.backup.BackupPreferencesListener;
import com.google.android.apps.mytracks.services.tasks.PeriodicTask;
import com.google.api.client.http.HttpTransport;
import android.app.Activity;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.content.SharedPreferences;
import android.view.MenuItem;
import android.widget.ListView;
import java.io.IOException;
/**
* A set of methods that may be implemented differently depending on the Android
* API level.
*
* @author Bartlomiej Niechwiej
*/
public interface ApiAdapter {
/**
* Gets a status announcer task.
* <p>
* Due to changes in API level 8.
*
* @param context the context
*/
public PeriodicTask getStatusAnnouncerTask(Context context);
/**
* Gets a {@link BackupPreferencesListener}.
* <p>
* Due to changes in API level 8.
*
* @param context the context
*/
public BackupPreferencesListener getBackupPreferencesListener(Context context);
/**
* 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();
/**
* 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 menuId the menu resource id
* @param actionModeTitleId the id of the list view item TextView to be used
* as the action mode title
* @param contextualActionModeCallback the callback when an item is selected
* in the contextual action mode
*/
public void configureListViewContextualMenu(Activity activity, ListView listView, int menuId,
int actionModeTitleId, ContextualActionModeCallback contextualActionModeCallback);
/**
* Configures the search widget.
*
* 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.
*
* Due to changes in API level 11.
*
* @param activity the activity
*/
public boolean handleSearchMenuSelection(Activity activity);
/**
* Handles the search key press. Returns true if handled.
*
* Due to changes in API level 14.
*
* @param menu the search menu
*/
public boolean handleSearchKey(MenuItem menu);
}
@@ -0,0 +1,56 @@
/*
* 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 if (Build.VERSION.SDK_INT >= 8) {
apiAdapter = new Api8Adapter();
return apiAdapter;
} else {
apiAdapter = new Api7Adapter();
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,173 @@
/*
* 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.Constants;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.content.SharedPreferences;
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) {
SharedPreferences preferences = context.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
boolean metricUnits = true;
if (preferences != null) {
metricUnits = preferences.getBoolean(
context.getString(R.string.metric_units_key), true);
}
return getChartUrl(distances, elevations, track,
context.getString(R.string.stat_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
*/
public 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 stats = track.getStatistics();
double effectiveMaxY = metricUnits
? stats.getMaxElevation()
: stats.getMaxElevation() * UnitConversions.M_TO_FT;
effectiveMaxY = ((int) (effectiveMaxY / 100)) * 100 + 100;
// Round it down.
double effectiveMinY = 0;
double minElevation = metricUnits
? stats.getMinElevation()
: stats.getMinElevation() * UnitConversions.M_TO_FT;
effectiveMinY = ((int) (minElevation / 100)) * 100;
if (stats.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 distKM = stats.getTotalDistance() * UnitConversions.M_TO_KM;
double distDisplay =
metricUnits ? distKM : (distKM * UnitConversions.KM_TO_MI);
int xInterval = ((int) (distDisplay / 6));
int yInterval = ((int) (ySpread / 600)) * 100;
if (yInterval < 100) {
yInterval = 25;
}
// Range
sb.append("&chxr=0,0,");
sb.append((int) distDisplay);
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++) {
int normalized =
(int) (getNormalizedElevation(
elevations.elementAt(i), effectiveMinY, ySpread) * 4095);
sb.append(ChartsExtendedEncoder.getEncodedValue(normalized));
}
return sb.toString();
}
private static double getNormalizedDistance(double d, Track track) {
return d / track.getStatistics().getTotalDistance();
}
private static double getNormalizedElevation(
double d, double effectiveMinY, double ySpread) {
return (d - 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,47 @@
/*
* 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.SharedPreferences;
import android.content.SharedPreferences.Editor;
/**
* Utilities for checking units.
*
* @author Jimmy Shih
*/
public class CheckUnitsUtils {
private static final String CHECK_UNITS_PREFERENCE_FILE = "checkunits";
private static final String CHECK_UNITS_PREFERENCE_KEY = "checkunits.checked";
private CheckUnitsUtils() {}
public static boolean getCheckUnitsValue(Context context) {
SharedPreferences sharedPreferences = context.getSharedPreferences(
CHECK_UNITS_PREFERENCE_FILE, Context.MODE_PRIVATE);
return sharedPreferences.getBoolean(CHECK_UNITS_PREFERENCE_KEY, false);
}
public static void setCheckUnitsValue(Context context) {
SharedPreferences sharedPreferences = context.getSharedPreferences(
CHECK_UNITS_PREFERENCE_FILE, Context.MODE_PRIVATE);
Editor editor = sharedPreferences.edit().putBoolean(CHECK_UNITS_PREFERENCE_KEY, true);
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
}
}
@@ -0,0 +1,102 @@
/*
* 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;
/**
* Utilities for creating dialogs.
*
* @author Jimmy Shih
*/
public class DialogUtils {
private DialogUtils() {}
/**
* Creates a confirmation dialog.
*
* @param context the context
* @param messageId the confirmation message id
* @param onClickListener the listener to invoke when the user clicks OK
*/
public static Dialog createConfirmationDialog(
Context context, int messageId, DialogInterface.OnClickListener onClickListener) {
return new AlertDialog.Builder(context)
.setCancelable(true)
.setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(context.getString(messageId))
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, onClickListener)
.setTitle(R.string.generic_confirm_title)
.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.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 com.google.android.maps.mytracks.R;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import java.util.Calendar;
/**
* 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 EULA_PREFERENCE_KEY = "eula.google_mobile_tos_accepted";
// Google's mobile page
private static final String HOST_NAME = "m.google.com";
private EulaUtils() {}
public static boolean getEulaValue(Context context) {
SharedPreferences sharedPreferences = context.getSharedPreferences(
EULA_PREFERENCE_FILE, Context.MODE_PRIVATE);
return sharedPreferences.getBoolean(EULA_PREFERENCE_KEY, false);
}
public static void setEulaValue(Context context) {
SharedPreferences sharedPreferences = context.getSharedPreferences(
EULA_PREFERENCE_FILE, Context.MODE_PRIVATE);
Editor editor = sharedPreferences.edit();
editor.putBoolean(EULA_PREFERENCE_KEY, true);
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
}
public static String getEulaMessage(Context context) {
return context.getString(R.string.eula_date)
+ "\n\n"
+ context.getString(R.string.eula_body, HOST_NAME)
+ "\n\n"
+ context.getString(R.string.eula_footer, HOST_NAME)
+ "\n\n"
+ "©" + Calendar.getInstance().get(Calendar.YEAR);
}
}
@@ -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,64 @@
/*
* 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.maps.GeoPoint;
/**
* A rectangle in geographical space.
*/
public class GeoRect {
public int top;
public int left;
public int bottom;
public int right;
public GeoRect() {
top = 0;
left = 0;
bottom = 0;
right = 0;
}
public GeoRect(GeoPoint center, int latSpan, int longSpan) {
top = center.getLatitudeE6() - latSpan / 2;
left = center.getLongitudeE6() - longSpan / 2;
bottom = center.getLatitudeE6() + latSpan / 2;
right = center.getLongitudeE6() + longSpan / 2;
}
public GeoPoint getCenter() {
return new GeoPoint(top / 2 + bottom / 2, left / 2 + right / 2);
}
public int getLatSpan() {
return bottom - top;
}
public int getLongSpan() {
return right - left;
}
public boolean contains(GeoPoint geoPoint) {
if (geoPoint.getLatitudeE6() >= top
&& geoPoint.getLatitudeE6() <= bottom
&& geoPoint.getLongitudeE6() >= left
&& geoPoint.getLongitudeE6() <= right) {
return true;
}
return false;
}
}
@@ -0,0 +1,253 @@
/*
* 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 com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.maps.GeoPoint;
import android.location.Location;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/**
* Utility class for decimating tracks at a given level of precision.
*
* @author Leif Hendrik Wilden
*/
public class 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)
*/
public 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
*/
public 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);
}
/**
* Limits number of points by dropping any points beyond the given number of
* points. Note: That'll actually discard points.
*
* @param track a track
* @param numberOfPoints maximum number of points
*/
public static void cut(Track track, int numberOfPoints) {
ArrayList<Location> locations = track.getLocations();
while (locations.size() > numberOfPoints) {
locations.remove(locations.size() - 1);
}
}
/**
* Splits a track in multiple tracks where each piece has less or equal than
* maxPoints.
*
* @param track the track to split
* @param maxPoints maximum number of points for each piece
* @return a list of one or more track pieces
*/
public static ArrayList<Track> split(Track track, int maxPoints) {
ArrayList<Track> result = new ArrayList<Track>();
final int nTotal = track.getLocations().size();
int n = 0;
Track piece = null;
do {
piece = new Track();
TripStatistics pieceStats = piece.getStatistics();
piece.setId(track.getId());
piece.setName(track.getName());
piece.setDescription(track.getDescription());
piece.setCategory(track.getCategory());
List<Location> pieceLocations = piece.getLocations();
for (int i = n; i < nTotal && pieceLocations.size() < maxPoints; i++) {
piece.addLocation(track.getLocations().get(i));
}
int nPointsPiece = pieceLocations.size();
if (nPointsPiece >= 2) {
pieceStats.setStartTime(pieceLocations.get(0).getTime());
pieceStats.setStopTime(pieceLocations.get(nPointsPiece - 1).getTime());
result.add(piece);
}
n += (pieceLocations.size() - 1);
} while (n < nTotal && piece.getLocations().size() > 1);
return result;
}
/**
* Test if a given GeoPoint is valid, i.e. within physical bounds.
*
* @param geoPoint the point to be tested
* @return true, if it is a physical location on earth.
*/
public static boolean isValidGeoPoint(GeoPoint geoPoint) {
return Math.abs(geoPoint.getLatitudeE6()) < 90E6
&& Math.abs(geoPoint.getLongitudeE6()) <= 180E6;
}
/**
* 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;
}
/**
* Gets a location from a GeoPoint.
*
* @param p a GeoPoint
* @return the corresponding location
*/
public static Location getLocation(GeoPoint p) {
Location result = new Location("");
result.setLatitude(p.getLatitudeE6() / 1.0E6);
result.setLongitude(p.getLongitudeE6() / 1.0E6);
return result;
}
public static GeoPoint getGeoPoint(Location location) {
return new GeoPoint((int) (location.getLatitude() * 1E6),
(int) (location.getLongitude() * 1E6));
}
/**
* This is a utility class w/ only static members.
*/
private LocationUtils() {
}
}
@@ -0,0 +1,127 @@
/*
* 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.maps.mytracks.R;
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 {
private PreferencesUtils() {}
/**
* Gets the recording track id key.
*
* @param context the context
*/
public static String getRecordingTrackIdKey(Context context) {
return getKey(context, R.string.recording_track_id_key);
}
/**
* Gets the recording track id.
*
* @param context the context
*/
public static long getRecordingTrackId(Context context) {
return getLong(context, R.string.recording_track_id_key);
}
/**
* Sets the recording track id.
*
* @param context the context
* @param trackId the track id
*/
public static void setRecordingTrackId(Context context, long trackId) {
setLong(context, R.string.recording_track_id_key, trackId);
}
/**
* Gets the selected track id key.
*
* @param context the context
*/
public static String getSelectedTrackIdKey(Context context) {
return getKey(context, R.string.selected_track_id_key);
}
/**
* Gets the selected track id.
*
* @param context the context
*/
public static long getSelectedTrackId(Context context) {
return getLong(context, R.string.selected_track_id_key);
}
/**
* Sets the selected track id.
*
* @param context the context
* @param trackId the track id
*/
public static void setSelectedTrackId(Context context, long trackId) {
setLong(context, R.string.selected_track_id_key, trackId);
}
/**
* Gets a preference key
*
* @param context the context
* @param keyId the key id
*/
private static String getKey(Context context, int keyId) {
return context.getString(keyId);
}
/**
* Gets a long preference value.
*
* @param context the context
* @param keyId the key id
*/
private 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
*/
private 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);
}
}
@@ -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,270 @@
/*
* 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.TextUtils;
import android.text.format.DateUtils;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
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 time based on user's phone date/time preferences.
*
* @param context the context
* @param time the time in milliseconds
*/
public static String formatTime(Context context, long time) {
return android.text.format.DateFormat.getTimeFormat(context).format(time);
}
/**
* 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) + " "
+ formatTime(context, time);
}
/**
* 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 metric true to use metric. False to use imperial
*/
public static String formatDistance(Context context, double distance, boolean metric) {
if (metric) {
if (distance > 2000.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 > 2) {
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);
}
}
}
/**
* Formats the speed.
*
* @param context the context
* @param speed the speed in meters per second
* @param metric true to use metric. False to use imperial
* @param reportSpeed true to report as speed. False to report as pace
*/
public static String formatSpeed(
Context context, double speed, boolean metric, boolean reportSpeed) {
if (Double.isNaN(speed) || Double.isInfinite(speed)) {
return context.getString(R.string.value_unknown);
}
if (metric) {
speed = speed * UnitConversions.MS_TO_KMH;
if (reportSpeed) {
return context.getString(R.string.value_float_kilometer_hour, speed);
} else {
double paceInMinute = speed == 0 ? 0.0 : 60 / speed;
return context.getString(R.string.value_float_minute_kilometer, paceInMinute);
}
} else {
speed = speed * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI;
if (reportSpeed) {
return context.getString(R.string.value_float_mile_hour, speed);
} else {
double paceInMinute = speed == 0 ? 0.0 : 60 / speed;
return context.getString(R.string.value_float_minute_mile, paceInMinute);
}
}
}
/**
* Formats the elapsed time and distance.
*
* @param context the context
* @param elapsedTime the elapsed time in milliseconds
* @param distance the distance in meters
* @param metric true to use metric. False to use imperial
*/
public static String formatTimeDistance(
Context context, long elapsedTime, double distance, boolean metric) {
return formatElapsedTime(elapsedTime) + " " + formatDistance(context, distance, metric);
}
/**
* 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;
}
}
@@ -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,137 @@
/*
* 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.services.ITrackRecordingService;
import com.google.android.apps.mytracks.services.TrackRecordingService;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
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 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;
}
/**
* Returns true if recording. Checks with the track recording service if
* available. If not, checks with the shared preferences.
*
* @param context the current context
* @param trackRecordingServiceConnection the track recording service
* connection
*/
public static boolean isRecording(
Context context, TrackRecordingServiceConnection trackRecordingServiceConnection) {
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection
.getServiceIfBound();
if (trackRecordingService != null) {
try {
return trackRecordingService.isRecording();
} catch (RemoteException e) {
Log.e(TAG, "Failed to check if service is recording", e);
} catch (IllegalStateException e) {
Log.e(TAG, "Failed to check if service is recording", e);
}
}
return PreferencesUtils.getRecordingTrackId(context) != -1L;
}
/**
* Stops the track recording service connection.
*
* @param context the context
* @param trackRecordingServiceConnection the track recording service
* connection
*/
public static void stop(
Context context, TrackRecordingServiceConnection trackRecordingServiceConnection) {
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection
.getServiceIfBound();
if (trackRecordingService != null) {
try {
/*
* Need to remember the recordingTrackId before calling endCurrentTrack.
* endCurrentTrack sets the value to -1L.
*/
long recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
trackRecordingService.endCurrentTrack();
if (recordingTrackId != -1L) {
Intent intent = new Intent(context, TrackEditActivity.class)
.putExtra(TrackEditActivity.EXTRA_SHOW_CANCEL, false)
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, recordingTrackId);
context.startActivity(intent);
}
} catch (Exception e) {
Log.e(TAG, "Unable to stop recording.", e);
}
} else {
PreferencesUtils.setRecordingTrackId(context, -1L);
}
trackRecordingServiceConnection.stop();
}
/**
* Resumes the track recording service connection.
*
* @param context the context
* @param trackRecordingServiceConnection the track recording service
* connection
*/
public static void resume(
Context context, TrackRecordingServiceConnection trackRecordingServiceConnection) {
trackRecordingServiceConnection.bindIfRunning();
if (!isRecordingServiceRunning(context)) {
PreferencesUtils.setRecordingTrackId(context, -1L);
}
}
}
@@ -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() {}
}