Rebranding to OpenTracks (code).

This commit is contained in:
Dennis Guse
2019-07-24 06:28:17 +02:00
parent cd52dc29c9
commit 233522a5e6
150 changed files with 690 additions and 693 deletions
@@ -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 de.dennisguse.opentracks.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 && (bluetoothClass.getDeviceClass() & BluetoothClass.Device.HEALTH_PULSE_RATE) != 0) {
deviceAddresses.add(device.getAddress());
deviceNames.add(device.getName());
}
}
}
}
@@ -0,0 +1,156 @@
/*
* 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 de.dennisguse.opentracks.util;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import de.dennisguse.opentracks.R;
/**
* Utilities for creating dialogs.
*
* @author Jimmy Shih
*/
public class DialogUtils {
private DialogUtils() {
}
/**
* Creates a confirmation dialog.
*
* @param context the context
* @param titleId the title
* @param message the message
* @param okListener the listener when OK is clicked
*/
public static Dialog createConfirmationDialog(final Context context, int titleId, String message, DialogInterface.OnClickListener okListener) {
final AlertDialog alertDialog = new AlertDialog.Builder(context)
.setCancelable(true)
.setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(message)
.setNegativeButton(R.string.generic_no, null)
.setPositiveButton(R.string.generic_yes, okListener)
.setTitle(titleId).create();
alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
setDialogTitleDivider(context, alertDialog);
}
});
return alertDialog;
}
/**
* 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, final Context context, int messageId, DialogInterface.OnCancelListener onCancelListener, Object... formatArgs) {
final 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);
progressDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
setDialogTitleDivider(context, progressDialog);
}
});
return progressDialog;
}
/**
* Sets the dialog title divider.
*
* @param context the context
* @param dialog the dialog
*/
public static void setDialogTitleDivider(Context context, Dialog dialog) {
try {
ViewGroup decorView = (ViewGroup) dialog.getWindow().getDecorView();
if (decorView == null) {
return;
}
FrameLayout windowContentView = (FrameLayout) decorView.getChildAt(0);
if (windowContentView == null) {
return;
}
FrameLayout contentView = (FrameLayout) windowContentView.getChildAt(0);
if (contentView == null) {
return;
}
LinearLayout parentPanel = (LinearLayout) contentView.getChildAt(0);
if (parentPanel == null) {
return;
}
LinearLayout topPanel = (LinearLayout) parentPanel.getChildAt(0);
if (topPanel == null) {
return;
}
View titleDivider = topPanel.getChildAt(2);
if (titleDivider == null) {
return;
}
titleDivider.setBackgroundColor(context.getResources().getColor(R.color.holo_orange_dark));
} catch (Exception e) {
// Can safely ignore
}
}
}
@@ -0,0 +1,281 @@
/*
* 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 de.dennisguse.opentracks.util;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import java.io.File;
/**
* Utilities for dealing with files.
*
* @author Rodrigo Damazio
*/
public class FileUtils {
/**
* Used to transfer picture from the camera.
*/
public static final String FILEPROVIDER = "de.dennisguse.opentracks.fileprovider";
/**
* The play tracks directory in the app's cache directory.
*/
public static final String PLAY_TRACKS_DIR = "temp";
/**
* Our external storage directory.
*/
static final String SDCARD_TOP_DIR = "MyTracks";
static final String TRACK_PREFIX = "MyTracks";
/**
* The maximum FAT32 path length. See the FAT32 spec at
* http://msdn.microsoft.com/en-us/windows/hardware/gg463080
*/
static final int MAX_FAT32_PATH_LENGTH = 260;
/**
* The pictures directory under the app's external storage directory.
*/
private static final String PICTURES_DIR = "pictures";
private FileUtils() {
}
/**
* Returns true if the external storage is available.
*/
public static boolean isExternalStorageAvailable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state)
|| Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
}
/**
* Returns true if the external storage is writable.
*/
public static boolean isExternalStorageWriteable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state);
}
/**
* Returns true if the directory exists.
*
* @param dir the directory
*/
public static boolean isDirectory(File dir) {
return dir.exists() && dir.isDirectory();
}
/**
* Ensures the 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 (isDirectory(dir)) {
return true;
}
return dir.mkdirs();
}
public static File getPhotoDir() {
return new File(getPath(PICTURES_DIR));
}
public static File getPhotoDir(long trackId) {
return new File(getPath(PICTURES_DIR, TRACK_PREFIX + trackId));
}
/**
* Gets the display name for a path on the external storage.
*
* @param components the components
*/
public static String getPathDisplayName(String... components) {
StringBuilder dirNameBuilder = new StringBuilder();
dirNameBuilder.append(File.separatorChar);
dirNameBuilder.append(SDCARD_TOP_DIR);
for (String component : components) {
dirNameBuilder.append(File.separatorChar);
dirNameBuilder.append(component);
}
return dirNameBuilder.toString();
}
/**
* Gets a path on the external storage.
*
* @param components the components
*/
public static String getPath(String... components) {
return Environment.getExternalStorageDirectory() + getPathDisplayName(components);
}
/**
* 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);
}
/**
* Gets the name from a file name, without the extension.
*
* @param fileName the file name
*/
public static String getName(String fileName) {
int index = fileName.lastIndexOf('.');
if (index == -1) {
return fileName;
}
return fileName.substring(0, index);
}
/**
* Gets the extension from a file name. Returns null if there is no extension.
*
* @param fileName the file name
*/
public static String getExtension(String fileName) {
int index = fileName.lastIndexOf('.');
if (index == -1) {
return null;
}
return fileName.substring(index + 1);
}
public static void updateMediaScanner(Context context, Uri uri) {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
mediaScanIntent.setData(uri);
context.sendBroadcast(mediaScanIntent);
}
/**
* 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 += "(" + 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
*/
static String sanitizeFileName(String name) {
StringBuilder builder = new StringBuilder(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)) {
builder.appendCodePoint(codePoint);
} else {
builder.append("_");
}
}
String result = builder.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
*/
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,89 @@
/*
* 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 de.dennisguse.opentracks.util;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.R;
import java.io.File;
/**
* Utilities for creating intents.
*
* @author Jimmy Shih
*/
public class IntentUtils {
public static final String TEXT_PLAIN_TYPE = "text/plain";
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 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 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 Intent newShareFileIntent(Context context, long trackId, String filePath, TrackFileFormat trackFileFormat) {
Track track = ContentProviderUtils.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());
}
public static Intent newShowOnMapIntent(Waypoint waypoint) {
return newShowOnMapIntent(waypoint.getLocation().getLatitude(), waypoint.getLocation().getLongitude(), waypoint.getName());
}
public static Intent newShowOnMapIntent(double latitude, double longitude, String label) {
//SEE https://developer.android.com/guide/components/intents-common.html#Maps
Intent intent = new Intent(Intent.ACTION_VIEW);
String uri = "geo:0,0?q=" + latitude + "," + longitude;
if (label != null && label.length() > 0) {
uri += "(" + label + ")";
}
intent.setData(Uri.parse(uri));
return intent;
}
}
@@ -0,0 +1,251 @@
/*
* 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 de.dennisguse.opentracks.util;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.net.Uri;
import android.text.format.DateUtils;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import de.dennisguse.opentracks.R;
/**
* Utilities to display a list item.
*
* @author Jimmy Shih
*/
public class ListItemUtils {
private static final int LIST_PREFERRED_ITEM_HEIGHT_DEFAULT = 128;
private ListItemUtils() {
}
/**
* Sets a list item.
*
* @param activity the activity
* @param view the list item view
* @param isRecording true if recording
* @param isPaused true if paused
* @param iconId the icon id
* @param iconContentDescriptionId the icon content description id
* @param name the name value
* @param totalTime the total time value
* @param totalDistance the total distance value
* @param markerCount the marker count
* @param startTime the start time value
* @param useRelativeTime true to display relative time if appropriate
* @param category the category value
* @param description the description value
* @param photoUrl the photo url
*/
public static void setListItem(Activity activity, View view, boolean isRecording,
boolean isPaused, int iconId, int iconContentDescriptionId, String name,
String totalTime, String totalDistance, int markerCount, long startTime,
boolean useRelativeTime, String category, String description, String photoUrl) {
// Set photo
ImageView photo = view.findViewById(R.id.list_item_photo);
ImageView textGradient = view.findViewById(R.id.list_item_text_gradient);
boolean hasPhoto = photoUrl != null && !photoUrl.equals("");
photo.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
textGradient.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
if (hasPhoto) {
photo.getLayoutParams().height = getPhotoHeight(activity);
photo.setImageResource(android.R.color.transparent);
photo.setImageURI(Uri.parse(photoUrl));
}
// Set icon
if (isRecording) {
iconId = isPaused ? R.drawable.ic_track_paused : R.drawable.ic_track_recording;
iconContentDescriptionId = isPaused ? R.string.image_pause : R.string.image_record;
}
ImageView iconImageView = view.findViewById(R.id.list_item_icon);
iconImageView.setImageResource(iconId);
iconImageView.setContentDescription(activity.getString(iconContentDescriptionId));
// Set name
TextView nameTextView = view.findViewById(R.id.list_item_name);
setTextView(activity, nameTextView, name, hasPhoto);
// Set totalTime/totalDistance
TextView timeDistanceTextView = view.findViewById(R.id.list_item_time_distance);
String timeDistanceText;
if (isRecording) {
timeDistanceTextView.setTextColor(activity.getResources().getColor(isPaused ? android.R.color.white : R.color.recording_text));
timeDistanceText = activity.getString(isPaused ? R.string.generic_paused : R.string.generic_recording);
} else {
// Match list_item_time_distance in list_item.xml
timeDistanceTextView.setTextAppearance(activity, R.style.TextSmall);
timeDistanceText = getTimeDistance(totalTime, totalDistance);
if (markerCount > 0) {
timeDistanceText += " \u2027";
}
}
setTextView(activity, timeDistanceTextView, timeDistanceText, hasPhoto);
// Set markerCount
ImageView markerCountIcon = view.findViewById(R.id.list_item_marker_count_icon);
TextView markerCountTextView = view.findViewById(R.id.list_item_marker_count);
boolean hasMarker = markerCount > 0;
markerCountIcon.setVisibility(hasMarker ? View.VISIBLE : View.GONE);
String markerCountValue = hasMarker ? String.valueOf(markerCount) : null;
if (hasMarker) {
// Scale markerCountIcon
int lineHeight = markerCountTextView.getLineHeight();
LayoutParams layoutParams = markerCountIcon.getLayoutParams();
layoutParams.width = lineHeight;
layoutParams.height = lineHeight;
}
setTextView(activity, markerCountTextView, markerCountValue, hasPhoto);
// Set date/time
String[] dateTime = getDateTime(isRecording, activity, startTime, useRelativeTime);
TextView dateTextView = view.findViewById(R.id.list_item_date);
setTextView(activity, dateTextView, dateTime[0], hasPhoto);
TextView timeTextView = view.findViewById(R.id.list_item_time);
setTextView(activity, timeTextView, dateTime[1], hasPhoto);
// Set category and description
TextView categoryDescriptionTextView = view.findViewById(
R.id.list_item_category_description);
String categoryDescription = isRecording ? null
: StringUtils.getCategoryDescription(category, description);
/*
* Place categoryDescription in either ownerTimeDistanceTextView or
* categoryDescriptionTextView
*/
if (timeDistanceTextView.getVisibility() == View.GONE
&& markerCountIcon.getVisibility() == View.GONE) {
setTextView(activity, categoryDescriptionTextView, null, hasPhoto);
// Match list_item_category_description in list_item.xml
timeDistanceTextView.setSingleLine(false);
timeDistanceTextView.setMaxLines(2);
setTextView(activity, timeDistanceTextView, categoryDescription, hasPhoto);
} else {
// Match list_item_time_distance in list_item.xml
timeDistanceTextView.setSingleLine(true);
setTextView(activity, categoryDescriptionTextView, categoryDescription, hasPhoto);
}
// Adjust iconImageView layout gravity
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) iconImageView.getLayoutParams();
params.gravity = timeDistanceTextView.getVisibility() == View.GONE
&& markerCountIcon.getVisibility() == View.GONE ? Gravity.TOP : Gravity.CENTER_VERTICAL;
}
/**
* Gets a string for share owner, total time, and total distance.
*
* @param totalTime the total time. Can be null
* @param totalDistance the total distance. Can be null
*/
private static String getTimeDistance(String totalTime, String totalDistance) {
StringBuilder builder = new StringBuilder();
if (totalTime != null && totalTime.length() != 0) {
if (builder.length() != 0) {
builder.append(" \u2027 ");
}
builder.append(totalTime);
}
if (totalDistance != null && totalDistance.length() != 0) {
if (builder.length() != 0) {
builder.append(" ");
}
builder.append("(").append(totalDistance).append(")");
}
return builder.toString();
}
/**
* Gets the date and time as an array of two strings.
*
* @param isRecording true if recording
* @param context the context
* @param time the start time
*/
private static String[] getDateTime(boolean isRecording, Context context, long time, boolean useRelativeTime) {
if (isRecording || time == 0L) {
return new String[]{null, null};
}
boolean isToday = DateUtils.isToday(time);
int timeFlags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL;
if (isToday) {
if (useRelativeTime) {
return new String[]{DateUtils.getRelativeTimeSpanString(
time, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE).toString(), null};
} else {
return new String[]{DateUtils.formatDateTime(context, time, timeFlags), null};
}
}
return new String[]{DateUtils.formatDateTime(
context, time, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL),
DateUtils.formatDateTime(context, time, timeFlags)};
}
/**
* Sets a text view.
*
* @param context the context
* @param textView the text view
* @param value the value for the text view
* @param addShadow true to add shadow
*/
public static void setTextView(Context context, TextView textView, String value, boolean addShadow) {
if (value == null || value.length() == 0) {
textView.setVisibility(View.GONE);
} else {
textView.setVisibility(View.VISIBLE);
textView.setText(value);
if (addShadow) {
textView.setShadowLayer(5, 0, 2, context.getResources().getColor(android.R.color.black));
} else {
textView.setShadowLayer(0, 0, 0, 0);
}
}
}
/**
* Gets the photo height.
*
* @param context the context
*/
private static int getPhotoHeight(Context context) {
int[] attrs = new int[]{android.R.attr.listPreferredItemHeight};
TypedArray typeArray = context.obtainStyledAttributes(attrs);
int height = typeArray.getDimensionPixelSize(0, LIST_PREFERRED_ITEM_HEIGHT_DEFAULT);
typeArray.recycle();
return 2 * height;
}
}
@@ -0,0 +1,178 @@
/*
* 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 de.dennisguse.opentracks.util;
import android.location.Location;
import android.util.Log;
import de.dennisguse.opentracks.content.Track;
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 static final String TAG = LocationUtils.class.getSimpleName();
// 1 minute in milliseconds
private static final long MAX_LOCATION_AGE_MS = (long) (UnitConversions.MIN_TO_S
* UnitConversions.S_TO_MS);
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<>();
double[] dists = new double[n];
dists[0] = 1;
dists[n - 1] = 1;
double maxDist;
double dist;
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(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<>();
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;
}
/**
* Returns true if a location is old.
*
* @param location the location
*/
public static boolean isLocationOld(Location location) {
return !LocationUtils.isValidLocation(location)
|| (System.currentTimeMillis() - location.getTime() > MAX_LOCATION_AGE_MS);
}
}
@@ -0,0 +1,245 @@
/*
* 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 de.dennisguse.opentracks.util;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import de.dennisguse.opentracks.Constants;
import de.dennisguse.opentracks.R;
/**
* 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 int AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT = 0;
// Values for auto_resume_track_timeout_key
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_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 CHART_X_AXIS_DEFAULT = "DISTANCE";
public static final String DEFAULT_ACTIVITY_DEFAULT = "";
// Value for split_frequency_key and voice_frequency_key
public static final int FREQUENCY_OFF = 0;
public static final int MAP_TYPE_DEFAULT = 1;
public static final int MAX_RECORDING_DISTANCE_DEFAULT = 200;
// Values for min_recording_interval_key
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 PHOTO_SIZE_DEFAULT = 1024; // 1024 kB
public static final int RECORDING_DISTANCE_INTERVAL_DEFAULT = 10;
// Values for recording_gps_accuracy
public static final int RECORDING_GPS_ACCURACY_DEFAULT = 50;
public static final int RECORDING_GPS_ACCURACY_EXCELLENT = 10;
public static final int RECORDING_GPS_ACCURACY_POOR = 2000;
public static final long RECORDING_TRACK_ID_DEFAULT = -1L;
public static final boolean RECORDING_TRACK_PAUSED_DEFAULT = true;
public static final int SPLIT_FREQUENCY_DEFAULT = 0;
// Stats
public static final String STATS_RATE_DEFAULT = "SPEED";
public static final boolean STATS_SHOW_COORDINATE_DEFAULT = false;
public static final boolean STATS_SHOW_GRADE_ELEVATION_DEFAULT = false;
public static final String STATS_UNITS_DEFAULT = "METRIC";
public static final String TRACK_NAME_DEFAULT = "DATE_ISO_8601_LOCATION";
// Track widget
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);
editor.apply();
}
/**
* 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) {
//NOTE: We assume that the data was stored as String due to use of ListPreference.
SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
String stringValue = sharedPreferences.getString(getKey(context, keyId), null);
int intValue = defaultValue;
try {
intValue = Integer.parseInt(stringValue);
} catch (NumberFormatException e) {
}
return intValue;
}
/**
* 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);
editor.apply();
}
/**
* 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);
editor.apply();
}
/**
* 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);
editor.apply();
}
/**
* Returns true if metric units.
*
* @param context the context
*/
public static boolean isMetricUnits(Context context) {
return STATS_UNITS_DEFAULT.equals(getString(context, R.string.stats_units_key, STATS_UNITS_DEFAULT));
}
/**
* Returns true if the preferred rate is speed, false if the preferred rate is
* pace.
*
* @param context the context
*/
public static boolean isReportSpeed(Context context) {
return STATS_RATE_DEFAULT.equals(getString(context, R.string.stats_rate_key, STATS_RATE_DEFAULT));
}
/**
* Returns true if chart x axis is by distance, false if by time.
*
* @param context the context
*/
public static boolean isChartByDistance(Context context) {
return CHART_X_AXIS_DEFAULT.equals(getString(context, R.string.chart_x_axis_key, CHART_X_AXIS_DEFAULT));
}
}
@@ -0,0 +1,366 @@
/*
* 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 de.dennisguse.opentracks.util;
import android.app.Activity;
import android.content.Context;
import android.location.Location;
import android.view.View;
import android.widget.Spinner;
import android.widget.TextView;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.sensors.SensorManager;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.R;
import java.util.Locale;
/**
* Utilities for updating the statistics UI labels and values.
*
* @author Jimmy Shih
*/
public class StatsUtils {
private static final String GRADE_PERCENTAGE = "%";
private static final String GRADE_FORMAT = "%1$d";
private StatsUtils() {
}
/**
* Sets the location values.
*
* @param context the context
* @param activity the activity for finding views. If null, the view cannot be
* null
* @param view the containing view for finding views. If null, the activity
* cannot be null
* @param location the location
* @param isRecording true if recording
*/
public static void setLocationValues(Context context, Activity activity, View view, Location location, boolean isRecording) {
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
boolean reportSpeed = PreferencesUtils.isReportSpeed(context);
// Set speed/pace
View speed = getView(activity, view, R.id.stats_speed);
speed.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
if (isRecording) {
double value = isRecording && location != null && location.hasSpeed() ? location.getSpeed() : Double.NaN;
setSpeed(context, speed, R.string.stats_speed, R.string.stats_pace, value, metricUnits, reportSpeed);
}
// Set elevation
boolean showGradeElevation = PreferencesUtils.getBoolean(context, R.string.stats_show_grade_elevation_key, PreferencesUtils.STATS_SHOW_GRADE_ELEVATION_DEFAULT) && isRecording;
View elevation = getView(activity, view, R.id.stats_elevation);
elevation.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
if (showGradeElevation) {
double altitude = location != null && location.hasAltitude() ? location.getAltitude() : Double.NaN;
setElevationValue(context, elevation, -1, altitude, metricUnits);
}
// Set coordinate
boolean showCoordinate = isRecording && PreferencesUtils.getBoolean(context, R.string.stats_show_coordinate_key, PreferencesUtils.STATS_SHOW_COORDINATE_DEFAULT);
View coordinateSeparator = getView(activity, view, R.id.stats_coordinate_separator);
View coordinateContainer = getView(activity, view, R.id.stats_coordinate_container);
if (coordinateSeparator != null) {
coordinateSeparator.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
}
coordinateContainer.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
if (showCoordinate) {
double latitude = location != null ? location.getLatitude() : Double.NaN;
double longitude = location != null ? location.getLongitude() : Double.NaN;
setCoordinateValue(context, getView(activity, view, R.id.stats_latitude), R.string.stats_latitude, latitude);
setCoordinateValue(context, getView(activity, view, R.id.stats_longitude), R.string.stats_longitude, longitude);
}
}
public static void setSensorData(Context context, Activity activity, SensorDataSet sensorDataSet, boolean isRecording) {
{
// SensorDataSet: heart rate
int isVisible = View.VISIBLE;
if (!isRecording || PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(PreferencesUtils.getString(context, R.string.bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT))) {
isVisible = View.INVISIBLE;
}
getView(activity, null, R.id.stats_sensor_container).setVisibility(isVisible);
View viewValue = getView(activity, null, R.id.stats_sensor_heart_rate_value);
View viewSensor = getView(activity, null, R.id.stats_sensor_heart_rate_sensor);
if (isRecording) {
setHeartRateData(context, viewValue, viewSensor, sensorDataSet);
}
}
}
/**
* Sets the total time value.
*
* @param activity the activity
* @param totalTime the total time
*/
public static void setTotalTimeValue(Activity activity, long totalTime) {
setTimeValue(activity, activity.findViewById(R.id.stats_total_time), R.string.stats_total_time, totalTime);
}
/**
* Sets the trip statistics values.
*
* @param context the context
* @param activity the activity for finding views. If null, then view cannot
* be null
* @param view the containing view for finding views. If null, the activity
* cannot be null
* @param tripStatistics the trip statistics
* @param trackIconValue the track icon value or null to hide the track icon
* spinner
*/
public static void setTripStatisticsValues(Context context, Activity activity, View view, TripStatistics tripStatistics, String trackIconValue) {
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
boolean reportSpeed = PreferencesUtils.isReportSpeed(context);
// Set total distance
double totalDistance = tripStatistics == null ? Double.NaN : tripStatistics.getTotalDistance();
setDistanceValue(context, getView(activity, view, R.id.stats_distance), totalDistance, metricUnits);
// Set activity type
Spinner spinner = (Spinner) getView(activity, view, R.id.stats_activity_type_icon);
spinner.setVisibility(trackIconValue != null ? View.VISIBLE : View.GONE);
if (trackIconValue != null) {
TrackIconUtils.setIconSpinner(spinner, trackIconValue);
}
// Set total time
setTimeValue(context, getView(activity, view, R.id.stats_total_time), R.string.stats_total_time, tripStatistics != null ? tripStatistics.getTotalTime() : -1L);
// Set moving time
setTimeValue(context, getView(activity, view, R.id.stats_moving_time), R.string.stats_moving_time, tripStatistics != null ? tripStatistics.getMovingTime() : -1L);
// Set average speed/pace
double averageSpeed = tripStatistics != null ? tripStatistics.getAverageSpeed() : Double.NaN;
setSpeed(context, getView(activity, view, R.id.stats_average_speed), R.string.stats_average_speed, R.string.stats_average_pace, averageSpeed, metricUnits, reportSpeed);
// Set max speed/pace
double maxSpeed = tripStatistics == null ? Double.NaN : tripStatistics.getMaxSpeed();
setSpeed(context, getView(activity, view, R.id.stats_max_speed), R.string.stats_max_speed, R.string.stats_fastest_pace, maxSpeed, metricUnits, reportSpeed);
// Set average moving speed/pace
double averageMovingSpeed = tripStatistics != null ? tripStatistics.getAverageMovingSpeed() : Double.NaN;
setSpeed(context, getView(activity, view, R.id.stats_average_moving_speed), R.string.stats_average_moving_speed, R.string.stats_average_moving_pace, averageMovingSpeed, metricUnits, reportSpeed);
// Set grade/elevation
boolean showGradeElevation = PreferencesUtils.getBoolean(context, R.string.stats_show_grade_elevation_key, PreferencesUtils.STATS_SHOW_GRADE_ELEVATION_DEFAULT);
View gradeElevationSeparator = getView(activity, view, R.id.stats_grade_elevation_separator);
gradeElevationSeparator.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
View gradeElevationContainer = getView(activity, view, R.id.stats_grade_elevation_container);
gradeElevationContainer.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
if (showGradeElevation) {
// Set grade
double minGrade = tripStatistics == null ? Double.NaN : tripStatistics.getMinGrade();
double maxGrade = tripStatistics == null ? Double.NaN : tripStatistics.getMaxGrade();
setGradeValue(context, getView(activity, view, R.id.stats_grade_min), R.string.stats_min, minGrade);
setGradeValue(context, getView(activity, view, R.id.stats_grade_max), R.string.stats_max, maxGrade);
// Set elevation
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(context, getView(activity, view, R.id.stats_elevation_gain), R.string.stats_gain, elevationGain, metricUnits);
setElevationValue(context, getView(activity, view, R.id.stats_elevation_min), R.string.stats_min, minElevation, metricUnits);
setElevationValue(context, getView(activity, view, R.id.stats_elevation_max), R.string.stats_max, maxElevation, metricUnits);
}
}
/**
* Sets speed.
*
* @param context the context
* @param view the containing view
* @param speedLabelId the speed label id
* @param paceLabelId the pace label id
* @param speed the speed in meters per second
* @param metricUnits true if metric units
* @param reportSpeed true if report speed
*/
private static void setSpeed(Context context, View view, int speedLabelId, int paceLabelId, double speed, boolean metricUnits, boolean reportSpeed) {
String[] parts = StringUtils.getSpeedParts(context, speed, metricUnits, reportSpeed);
setItem(context, view, reportSpeed ? speedLabelId : paceLabelId, parts[0], parts[1]);
}
/**
* Sets distance value.
*
* @param context the context
* @param view the containing view
* @param distance the distance in meters
* @param metricUnits true if metric units
*/
private static void setDistanceValue(Context context, View view, double distance, boolean metricUnits) {
String[] parts = StringUtils.getDistanceParts(context, distance, metricUnits);
setItem(context, view, R.string.stats_distance, parts[0], parts[1]);
}
/**
* Sets a time value.
*
* @param context the context
* @param view the containing view
* @param labelId the label id
* @param time the time
*/
private static void setTimeValue(Context context, View view, int labelId, long time) {
String value = time == -1L ? null : StringUtils.formatElapsedTime(time);
setItem(context, view, labelId, value, null);
}
/**
* Sets an elevation value.
*
* @param context the context
* @param view the containing view
* @param labelId the label id
* @param elevation the elevation in meters
* @param metricUnits true if metric units
*/
private static void setElevationValue(Context context, View view, int labelId, double elevation, boolean metricUnits) {
String value;
String unit;
if (Double.isNaN(elevation) || Double.isInfinite(elevation)) {
value = null;
unit = null;
} else {
if (metricUnits) {
value = StringUtils.formatDecimal(elevation);
unit = context.getString(R.string.unit_meter);
} else {
elevation *= UnitConversions.M_TO_FT;
value = StringUtils.formatDecimal(elevation);
unit = context.getString(R.string.unit_feet);
}
}
setItem(context, view, labelId, value, unit);
}
/**
* Sets a grade value.
*
* @param context the context
* @param view the containing view
* @param labelId the label id
* @param grade the grade in fraction between 0 and 1
*/
private static void setGradeValue(Context context, View view, int labelId, double grade) {
String value = Double.isNaN(grade) || Double.isInfinite(grade) ? null
: String.format(Locale.getDefault(), GRADE_FORMAT, Math.round(grade * 100));
setItem(context, view, labelId, value, GRADE_PERCENTAGE);
}
/**
* Sets a coordinate value.
*
* @param context the context
* @param view the containing view
* @param labelId the label id
* @param coordinate the coordinate in degrees
*/
private static void setCoordinateValue(Context context, View view, int labelId, double coordinate) {
String value = Double.isNaN(coordinate) || Double.isInfinite(coordinate) ? null
: StringUtils.formatCoordinate(coordinate);
setItem(context, view, labelId, value, null);
}
/**
* Sets a heart rate value (sensor)
*
* @param context the context
* @param viewValue the containing view
* @param sensorDataSet the sensorDataSet
*/
private static void setHeartRateData(Context context, View viewValue, View viewSensor, SensorDataSet sensorDataSet) {
String heartRate = context.getString(R.string.value_unknown);
String sensor = context.getString(R.string.value_unknown);
if (sensorDataSet != null && sensorDataSet.hasHeartRate() && sensorDataSet.isRecent(SensorManager.MAX_SENSOR_DATE_SET_AGE)) {
heartRate = StringUtils.formatDecimal(sensorDataSet.getHeartRate(), 0);
sensor = sensorDataSet.getSensorName();
}
setItem(context, viewValue, R.string.sensor_state_heart_rate, heartRate, context.getString(R.string.sensor_unit_beats_per_minute));
setItem(context, viewSensor, R.string.settings_sensor_bluetooth_sensor, sensor, null);
}
/**
* Sets an item.
*
* @param context the context
* @param view the containing view
* @param labelId the label id. -1 to hide the label
* @param value the value, can be null
* @param unit the unit. Null to hide the unit
*/
private static void setItem(Context context, View view, int labelId, CharSequence value, CharSequence unit) {
TextView labelTextView = view.findViewById(R.id.stats_label);
if (labelTextView != null) {
if (labelId == -1) {
labelTextView.setVisibility(View.GONE);
} else {
labelTextView.setVisibility(View.VISIBLE);
labelTextView.setText(labelId);
}
}
TextView valueTextView = view.findViewById(R.id.stats_value);
TextView unitTextView = view.findViewById(R.id.stats_unit);
if (valueTextView == null || unitTextView == null) {
return;
}
if (value == null) {
value = context.getString(R.string.value_unknown);
unitTextView.setVisibility(View.GONE);
} else {
if (unit == null) {
unitTextView.setVisibility(View.GONE);
} else {
unitTextView.setVisibility(View.VISIBLE);
unitTextView.setText(unit);
}
}
valueTextView.setText(value);
}
/**
* Get a view.
*
* @param activity the activity
* @param view the containing view
* @param id the id
*/
//TODO What is the difference between the two cases? Is it (still) necessary?
private static View getView(Activity activity, View view, int id) {
if (activity != null) {
return activity.findViewById(id);
} else {
return view.findViewById(id);
}
}
}
@@ -0,0 +1,421 @@
/*
* 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 de.dennisguse.opentracks.util;
import android.content.Context;
import android.location.Location;
import android.text.Html;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.format.DateUtils;
import de.dennisguse.opentracks.R;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
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 String COORDINATE_DEGREE = "\u00B0";
private static final SimpleDateFormat ISO_8601_DATE_TIME_FORMAT = new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
private static final SimpleDateFormat ISO_8601_BASE = new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss", Locale.US);
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 DateUtils.formatDateTime(
context, time, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE) + " "
+ DateUtils.formatDateTime(context, time, DateUtils.FORMAT_SHOW_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) {
/*
* TODO
* Temporary workaround for DateUtils.formatElapsedTime(time * MS_TO_S). In API
* level 17, it returns strings like "1:0:00" instead of "1:00:00", which
* breaks several unit tests.
*/
if (time < 0) {
return "-";
}
long hours = 0;
long minutes = 0;
long elapsedSeconds = (long) (time * UnitConversions.MS_TO_S);
if (elapsedSeconds >= 3600) {
hours = elapsedSeconds / 3600;
elapsedSeconds -= hours * 3600;
}
if (elapsedSeconds >= 60) {
minutes = elapsedSeconds / 60;
elapsedSeconds -= minutes * 60;
}
long seconds = elapsedSeconds;
if (hours > 0) {
return String.format(Locale.US, "%d:%02d:%02d", hours, minutes, seconds);
} else {
return String.format(Locale.US, "%02d:%02d", minutes, seconds);
}
}
/**
* 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);
}
}
}
public static String formatDecimal(double value) {
return StringUtils.formatDecimal(value, 2);
}
public static String formatDecimal(double value, int decimalPlaces) {
String result = String.format(Locale.getDefault(), "%1$,." + decimalPlaces + "f", value);
return result.replaceAll("[0]*$", "").replaceAll("\\.$", "");
}
/**
* Formats a coordinate
*
* @param coordinate the coordinate
*/
public static String formatCoordinate(double coordinate) {
return Location.convert(coordinate, Location.FORMAT_DEGREES) + COORDINATE_DEGREE;
}
/**
* Gets the distance in an array of two strings. The first string is the
* distance. The second string is the unit. The first string is null if the
* distance is invalid.
*
* @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] = null;
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] = formatDecimal(distance);
result[1] = context.getString(unitId);
return result;
}
/**
* Gets the speed in an array of two strings. The first string is the speed.
* The second string is the unit. The first string is null if speed is
* invalid.
*
* @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] = null;
return result;
}
speed *= UnitConversions.MS_TO_KMH;
if (!metricUnits) {
speed *= UnitConversions.KM_TO_MI;
}
if (reportSpeed) {
result[0] = StringUtils.formatDecimal(speed);
} else {
// convert from hours to minutes
double pace = speed == 0 ? 0.0 : 60.0 / speed;
int minutes = (int) pace;
int seconds = (int) Math.round((pace - minutes) * 60.0);
result[0] = String.format(Locale.US, "%d:%02d", minutes, seconds);
}
return result;
}
/**
* Gets a string for category.
*
* @param category the category
*/
public static String getCategory(String category) {
if (category == null || category.length() == 0) {
return null;
}
return "[" + category + "]";
}
/**
* Gets a string for category and description.
*
* @param category the category
* @param description the description
*/
public static String getCategoryDescription(String category, String description) {
if (category == null || category.length() == 0) {
return description;
}
StringBuilder builder = new StringBuilder();
builder.append("[").append(category).append("]");
if (description != null && description.length() != 0) {
builder.append(" ").append(description);
}
return builder.toString();
}
/**
* 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 = Math.round(fractionalSeconds * UnitConversions.S_TO_MS);
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 = (long) (time * UnitConversions.MS_TO_S);
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,89 @@
/*
* 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 de.dennisguse.opentracks.util;
import android.annotation.SuppressLint;
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 accessing basic Android functionality.
*
* @author Rodrigo Damazio
*/
public class SystemUtils {
private static final String TAG = SystemUtils.class.getSimpleName();
private SystemUtils() {
}
/**
* Get the app version from the manifest.
*
* @return the version, or an empty string in case of failure.
*/
public static String getAppVersion(Context context) {
try {
PackageInfo pi = context.getPackageManager()
.getPackageInfo("de.dennisguse.opentracks", PackageManager.GET_META_DATA);
return pi.versionName;
} catch (NameNotFoundException e) {
Log.w(TAG, "Failed to get version info.", e);
return "";
}
}
/**
* Acquire a wake lock if not already acquired.
*
* @param context the context
* @param wakeLock wake lock or null
*/
@SuppressLint("WakelockTimeout")
public static WakeLock acquireWakeLock(Context context, WakeLock wakeLock) {
Log.i(TAG, "Acquiring wake lock.");
try {
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (powerManager == null) {
Log.e(TAG, "Power manager null.");
return wakeLock;
}
if (wakeLock == null) {
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
if (wakeLock == null) {
Log.e(TAG, "Cannot create a new wake lock.");
return null;
}
}
if (!wakeLock.isHeld()) {
wakeLock.acquire();
if (!wakeLock.isHeld()) {
Log.e(TAG, "Cannot acquire wake lock.");
}
}
} catch (RuntimeException e) {
Log.e(TAG, e.getMessage(), e);
}
return wakeLock;
}
}
@@ -0,0 +1,242 @@
/*
* 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 de.dennisguse.opentracks.util;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.util.Pair;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.Spinner;
import androidx.annotation.NonNull;
import de.dennisguse.opentracks.R;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
/**
* 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 static final int[] AIRPLANE_LIST = new int[]{R.string.activity_type_airplane,
R.string.activity_type_commercial_airplane, R.string.activity_type_rc_airplane};
private static final int[] BIKE_LIST = 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 final int[] BOAT_LIST = 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 final int[] DRIVE_LIST = 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 final int[] RUN_LIST = 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 final int[] SKI_LIST = new int[]{
R.string.activity_type_cross_country_skiing, R.string.activity_type_skiing};
private static final int[] SNOW_BOARDING_LIST = new int[]{
R.string.activity_type_snow_boarding};
private static final int[] WALK_LIST = 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};
private static final LinkedHashMap<String, Pair<Integer, Integer>>
MAP = new LinkedHashMap<>();
static {
MAP.put(RUN, new Pair<>(R.string.activity_type_running, R.drawable.ic_track_run));
MAP.put(WALK, new Pair<>(R.string.activity_type_walking, R.drawable.ic_track_walk));
MAP.put(BIKE, new Pair<>(R.string.activity_type_biking, R.drawable.ic_track_bike));
MAP.put(DRIVE, new Pair<>(R.string.activity_type_driving, R.drawable.ic_track_drive));
MAP.put(SKI, new Pair<>(R.string.activity_type_skiing, R.drawable.ic_track_ski));
MAP.put(SNOW_BOARDING, new Pair<>(R.string.activity_type_snow_boarding, R.drawable.ic_track_snow_boarding));
MAP.put(AIRPLANE, new Pair<>(R.string.activity_type_airplane, R.drawable.ic_track_airplane));
MAP.put(BOAT, new Pair<>(R.string.activity_type_boat, R.drawable.ic_track_boat));
}
private TrackIconUtils() {
}
/**
* Gets the icon drawable.
*
* @param iconValue the icon value
*/
public static int getIconDrawable(String iconValue) {
if (iconValue == null || iconValue.equals("")) {
return R.drawable.ic_track_generic;
}
Pair<Integer, Integer> pair = MAP.get(iconValue);
return pair == null ? R.drawable.ic_track_generic : pair.second;
}
/**
* Gets the icon activity type.
*
* @param iconValue the icon value
*/
public static int getIconActivityType(String iconValue) {
if (iconValue == null || iconValue.equals("")) {
return R.string.activity_type_walking;
}
Pair<Integer, Integer> pair = MAP.get(iconValue);
return pair == null ? R.string.activity_type_walking : pair.first;
}
/**
* Gets all icon values.
*/
public static List<String> getAllIconValues() {
return new ArrayList<>(MAP.keySet());
}
/**
* Gets the icon value.
*
* @param context the context
* @param activityType the activity type
*/
public static String getIconValue(Context context, String activityType) {
if (activityType == null || activityType.equals("")) {
return "";
}
if (inList(context, activityType, AIRPLANE_LIST)) {
return AIRPLANE;
}
if (inList(context, activityType, BIKE_LIST)) {
return BIKE;
}
if (inList(context, activityType, BOAT_LIST)) {
return BOAT;
}
if (inList(context, activityType, DRIVE_LIST)) {
return DRIVE;
}
if (inList(context, activityType, RUN_LIST)) {
return RUN;
}
if (inList(context, activityType, SKI_LIST)) {
return SKI;
}
if (inList(context, activityType, SNOW_BOARDING_LIST)) {
return SNOW_BOARDING;
}
if (inList(context, activityType, WALK_LIST)) {
return WALK;
}
return "";
}
public static void setIconSpinner(Spinner spinner, String iconValue) {
ArrayAdapter<StringBuilder> adapter = (ArrayAdapter<StringBuilder>) spinner.getAdapter();
StringBuilder stringBuilder = adapter.getItem(0);
stringBuilder.delete(0, stringBuilder.length());
stringBuilder.append(iconValue);
adapter.notifyDataSetChanged();
}
public static ArrayAdapter<StringBuilder> getIconSpinnerAdapter(
final Context context, String iconValue) {
return new ArrayAdapter<StringBuilder>(context, android.R.layout.simple_spinner_item,
new StringBuilder[]{new StringBuilder(iconValue)}) {
@NonNull
@Override
public View getView(int position, View convertView, @NonNull android.view.ViewGroup parent) {
ImageView imageView = convertView != null ? (ImageView) convertView : new ImageView(getContext());
Bitmap source = BitmapFactory.decodeResource(context.getResources(), TrackIconUtils.getIconDrawable(getItem(position) + ""));
imageView.setImageBitmap(source);
imageView.setPadding(4, 4, -4, -4);
return imageView;
}
};
}
/**
* 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;
}
/**
* Sets the menu icon color.
*
* @param menu the menu
*/
public static void setMenuIconColor(Menu menu) {
int size = menu.size();
for (int i = 0; i < size; i++) {
MenuItem menuitem = menu.getItem(i);
setMenuIconColor(menuitem);
}
}
/**
* Sets the menu icon color.
*
* @param menuitem the menu item
*/
public static void setMenuIconColor(MenuItem menuitem) {
revertMenuIconColor(menuitem);
}
/**
* Reverts the menu icon color.
*
* @param menuitem the menu item
*/
private static void revertMenuIconColor(MenuItem menuitem) {
Drawable drawable = menuitem.getIcon();
if (drawable != null) {
final PorterDuffColorFilter colorFilter = new PorterDuffColorFilter(Color.rgb(255, 255, 255), PorterDuff.Mode.MULTIPLY);
drawable.setColorFilter(colorFilter);
}
}
}
@@ -0,0 +1,118 @@
/*
* 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 de.dennisguse.opentracks.util;
import android.content.Context;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.R;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Locale;
/**
* 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, can be null
*/
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_location_value))) {
// Use the startTime if location is null
return location != null ? getReverseGeoCoding(context, location) : StringUtils.formatDateTime(context, startTime);
} else if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_local_value))) {
return StringUtils.formatDateTime(context, startTime);
} else if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_iso_8601_value))) {
return new SimpleDateFormat(ISO_8601_FORMAT, Locale.US).format(startTime);
} else if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_iso_8601_location_value))) {
StringBuilder builder = new StringBuilder(new SimpleDateFormat(ISO_8601_FORMAT, Locale.US).format(startTime));
if (location != null) {
String address = getReverseGeoCoding(context, location);
if (address != null && !address.equals("")) {
builder.append(" ").append(address);
}
}
return builder.toString();
} else {
// R.string.settings_recording_track_name_number_value)
return context.getString(R.string.track_name_format, trackId);
}
}
/**
* 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 || !Geocoder.isPresent()) {
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,181 @@
/*
* 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 de.dennisguse.opentracks.util;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;
import de.dennisguse.opentracks.TrackEditActivity;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.services.ITrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.R;
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) {
ITrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service != null) {
service.resumeCurrentTrack();
}
}
/**
* Pauses the recording track.
*
* @param trackRecordingServiceConnection the track recording service
* connection
*/
public static void pauseTrack(TrackRecordingServiceConnection trackRecordingServiceConnection) {
ITrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service != null) {
service.pauseCurrentTrack();
}
}
/**
* 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 long 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 {
long markerId = trackRecordingService.insertWaypoint(waypointCreationRequest);
if (markerId != -1L) {
Toast.makeText(context, R.string.marker_add_success, Toast.LENGTH_SHORT).show();
return markerId;
}
} catch (IllegalStateException e) {
Log.e(TAG, "Unable to add marker.", e);
}
}
Toast.makeText(context, R.string.marker_add_error, Toast.LENGTH_LONG).show();
return -1L;
}
}
@@ -0,0 +1,52 @@
/*
* Copyright 2013 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 de.dennisguse.opentracks.util;
import android.content.Context;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.TracksColumns;
/**
* Utilities for updating track.
*
* @author Jimmy Shih
*/
public class TrackUtils {
public static String TRACK_SORT_ORDER = TracksColumns.STARTTIME + " DESC";
private TrackUtils() {
}
public static void updateTrack(Context context, Track track, String name, String category, String description, ContentProviderUtils contentProviderUtils) {
if (name != null) {
track.setName(name);
}
if (category != null) {
track.setCategory(category);
track.setIcon(TrackIconUtils.getIconValue(context, category));
}
if (description != null) {
track.setDescription(description);
}
track.setModifiedTime(System.currentTimeMillis());
contentProviderUtils.updateTrack(track);
}
}
@@ -0,0 +1,65 @@
/*
* 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 de.dennisguse.opentracks.util;
/**
* Unit conversion constants.
*
* @author Sandor Dornbush
*/
public class UnitConversions {
// multiplication factor to convert seconds to milliseconds
public static final double S_TO_MS = 1000.0;
// Time
// 1 second in milliseconds
public static final long ONE_SECOND = (long) UnitConversions.S_TO_MS;
// multiplication factor to convert milliseconds to seconds
public static final double MS_TO_S = 1 / S_TO_MS;
// multiplication factor to convert minutes to seconds
public static final double MIN_TO_S = 60.0;
// multiplication factor to convert seconds to minutes
public static final double S_TO_MIN = 1 / MIN_TO_S;
// multiplication factor to convert hours to minutes
public static final double HR_TO_MIN = 60.0;
// multiplication factor to convert minutes to hours
public static final double MIN_TO_HR = 1 / HR_TO_MIN;
// multiplication factor to convert kilometers to miles
public static final double KM_TO_MI = 0.621371192;
// Distance
// 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 covert kilometers to meters
public static final double KM_TO_M = 1000.0;
// multiplication factor to convert meters to kilometers
public static final double M_TO_KM = 1 / KM_TO_M;
// 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 meters per second to kilometers per hour
public static final double MS_TO_KMH = M_TO_KM / (S_TO_MIN * MIN_TO_HR);
// Others
// multiplication factor to convert degrees to radians
public static final double DEG_TO_RAD = Math.PI / 180.0;
private UnitConversions() {
}
}