mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-09 04:43:06 +02:00
Rebranding to OpenTracks (code).
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
+181
@@ -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() {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user