Cleanup: mainly code formatting and made LocationIterator implement autoclosable.

This commit is contained in:
Dennis Guse
2019-09-07 12:23:56 +02:00
parent 025dee6821
commit 87d92d25f9
104 changed files with 1076 additions and 1526 deletions
@@ -25,16 +25,16 @@ import android.graphics.Path;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.stats.ExtremityMonitor;
import java.text.NumberFormat;
import de.dennisguse.opentracks.stats.ExtremityMonitor;
/**
* This class encapsulates the meta data for one series of the chart values.
*
* @author Sandor Dornbush
*/
public class ChartValueSeries {
class ChartValueSeries {
private static final float STROKE_WIDTH = 2f;
@@ -68,8 +68,7 @@ public class ChartValueSeries {
* @param fillColor the fill color
* @param strokeColor the stroke color
*/
public ChartValueSeries(Context context, int absoluteMin, int absoluteMax, int[] intervalValues,
int metricTitleId, int imperialTitleId, int fillColor, int strokeColor) {
ChartValueSeries(Context context, int absoluteMin, int absoluteMax, int[] intervalValues, int metricTitleId, int imperialTitleId, int fillColor, int strokeColor) {
this.absoluteMin = absoluteMin;
this.absoluteMax = absoluteMax;
this.intervalValues = intervalValues;
@@ -108,7 +107,7 @@ public class ChartValueSeries {
/**
* Returns true if the series is enabled.
*/
public boolean isEnabled() {
boolean isEnabled() {
return enabled;
}
@@ -117,14 +116,14 @@ public class ChartValueSeries {
*
* @param enabled true to enable
*/
public void setEnabled(boolean enabled) {
void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* Returns true if the series has data.
*/
public boolean hasData() {
boolean hasData() {
return extremityMonitor.hasData();
}
@@ -133,14 +132,14 @@ public class ChartValueSeries {
*
* @param value the new value
*/
public void update(double value) {
void update(double value) {
extremityMonitor.update(value);
}
/**
* Gets the path.
*/
public Path getPath() {
Path getPath() {
return path;
}
@@ -149,7 +148,7 @@ public class ChartValueSeries {
*
* @param canvas the canvas
*/
public void drawPath(Canvas canvas) {
void drawPath(Canvas canvas) {
canvas.drawPath(path, fillPaint);
canvas.drawPath(path, strokePaint);
}
@@ -157,7 +156,7 @@ public class ChartValueSeries {
/**
* Updates the y axis dimension.
*/
public void updateDimension() {
void updateDimension() {
double min = hasData() ? extremityMonitor.getMin() : 0.0;
double max = hasData() ? extremityMonitor.getMax() : 1.0;
min = Math.max(min, absoluteMin);
@@ -204,14 +203,15 @@ public class ChartValueSeries {
/**
* Gets the interval value.
*/
public int getInterval() {
@VisibleForTesting
int getInterval() {
return interval;
}
/**
* Gets the min marker value.
*/
public int getMinMarkerValue() {
int getMinMarkerValue() {
return minMarkerValue;
}
@@ -226,28 +226,28 @@ public class ChartValueSeries {
/**
* Gets the title id.
*/
public int getTitleId(boolean metricUnits) {
int getTitleId(boolean metricUnits) {
return metricUnits ? metricTitleId : imperialTitleId;
}
/**
* Gets the title paint.
*/
public Paint getTitlePaint() {
Paint getTitlePaint() {
return titlePaint;
}
/**
* Gets the marker paint.
*/
public Paint getMarkerPaint() {
Paint getMarkerPaint() {
return markerPaint;
}
/**
* Gets the largest marker.
*/
public String getLargestMarker() {
String getLargestMarker() {
String minMarker = numberFormat.format(getMinMarkerValue());
String maxMarker = numberFormat.format(getMaxMarkerValue());
return minMarker.length() >= maxMarker.length() ? minMarker : maxMarker;
@@ -258,7 +258,7 @@ public class ChartValueSeries {
*
* @param value the value
*/
public String formatMarker(int value) {
String formatMarker(int value) {
return numberFormat.format(value);
}
}
@@ -565,7 +565,7 @@ public class ChartView extends View {
* @param canvas the canvas
*/
private void drawSeriesTitles(Canvas canvas) {
int[] titleDimensions = getTitleDimenions();
int[] titleDimensions = getTitleDimensions();
int lines = titleDimensions[0];
int lineHeight = titleDimensions[1];
int count = 0;
@@ -583,10 +583,10 @@ public class ChartView extends View {
}
/**
* Gets the title dimensions. Returns an array of 2 integers, first element is
* the number of lines and the second element is the line height.
* Gets the title dimensions.
* Returns an array of 2 integers, first element is the number of lines and the second element is the line height.
*/
private int[] getTitleDimenions() {
private int[] getTitleDimensions() {
int lines = 0;
int lineHeight = 0;
for (int i = 0; i < series.length; i++) {
@@ -763,8 +763,7 @@ public class ChartView extends View {
}
/**
* Updates paths. The path needs to be updated any time after the data or the
* dimensions change.
* Updates paths. The path needs to be updated any time after the data or the dimensions change.
*/
private void updatePaths() {
synchronized (chartData) {
@@ -862,7 +861,7 @@ public class ChartView extends View {
}
leftBorder = (int) (density * BORDER + markerLength);
int[] titleDimensions = getTitleDimenions();
int[] titleDimensions = getTitleDimensions();
topBorder = (int) (density * BORDER + titleDimensions[0] * (titleDimensions[1] + spacer));
Rect xAxisLabelRect = getRect(axisPaint, getXAxisLabel());
// border + x axis marker + spacer + .5 x axis label
@@ -1,33 +0,0 @@
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks;
/**
* Common constants.
*
* @author Leif Hendrik Wilden
*/
public class Constants {
/**
* Maximum number of waypoints that will be loaded at one time.
*/
public static final int MAX_LOADED_WAYPOINTS_POINTS = 10000;
private Constants() {
}
}
@@ -25,13 +25,12 @@ import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.viewpager.widget.ViewPager;
import java.util.ArrayList;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
import de.dennisguse.opentracks.fragments.MarkerDetailFragment;
import de.dennisguse.opentracks.R;
import java.util.ArrayList;
/**
* An activity to display marker detail info.
@@ -41,6 +40,7 @@ import java.util.ArrayList;
public class MarkerDetailActivity extends AbstractActivity implements DeleteMarkerCaller {
public static final String EXTRA_MARKER_ID = "marker_id";
private static final String TAG = MarkerDetailActivity.class.getSimpleName();
private ArrayList<Long> markerIds;
@@ -108,7 +108,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
*/
private class MarkerDetailPagerAdapter extends FragmentStatePagerAdapter {
public MarkerDetailPagerAdapter(FragmentManager fragmentManager) {
MarkerDetailPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
@@ -41,6 +41,7 @@ public class MarkerEditActivity extends AbstractActivity {
public static final String EXTRA_TRACK_ID = "track_id";
public static final String EXTRA_MARKER_ID = "marker_id";
private static final String TAG = MarkerEditActivity.class.getSimpleName();
private long trackId;
private long markerId;
@@ -127,8 +128,7 @@ public class MarkerEditActivity extends AbstractActivity {
if (newMarker) {
statisticsSection.setVisibility(View.GONE);
waypointSection.setVisibility(View.VISIBLE);
int nextWaypointNumber = trackId == -1L ? -1
: ContentProviderUtils.Factory.get(this).getNextWaypointNumber(trackId, WaypointType.WAYPOINT);
int nextWaypointNumber = trackId == -1L ? -1 : ContentProviderUtils.Factory.get(this).getNextWaypointNumber(trackId, WaypointType.WAYPOINT);
if (nextWaypointNumber == -1) {
nextWaypointNumber = 0;
}
@@ -65,8 +65,11 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
WaypointsColumns.NAME, WaypointsColumns.DESCRIPTION, WaypointsColumns.CATEGORY,
WaypointsColumns.TYPE, WaypointsColumns.TIME, WaypointsColumns.PHOTOURL,
WaypointsColumns.LATITUDE, WaypointsColumns.LONGITUDE};
private ContentProviderUtils contentProviderUtils;
private SharedPreferences sharedPreferences;
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
/*
@@ -87,16 +87,25 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
private static final String PHOTO_URL_FIELD = "photoUrl";
private static final String MARKER_LATITUDE_FIELD = "latitude";
private static final String MARKER_LONGITUDE_FIELD = "longitude";
private static final String TRACK_ID_FIELD = "trackId";
private static final String MARKER_ID_FIELD = "markerId";
private ContentProviderUtils contentProviderUtils;
private SharedPreferences sharedPreferences;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private SearchEngine searchEngine;
private ArrayAdapter<Map<String, Object>> arrayAdapter;
private boolean metricUnits = true;
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
// Callback when an item is selected in the contextual action mode
private ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
@Override
@@ -132,9 +141,8 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
return handleContextItem(itemId, positions);
}
};
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
private final OnSharedPreferenceChangeListener
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.stats_units_key))) {
@@ -171,12 +179,11 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
@NonNull
@Override
public View getView(int position, View convertView, @NonNull android.view.ViewGroup parent) {
View view;
View view = convertView;
if (convertView == null) {
view = getLayoutInflater().inflate(R.layout.list_item, parent, false);
} else {
view = convertView;
}
Map<String, Object> resultMap = getItem(position);
boolean isRecording = (boolean) resultMap.get(IS_RECORDING_FIELD);
boolean isPaused = (boolean) resultMap.get(IS_PAUSED_FIELD);
@@ -207,13 +214,11 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
Map<String, Object> item = arrayAdapter.getItem(position);
Long trackId = (Long) item.get(TRACK_ID_FIELD);
Long markerId = (Long) item.get(MARKER_ID_FIELD);
Intent intent;
Intent intent = IntentUtils.newIntent(SearchListActivity.this, TrackDetailActivity.class);
if (markerId != null) {
intent = IntentUtils.newIntent(SearchListActivity.this, TrackDetailActivity.class)
.putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerId);
intent = intent.putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerId);
} else {
intent = IntentUtils.newIntent(SearchListActivity.this, TrackDetailActivity.class)
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
intent = intent.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
}
startActivity(intent);
}
@@ -391,13 +396,11 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
//TODO Replace use of map<string, object>, but rather provide Track or Waypoint directly.
ArrayList<Map<String, Object>> output = new ArrayList<>(scoredResults.size());
for (ScoredResult result : scoredResults) {
Map<String, Object> resultMap = new HashMap<>();
if (result.track != null) {
prepareTrackForDisplay(result.track, resultMap);
output.add(prepareTrackForDisplay(result.track));
} else {
prepareMarkerForDisplay(result.waypoint, resultMap);
output.add(prepareMarkerForDisplay(result.waypoint));
}
output.add(resultMap);
}
return output;
}
@@ -405,11 +408,12 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
/**
* Prepares a marker for display by filling in a result map.
*
* @param waypoint the marker
* @param resultMap the result map
* @param waypoint the marker
* @return the result map
*/
@Deprecated
private void prepareMarkerForDisplay(Waypoint waypoint, Map<String, Object> resultMap) {
private Map<String, Object> prepareMarkerForDisplay(Waypoint waypoint) {
Map<String, Object> resultMap = new HashMap<>();
//TODO: It may be more appropriate to obtain the track name as a join in the retrieval phase of the search.
String trackName = null;
long trackId = waypoint.getTrackId();
@@ -440,17 +444,20 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
resultMap.put(MARKER_LATITUDE_FIELD, waypoint.getLocation().getLatitude());
resultMap.put(MARKER_LONGITUDE_FIELD, waypoint.getLocation().getLongitude());
return resultMap;
}
/**
* Prepares a track for display by filling in a result map.
*
* @param track the track
* @param resultMap the result map
* @param track the track
* @return the result map
*/
@Deprecated
private void prepareTrackForDisplay(Track track, Map<String, Object> resultMap) {
TripStatistics tripStatitics = track.getTripStatistics();
private Map<String, Object> prepareTrackForDisplay(Track track) {
Map<String, Object> resultMap = new HashMap<>();
TripStatistics tripStatistics = track.getTripStatistics();
String icon = track.getIcon();
String category = icon != null && !icon.equals("") ? null : track.getCategory();
@@ -459,15 +466,17 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
resultMap.put(ICON_ID_FIELD, TrackIconUtils.getIconDrawable(icon));
resultMap.put(ICON_CONTENT_DESCRIPTION_ID_FIELD, R.string.image_track);
resultMap.put(NAME_FIELD, track.getName());
resultMap.put(TOTAL_TIME_FIELD, StringUtils.formatElapsedTime(tripStatitics.getTotalTime()));
resultMap.put(TOTAL_DISTANCE_FIELD, StringUtils.formatDistance(this, tripStatitics.getTotalDistance(), metricUnits));
resultMap.put(TOTAL_TIME_FIELD, StringUtils.formatElapsedTime(tripStatistics.getTotalTime()));
resultMap.put(TOTAL_DISTANCE_FIELD, StringUtils.formatDistance(this, tripStatistics.getTotalDistance(), metricUnits));
resultMap.put(MARKER_COUNT_FIELD, contentProviderUtils.getWaypointCount(track.getId()));
resultMap.put(START_TIME_FIELD, tripStatitics.getStartTime());
resultMap.put(START_TIME_FIELD, tripStatistics.getStartTime());
resultMap.put(CATEGORY_FIELD, category);
resultMap.put(DESCRIPTION_FIELD, track.getDescription());
resultMap.put(PHOTO_URL_FIELD, null);
resultMap.put(TRACK_ID_FIELD, track.getId());
resultMap.put(MARKER_ID_FIELD, null);
return resultMap;
}
@Override
@@ -6,6 +6,12 @@ import android.util.Log;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* Code that is executed when the application starts.
* <p>
* NOTE: How often actual application startup happens depends on the OS.
* Not every start of an activity will trigger this.
*/
public class Startup extends Application {
private final static String TAG = Startup.class.getSimpleName();
@@ -97,9 +97,8 @@ public class TabsAdapter extends FragmentPagerAdapter implements TabHost.OnTabCh
@Override
public void onPageSelected(int position) {
/*
* Unfortunately when TabHost changes the current tab, it kindly also takes
* care of putting focus on it when not in touch mode. The jerk. This hack
* tries to prevent this from pulling focus out of our ViewPager.
* Unfortunately when TabHost changes the current tab, it kindly also takes care of putting focus on it when not in touch mode.
* The jerk. This hack tries to prevent this from pulling focus out of our ViewPager.
*/
TabWidget tabWidget = tabHost.getTabWidget();
int oldFocusability = tabWidget.getDescendantFocusability();
@@ -122,15 +121,14 @@ public class TabsAdapter extends FragmentPagerAdapter implements TabHost.OnTabCh
private final Class<?> clss;
private final Bundle bundle;
public TabInfo(Class<?> clss, Bundle bunlde) {
TabInfo(Class<?> clss, Bundle bundle) {
this.clss = clss;
this.bundle = bunlde;
this.bundle = bundle;
}
}
/**
* A dummy {@link TabContentFactory} that creates an empty view to satisfy the
* {@link TabHost} API.
* A dummy {@link TabContentFactory} that creates an empty view to satisfy the {@link TabHost} API.
*
* @author Jimmy Shih
*/
@@ -138,7 +136,7 @@ public class TabsAdapter extends FragmentPagerAdapter implements TabHost.OnTabCh
private final Context context;
public DummyTabFactory(Context context) {
DummyTabFactory(Context context) {
this.context = context;
}
@@ -27,7 +27,6 @@ import de.dennisguse.opentracks.services.ITrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.UnitConversions;
import de.dennisguse.opentracks.R;
/**
* Track controller for record, pause, resume, and stop.
@@ -67,7 +66,7 @@ public class TrackController {
}
};
public TrackController(Activity activity, TrackRecordingServiceConnection trackRecordingServiceConnection, boolean alwaysShow, OnClickListener recordListener, OnClickListener stopListener) {
TrackController(Activity activity, TrackRecordingServiceConnection trackRecordingServiceConnection, boolean alwaysShow, OnClickListener recordListener, OnClickListener stopListener) {
this.activity = activity;
this.trackRecordingServiceConnection = trackRecordingServiceConnection;
this.alwaysShow = alwaysShow;
@@ -121,21 +120,21 @@ public class TrackController {
}
}
public void onResume(boolean recording, boolean paused) {
void onResume(boolean recording, boolean paused) {
isResumed = true;
update(recording, paused);
}
public void onPause() {
void onPause() {
isResumed = false;
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
}
public void hide() {
void hide() {
containerView.setVisibility(View.GONE);
}
public void show() {
void show() {
containerView.setVisibility(View.VISIBLE);
}
@@ -71,7 +71,9 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
public static final String EXTRA_TRACK_ID = "track_id";
public static final String EXTRA_MARKER_ID = "marker_id";
private static final String TAG = TrackDetailActivity.class.getSimpleName();
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
private static final String PHOTO_URI_KEY = "photo_uri_key";
private static final String HAS_PHOTO_KEY = "has_photo_key";
@@ -101,8 +103,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
private final Runnable bindChangedCallback = new Runnable() {
@Override
public void run() {
// After binding changes (is available), update the total time in
// trackController.
// After binding changes (is available), update the total time in trackController.
runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -153,12 +154,14 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
});
}
};
private MenuItem insertMarkerMenuItem;
private MenuItem insertPhotoMenuItem;
private MenuItem markerListMenuItem;
private MenuItem shareMenuItem;
private MenuItem voiceFrequencyMenuItem;
private MenuItem splitFrequencyMenuItem;
private final OnClickListener recordListener = new OnClickListener() {
@Override
public void onClick(View v) {
@@ -37,8 +37,7 @@ import de.dennisguse.opentracks.util.TrackRecordingServiceConnectionUtils;
import de.dennisguse.opentracks.util.TrackUtils;
/**
* An activity that let's the user see and edit the user editable track meta
* data such as track name, activity type, and track description.
* An activity that let's the user see and edit the user editable track meta data such as track name, activity type, and track description.
*
* @author Leif Hendrik Wilden
*/
@@ -48,6 +47,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
public static final String EXTRA_NEW_TRACK = "new_track";
private static final String TAG = TrackEditActivity.class.getSimpleName();
private static final String ICON_VALUE_KEY = "icon_value_key";
private TrackRecordingServiceConnection trackRecordingServiceConnection;
@@ -55,7 +55,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
private Track track;
private String iconValue;
private EditText name;
private EditText nameEditText;
private AutoCompleteTextView activityType;
private Spinner activityTypeIcon;
private EditText description;
@@ -80,14 +80,13 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
return;
}
name = findViewById(R.id.track_edit_name);
name.setText(track.getName());
nameEditText = findViewById(R.id.track_edit_name);
nameEditText.setText(track.getName());
activityType = findViewById(R.id.track_edit_activity_type);
activityType.setText(track.getCategory());
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
activityType.setAdapter(adapter);
activityType.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
@@ -139,11 +138,11 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
description = findViewById(R.id.track_edit_description);
description.setText(track.getDescription());
Button save = findViewById(R.id.track_edit_save);
save.setOnClickListener(new View.OnClickListener() {
Button saveButton = findViewById(R.id.track_edit_save);
saveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TrackUtils.updateTrack(TrackEditActivity.this, track, name.getText().toString(),
TrackUtils.updateTrack(TrackEditActivity.this, track, nameEditText.getText().toString(),
activityType.getText().toString(), description.getText().toString(),
contentProviderUtils);
finish();
@@ -76,9 +76,11 @@ import de.dennisguse.opentracks.util.TrackUtils;
public class TrackListActivity extends AbstractListActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller {
private static final String TAG = TrackListActivity.class.getSimpleName();
private static final String[] PROJECTION = new String[]{TracksColumns._ID, TracksColumns.NAME,
TracksColumns.DESCRIPTION, TracksColumns.CATEGORY, TracksColumns.STARTTIME,
TracksColumns.TOTALDISTANCE, TracksColumns.TOTALTIME, TracksColumns.ICON};
// The following are set in onCreate
private ContentProviderUtils contentProviderUtils;
private SharedPreferences sharedPreferences;
@@ -86,6 +88,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
private TrackController trackController;
private ListView listView;
private ResourceCursorAdapter resourceCursorAdapter;
private final LoaderCallbacks<Cursor> loaderCallbacks = new LoaderCallbacks<Cursor>() {
@Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
@@ -103,9 +106,11 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
resourceCursorAdapter.swapCursor(null);
}
};
// Preferences
private boolean metricUnits = true;
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
// Callback when an item is selected in the contextual action mode
private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
@@ -126,7 +131,9 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
return handleContextItem(itemId, ids);
}
};
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
/*
* Note that sharedPreferenceChangeListener cannot be an anonymous inner class.
* Anonymous inner class will get garbage collected.
@@ -165,11 +172,13 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
}
}
};
// Menu items
private MenuItem searchMenuItem;
private MenuItem startGpsMenuItem;
private MenuItem aggregatedStatisticsMenuItem;
private MenuItem deleteAllMenuItem;
private final OnClickListener stopListener = new OnClickListener() {
@Override
public void onClick(View v) {
@@ -178,15 +187,17 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
TrackListActivity.this, trackRecordingServiceConnection, true);
}
};
private boolean startGps = false; // true to start gps
private boolean startNewRecording = false; // true to start a new recording
// Callback when the trackRecordingServiceConnection binding changes.
private final Runnable bindChangedCallback = new Runnable() {
@Override
public void run() {
/*
* After binding changes (e.g., becomes available), update the total time
* in trackController.
* After binding changes (e.g., becomes available), update the total time in trackController.
*/
runOnUiThread(new Runnable() {
@Override
@@ -212,8 +223,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
Intent intent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class)
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
startActivity(intent);
Toast.makeText(
TrackListActivity.this, R.string.track_list_record_success, Toast.LENGTH_SHORT)
Toast.makeText(TrackListActivity.this, R.string.track_list_record_success, Toast.LENGTH_SHORT)
.show();
}
if (startGps) {
@@ -222,6 +232,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
}
}
};
private final OnClickListener recordListener = new OnClickListener() {
public void onClick(View v) {
if (recordingTrackId == PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
@@ -255,14 +266,17 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
trackController = new TrackController(this, trackRecordingServiceConnection, true, recordListener, stopListener);
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
// Show trackController when search dialog is dismissed
SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE);
searchManager.setOnDismissListener(new SearchManager.OnDismissListener() {
@Override
public void onDismiss() {
trackController.show();
}
});
if (searchManager != null) {
searchManager.setOnDismissListener(new SearchManager.OnDismissListener() {
@Override
public void onDismiss() {
trackController.show();
}
});
}
listView = findViewById(R.id.track_list);
listView.setEmptyView(findViewById(R.id.track_list_empty_view));
@@ -274,6 +288,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
startActivity(newIntent);
}
});
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
@Override
public void bindView(View view, Context context, Cursor cursor) {
@@ -395,7 +410,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
switch (item.getItemId()) {
case R.id.track_list_start_gps:
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
if (locationManager != null && !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(intent);
} else {
@@ -8,6 +8,9 @@ import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* Wraps a {@link ContentResolver} and provides access as {@link IContentResolver}.
*/
public class ContentResolverWrapper implements IContentResolver {
private ContentResolver contentResolver;
@@ -35,6 +35,11 @@ import de.dennisguse.opentracks.content.Waypoint.WaypointType;
*/
public interface ContentProviderUtils {
/**
* Maximum number of waypoints that will be loaded at one time.
*/
int MAX_LOADED_WAYPOINTS_POINTS = 10000;
/**
* The authority (the first part of the URI) for the app's content provider.
*/
@@ -56,7 +61,8 @@ public interface ContentProviderUtils {
};
/**
* Clears a track. Removes waypoints and trackpoints. Only keeps the track id.
* Clears a track: removes waypoints and trackpoints.
* Only keeps the track id.
*
* @param trackId the track id
*/
@@ -82,20 +88,19 @@ public interface ContentProviderUtils {
void deleteTrack(Context context, long trackId);
/**
* Gets all the tracks. If no track exists, an empty list is returned.
* <p>
* Note that the returned tracks do not have any track points attached.
* Gets all the tracks.
* If no track exists, an empty list is returned.
* NOTE: the returned tracks do not have any track points attached.
*/
List<Track> getAllTracks();
/**
* Gets the last track. Returns null if doesn't exist.
* Gets the last track or null.
*/
Track getLastTrack();
/**
* Gets a track by a track id. Returns null if not found.
* <p>
* Gets a track by a track id or null
* Note that the returned track doesn't have any track points attached.
*
* @param trackId the track id.
@@ -103,8 +108,8 @@ public interface ContentProviderUtils {
Track getTrack(long trackId);
/**
* Gets a track cursor. The caller owns the returned cursor and is responsible
* for closing it.
* Gets a track cursor.
* The caller owns the returned cursor and is responsible for closing it.
*
* @param selection the selection. Can be null
* @param selectionArgs the selection arguments. Can be null
@@ -114,8 +119,7 @@ public interface ContentProviderUtils {
/**
* Inserts a track.
* <p>
* Note: This doesn't insert any track points.
* NOTE: This doesn't insert any track points.
*
* @param track the track
* @return the content provider URI of the inserted track.
@@ -124,8 +128,7 @@ public interface ContentProviderUtils {
/**
* Updates a track.
* <p>
* Note: This doesn't update any track points.
* NOTE: This doesn't update any track points.
*
* @param track the track
*/
@@ -139,19 +142,19 @@ public interface ContentProviderUtils {
Waypoint createWaypoint(Cursor cursor);
/**
* Deletes a waypoint. If deleting a statistics waypoint, this will also
* correct the next statistics waypoint after the deleted one to reflect the
* deletion. The generator is used to update the next statistics waypoint.
* Deletes a waypoint.
* If deleting a statistics waypoint, this will also correct the next statistics waypoint after the deleted one to reflect the deletion.
* The generator is used to update the next statistics waypoint.
*
* @param waypointId the waypoint id
* @param descriptionGenerator the description generator. Can be null for
* waypoint marker
* @param descriptionGenerator the description generator. Can be null for waypoint marker
*/
void deleteWaypoint(Context context, long waypointId, DescriptionGenerator descriptionGenerator);
/**
* Gets the first waypoint id for a track. The first waypoint is special as it
* contains the stats for the track. Returns -1L if it doesn't exist.
* Gets the first waypoint id for a track.
* The first waypoint is special as it contains the stats for the track.
* Returns -1L if it doesn't exist.
*
* @param trackId the track id
*/
@@ -166,8 +169,8 @@ public interface ContentProviderUtils {
Waypoint getLastWaypoint(long trackId, WaypointType waypointType);
/**
* Gets the next waypoint number for a type. Returns -1 if not able to get the
* next waypoint number.
* Gets the next waypoint number for a type.
* Returns -1 if not able to get the next waypoint number.
*
* @param trackId the track id
* @param waypointType the waypoint type
@@ -175,15 +178,16 @@ public interface ContentProviderUtils {
int getNextWaypointNumber(long trackId, WaypointType waypointType);
/**
* Gets a waypoint from a waypoint id. Returns null if not found.
* Gets a waypoint from a waypoint id.
* Returns null if not found.
*
* @param waypointId the waypoint id
*/
Waypoint getWaypoint(long waypointId);
/**
* Gets a waypoint cursor. The caller owns the returned cursor and is
* responsible for closing it.
* Gets a waypoint cursor.
* he caller owns the returned cursor and is responsible for closing it.
*
* @param selection the selection. Can be null
* @param selectionArgs the selection arguments. Can be null
@@ -194,13 +198,12 @@ public interface ContentProviderUtils {
Cursor getWaypointCursor(String selection, String[] selectionArgs, String sortOrder, int maxWaypoints);
/**
* Gets a waypoint cursor for a track. The caller owns the returned cursor and
* is responsible for closing it.
* Gets a waypoint cursor for a track.
* The caller owns the returned cursor and is responsible for closing it.
*
* @param trackId the track id
* @param minWaypointId the minimum waypoint id. -1L to ignore
* @param maxWaypoints the maximum number of waypoints to return. -1 for no
* limit
* @param maxWaypoints the maximum number of waypoints to return. -1 for no limit
*/
Cursor getWaypointCursor(long trackId, long minWaypointId, int maxWaypoints);
@@ -220,7 +223,8 @@ public interface ContentProviderUtils {
Uri insertWaypoint(Waypoint waypoint);
/**
* Updates a waypoint. Returns true if successful.
* Updates a waypoint.
* Returns true if successful.
*
* @param waypoint the waypoint
*/
@@ -245,14 +249,16 @@ public interface ContentProviderUtils {
Location createTrackPoint(Cursor cursor);
/**
* Gets the first location id for a track. Returns -1L if it doesn't exist.
* Gets the first location id for a track.
* Returns -1L if it doesn't exist.
*
* @param trackId the track id
*/
long getFirstTrackPointId(long trackId);
/**
* Gets the last location id for a track. Returns -1L if it doesn't exist.
* Gets the last location id for a track.
* Returns -1L if it doesn't exist.
*
* @param trackId the track id
*/
@@ -268,28 +274,23 @@ public interface ContentProviderUtils {
long getTrackPointId(long trackId, Location location);
/**
* Gets the first valid location for a track. Returns null if it doesn't
* exist.
* Gets the first valid location for a track.
* Returns null if it doesn't exist.
*
* @param trackId the track id
*/
Location getFirstValidTrackPoint(long trackId);
/**
* Gets the last valid location for a track. Returns null if it doesn't exist.
* Gets the last valid location for a track.
* Returns null if it doesn't exist.
*
* @param trackId the track id
*/
Location getLastValidTrackPoint(long trackId);
/**
* Gets the last valid location.
*/
Location getLastValidTrackPoint();
/**
* Creates a location cursor. The caller owns the returned cursor and is
* responsible for closing it.
* Creates a location cursor. The caller owns the returned cursor and is responsible for closing it.
*
* @param trackId the track id
* @param startTrackPointId the starting track point id. -1L to ignore
@@ -299,20 +300,15 @@ public interface ContentProviderUtils {
Cursor getTrackPointCursor(long trackId, long startTrackPointId, int maxLocations, boolean descending);
/**
* Creates a new read-only iterator over a given track's points. It provides a
* lightweight way of iterating over long tracks without failing due to the
* underlying cursor limitations. Since it's a read-only iterator,
* {@link Iterator#remove()} always throws
* {@link UnsupportedOperationException}. Each call to
* {@link LocationIterator#next()} may advance to the next DB record, and if
* so, the iterator calls {@link LocationFactory#createLocation()} and
* populates it with information retrieved from the record. When done with
* iteration, {@link LocationIterator#close()} must be called.
* Creates a new read-only iterator over a given track's points.
* It provides a lightweight way of iterating over long tracks without failing due to the underlying cursor limitations.
* Since it's a read-only iterator, {@link Iterator#remove()} always throws {@link UnsupportedOperationException}.
* Each call to {@link LocationIterator#next()} may advance to the next DB record, and if so, the iterator calls {@link LocationFactory#createLocation()} and populates it with information retrieved from the record.
* When done with iteration, {@link LocationIterator#close()} must be called.
*
* @param trackId the track id
* @param startTrackPointId the starting track point id. -1L to ignore
* @param descending true to sort the result in descending order (latest
* location first)
* @param descending true to sort the result in descending order (latest location first)
* @param locationFactory the location factory
*/
LocationIterator getTrackPointLocationIterator(long trackId, long startTrackPointId, boolean descending, LocationFactory locationFactory);
@@ -327,10 +323,9 @@ public interface ContentProviderUtils {
Uri insertTrackPoint(Location location, long trackId);
/**
* A lightweight wrapper around the original {@link Cursor} with a method to
* clean up.
* A lightweight wrapper around the original {@link Cursor} with a method to clean up.
*/
interface LocationIterator extends Iterator<Location> {
interface LocationIterator extends Iterator<Location>, AutoCloseable {
/**
* Gets the most recently retrieved track point id by {@link #next()}.
@@ -349,8 +344,8 @@ public interface ContentProviderUtils {
interface LocationFactory {
/**
* Creates a new {@link Location}. An implementation can create new
* instances or reuse existing instances for optimization.
* Creates a new {@link Location}.
* An implementation can create new instances or reuse existing instances for optimization.
*/
Location createLocation();
}
@@ -358,6 +353,8 @@ public interface ContentProviderUtils {
/**
* A factory which can produce instances of {@link ContentProviderUtils}, and can be overridden for testing.
*/
//TODO Is this still used? From a quick glance, it doesn't looks like it; probably a left over from testing.
@Deprecated
class Factory {
private static Factory instance = new Factory();
@@ -379,8 +376,8 @@ public interface ContentProviderUtils {
}
/**
* Overrides the factory instance for testing. Don't forget to set it back
* to the original value after testing.
* Overrides the factory instance for testing.
* Don't forget to set it back to the original value after testing.
*
* @param factory the factory
*/
@@ -394,7 +391,7 @@ public interface ContentProviderUtils {
*
* @param context the context
*/
protected ContentProviderUtils newForContext(Context context) {
private ContentProviderUtils newForContext(Context context) {
return new ContentProviderUtilsImpl(context.getContentResolver());
}
}
@@ -842,14 +842,6 @@ public class ContentProviderUtilsImpl implements ContentProviderUtils {
return findTrackPointBy(selection, selectionArgs);
}
@Override
public Location getLastValidTrackPoint() {
String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID + ") from "
+ TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.LATITUDE + "<="
+ MAX_LATITUDE + ")";
return findTrackPointBy(selection, null);
}
@Override
public Cursor getTrackPointCursor(long trackId, long startTrackPointId, int maxLocations, boolean descending) {
if (trackId < 0) {
@@ -35,8 +35,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
/**
* A {@link ContentProvider} that handles access to track points, tracks, and
* waypoints tables.
* A {@link ContentProvider} that handles access to track points, tracks, and waypoints tables.
*
* @author Leif Hendrik Wilden
*/
@@ -44,10 +43,14 @@ public abstract class CustomContentProvider extends ContentProvider {
@VisibleForTesting
static final int DATABASE_VERSION = 23;
@VisibleForTesting
static final String DATABASE_NAME = "database.db";
private static final String TAG = CustomContentProvider.class.getSimpleName();
private final UriMatcher uriMatcher;
private SQLiteDatabase db;
public CustomContentProvider() {
@@ -30,12 +30,12 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
*
* @author Rodrigo Damazio
*/
public class DataSource {
class DataSource {
private final ContentResolver contentResolver;
private final SharedPreferences sharedPreferences;
public DataSource(Context context) {
DataSource(Context context) {
contentResolver = context.getContentResolver();
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
}
@@ -46,7 +46,7 @@ public class DataSource {
* @param uri the uri
* @param observer the observer
*/
public void registerContentObserver(Uri uri, ContentObserver observer) {
void registerContentObserver(Uri uri, ContentObserver observer) {
contentResolver.registerContentObserver(uri, false, observer);
}
@@ -55,7 +55,7 @@ public class DataSource {
*
* @param observer the observer
*/
public void unregisterContentObserver(ContentObserver observer) {
void unregisterContentObserver(ContentObserver observer) {
contentResolver.unregisterContentObserver(observer);
}
@@ -64,7 +64,7 @@ public class DataSource {
*
* @param listener the listener
*/
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
sharedPreferences.registerOnSharedPreferenceChangeListener(listener);
}
@@ -73,8 +73,7 @@ public class DataSource {
*
* @param listener the listener
*/
public void unregisterOnSharedPreferenceChangeListener(
OnSharedPreferenceChangeListener listener) {
void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener);
}
}
@@ -26,17 +26,19 @@ import java.util.EnumSet;
import java.util.Set;
/**
* Data source manager. Creates observers/listeners and manages their
* registration with {@link DataSource}. The observers/listeners calls
* {@link DataSourceListener} when data changes.
* Creates observers/listeners and manages their registration with {@link DataSource}.
* The observers/listeners calls {@link DataSourceListener} when data changes.
*
* @author Rodrigo Damazio
*/
public class DataSourceManager {
class DataSourceManager {
private static final String TAG = DataSourceManager.class.getSimpleName();
private final DataSource dataSource;
private final DataSourceListener dataSourceListener;
// Registered listeners
private final Set<TrackDataType> registeredListeners = EnumSet.noneOf(TrackDataType.class);
private final Handler handler;
@@ -45,7 +47,7 @@ public class DataSourceManager {
private final TrackPointsTableObserver trackPointsTableObserver;
private final PreferenceListener preferenceListener;
public DataSourceManager(DataSource dataSource, DataSourceListener dataSourceListener) {
DataSourceManager(DataSource dataSource, DataSourceListener dataSourceListener) {
this.dataSource = dataSource;
this.dataSourceListener = dataSourceListener;
@@ -61,7 +63,7 @@ public class DataSourceManager {
*
* @param listeners the listeners
*/
public void updateListeners(EnumSet<TrackDataType> listeners) {
void updateListeners(EnumSet<TrackDataType> listeners) {
EnumSet<TrackDataType> neededListeners = EnumSet.copyOf(listeners);
/*
@@ -156,7 +158,7 @@ public class DataSourceManager {
/**
* Unregisters all listeners with data source.
*/
public void unregisterAllListeners() {
void unregisterAllListeners() {
for (TrackDataType trackDataType : TrackDataType.values()) {
unregisterListener(trackDataType);
}
@@ -169,7 +171,7 @@ public class DataSourceManager {
*/
private class TracksTableObserver extends ContentObserver {
public TracksTableObserver() {
TracksTableObserver() {
super(handler);
}
@@ -186,7 +188,7 @@ public class DataSourceManager {
*/
private class WaypointsTableObserver extends ContentObserver {
public WaypointsTableObserver() {
WaypointsTableObserver() {
super(handler);
}
@@ -203,7 +205,7 @@ public class DataSourceManager {
*/
private class TrackPointsTableObserver extends ContentObserver {
public TrackPointsTableObserver() {
TrackPointsTableObserver() {
super(handler);
}
@@ -18,8 +18,7 @@ package de.dennisguse.opentracks.content;
import de.dennisguse.opentracks.stats.TripStatistics;
/**
* An interface for an object that can generate descriptions of track and
* waypoint.
* An interface for an object that can generate descriptions of track and waypoint.
*
* @author Sandor Dornbush
*/
@@ -115,8 +115,7 @@ public class DescriptionGeneratorImpl implements DescriptionGenerator {
writeSpeed(stats.getMaxSpeed(), builder, R.string.description_max_speed, lineBreak);
// Average pace
writePace(
stats.getAverageSpeed(), builder, R.string.description_average_pace_in_minute, lineBreak);
writePace(stats.getAverageSpeed(), builder, R.string.description_average_pace_in_minute, lineBreak);
// Average moving pace
writePace(stats.getAverageMovingSpeed(), builder,
@@ -132,8 +131,7 @@ public class DescriptionGeneratorImpl implements DescriptionGenerator {
writeElevation(stats.getMinElevation(), builder, R.string.description_min_elevation, lineBreak);
// Elevation gain
writeElevation(
stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak);
writeElevation(stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak);
// Max grade
writeGrade(stats.getMaxGrade(), builder, R.string.description_max_grade, lineBreak);
@@ -23,6 +23,7 @@ import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -97,34 +98,35 @@ public class SearchEngine {
/**
* Oldest timestamp for which we rank based on time (2000-01-01 00:00:00.000)
*/
private static final long OLDEST_ALLOWED_TIMESTAMP = 946692000000L;
public static final long OLDEST_ALLOWED_TIMESTAMP = 946692000000L;
/**
* Comparador for scored results.
*/
private static final Comparator<ScoredResult> SCORED_RESULT_COMPARATOR =
new Comparator<ScoredResult>() {
@Override
public int compare(ScoredResult r1, ScoredResult r2) {
// Score ordering.
int scoreDiff = Double.compare(r2.score, r1.score);
if (scoreDiff != 0) {
return scoreDiff;
}
private static final Comparator<ScoredResult> SCORED_RESULT_COMPARATOR = new Comparator<ScoredResult>() {
@Override
public int compare(ScoredResult r1, ScoredResult r2) {
// Score ordering.
int scoreDiff = Double.compare(r2.score, r1.score);
if (scoreDiff != 0) {
return scoreDiff;
}
// Make tracks come before waypoints.
if (r1.waypoint != null && r2.track != null) {
return 1;
} else if (r1.track != null && r2.waypoint != null) {
return -1;
}
// Make tracks come before waypoints.
if (r1.waypoint != null && r2.track != null) {
return 1;
} else if (r1.track != null && r2.waypoint != null) {
return -1;
}
// Finally, use arbitrary ordering, by ID.
long id1 = r1.track != null ? r1.track.getId() : r1.waypoint.getId();
long id2 = r2.track != null ? r2.track.getId() : r2.waypoint.getId();
long idDiff = id2 - id1;
return Long.signum(idDiff);
}
};
// Finally, use arbitrary ordering, by ID.
long id1 = r1.track != null ? r1.track.getId() : r1.waypoint.getId();
long id2 = r2.track != null ? r2.track.getId() : r2.waypoint.getId();
long idDiff = id2 - id1;
return Long.signum(idDiff);
}
};
private final ContentProviderUtils providerUtils;
public SearchEngine(ContentProviderUtils providerUtils) {
@@ -134,6 +136,7 @@ public class SearchEngine {
/**
* Squashes a number by calculating 1 / log (1 + x).
*/
//TODO Why is this done?
private static double squash(double x) {
return 1.0 / Math.log1p(x);
}
@@ -145,15 +148,10 @@ public class SearchEngine {
* @return a set of results, sorted according to their score
*/
public SortedSet<ScoredResult> search(SearchQuery query) {
ArrayList<Track> tracks = new ArrayList<>();
ArrayList<Waypoint> waypoints = new ArrayList<>();
TreeSet<ScoredResult> scoredResults = new TreeSet<>(SCORED_RESULT_COMPARATOR);
retrieveTracks(query, tracks);
retrieveWaypoints(query, waypoints);
scoreTrackResults(tracks, query, scoredResults);
scoreWaypointResults(waypoints, query, scoredResults);
scoreTrackResults(retrieveTracks(query), query, scoredResults);
scoreWaypointResults(retrieveWaypoints(query), query, scoredResults);
return scoredResults;
}
@@ -161,18 +159,19 @@ public class SearchEngine {
/**
* Retrieves tracks matching the given query from the database.
*
* @param query the query to retrieve for
* @param tracks list to fill with the resulting tracks
* @param query the query to retrieve for
*/
private void retrieveTracks(SearchQuery query, ArrayList<Track> tracks) {
private List<Track> retrieveTracks(SearchQuery query) {
ArrayList<Track> tracks = new ArrayList<>();
String queryLikeSelection = "%" + query.textQuery + "%";
String[] trackSelectionArgs = new String[]{
queryLikeSelection,
queryLikeSelection,
queryLikeSelection};
queryLikeSelection
};
try (Cursor cursor = providerUtils.getTrackCursor(
TRACK_SELECTION_QUERY, trackSelectionArgs, TRACK_SELECTION_ORDER)) {
try (Cursor cursor = providerUtils.getTrackCursor(TRACK_SELECTION_QUERY, trackSelectionArgs, TRACK_SELECTION_ORDER)) {
if (cursor != null) {
tracks.ensureCapacity(cursor.getCount());
while (cursor.moveToNext()) {
@@ -180,22 +179,26 @@ public class SearchEngine {
}
}
}
return tracks;
}
/**
* Retrieves waypoints matching the given query from the database.
*
* @param query the query to retrieve for
* @param waypoints list to fill with the resulting waypoints
* @param query the query to retrieve for
*/
private void retrieveWaypoints(SearchQuery query, ArrayList<Waypoint> waypoints) {
private List<Waypoint> retrieveWaypoints(SearchQuery query) {
ArrayList<Waypoint> waypoints = new ArrayList<>();
String queryLikeSelection2 = "%" + query.textQuery + "%";
String[] waypointSelectionArgs = new String[]{
queryLikeSelection2,
queryLikeSelection2,
queryLikeSelection2};
try (Cursor cursor = providerUtils.getWaypointCursor(WAYPOINT_SELECTION_QUERY, waypointSelectionArgs,
WAYPOINT_SELECTION_ORDER, MAX_SCORED_WAYPOINTS)) {
queryLikeSelection2
};
try (Cursor cursor = providerUtils.getWaypointCursor(WAYPOINT_SELECTION_QUERY, waypointSelectionArgs, WAYPOINT_SELECTION_ORDER, MAX_SCORED_WAYPOINTS)) {
if (cursor != null) {
waypoints.ensureCapacity(cursor.getCount());
while (cursor.moveToNext()) {
@@ -206,6 +209,8 @@ public class SearchEngine {
}
}
}
return waypoints;
}
/**
@@ -238,7 +243,6 @@ public class SearchEngine {
score *= getTitleBoost(query, track.getName(), track.getDescription(), track.getCategory());
TripStatistics statistics = track.getTripStatistics();
// TODO: Also boost for proximity to the currently-centered position on the map.
score *= getDistanceBoost(query, statistics.getMeanLatitude(), statistics.getMeanLongitude());
long meanTimestamp = (statistics.getStartTime() + statistics.getStopTime()) / 2L;
@@ -281,7 +285,6 @@ public class SearchEngine {
Location location = waypoint.getLocation();
score *= getTitleBoost(query, waypoint.getName(), waypoint.getDescription(), waypoint.getCategory());
// TODO: Also boost for proximity to the currently-centered position on the map.
score *= getDistanceBoost(query, location.getLatitude(), location.getLongitude());
score *= getTimeBoost(query, location.getTime());
@@ -302,8 +305,7 @@ public class SearchEngine {
* @param category the category of the track or waypoint
* @return the total boost to be applied to the result
*/
private double getTitleBoost(SearchQuery query,
String name, String description, String category) {
private double getTitleBoost(SearchQuery query, String name, String description, String category) {
// Title boost: track name > description > category.
double boost = 1.0;
if (name.toLowerCase(Locale.getDefault()).contains(query.textQuery)) {
@@ -332,8 +334,7 @@ public class SearchEngine {
}
// Score recent tracks higher.
long timeAgoHours = (long) ((query.currentTimestamp - timestamp) * UnitConversions.MS_TO_S
* UnitConversions.S_TO_MIN * UnitConversions.MIN_TO_HR);
long timeAgoHours = (long) ((query.currentTimestamp - timestamp) * UnitConversions.MS_TO_S * UnitConversions.S_TO_MIN * UnitConversions.MIN_TO_HR);
if (timeAgoHours > 0L) {
return squash(timeAgoHours);
} else {
@@ -364,6 +365,7 @@ public class SearchEngine {
// Score tracks close to the current location higher.
double distanceKm = distanceResults[0] * UnitConversions.M_TO_KM;
if (distanceKm > 0.0) {
// Use the inverse of the amortized distance.
return squash(distanceKm);
@@ -377,13 +379,12 @@ public class SearchEngine {
* Description of a search query, along with all contextual data needed to execute it.
*/
public static class SearchQuery {
public final String textQuery;
public final Location currentLocation;
public final long currentTrackId;
public final long currentTimestamp;
final String textQuery;
final Location currentLocation;
final long currentTrackId;
final long currentTimestamp;
public SearchQuery(String textQuery, Location currentLocation, long currentTrackId,
long currentTimestamp) {
public SearchQuery(String textQuery, Location currentLocation, long currentTrackId, long currentTimestamp) {
this.textQuery = textQuery.toLowerCase(Locale.getDefault());
this.currentLocation = currentLocation;
this.currentTrackId = currentTrackId;
@@ -397,7 +398,7 @@ public class SearchEngine {
public static class ScoredResult {
public final Track track;
public final Waypoint waypoint;
public final double score;
final double score;
ScoredResult(Track track, double score) {
this.track = track;
@@ -27,9 +27,9 @@ import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
/**
* A content provider that mimics the behavior of {@link androidx.core.content.FileProvider}, which shares virtual (non-existing) KML-files.
* The actual content of the virtual files is generated by using the functionality defined in {@link CustomContentProvider}.
* <p>
*
* Moreover, it manages access to OpenTrack's database via {@link CustomContentProvider}.
* <p>
*
* Explanation:
* Although a request is handled by a {@link android.content.ContentProvider} (with temporarily granted permission), Android's security infrastructure prevents forwarding queries to non-exported {@link android.content.ContentProvider}.
* Thus, if {@link ShareContentProvider} and {@link CustomContentProvider} would be two different instances, the data would not be accessible to external apps.
@@ -20,9 +20,10 @@ import android.location.Location;
import android.os.Parcel;
import android.os.Parcelable;
import de.dennisguse.opentracks.stats.TripStatistics;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.stats.TripStatistics;
/**
* A track.
@@ -43,22 +44,26 @@ public class Track implements Parcelable {
return new Track[size];
}
};
private long id = -1L;
private String name = "";
private String description = "";
private String category = "";
private long startId = -1L;
private long stopId = -1L;
/*
* The number of location points (present even if the points themselves are
* not loaded)
* The number of location points (present even if the points themselves are not loaded)
*/
private int numberOfPoints = 0;
private String icon = "";
@Deprecated //TODO Is this still used or a left over from Google Drive integration?
private long modifiedTime = -1L;
private TripStatistics tripStatistics = new TripStatistics();
// Location points (which may not have been loaded)
private ArrayList<Location> locations = new ArrayList<>();
private List<Location> locations = new ArrayList<>();
public Track() {
}
@@ -190,7 +195,7 @@ public class Track implements Parcelable {
locations.add(location);
}
public ArrayList<Location> getLocations() {
public List<Location> getLocations() {
return locations;
}
@@ -35,24 +35,25 @@ import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* Track data hub. Receives data from {@link DataSource} and distributes it to
* {@link TrackDataListener} after some processing.
* Track data hub. Receives data from {@link DataSource} and distributes it to {@link TrackDataListener} after some processing.
*
* @author Rodrigo Damazio
*/
public class TrackDataHub implements DataSourceListener {
/**
* Target number of track points displayed by the map overlay. We may display
* more than this number of points.
* Target number of track points displayed by the map overlay.
* We may display more than this number of points.
*/
public static final int TARGET_DISPLAYED_TRACK_POINTS = 5000;
static final int TARGET_DISPLAYED_TRACK_POINTS = 5000;
/**
* Maximum number of waypoints to displayed.
*/
@VisibleForTesting
static final int MAX_DISPLAYED_WAYPOINTS = 128;
private static final int MAX_DISPLAYED_WAYPOINTS = 128;
private static final String TAG = TrackDataHub.class.getSimpleName();
private final Context context;
private final TrackDataManager trackDataManager;
private final ContentProviderUtils contentProviderUtils;
@@ -87,8 +88,7 @@ public class TrackDataHub implements DataSourceListener {
* @param targetNumPoints the target number of points
*/
@VisibleForTesting
TrackDataHub(Context context, TrackDataManager trackDataManager,
ContentProviderUtils contentProviderUtils, int targetNumPoints) {
private TrackDataHub(Context context, TrackDataManager trackDataManager, ContentProviderUtils contentProviderUtils, int targetNumPoints) {
this.context = context;
this.trackDataManager = trackDataManager;
this.contentProviderUtils = contentProviderUtils;
@@ -96,17 +96,11 @@ public class TrackDataHub implements DataSourceListener {
resetSamplingState();
}
/**
* Creates a new instance.
*/
public synchronized static TrackDataHub newInstance(Context context) {
return new TrackDataHub(context, new TrackDataManager(), ContentProviderUtils.Factory.get(
context), TARGET_DISPLAYED_TRACK_POINTS);
}
/**
* Starts.
*/
public void start() {
if (started) {
Log.i(TAG, "TrackDataHub already started, ignoring start.");
@@ -131,9 +125,6 @@ public class TrackDataHub implements DataSourceListener {
});
}
/**
* Stops.
*/
public void stop() {
if (!started) {
Log.i(TAG, "TrackDataHub not started, ignoring stop.");
@@ -151,11 +142,6 @@ public class TrackDataHub implements DataSourceListener {
dataSourceManager = null;
}
/**
* Loads a track.
*
* @param trackId the track id
*/
public void loadTrack(final long trackId) {
runInHandlerThread(new Runnable() {
@Override
@@ -222,8 +208,7 @@ public class TrackDataHub implements DataSourceListener {
* Returns true if the selected track is recording.
*/
public boolean isSelectedTrackRecording() {
return selectedTrackId == recordingTrackId
&& recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
return selectedTrackId == recordingTrackId && recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
}
/**
@@ -258,8 +243,7 @@ public class TrackDataHub implements DataSourceListener {
runInHandlerThread(new Runnable() {
@Override
public void run() {
notifyTrackPointsTableUpdate(
true, trackDataManager.getListeners(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE),
notifyTrackPointsTableUpdate(true, trackDataManager.getListeners(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE),
trackDataManager.getListeners(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE));
}
});
@@ -270,47 +254,36 @@ public class TrackDataHub implements DataSourceListener {
runInHandlerThread(new Runnable() {
@Override
public void run() {
if (key == null
|| key.equals(PreferencesUtils.getKey(context, R.string.recording_track_id_key))) {
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_id_key))) {
recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key);
}
if (key == null
|| key.equals(PreferencesUtils.getKey(context, R.string.recording_track_paused_key))) {
recordingTrackPaused = PreferencesUtils.getBoolean(
context, R.string.recording_track_paused_key,
PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_paused_key))) {
recordingTrackPaused = PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
}
if (key == null
|| key.equals(PreferencesUtils.getKey(context, R.string.stats_units_key))) {
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.stats_units_key))) {
metricUnits = PreferencesUtils.isMetricUnits(context);
if (key != null) {
for (TrackDataListener trackDataListener :
trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
if (trackDataListener.onMetricUnitsChanged(metricUnits)) {
loadDataForListener(trackDataListener);
}
}
}
}
if (key == null
|| key.equals(PreferencesUtils.getKey(context, R.string.stats_rate_key))) {
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.stats_rate_key))) {
reportSpeed = PreferencesUtils.isReportSpeed(context);
if (key != null) {
for (TrackDataListener trackDataListener :
trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
if (trackDataListener.onReportSpeedChanged(reportSpeed)) {
loadDataForListener(trackDataListener);
}
}
}
}
if (key == null
|| key.equals(PreferencesUtils.getKey(context, R.string.recording_gps_accuracy_key))) {
recordingGpsAccuracy = PreferencesUtils.getInt(context, R.string.recording_gps_accuracy_key,
PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT);
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_gps_accuracy_key))) {
recordingGpsAccuracy = PreferencesUtils.getInt(context, R.string.recording_gps_accuracy_key, PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT);
if (key != null) {
for (TrackDataListener trackDataListener :
trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
if (trackDataListener.onRecordingGpsAccuracy(recordingGpsAccuracy)) {
loadDataForListener(trackDataListener);
}
@@ -359,7 +332,7 @@ public class TrackDataHub implements DataSourceListener {
}
/**
* Loads data for a listener. To be run in the {@link #handler} thread.
* Loads data for a listener; to be run in the {@link #handler} thread.
*
* @param trackDataListener the track data listener.
*/
@@ -386,8 +359,7 @@ public class TrackDataHub implements DataSourceListener {
if (isOnlyListener) {
resetSamplingState();
}
Set<TrackDataListener> sampledOutListeners = hasSampledOut ? trackDataListeners
: Collections.<TrackDataListener>emptySet();
Set<TrackDataListener> sampledOutListeners = hasSampledOut ? trackDataListeners : Collections.<TrackDataListener>emptySet();
notifyTrackPointsTableUpdate(isOnlyListener, trackDataListeners, sampledOutListeners);
}
@@ -397,7 +369,7 @@ public class TrackDataHub implements DataSourceListener {
}
/**
* Notifies track table update. To be run in the {@link #handler} thread.
* Notifies track table update; to be run in the {@link #handler} thread.
*
* @param trackDataListeners the track data listeners to notify
*/
@@ -412,9 +384,8 @@ public class TrackDataHub implements DataSourceListener {
}
/**
* Notifies waypoint table update. Currently, reloads all the waypoints up to
* {@link #MAX_DISPLAYED_WAYPOINTS}. To be run in the {@link #handler}
* thread.
* Notifies waypoint table update.
* Currently, reloads all the waypoints up to {@link #MAX_DISPLAYED_WAYPOINTS}. To be run in the {@link #handler} thread.
*
* @param trackDataListeners the track data listeners to notify
*/
@@ -448,18 +419,17 @@ public class TrackDataHub implements DataSourceListener {
}
/**
* Notifies track points table update. To be run in the {@link #handler}
* thread.
* Notifies track points table update; to be run in the {@link #handler} thread.
*
* @param updateSamplingState true to update the sampling state
* @param sampledInListeners the sampled-in listeners
* @param sampledOutListeners the sampled-out listeners
*/
private void notifyTrackPointsTableUpdate(boolean updateSamplingState,
Set<TrackDataListener> sampledInListeners, Set<TrackDataListener> sampledOutListeners) {
private void notifyTrackPointsTableUpdate(boolean updateSamplingState, Set<TrackDataListener> sampledInListeners, Set<TrackDataListener> sampledOutListeners) {
if (sampledInListeners.isEmpty() && sampledOutListeners.isEmpty()) {
return;
}
if (updateSamplingState && numLoadedPoints >= targetNumPoints) {
// Reload and resample the track at a lower frequency.
Log.i(TAG, "Resampling track after " + numLoadedPoints + " points.");
@@ -477,11 +447,8 @@ public class TrackDataHub implements DataSourceListener {
long lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId);
int samplingFrequency = -1;
boolean includeNextPoint = false;
LocationIterator locationIterator = null;
try {
locationIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId,
localLastSeenLocationId + 1, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, localLastSeenLocationId + 1, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY)) {
while (locationIterator.hasNext()) {
Location location = locationIterator.next();
@@ -509,8 +476,7 @@ public class TrackDataHub implements DataSourceListener {
}
} else {
// Also include the last point if the selected track is not recording.
if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0)
|| (locationId == lastTrackPointId && !isSelectedTrackRecording())) {
if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0) || (locationId == lastTrackPointId && !isSelectedTrackRecording())) {
includeNextPoint = false;
for (TrackDataListener trackDataListener : sampledInListeners) {
trackDataListener.onSampledInTrackPoint(location);
@@ -525,10 +491,6 @@ public class TrackDataHub implements DataSourceListener {
localNumLoadedPoints++;
localLastSeenLocationId = locationId;
}
} finally {
if (locationIterator != null) {
locationIterator.close();
}
}
if (updateSamplingState) {
@@ -555,7 +517,7 @@ public class TrackDataHub implements DataSourceListener {
* Creates a {@link DataSource}.
*/
@VisibleForTesting
protected DataSource newDataSource() {
private DataSource newDataSource() {
return new DataSource(context);
}
@@ -565,7 +527,7 @@ public class TrackDataHub implements DataSourceListener {
* @param runnable the runnable
*/
@VisibleForTesting
protected void runInHandlerThread(Runnable runnable) {
private void runInHandlerThread(Runnable runnable) {
if (handler == null) {
// Use a Throwable to ensure the stack trace is logged.
Log.d(TAG, "handler is null.", new Throwable());
@@ -57,10 +57,8 @@ public interface TrackDataListener {
void onSegmentSplit(Location location);
/**
* Called when finish sending new track points. This gets called after every
* batch of calls to {@link #onSampledInTrackPoint(Location)},
* {@link #onSampledOutTrackPoint(Location)} and
* {@link #onSegmentSplit(Location)}.
* Called when finish sending new track points.
* This gets called after every batch of calls to {@link #onSampledInTrackPoint(Location)}, {@link #onSampledOutTrackPoint(Location)} and {@link #onSegmentSplit(Location)}.
*/
void onNewTrackPointsDone();
@@ -77,9 +75,8 @@ public interface TrackDataListener {
void onNewWaypoint(Waypoint waypoint);
/**
* Called when finish sending new waypoints. This gets called after every
* batch of calls to {@link #clearWaypoints()} and
* {@link #onNewWaypoint(Waypoint)}.
* Called when finish sending new waypoints.
* This gets called after every batch of calls to {@link #clearWaypoints()} and {@link #onNewWaypoint(Waypoint)}.
*/
void onNewWaypointsDone();
@@ -26,24 +26,19 @@ import java.util.Map;
import java.util.Set;
/**
* Manages register/unregister {@link TrackDataListener} and keeping the state
* for each registered listener.
* Manages register/unregister {@link TrackDataListener} and keeping the state for each registered listener.
*
* @author Rodrigo Damazio
*/
public class TrackDataManager {
class TrackDataManager {
private static final String TAG = TrackDataManager.class.getSimpleName();
// Map of listener to its track data types
private final Map<TrackDataListener, EnumSet<TrackDataType>>
listenerToTypesMap = new HashMap<>();
private final Map<TrackDataListener, EnumSet<TrackDataType>> listenerToTypesMap = new HashMap<>();
// Map of track data type to listeners
private final Map<TrackDataType, Set<TrackDataListener>>
typeToListenersMap = new EnumMap<>(TrackDataType.class);
private final Map<TrackDataType, Set<TrackDataListener>> typeToListenersMap = new EnumMap<>(TrackDataType.class);
public TrackDataManager() {
TrackDataManager() {
for (TrackDataType trackDataType : TrackDataType.values()) {
typeToListenersMap.put(trackDataType, new LinkedHashSet<TrackDataListener>());
}
@@ -55,8 +50,7 @@ public class TrackDataManager {
* @param listener the listener
* @param trackDataTypes the track data types the listener is interested
*/
public void registerListener(
TrackDataListener listener, EnumSet<TrackDataType> trackDataTypes) {
void registerListener(TrackDataListener listener, EnumSet<TrackDataType> trackDataTypes) {
if (listenerToTypesMap.containsKey(listener)) {
Log.w(TAG, "Tried to register a listener that is already registered. Ignore.");
return;
@@ -72,7 +66,7 @@ public class TrackDataManager {
*
* @param listener the listener
*/
public void unregisterListener(TrackDataListener listener) {
void unregisterListener(TrackDataListener listener) {
EnumSet<TrackDataType> removedTypes = listenerToTypesMap.remove(listener);
if (removedTypes == null) {
Log.w(TAG, "Tried to unregister a listener that is not registered. Ignore.");
@@ -88,7 +82,7 @@ public class TrackDataManager {
/**
* Gets the number of {@link TrackDataListener}.
*/
public int getNumberOfListeners() {
int getNumberOfListeners() {
return listenerToTypesMap.size();
}
@@ -97,7 +91,7 @@ public class TrackDataManager {
*
* @param listener the listener
*/
public EnumSet<TrackDataType> getTrackDataTypes(TrackDataListener listener) {
EnumSet<TrackDataType> getTrackDataTypes(TrackDataListener listener) {
return listenerToTypesMap.get(listener);
}
@@ -106,14 +100,14 @@ public class TrackDataManager {
*
* @param type the type
*/
public Set<TrackDataListener> getListeners(TrackDataType type) {
Set<TrackDataListener> getListeners(TrackDataType type) {
return typeToListenersMap.get(type);
}
/**
* Gets all the registered {@link TrackDataType}.
*/
public EnumSet<TrackDataType> getRegisteredTrackDataTypes() {
EnumSet<TrackDataType> getRegisteredTrackDataTypes() {
EnumSet<TrackDataType> types = EnumSet.noneOf(TrackDataType.class);
for (EnumSet<TrackDataType> value : listenerToTypesMap.values()) {
types.addAll(value);
@@ -27,25 +27,9 @@ import android.provider.BaseColumns;
public interface TracksColumns extends BaseColumns {
String TABLE_NAME = "tracks";
/**
* Tracks provider uri.
*/
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/tracks");
/**
* Track content type.
*/
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.track";
/**
* Track id content type.
*/
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.track";
/**
* Tracks table default sort order.
*/
String DEFAULT_SORT_ORDER = "_id";
// Columns
@@ -42,6 +42,7 @@ public final class Waypoint implements Parcelable {
return new Waypoint[size];
}
};
private long id = -1L;
private String name = "";
private String description = "";
@@ -60,9 +61,7 @@ public final class Waypoint implements Parcelable {
public Waypoint() {
}
public Waypoint(String name, String description, String category, String icon, long trackId,
WaypointType type, double length, long duration, long startId, long stopId, Location location,
TripStatistics tripStatistics, String photoUrl) {
public Waypoint(String name, String description, String category, String icon, long trackId, WaypointType type, double length, long duration, long startId, long stopId, Location location, TripStatistics tripStatistics, String photoUrl) {
this.name = name;
this.description = description;
this.category = category;
@@ -36,8 +36,10 @@ public class WaypointCreationRequest implements Parcelable {
public static final Creator CREATOR = new Creator();
private WaypointType type;
// true if this marker contains the track statistics
private boolean isTrackStatistics;
private String name;
private String category;
private String description;
@@ -29,7 +29,11 @@ import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import java.util.ArrayList;
import java.util.EnumSet;
import de.dennisguse.opentracks.ChartView;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackDetailActivity;
import de.dennisguse.opentracks.content.SensorDataSetLocation;
import de.dennisguse.opentracks.content.Track;
@@ -43,10 +47,6 @@ import de.dennisguse.opentracks.stats.TripStatisticsUpdater;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.UnitConversions;
import de.dennisguse.opentracks.R;
import java.util.ArrayList;
import java.util.EnumSet;
/**
* A fragment to display track chart to the user.
@@ -79,8 +79,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
private ZoomControls zoomControls;
/**
* A runnable that will enable/disable zoom controls and orange pointer as
* appropriate and redraw.
* A runnable that will enable/disable zoom controls and orange pointer as appropriate and redraw.
*/
private final Runnable updateChart = new Runnable() {
@Override
@@ -101,15 +100,13 @@ public class ChartFragment extends Fragment implements TrackDataListener {
super.onCreate(savedInstanceState);
/*
* Create a chartView here to store data thus won't need to reload all the
* data on every onStart or onResume.
* Create a chartView here to store data thus won't need to reload all the data on every onStart or onResume.
*/
chartView = new ChartView(getActivity());
}
@Override
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.chart, container, false);
zoomControls = view.findViewById(R.id.chart_zoom_controls);
zoomControls.setOnZoomInClickListener(new View.OnClickListener() {
@@ -131,8 +128,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
public void onStart() {
super.onStart();
ViewGroup layout = getActivity().findViewById(R.id.chart_view_layout);
LayoutParams layoutParams = new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
layout.addView(chartView, layoutParams);
}
@@ -350,9 +346,9 @@ public class ChartFragment extends Fragment implements TrackDataListener {
chartShow[index] = value;
chartView.setChartValueSeriesEnabled(index, value);
return true;
} else {
return false;
}
return false;
}
/**
@@ -41,7 +41,7 @@ public class ChooseActivityTypeImageAdapter extends BaseAdapter {
private final int padding;
private int selected = -1;
public ChooseActivityTypeImageAdapter(Context context, List<Integer> imageIds, int width, int height, int padding) {
ChooseActivityTypeImageAdapter(Context context, List<Integer> imageIds, int width, int height, int padding) {
this.context = context;
this.imageIds = imageIds;
this.width = width;
@@ -21,8 +21,8 @@ import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import de.dennisguse.opentracks.util.DialogUtils;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.DialogUtils;
/**
* A DialogFragment to confirm delete.
@@ -38,8 +38,7 @@ public class ConfirmDeleteDialogFragment extends AbstractDialogFragment {
/**
* Create a new instance.
*
* @param trackIds list of track ids to delete. To delete all, set to size 1
* with trackIds[0] == -1L
* @param trackIds list of track ids to delete. To delete all, set to size 1 with trackIds[0] == -1L
*/
public static ConfirmDeleteDialogFragment newInstance(long[] trackIds) {
Bundle bundle = new Bundle();
@@ -65,10 +64,8 @@ public class ConfirmDeleteDialogFragment extends AbstractDialogFragment {
final long[] trackIds = getArguments().getLongArray(KEY_TRACK_IDS);
int titleId;
int messageId;
titleId = trackIds.length > 1 ? R.string.generic_delete_selected_confirm_title
: R.string.track_delete_one_confirm_title;
messageId = trackIds.length > 1 ? R.string.track_delete_multiple_confirm_message
: R.string.track_delete_one_confirm_message;
titleId = trackIds.length > 1 ? R.string.generic_delete_selected_confirm_title : R.string.track_delete_one_confirm_title;
messageId = trackIds.length > 1 ? R.string.track_delete_multiple_confirm_message : R.string.track_delete_one_confirm_message;
return DialogUtils.createConfirmationDialog(
getActivity(), titleId, getString(messageId), new DialogInterface.OnClickListener() {
@Override
@@ -23,10 +23,10 @@ import android.os.Bundle;
import androidx.fragment.app.FragmentActivity;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
import de.dennisguse.opentracks.util.DialogUtils;
import de.dennisguse.opentracks.R;
/**
* A DialogFragment to delete marker.
@@ -68,10 +68,8 @@ public class DeleteMarkerDialogFragment extends AbstractDialogFragment {
titleId = R.string.generic_delete_all_confirm_title;
messageId = R.string.marker_delete_all_confirm_message;
} else {
titleId = markerIds.length > 1 ? R.string.generic_delete_selected_confirm_title
: R.string.marker_delete_one_confirm_title;
messageId = markerIds.length > 1 ? R.string.marker_delete_multiple_confirm_message
: R.string.marker_delete_one_confirm_message;
titleId = markerIds.length > 1 ? R.string.generic_delete_selected_confirm_title : R.string.marker_delete_one_confirm_title;
messageId = markerIds.length > 1 ? R.string.marker_delete_multiple_confirm_message : R.string.marker_delete_one_confirm_message;
}
return DialogUtils.createConfirmationDialog(
fragmentActivity, titleId, getString(messageId), new DialogInterface.OnClickListener() {
@@ -96,7 +96,7 @@ public class FileTypeDialogFragment extends AbstractDialogFragment {
onDismissed();
}
protected void onDismissed() {
private void onDismissed() {
dismiss();
caller.onDismissed();
}
@@ -23,9 +23,9 @@ import android.os.Bundle;
import androidx.fragment.app.FragmentActivity;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.R;
/**
* A DialogFragment to configure frequency.
@@ -82,8 +82,8 @@ public class FrequencyDialogFragment extends AbstractDialogFragment {
}
/**
* Gets the list index for a frequency value. Returns 0 if the value is not on
* the list.
* Gets the list index for a frequency value.
* Returns 0 if the value is not on the list.
*/
private int getListIndex(int frequencyValue) {
String[] values = getResources().getStringArray(R.array.frequency_values);
@@ -69,6 +69,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
private Location lastLocation = null;
private TripStatistics lastTripStatistics = null;
private SensorDataSet sensorDataSet = null;
private final Runnable updateUIeachSecond = new Runnable() {
public void run() {
if (isResumed() && isSelectedTrackRecording()) {
@@ -38,8 +38,8 @@ import de.dennisguse.opentracks.util.DialogUtils;
import de.dennisguse.opentracks.util.FileUtils;
/**
* An activity for saving tracks to the external storage. If saving a specific
* track, option to save it to a temp directory and play the track afterward.
* An activity for saving tracks to the external storage.
* If saving a specific track, option to save it to a temp directory and play the track afterward.
*
* @author Rodrigo Damazio
*/
@@ -53,13 +53,11 @@ public class ExportActivity extends FragmentActivity implements FileTypeDialogFr
private String directoryDisplayName;
private ExportAsyncTask exportAsyncTask;
private ProgressDialog progressDialog;
// the number of tracks successfully saved
private int successCount;
// the number of tracks to save
private int totalCount;
private int processedTrackCount;
private int totalTrackCount;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -144,8 +142,8 @@ public class ExportActivity extends FragmentActivity implements FileTypeDialogFr
int titleId;
String message;
String totalTracks = getResources()
.getQuantityString(R.plurals.tracks, totalCount, totalCount);
if (successCount == totalCount) {
.getQuantityString(R.plurals.tracks, totalTrackCount, totalTrackCount);
if (processedTrackCount == totalTrackCount) {
iconId = R.drawable.ic_dialog_success;
titleId = R.string.generic_success_title;
message = getString(
@@ -153,7 +151,7 @@ public class ExportActivity extends FragmentActivity implements FileTypeDialogFr
} else {
iconId = android.R.drawable.ic_dialog_alert;
titleId = R.string.generic_error_title;
message = getString(R.string.export_external_storage_error, successCount, totalTracks,
message = getString(R.string.export_external_storage_error, processedTrackCount, totalTracks,
directoryDisplayName);
}
AlertDialog.Builder builder = new AlertDialog.Builder(this).setCancelable(true)
@@ -192,8 +190,8 @@ public class ExportActivity extends FragmentActivity implements FileTypeDialogFr
* @param aTotalCount the number of tracks to save
*/
public void onAsyncTaskCompleted(int aSuccessCount, int aTotalCount) {
successCount = aSuccessCount;
totalCount = aTotalCount;
processedTrackCount = aSuccessCount;
totalTrackCount = aTotalCount;
removeDialog(DIALOG_PROGRESS_ID);
showDialog(DIALOG_RESULT_ID);
}
@@ -52,17 +52,10 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private ExportActivity exportActivity;
private WakeLock wakeLock;
// true if the AsyncTask has completed
private boolean completed;
// the number of tracks successfully saved
private int successCount;
// the number of tracks to save
private int totalCount;
// the last successfully saved path
private String savedPath;
private int processedTrackCount;
private int totalTrackCount;
/**
* Creates an AsyncTask.
@@ -79,9 +72,8 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
contentProviderUtils = ContentProviderUtils.Factory.get(context);
completed = false;
successCount = 0;
totalCount = 0;
savedPath = null;
processedTrackCount = 0;
totalTrackCount = 0;
}
/**
@@ -92,7 +84,7 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
public void setActivity(ExportActivity exportActivity) {
this.exportActivity = exportActivity;
if (completed && exportActivity != null) {
exportActivity.onAsyncTaskCompleted(successCount, totalCount);
exportActivity.onAsyncTaskCompleted(processedTrackCount, totalTrackCount);
}
}
@@ -132,7 +124,7 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
protected void onPostExecute(Boolean result) {
completed = true;
if (exportActivity != null) {
exportActivity.onAsyncTaskCompleted(successCount, totalCount);
exportActivity.onAsyncTaskCompleted(processedTrackCount, totalTrackCount);
}
}
@@ -140,7 +132,7 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
protected void onCancelled() {
completed = true;
if (exportActivity != null) {
exportActivity.onAsyncTaskCompleted(successCount, totalCount);
exportActivity.onAsyncTaskCompleted(processedTrackCount, totalTrackCount);
}
}
@@ -172,7 +164,6 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
if (trackExporter.writeTrack(fileOutputStream)) {
savedPath = file.getAbsolutePath();
return true;
} else {
if (!file.delete()) {
@@ -198,17 +189,17 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
if (cursor == null) {
return false;
}
totalCount = cursor.getCount();
for (int i = 0; i < totalCount; i++) {
totalTrackCount = cursor.getCount();
for (int i = 0; i < totalTrackCount; i++) {
if (isCancelled()) {
return false;
}
cursor.moveToPosition(i);
Track track = contentProviderUtils.createTrack(cursor);
if (track != null && saveTracks(new Track[]{track})) {
successCount++;
processedTrackCount++;
}
publishProgress(i + 1, totalCount);
publishProgress(i + 1, totalTrackCount);
}
return true;
}
@@ -22,7 +22,6 @@ import android.util.Log;
import java.io.OutputStream;
import de.dennisguse.opentracks.Constants;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
import de.dennisguse.opentracks.content.SensorDataSetLocation;
@@ -89,17 +88,13 @@ public class FileTrackExporter implements TrackExporter {
*/
private void writeWaypoints(Track track) throws InterruptedException {
/*
* TODO: Stream through the waypoints in chunks. I am leaving the number of
* waypoints very high which should not be a problem because we don't try to
* load them into objects all at the same time.
* TODO: Stream through the waypoints in chunks.
* I am leaving the number of waypoints very high which should not be a problem, because we don't try to load them into objects all at the same time.
*/
boolean hasWaypoints = false;
try (Cursor cursor = contentProviderUtils.getWaypointCursor(track.getId(), -1L, Constants.MAX_LOADED_WAYPOINTS_POINTS)) {
try (Cursor cursor = contentProviderUtils.getWaypointCursor(track.getId(), -1L, ContentProviderUtils.MAX_LOADED_WAYPOINTS_POINTS)) {
if (cursor != null && cursor.moveToFirst()) {
/*
* Yes, this will skip the first waypoint and that is intentional as the
* first waypoint holds the stats for the track.
*/
// Intentionally skip first waypoint (contains statistics).
while (cursor.moveToNext()) {
if (Thread.interrupted()) {
throw new InterruptedException();
@@ -127,10 +122,8 @@ public class FileTrackExporter implements TrackExporter {
boolean isLastLocationValid = false;
TrackWriterLocationFactory locationFactory = new TrackWriterLocationFactory();
int locationNumber = 0;
LocationIterator locationIterator = null;
try {
locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, locationFactory);
try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, locationFactory)) {
while (locationIterator.hasNext()) {
if (Thread.interrupted()) {
@@ -188,10 +181,6 @@ public class FileTrackExporter implements TrackExporter {
trackWriter.writeBeginTrack(track, null);
trackWriter.writeEndTrack(track, null);
}
} finally {
if (locationIterator != null) {
locationIterator.close();
}
}
}
@@ -224,7 +213,7 @@ public class FileTrackExporter implements TrackExporter {
return currentLocation;
}
public void swapLocations() {
void swapLocations() {
Location tempLocation = lastLocation;
lastLocation = currentLocation;
currentLocation = tempLocation;
@@ -32,7 +32,7 @@ import de.dennisguse.opentracks.util.StringUtils;
*
* @author Sandor Dornbush
*/
//TODO Can we export Sensor.Data in GPX?
//TODO Can we export SensorData in GPX?
public class GpxTrackWriter implements TrackWriter {
private static final NumberFormat ELEVATION_FORMAT = NumberFormat.getInstance(Locale.US);
@@ -188,7 +188,6 @@ public class GpxTrackWriter implements TrackWriter {
* @param location the location
*/
private String formatLocation(Location location) {
return "lat=\"" + COORDINATE_FORMAT.format(location.getLatitude()) + "\" lon=\""
+ COORDINATE_FORMAT.format(location.getLongitude()) + "\"";
return "lat=\"" + COORDINATE_FORMAT.format(location.getLatitude()) + "\" lon=\"" + COORDINATE_FORMAT.format(location.getLongitude()) + "\"";
}
}
@@ -55,23 +55,26 @@ import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.UnitConversions;
/**
* Abstract class for various file track importers like {@link GpxFileTrackImporter} and
* {@link KmlFileTrackImporter}.
* Abstract class for file track importers.
*
* @author Jimmy Shih
*/
abstract class AbstractFileTrackImporter extends DefaultHandler implements TrackImporter {
private static final String TAG = AbstractFileTrackImporter.class.getSimpleName();
// The maximum number of buffered locations for bulk-insertion
private static final int MAX_BUFFERED_LOCATIONS = 512;
private final Context context;
private final long importTrackId;
private final ContentProviderUtils contentProviderUtils;
private final int recordingDistanceInterval;
private final List<Long> trackIds;
private final List<Waypoint> waypoints;
// The current element content
//TODO Should be made private and getter be used by child classes.
protected String content;
protected String name;
protected String description;
@@ -82,25 +85,24 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
protected String time;
protected String waypointType;
protected String photoUrl;
// The current track data
private TrackData trackData;
// The SAX locator to get the current line information
private Locator locator;
/**
* Constructor.
*
* @param context the context
* @param importTrackId the track id to import to. -1L to import to a new
* track.
* @param importTrackId the track id to import to. -1L to import to a new track.
*/
AbstractFileTrackImporter(
Context context, long importTrackId, ContentProviderUtils contentProviderUtils) {
AbstractFileTrackImporter(Context context, long importTrackId, ContentProviderUtils contentProviderUtils) {
this.context = context;
this.importTrackId = importTrackId;
this.contentProviderUtils = contentProviderUtils;
this.recordingDistanceInterval = PreferencesUtils.getInt(context,
R.string.recording_distance_interval_key,
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
this.recordingDistanceInterval = PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
trackIds = new ArrayList<>();
waypoints = new ArrayList<>();
}
@@ -116,11 +118,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
if (content == null) {
content = newContent;
} else {
/*
* In 99% of the cases, a single call to this method will be made for each
* sequence of characters we're interested in, so we'll rarely be
* concatenating strings, thus not justifying the use of a StringBuilder.
*/
// In 99% of the cases, a single call to this method will be made for each sequence of characters we're interested in, so we'll rarely be concatenating strings, thus not justifying the use of a StringBuilder.
content += newContent;
}
}
@@ -164,15 +162,10 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
int waypointPosition = -1;
Waypoint waypoint = null;
Location location = null;
TripStatisticsUpdater trackTripStatisticstrackUpdater = new TripStatisticsUpdater(
track.getTripStatistics().getStartTime());
TripStatisticsUpdater markerTripStatisticsUpdater = new TripStatisticsUpdater(
track.getTripStatistics().getStartTime());
LocationIterator locationIterator = null;
TripStatisticsUpdater trackTripStatisticstrackUpdater = new TripStatisticsUpdater(track.getTripStatistics().getStartTime());
TripStatisticsUpdater markerTripStatisticsUpdater = new TripStatisticsUpdater(track.getTripStatistics().getStartTime());
try {
locationIterator = contentProviderUtils.getTrackPointLocationIterator(
track.getId(), -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY)) {
while (true) {
if (waypoint == null) {
@@ -198,7 +191,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
waypoint = null;
} else {
// The waypoint location time matches the track point time
if (!LocationUtils.isValidLocation(location)) {
// Invalid location, load the next location
location = null;
@@ -206,8 +198,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
}
// Valid location
if (location.getLatitude() == waypoint.getLocation().getLatitude()
&& location.getLongitude() == waypoint.getLocation().getLongitude()) {
if (location.getLatitude() == waypoint.getLocation().getLatitude() && location.getLongitude() == waypoint.getLocation().getLongitude()) {
// Get tripStatistics, description, and icon
TripStatistics tripStatistics;
@@ -216,8 +207,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
if (waypoint.getType() == WaypointType.STATISTICS) {
tripStatistics = markerTripStatisticsUpdater.getTripStatistics();
markerTripStatisticsUpdater = new TripStatisticsUpdater(location.getTime());
waypointDescription = new DescriptionGeneratorImpl(context)
.generateWaypointDescription(tripStatistics);
waypointDescription = new DescriptionGeneratorImpl(context).generateWaypointDescription(tripStatistics);
icon = context.getString(R.string.marker_statistics_icon_url);
} else {
tripStatistics = null;
@@ -230,9 +220,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
long duration = trackTripStatisticstrackUpdater.getTripStatistics().getTotalTime();
// Insert waypoint
Waypoint newWaypoint = new Waypoint(waypoint.getName(), waypointDescription,
waypoint.getCategory(), icon, track.getId(), waypoint.getType(), length, duration,
-1L, -1L, location, tripStatistics, waypoint.getPhotoUrl());
Waypoint newWaypoint = new Waypoint(waypoint.getName(), waypointDescription, waypoint.getCategory(), icon, track.getId(), waypoint.getType(), length, duration, -1L, -1L, location, tripStatistics, waypoint.getPhotoUrl());
contentProviderUtils.insertWaypoint(newWaypoint);
}
@@ -240,10 +228,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
waypoint = null;
}
}
} finally {
if (locationIterator != null) {
locationIterator.close();
}
}
}
@@ -258,8 +242,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
trackId = Long.parseLong(uri.getLastPathSegment());
} else {
if (trackIds.size() > 0) {
throw new SAXException(createErrorMessage(
"Cannot import more than one track to an existing track " + importTrackId));
throw new SAXException(createErrorMessage("Cannot import more than one track to an existing track " + importTrackId));
}
trackId = importTrackId;
contentProviderUtils.clearTrack(context, trackId);
@@ -299,13 +282,9 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
protected void onTrackSegmentStart() {
trackData.numberOfSegments++;
/*
* If not the first segment, add a pause separator if there is at least one
* location in the last segment.
*/
//If not the first segment, add a pause separator if there is at least one location in the last segment.
if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment != null) {
insertLocation(createLocation(TrackRecordingService.PAUSE_LATITUDE, 0.0, 0.0,
trackData.lastLocationInCurrentSegment.getTime()));
insertLocation(createLocation(TrackRecordingService.PAUSE_LATITUDE, 0.0, 0.0, trackData.lastLocationInCurrentSegment.getTime()));
}
trackData.lastLocationInCurrentSegment = null;
}
@@ -362,10 +341,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
} else {
/*
* We don't have a speed and bearing in GPX, make something up from the
* last two points. GPS points tend to have some inherent imprecision,
* speed and bearing will likely be off, so the statistics for things
* like max speed will also be off.
* We don't have a speed and bearing in GPX, make something up from the last two points.
* GPS points tend to have some inherent imprecision, speed and bearing will likely be off, so the statistics for things like max speed will also be off.
*/
double duration = timeDifference * UnitConversions.MS_TO_S;
double speed = trackData.lastLocationInCurrentSegment.distanceTo(location) / duration;
@@ -379,10 +356,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
}
if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment == null) {
/*
* If not the first segment, add a resume separator before adding the
* first location.
*/
// If not the first segment, add a resume separator before adding the first location.
insertLocation(
createLocation(TrackRecordingService.RESUME_LATITUDE, 0.0, 0.0, location.getTime()));
}
@@ -441,17 +415,14 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
latitudeValue = Double.parseDouble(latitude);
longitudeValue = Double.parseDouble(longitude);
} catch (NumberFormatException e) {
throw new SAXException(createErrorMessage(String.format(
Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e);
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e);
}
Double altitudeValue = null;
if (altitude != null) {
try {
altitudeValue = Double.parseDouble(altitude);
} catch (NumberFormatException e) {
throw new SAXException(
createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)),
e);
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)), e);
}
}
@@ -462,8 +433,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
try {
timeValue = StringUtils.getTime(time);
} catch (IllegalArgumentException e) {
throw new SAXException(
createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
}
}
return createLocation(latitudeValue, longitudeValue, altitudeValue, timeValue);
@@ -477,8 +447,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
* @param altitudeValue the altitude value
* @param timeValue the time value
*/
private Location createLocation(
double latitudeValue, double longitudeValue, Double altitudeValue, long timeValue) {
private Location createLocation(double latitudeValue, double longitudeValue, Double altitudeValue, long timeValue) {
Location location = new Location(LocationManager.GPS_PROVIDER);
location.setLatitude(latitudeValue);
location.setLongitude(longitudeValue);
@@ -501,8 +470,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
*/
private void insertLocation(Location location) {
if (trackData.tripStatisticsUpdater == null) {
trackData.tripStatisticsUpdater = new TripStatisticsUpdater(
location.getTime() != -1L ? location.getTime() : trackData.importTime);
trackData.tripStatisticsUpdater = new TripStatisticsUpdater(location.getTime() != -1L ? location.getTime() : trackData.importTime);
}
trackData.tripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
@@ -524,8 +492,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
if (data.numBufferedLocations <= 0) {
return;
}
contentProviderUtils.bulkInsertTrackPoint(
data.bufferedLocations, data.numBufferedLocations, data.track.getId());
contentProviderUtils.bulkInsertTrackPoint(data.bufferedLocations, data.numBufferedLocations, data.track.getId());
data.numBufferedLocations = 0;
if (data.track.getStartId() == -1L) {
data.track.setStartId(contentProviderUtils.getFirstTrackPointId(data.track.getId()));
@@ -541,20 +508,16 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
private void insertFirstWaypoint(Track track) {
String waypointName = context.getString(R.string.marker_split_name_format, 0);
String waypointCategory = "";
TripStatisticsUpdater updater = new TripStatisticsUpdater(
track.getTripStatistics().getStartTime());
TripStatisticsUpdater updater = new TripStatisticsUpdater(track.getTripStatistics().getStartTime());
TripStatistics tripStatistics = updater.getTripStatistics();
String waypointDescription = new DescriptionGeneratorImpl(context).generateWaypointDescription(
tripStatistics);
String waypointDescription = new DescriptionGeneratorImpl(context).generateWaypointDescription(tripStatistics);
String icon = context.getString(R.string.marker_statistics_icon_url);
double length = 0.0;
long duration = 0L;
Location waypointLocation = new Location("");
waypointLocation.setLatitude(100);
waypointLocation.setLongitude(180);
Waypoint waypoint = new Waypoint(waypointName, waypointDescription, waypointCategory, icon,
track.getId(), WaypointType.STATISTICS, length, duration, -1L, -1L, waypointLocation,
tripStatistics, "");
Waypoint waypoint = new Waypoint(waypointName, waypointDescription, waypointCategory, icon, track.getId(), WaypointType.STATISTICS, length, duration, -1L, -1L, waypointLocation, tripStatistics, "");
contentProviderUtils.insertWaypoint(waypoint);
}
@@ -573,17 +536,13 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
* @author Jimmy Shih
*/
private class TrackData {
// The current track
Track track = new Track();
// The number of segments processed for the current track
int numberOfSegments = 0;
/*
* The last location in the current segment. Null if the current segment
* doesn't have a last location.
*/
// The last location in the current segment; Null if the current segment doesn't have a last location
Location lastLocationInCurrentSegment;
// The number of locations processed for the current track
@@ -64,8 +64,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
}
@Override
public void startElement(String uri, String localName, String tag, Attributes attributes)
throws SAXException {
public void startElement(String uri, String localName, String tag, Attributes attributes) throws SAXException {
switch (tag) {
case TAG_WAYPOINT:
onWaypointStart(attributes);
@@ -185,7 +184,6 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
* On waypoint end.
*/
private void onWaypointEnd() throws SAXException {
addWaypoint(WaypointType.STATISTICS.name().equals(waypointType) ? WaypointType.STATISTICS
: WaypointType.WAYPOINT);
addWaypoint(WaypointType.STATISTICS.name().equals(waypointType) ? WaypointType.STATISTICS : WaypointType.WAYPOINT);
}
}
@@ -56,11 +56,9 @@ public class ImportActivity extends FragmentActivity implements FileTypeDialogFr
// the path on the external storage to import
private String directoryDisplayName;
// the number of files successfully imported
private int successCount;
private int importedTrackCount;
// the number of files to import
private int totalCount;
private int totalTrackCount;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -144,9 +142,9 @@ public class ImportActivity extends FragmentActivity implements FileTypeDialogFr
int titleId;
String message;
String totalFiles = getResources()
.getQuantityString(R.plurals.files, totalCount, totalCount);
if (successCount == totalCount) {
if (totalCount == 0) {
.getQuantityString(R.plurals.files, totalTrackCount, totalTrackCount);
if (importedTrackCount == totalTrackCount) {
if (totalTrackCount == 0) {
iconId = android.R.drawable.ic_dialog_info;
titleId = R.string.import_no_file_title;
message = getString(R.string.import_no_file, directoryDisplayName);
@@ -159,7 +157,7 @@ public class ImportActivity extends FragmentActivity implements FileTypeDialogFr
iconId = android.R.drawable.ic_dialog_alert;
titleId = R.string.generic_error_title;
message = getString(
R.string.import_error, successCount, totalFiles, directoryDisplayName);
R.string.import_error, importedTrackCount, totalFiles, directoryDisplayName);
}
final Dialog dialog = new AlertDialog.Builder(this).setCancelable(true).setIcon(iconId)
.setMessage(message).setOnCancelListener(new DialogInterface.OnCancelListener() {
@@ -195,8 +193,8 @@ public class ImportActivity extends FragmentActivity implements FileTypeDialogFr
* @param aTotalCount the number of files to import
*/
public void onAsyncTaskCompleted(int aSuccessCount, int aTotalCount) {
successCount = aSuccessCount;
totalCount = aTotalCount;
importedTrackCount = aSuccessCount;
totalTrackCount = aTotalCount;
removeDialog(DIALOG_PROGRESS_ID);
showDialog(DIALOG_RESULT_ID);
}
@@ -53,11 +53,9 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
// true if the AsyncTask has completed
private boolean completed;
// the number of files successfully imported
private int successCount;
private int importTrackCount;
// the number of files to import
private int totalCount;
private int totalTrackCount;
// the last successfully imported track id
private long trackId;
@@ -76,8 +74,8 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
context = importActivity.getApplicationContext();
completed = false;
successCount = 0;
totalCount = 0;
importTrackCount = 0;
totalTrackCount = 0;
trackId = -1L;
}
@@ -89,7 +87,7 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
public void setActivity(ImportActivity importActivity) {
this.importActivity = importActivity;
if (completed && importActivity != null) {
importActivity.onAsyncTaskCompleted(successCount, totalCount);
importActivity.onAsyncTaskCompleted(importTrackCount, totalTrackCount);
}
}
@@ -112,20 +110,20 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
}
List<File> files = getFiles();
totalCount = files.size();
if (totalCount == 0) {
totalTrackCount = files.size();
if (totalTrackCount == 0) {
return true;
}
for (int i = 0; i < totalCount; i++) {
for (int i = 0; i < totalTrackCount; i++) {
if (isCancelled()) {
// If cancelled, return true to show the number of files imported
return true;
}
if (importFile(files.get(i))) {
successCount++;
importTrackCount++;
}
publishProgress(i + 1, totalCount);
publishProgress(i + 1, totalTrackCount);
}
return true;
} finally {
@@ -146,7 +144,7 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
protected void onPostExecute(Boolean result) {
completed = true;
if (importActivity != null) {
importActivity.onAsyncTaskCompleted(successCount, totalCount);
importActivity.onAsyncTaskCompleted(importTrackCount, totalTrackCount);
}
}
@@ -154,7 +152,7 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
protected void onCancelled() {
completed = true;
if (importActivity != null) {
importActivity.onAsyncTaskCompleted(successCount, totalCount);
importActivity.onAsyncTaskCompleted(importTrackCount, totalTrackCount);
}
}
@@ -190,9 +188,9 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
}
/**
* Gets a list of files. If importAll is true, returns a list of the files
* under the path directory. If importAll is false, returns a list containing
* just the path file.
* Gets a list of files.
* If importAll is true, returns a list of the files under the path directory.
* If importAll is false, returns a list containing just the path file.
*/
private List<File> getFiles() {
List<File> files = new ArrayList<>();
@@ -22,16 +22,16 @@ import android.net.Uri;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.SensorDataSetLocation;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import java.util.ArrayList;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.SensorDataSetLocation;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
/**
* Imports a KML file.
*
@@ -82,8 +82,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
}
@VisibleForTesting
KmlFileTrackImporter(
Context context, long importTrackId, ContentProviderUtils contentProviderUtils) {
KmlFileTrackImporter(Context context, long importTrackId, ContentProviderUtils contentProviderUtils) {
super(context, importTrackId, contentProviderUtils);
}
@@ -93,11 +92,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
switch (tag) {
case TAG_PLACEMARK:
case TAG_PHOTO_OVERLAY:
/*
* Note that a track is contained in a Placemark, calling onWaypointStart
* will clear various track variables like name, category, and
* description.
*/
// Note that a track is contained in a Placemark, calling onWaypointStart will clear various track variables like name, category, and description.
onWaypointStart();
break;
case TAG_GX_MULTI_TRACK:
@@ -118,13 +113,11 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
@Override
public void endElement(String uri, String localName, String tag) throws SAXException {
//TODO Check if order is relevant (uses localname and tag); and convert to switch statement
if (tag.equals(TAG_KML)) {
onFileEnd();
} else if (tag.equals(TAG_PLACEMARK) || tag.equals(TAG_PHOTO_OVERLAY)) {
/*
* Note that a track is contained in a Placemark, calling onWaypointend is
* save since waypointType is not set for a track.
*/
// Note that a track is contained in a Placemark, calling onWaypointend is save since waypointType is not set for a track.
onWaypointEnd();
} else if (localName.equals(TAG_COORDINATES)) {
onWaypointLocationEnd();
@@ -187,19 +180,23 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
*/
private void onWaypointEnd() throws SAXException {
// Add a waypoint if the waypointType matches
WaypointType type = null;
if (WAYPOINT_STYLE.equals(waypointType)) {
type = WaypointType.WAYPOINT;
} else if (STATISTICS_STYLE.equals(waypointType)) {
type = WaypointType.STATISTICS;
}
if (type == null) {
return;
WaypointType type;
switch (waypointType) {
case WAYPOINT_STYLE:
type = WaypointType.WAYPOINT;
break;
case STATISTICS_STYLE:
type = WaypointType.STATISTICS;
break;
default:
return;
}
if (photoUrl != null) {
Uri uri = Uri.parse(photoUrl);
photoUrl = getPhotoUrl(uri.getLastPathSegment());
}
addWaypoint(type);
}
@@ -20,10 +20,6 @@ import android.content.Context;
import android.net.Uri;
import android.util.Log;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter;
import de.dennisguse.opentracks.util.FileUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -33,6 +29,11 @@ import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* Imports a KMZ file.
*
@@ -41,6 +42,7 @@ import java.util.zip.ZipInputStream;
public class KmzTrackImporter implements TrackImporter {
private static final String TAG = KmzTrackImporter.class.getSimpleName();
private static final int BUFFER_SIZE = 4096;
private final Context context;
@@ -50,22 +52,19 @@ public class KmzTrackImporter implements TrackImporter {
* Constructor.
*
* @param context the context
* @param importTrackId track id to import to. This should not be -1L so that
* images in the kmz file can be imported.
* @param importTrackId track id to import to. This should not be -1L so that images in the kmz file can be imported.
*/
public KmzTrackImporter(Context context, long importTrackId) {
KmzTrackImporter(Context context, long importTrackId) {
this.context = context;
this.importTrackId = importTrackId;
}
@Override
public long importFile(InputStream inputStream) {
ZipInputStream zipInputStream = null;
long trackId = importTrackId;
try {
try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
ZipEntry zipEntry;
zipInputStream = new ZipInputStream(inputStream);
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
if (Thread.interrupted()) {
Log.d(TAG, "Thread interrupted");
@@ -93,14 +92,6 @@ public class KmzTrackImporter implements TrackImporter {
Log.e(TAG, "Unable to import file", e);
cleanImport(trackId);
return -1L;
} finally {
if (zipInputStream != null) {
try {
zipInputStream.close();
} catch (IOException e) {
Log.e(TAG, "Unable to close zip input stream", e);
}
}
}
}
@@ -110,7 +101,7 @@ public class KmzTrackImporter implements TrackImporter {
* @param trackId the trackId
*/
private void cleanImport(long trackId) {
if (trackId != -1L) {
if (trackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
contentProviderUtils.deleteTrack(context, trackId);
}
@@ -169,19 +160,17 @@ public class KmzTrackImporter implements TrackImporter {
* @param fileName the file name
*/
private void readImageFile(ZipInputStream zipInputStream, String fileName) throws IOException {
FileOutputStream fileOutputStream = null;
try {
if (importTrackId == -1L) {
return;
}
if (fileName.equals("")) {
return;
}
File dir = FileUtils.getPhotoDir(importTrackId);
FileUtils.ensureDirectoryExists(dir);
if (importTrackId == -1L) {
return;
}
if (fileName.equals("")) {
return;
}
File dir = FileUtils.getPhotoDir(importTrackId);
FileUtils.ensureDirectoryExists(dir);
File file = new File(dir, fileName);
File file = new File(dir, fileName);
fileOutputStream = new FileOutputStream(file);
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
byte[] buffer = new byte[BUFFER_SIZE];
int count;
while ((count = zipInputStream.read(buffer)) != -1) {
@@ -189,10 +178,6 @@ public class KmzTrackImporter implements TrackImporter {
}
FileUtils.updateMediaScanner(context, Uri.fromFile(file));
} finally {
if (fileOutputStream != null) {
fileOutputStream.close();
}
}
}
}
@@ -28,7 +28,7 @@ public interface TrackImporter {
* Import a file.
*
* @param inputStream the file's input stream
* @return the imported track id or -1L.
* @return the imported track id or RECORDING_TRACK_ID_DEFAULT.
*/
long importFile(InputStream inputStream);
}
@@ -17,8 +17,7 @@
package de.dennisguse.opentracks.services;
/**
* This is a simple location listener policy that will always dictate the same
* polling interval.
* This is a simple location listener policy that will always dictate the same polling interval.
*
* @author Sandor Dornbush
*/
@@ -41,7 +40,7 @@ public class AbsoluteLocationListenerPolicy implements LocationListenerPolicy {
}
@Override
public int getMinDistance() {
public int getMinDistance_m() {
return 0;
}
@@ -17,53 +17,48 @@
package de.dennisguse.opentracks.services;
/**
* A {@link LocationListenerPolicy} that will change based on how long the user
* has been stationary. This policy will dictate a policy based on a min, max
* and idle time. The policy will dictate an interval bounded by min and max,
* and is half of the idle time.
* A {@link LocationListenerPolicy} that will change based on how long the user has been stationary.
* This policy will dictate a policy based on a min, max and idle time.
* The policy will dictate an interval bounded by min and max, and is half of the idle time.
*
* @author Sandor Dornbush
*/
public class AdaptiveLocationListenerPolicy implements LocationListenerPolicy {
private final long minInterval;
private final long maxInterval;
private final int minDistance;
private final long minInterval_ms;
private final long maxInterval_ms;
private final int minDistance_m;
// The time the user has been idle at the current location, in milliseconds.
private long idleTime;
/**
* Creates a policy that will be bounded by the given minInterval and
* maxInterval.
* Creates a policy that will be bounded by the given minInterval_ms and maxInterval_ms.
*
* @param minInterval the smallest interval this policy will dictate, in
* milliseconds
* @param maxInterval the largest interval this policy will dictate, in
* milliseconds
* @param minDistance the minimum distance in meters
* @param minInterval_ms the smallest interval this policy will dictate
* @param maxInterval_ms the largest interval this policy will dictate
* @param minDistance_m the minimum distance
*/
public AdaptiveLocationListenerPolicy(long minInterval, long maxInterval, int minDistance) {
this.minInterval = minInterval;
this.maxInterval = maxInterval;
this.minDistance = minDistance;
public AdaptiveLocationListenerPolicy(long minInterval_ms, long maxInterval_ms, int minDistance_m) {
this.minInterval_ms = minInterval_ms;
this.maxInterval_ms = maxInterval_ms;
this.minDistance_m = minDistance_m;
}
/*
* Returns an interval half of the idle time, but bounded by minInteval and
* maxInterval.
* Returns an interval half of the idle time, but bounded by minInterval_ms and maxInterval_ms.
*/
@Override
public long getDesiredPollingInterval() {
long desiredInterval = idleTime / 2;
long desiredInterval_ms = idleTime / 2;
// Round to second to avoid setting the interval too often
desiredInterval = (desiredInterval / 1000) * 1000;
return Math.max(Math.min(maxInterval, desiredInterval), minInterval);
desiredInterval_ms = (desiredInterval_ms / 1000) * 1000;
return Math.max(Math.min(maxInterval_ms, desiredInterval_ms), minInterval_ms);
}
@Override
public int getMinDistance() {
return minDistance;
public int getMinDistance_m() {
return minDistance_m;
}
@Override
@@ -30,12 +30,10 @@ import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.widgets.TrackWidgetProvider;
/**
* A service to control starting and stopping of a recording. This service,
* through the AndroidManifest.xml, is configured to only allow components of
* the same application to invoke it. Thus this service can be used my the
* app widget, {@link TrackWidgetProvider}, but not by other applications. This
* application delegates starting and stopping a recording to
* {@link TrackRecordingService} using RPC calls.
* A service to control starting and stopping of a recording.
* This service, through the AndroidManifest.xml, is configured to only allow components of the same application to invoke it.
* Thus this service can be used by the app widget, {@link TrackWidgetProvider}, but not by other applications.
* This application delegates starting and stopping a recording to {@link TrackRecordingService} using RPC calls.
*
* @author Jimmy Shih
*/
@@ -60,9 +58,8 @@ public class ControlRecordingService extends IntentService implements ServiceCon
}
@VisibleForTesting
@Nullable
@Override
public IBinder onBind(Intent intent) {
public IBinder onBind(@Nullable Intent intent) {
return new LocalBinder();
}
@@ -104,8 +101,7 @@ public class ControlRecordingService extends IntentService implements ServiceCon
}
/**
* Notifies all threads that connection to {@link TrackRecordingService} is
* available.
* Notifies all threads that connection to {@link TrackRecordingService} is available.
*/
private synchronized void notifyConnected() {
connected = true;
@@ -120,7 +116,6 @@ public class ControlRecordingService extends IntentService implements ServiceCon
try {
wait();
} catch (InterruptedException e) {
// can safely ignore
}
}
@@ -133,7 +128,7 @@ public class ControlRecordingService extends IntentService implements ServiceCon
}
@VisibleForTesting
public class LocalBinder extends Binder {
class LocalBinder extends Binder {
ControlRecordingService getService() {
return ControlRecordingService.this;
}
@@ -17,9 +17,10 @@ package de.dennisguse.opentracks.services;
import android.location.Location;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.content.sensor.SensorState;
/**
* App's service.
@@ -97,6 +98,7 @@ public interface ITrackRecordingService {
*
* @param location the track point to be inserted
*/
@VisibleForTesting
void insertTrackPoint(Location location);
/**
@@ -105,11 +107,4 @@ public interface ITrackRecordingService {
* @return SensorDataSet object.
*/
SensorDataSet getSensorData();
/**
* Gets the current sensor manager state.
* <p>
* return a Sensor.SensorState enum value.
*/
SensorState getSensorState();
}
@@ -17,9 +17,7 @@
package de.dennisguse.opentracks.services;
/**
* This is an interface for classes that will manage the location listener
* policy. Different policy options are: {@link AbsoluteLocationListenerPolicy}
* and {@link AdaptiveLocationListenerPolicy}.
* This is an interface for classes that will manage the location listener policy.
*
* @author Sandor Dornbush
*/
@@ -35,13 +33,12 @@ public interface LocationListenerPolicy {
/**
* Returns the minimum distance between updates.
*/
int getMinDistance();
int getMinDistance_m();
/**
* Notifies the amount of time the user has been idle at his current location.
*
* @param idleTime the time that the user has been idle at his current
* location
* @param idleTime the time that the user has been idle at his current location
*/
void updateIdleTime(long idleTime);
}
@@ -57,7 +57,6 @@ import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.content.WaypointCreationRequest;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.content.sensor.SensorState;
import de.dennisguse.opentracks.services.sensors.RemoteSensorManager;
import de.dennisguse.opentracks.services.sensors.RemoteSensorManagerFactory;
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory;
@@ -947,7 +946,7 @@ public class TrackRecordingService extends Service {
}
try {
long interval = locationListenerPolicy.getDesiredPollingInterval();
locationManagerConnector.requestLocationUpdates(interval, locationListenerPolicy.getMinDistance(), locationListener);
locationManagerConnector.requestLocationUpdates(interval, locationListenerPolicy.getMinDistance_m(), locationListener);
currentRecordingInterval = interval;
} catch (RuntimeException e) {
Log.e(TAG, "Could not register location listener.", e);
@@ -1116,15 +1115,6 @@ public class TrackRecordingService extends Service {
return trackRecordingService.remoteSensorManager.getSensorDataSet();
}
@Override
public SensorState getSensorState() {
if (trackRecordingService.remoteSensorManager == null) {
Log.d(TAG, "remoteSensorManager is null.");
return SensorState.NONE;
}
return trackRecordingService.remoteSensorManager.getSensorState();
}
/**
* Detaches from the track recording service. Clears the reference to the
* outer class to minimize the leak.
@@ -25,12 +25,13 @@ import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
import android.util.Log;
import de.dennisguse.opentracks.util.TrackRecordingServiceConnectionUtils;
import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.util.TrackRecordingServiceConnectionUtils;
/**
* Wrapper for the track recording service. This handles service
* start/bind/unbind/stop. The service must be started before it can be bound.
* Wrapper for the track recording service.
* This handles service start/bind/unbind/stop.
* The service must be started before it can be bound.
* Returns the service if it is started and bound.
*
* @author Rodrigo Damazio
@@ -38,8 +39,10 @@ import de.dennisguse.opentracks.BuildConfig;
public class TrackRecordingServiceConnection {
private static final String TAG = TrackRecordingServiceConnection.class.getSimpleName();
private final Context context;
private final Runnable callback;
private ITrackRecordingService trackRecordingService;
private final DeathRecipient deathRecipient = new DeathRecipient() {
@Override
@@ -48,6 +51,7 @@ public class TrackRecordingServiceConnection {
setTrackRecordingService(null);
}
};
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
@@ -130,8 +134,7 @@ public class TrackRecordingServiceConnection {
return;
}
if (!startIfNeeded
&& !TrackRecordingServiceConnectionUtils.isRecordingServiceRunning(context)) {
if (!startIfNeeded && !TrackRecordingServiceConnectionUtils.isRecordingServiceRunning(context)) {
Log.d(TAG, "Service is not started. Not binding it.");
return;
}
@@ -30,11 +30,11 @@ import android.util.Log;
import androidx.annotation.NonNull;
import java.util.UUID;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.content.sensor.SensorState;
import java.util.UUID;
/**
* Manages connection to Bluetooth LE heart rate monitor.
* TODO: shutdown connection.
@@ -42,16 +42,20 @@ import java.util.UUID;
public class BluetoothConnectionManager {
// Message types sent to handler
public static final int MESSAGE_DEVICE_NAME = 1;
public static final int MESSAGE_READ = 2;
public static final String KEY_DEVICE_NAME = "device_name";
static final int MESSAGE_DEVICE_NAME = 1;
static final int MESSAGE_READ = 2;
static final String KEY_DEVICE_NAME = "device_name";
private static final UUID HEART_RATE_SERVICE_UUID = new UUID(0x180D00001000L, 0x800000805f9b34fbL);
private static final UUID HEART_RATE_MEASUREMENT_CHAR_UUID = new UUID(0x2A3700001000L, 0x800000805f9b34fbL);
private static final UUID CLIENT_CHARACTERISTIC_CONFIG_UUID = new UUID(0x290200001000L, 0x800000805f9b34fbL);
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
private final Context context;
private final Handler handler;
private SensorState sensorState;
private BluetoothGattCallback connectCallback = new BluetoothGattCallback() {
@@ -100,7 +104,7 @@ public class BluetoothConnectionManager {
*
* @param handler a handler for sending messages back to the UI activity
*/
public BluetoothConnectionManager(Context context, Handler handler) {
BluetoothConnectionManager(Context context, Handler handler) {
this.context = context;
this.handler = handler;
this.sensorState = SensorState.NONE;
@@ -109,7 +113,7 @@ public class BluetoothConnectionManager {
/**
* Gets the sensor state.
*/
public synchronized SensorState getSensorState() {
synchronized SensorState getSensorState() {
return sensorState;
}
@@ -2,12 +2,12 @@ package de.dennisguse.opentracks.services.sensors;
import android.bluetooth.BluetoothGattCharacteristic;
public final class BluetoothLEUtils {
final class BluetoothLEUtils {
private BluetoothLEUtils() {
}
public static int parseHeartRate(BluetoothGattCharacteristic characteristic) {
static int parseHeartRate(BluetoothGattCharacteristic characteristic) {
//DOCUMENTATION https://www.bluetooth.com/specifications/gatt/characteristics/
byte[] raw = characteristic.getValue();
int index = ((raw[0] & 0x1) == 1) ? 2 : 1;
@@ -34,7 +34,6 @@ import de.dennisguse.opentracks.util.UnitConversions;
/**
* Bluetooth sensor manager.
* <p>
* TODO: Handle a BluetoothGatt.STATE_DISCONNECTED
*
* @author Sandor Dornbush
@@ -42,10 +41,15 @@ import de.dennisguse.opentracks.util.UnitConversions;
public class BluetoothRemoteSensorManager extends RemoteSensorManager {
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
private static final BluetoothAdapter bluetoothAdapter = getDefaultBluetoothAdapter();
private final Context context;
private final BluetoothConnectionManager bluetoothConnectionManager;
private SensorDataSet sensorDataSet = null;
// Handler that gets information back from the bluetoothConnectionManager
private final Handler messageHandler = new Handler(Looper.getMainLooper()) {
@Override
@@ -71,7 +75,7 @@ public class BluetoothRemoteSensorManager extends RemoteSensorManager {
/**
* @param context the context
*/
public BluetoothRemoteSensorManager(Context context) {
BluetoothRemoteSensorManager(Context context) {
this.context = context;
bluetoothConnectionManager = new BluetoothConnectionManager(context, messageHandler);
}
@@ -109,7 +113,6 @@ public class BluetoothRemoteSensorManager extends RemoteSensorManager {
if (adapters.get(0) == null) {
Log.w(TAG, "No bluetooth adapter found.");
return null;
}
return adapters.get(0);
}
@@ -125,6 +128,7 @@ public class BluetoothRemoteSensorManager extends RemoteSensorManager {
Log.w(TAG, "Bluetooth not enabled.");
return;
}
String address = PreferencesUtils.getString(context, R.string.bluetooth_sensor_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
if (PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(address)) {
Log.w(TAG, "No bluetooth address.");
@@ -78,13 +78,10 @@ public abstract class RemoteSensorManager {
setUpChannel();
}
break;
case NONE:
case DISCONNECTED:
setUpChannel();
break;
default:
// CONNECTED or SENDING
break;
default: // NONE, CONNECTED or SENDING
}
}
};
@@ -26,6 +26,8 @@ import android.content.Context;
public class RemoteSensorManagerFactory {
private static RemoteSensorManager remoteSensorManagerSystem = null;
//TODO Check if still needed? Is there a missing features?
private static RemoteSensorManager remoteSensorManagerTemporary = null;
private RemoteSensorManagerFactory() {
@@ -70,7 +72,7 @@ public class RemoteSensorManagerFactory {
/**
* Releases the temp sensor manager.
*/
public static void releaseSensorManagerTemporary() {
private static void releaseSensorManagerTemporary() {
if (remoteSensorManagerTemporary != null) {
remoteSensorManagerTemporary.stopSensor();
}
@@ -27,14 +27,14 @@ import android.util.Log;
import androidx.annotation.VisibleForTesting;
import java.util.Locale;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.UnitConversions;
import de.dennisguse.opentracks.R;
import java.util.Locale;
/**
* This class will periodically announce the user's trip statistics.
@@ -50,7 +50,9 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
static final float TTS_SPEECH_RATE = 0.9f;
private static final String TAG = AnnouncementPeriodicTask.class.getSimpleName();
private final Context context;
private final AudioManager audioManager;
private final UtteranceProgressListener utteranceListener = new UtteranceProgressListener() {
@Override
@@ -69,13 +71,16 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
public void onError(String utteranceId) {
}
};
private TextToSpeech tts;
// Response from TTS after its initialization
private int initStatus = TextToSpeech.ERROR;
// True if TTS engine is ready
private boolean ready = false;
private boolean ttsReady = false;
// True if speech is allowed
private boolean speechAllowed;
/**
* Listener which updates {@link #speechAllowed} when the phone state changes.
*/
@@ -90,7 +95,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
}
};
public AnnouncementPeriodicTask(Context context) {
AnnouncementPeriodicTask(Context context) {
this.context = context;
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
@@ -131,13 +136,13 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
}
synchronized (this) {
if (!ready) {
ready = initStatus == TextToSpeech.SUCCESS;
if (ready) {
if (!ttsReady) {
ttsReady = initStatus == TextToSpeech.SUCCESS;
if (ttsReady) {
onTtsReady();
}
}
if (!ready) {
if (!ttsReady) {
Log.i(TAG, "TTS not ready.");
return;
}
@@ -170,9 +175,8 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
Log.w(TAG, "Default locale not available, use English.");
locale = Locale.ENGLISH;
/*
* TODO: instead of using english, load the language if missing and show a
* toast if not supported. Not able to change the resource strings to
* English.
* TODO: instead of using english, load the language if missing and show a toast if not supported.
* Not able to change the resource strings to English.
*/
}
tts.setLanguage(locale);
@@ -189,8 +193,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
* @param announcement the announcement
*/
private void speakAnnouncement(String announcement) {
int result = audioManager.requestAudioFocus(
null, TextToSpeech.Engine.DEFAULT_STREAM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
int result = audioManager.requestAudioFocus(null, TextToSpeech.Engine.DEFAULT_STREAM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
Log.w(TAG, "Failed to request audio focus.");
}
@@ -223,7 +226,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
boolean reportSpeed = PreferencesUtils.isReportSpeed(context);
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
double speed = tripStatistics.getAverageMovingSpeed() * UnitConversions.MS_TO_KMH;
double distancePerTime = tripStatistics.getAverageMovingSpeed() * UnitConversions.MS_TO_KMH;
if (distance == 0) {
return context.getString(R.string.voice_total_distance_zero);
@@ -231,26 +234,22 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
if (!metricUnits) {
distance *= UnitConversions.KM_TO_MI;
speed *= UnitConversions.KM_TO_MI;
distancePerTime *= UnitConversions.KM_TO_MI;
}
String rate;
if (reportSpeed) {
int speedId = metricUnits ? R.plurals.voiceSpeedKilometersPerHour
: R.plurals.voiceSpeedMilesPerHour;
rate = context.getResources().getQuantityString(speedId, getQuantityCount(speed), speed);
int speedId = metricUnits ? R.plurals.voiceSpeedKilometersPerHour : R.plurals.voiceSpeedMilesPerHour;
rate = context.getResources().getQuantityString(speedId, getQuantityCount(distancePerTime), distancePerTime);
} else {
speed = speed == 0 ? 0.0 : 1 / speed;
double timePerDistance = distancePerTime == 0 ? 0.0 : 1 / distancePerTime;
int paceId = metricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile;
long time = Math.round(
speed * UnitConversions.HR_TO_MIN * UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS);
long time = Math.round(timePerDistance * UnitConversions.HR_TO_MIN * UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS);
rate = context.getString(paceId, getAnnounceTime(time));
}
int totalDistanceId = metricUnits ? R.plurals.voiceTotalDistanceKilometers
: R.plurals.voiceTotalDistanceMiles;
String totalDistance = context.getResources()
.getQuantityString(totalDistanceId, getQuantityCount(distance), distance);
int totalDistanceId = metricUnits ? R.plurals.voiceTotalDistanceKilometers : R.plurals.voiceTotalDistanceMiles;
String totalDistance = context.getResources().getQuantityString(totalDistanceId, getQuantityCount(distance), distance);
return context.getString(R.string.voice_template, totalDistance,
getAnnounceTime(tripStatistics.getMovingTime()), rate);
@@ -264,8 +263,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
*/
@VisibleForTesting
protected void listenToPhoneState(PhoneStateListener listener, int events) {
TelephonyManager telephony = (TelephonyManager) context.getSystemService(
Context.TELEPHONY_SERVICE);
TelephonyManager telephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if (telephony != null) {
telephony.listen(listener, events);
}
@@ -297,15 +295,13 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
}
/**
* Gets the plural count to be used by getQuantityString. getQuantityString
* only supports integer quantities, not a double quantity like "2.2".
* Gets the plural count to be used by getQuantityString.
* getQuantityString only supports integer quantities, not a double quantity like "2.2".
* <p>
* As a temporary workaround, we convert a double quantity to an integer
* quantity. If the double quantity is exactly 0, 1, or 2, then we can return
* these integer quantities. Otherwise, we cast the double quantity to an
* integer quantity. However, we need to make sure that if the casted value is
* 0, 1, or 2, we don't return those, instead, return the next biggest integer
* 3.
* As a temporary workaround, we convert a double quantity to an integer quantity.
* If the double quantity is exactly 0, 1, or 2, then we can return these integer quantities.
* Otherwise, we cast the double quantity to an integer quantity.
* However, we need to make sure that if the casted value is 0, 1, or 2, we don't return those, instead, return the next biggest integer 3.
*
* @param d the double value
*/
@@ -317,6 +313,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
} else if (d == 2) {
return 2;
} else {
//TODO This seems weird; why not use Math.round(d) or Math.ceil()?
int count = (int) d;
return count < 3 ? 3 : count;
}
@@ -24,6 +24,7 @@ import de.dennisguse.opentracks.services.TrackRecordingService;
* @author Sandor Dornbush
*/
public interface PeriodicTask {
/**
* Sets up this task for subsequent calls to the run method.
*/
@@ -36,9 +36,10 @@ public class PeriodicTaskExecutor {
private final PeriodicTaskFactory periodicTaskFactory;
/**
* The task frequency. A positive value is a time frequency (minutes). A
* negative value is a distance frequency (km or mi). A zero value is to turn
* off periodic task.
* The task frequency.
* A positive value is a time frequency (minutes).
* A negative value is a distance frequency (km or mi).
* A zero value is to turn off periodic task.
*/
private int taskFrequency = PreferencesUtils.FREQUENCY_OFF;
@@ -52,8 +53,7 @@ public class PeriodicTaskExecutor {
// The next distance for the distance periodic task
private double nextTaskDistance = Double.MAX_VALUE;
public PeriodicTaskExecutor(
TrackRecordingService trackRecordingService, PeriodicTaskFactory periodicTaskFactory) {
public PeriodicTaskExecutor(TrackRecordingService trackRecordingService, PeriodicTaskFactory periodicTaskFactory) {
this.trackRecordingService = trackRecordingService;
this.periodicTaskFactory = periodicTaskFactory;
}
@@ -67,11 +67,9 @@ public class PeriodicTaskExecutor {
return;
}
if (!isTimeFrequency()) {
if (timerTaskExecutor != null) {
timerTaskExecutor.shutdown();
timerTaskExecutor = null;
}
if (!isTimeFrequency() && timerTaskExecutor != null) {
timerTaskExecutor.shutdown();
timerTaskExecutor = null;
}
if (taskFrequency == PreferencesUtils.FREQUENCY_OFF) {
Log.d(TAG, "Task frequency is off.");
@@ -119,12 +117,13 @@ public class PeriodicTaskExecutor {
if (!isDistanceFrequency() || periodicTask == null) {
return;
}
TripStatistics tripStatistics = trackRecordingService.getTripStatistics();
if (tripStatistics == null) {
return;
}
double distance = tripStatistics.getTotalDistance()
* UnitConversions.M_TO_KM;
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
if (!metricUnits) {
distance *= UnitConversions.KM_TO_MI;
}
@@ -159,8 +158,7 @@ public class PeriodicTaskExecutor {
* Calculates the next distance for the distance periodic task.
*/
private void calculateNextTaskDistance() {
if (!trackRecordingService.isRecording() || trackRecordingService.isPaused()
|| periodicTask == null) {
if (!trackRecordingService.isRecording() || trackRecordingService.isPaused() || periodicTask == null) {
return;
}
@@ -175,11 +173,11 @@ public class PeriodicTaskExecutor {
return;
}
double distance = tripStatistics.getTotalDistance()
* UnitConversions.M_TO_KM;
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
if (!metricUnits) {
distance *= UnitConversions.KM_TO_MI;
}
// The index will be negative since the frequency is negative.
int index = (int) (distance / taskFrequency);
index -= 1;
@@ -16,19 +16,19 @@
package de.dennisguse.opentracks.services.tasks;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.stats.TripStatistics;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.stats.TripStatistics;
/**
* This class will periodically perform a task.
*
* @author Sandor Dornbush
*/
public class TimerTaskExecutor {
class TimerTaskExecutor {
private final PeriodicTask periodicTask;
private final TrackRecordingService trackRecordingService;
@@ -42,12 +42,12 @@ public class TimerTaskExecutor {
}
/**
* Schedules the periodic task at an interval.
* Schedules the periodic task in milliseconds.
*
* @param interval the interval in milliseconds
* @param interval_ms the interval_ms
*/
public void scheduleTask(long interval) {
if (interval <= 0) {
void scheduleTask(long interval_ms) {
if (interval_ms <= 0) {
return;
}
@@ -69,14 +69,11 @@ public class TimerTaskExecutor {
}
};
timer = new Timer(TimerTaskExecutor.class.getSimpleName());
long next = System.currentTimeMillis() + interval - (tripStatistics.getTotalTime() % interval);
timer.scheduleAtFixedRate(timerTask, new Date(next), interval);
long next = System.currentTimeMillis() + interval_ms - (tripStatistics.getTotalTime() % interval_ms);
timer.scheduleAtFixedRate(timerTask, new Date(next), interval_ms);
}
/**
* Shuts down.
*/
public void shutdown() {
void shutdown() {
if (timerTask != null) {
timerTask.cancel();
timerTask = null;
@@ -77,8 +77,7 @@ public class ActivityTypePreference extends DialogPreference {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
String iconValue = TrackIconUtils.getIconValue(
getContext(), textView.getText().toString());
String iconValue = TrackIconUtils.getIconValue(getContext(), textView.getText().toString());
TrackIconUtils.setIconSpinner(spinner, iconValue);
}
}
@@ -136,7 +135,7 @@ public class ActivityTypePreference extends DialogPreference {
*
* @param activity the activity
*/
public void setRecordingSettingsActivity(RecordingSettingsActivity activity) {
void setRecordingSettingsActivity(RecordingSettingsActivity activity) {
recordingSettingsActivity = activity;
}
@@ -145,10 +144,9 @@ public class ActivityTypePreference extends DialogPreference {
*
* @param iconValue the icon value
*/
public void updateValue(String iconValue) {
void updateValue(String iconValue) {
TrackIconUtils.setIconSpinner(spinner, iconValue);
textView.setText(
recordingSettingsActivity.getString(TrackIconUtils.getIconActivityType(iconValue)));
textView.setText(recordingSettingsActivity.getString(TrackIconUtils.getIconActivityType(iconValue)));
textView.clearFocus();
}
}
@@ -22,20 +22,19 @@ import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.UnitConversions;
import de.dennisguse.opentracks.R;
/**
* An activity for accessing recording settings.
*
* @author Jimmy Shih
*/
public class RecordingSettingsActivity extends PreferenceActivity
implements ChooseActivityTypeCaller {
public class RecordingSettingsActivity extends PreferenceActivity implements ChooseActivityTypeCaller {
private static final int DIALOG_CHOOSE_ACTIVITY = 0;
@@ -51,13 +50,10 @@ public class RecordingSettingsActivity extends PreferenceActivity
configFrequencyPreference(R.string.voice_frequency_key, metricUnits);
configFrequencyPreference(R.string.split_frequency_key, metricUnits);
configDefaultActivity();
configListPreference(R.string.min_recording_interval_key, R.array.min_recording_interval_values,
metricUnits);
configListPreference(R.string.min_recording_interval_key, R.array.min_recording_interval_values, metricUnits);
configListPreference(R.string.recording_distance_interval_key, R.array.recording_distance_interval_values, metricUnits);
configListPreference(R.string.max_recording_distance_key, R.array.max_recording_distance_values,
metricUnits);
configListPreference(R.string.recording_gps_accuracy_key, R.array.recording_gps_accuracy_values,
metricUnits);
configListPreference(R.string.max_recording_distance_key, R.array.max_recording_distance_values, metricUnits);
configListPreference(R.string.recording_gps_accuracy_key, R.array.recording_gps_accuracy_values, metricUnits);
configListPreference(R.string.auto_resume_track_timeout_key, R.array.auto_resume_track_timeout_values, metricUnits);
}
@@ -80,31 +76,25 @@ public class RecordingSettingsActivity extends PreferenceActivity
}
private void configDefaultActivity() {
activityTypePreference = (ActivityTypePreference) findPreference(
getString(R.string.default_activity_key));
activityTypePreference = (ActivityTypePreference) findPreference(getString(R.string.default_activity_key));
String defaultActivity = PreferencesUtils.getString(
this, R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT);
activityTypePreference.setOnPreferenceChangeListener(
new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference pref, Object newValue) {
String stringValue = (String) newValue;
pref.setSummary(stringValue != null
&& !stringValue.equals(PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT) ? stringValue
: getString(R.string.value_unknown));
return true;
}
});
activityTypePreference.setSummary(defaultActivity != null
&& !defaultActivity.equals(PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT) ? defaultActivity
: getString(R.string.value_unknown));
activityTypePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference pref, Object newValue) {
String stringValue = (String) newValue;
pref.setSummary(stringValue != null && !stringValue.equals(PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT) ? stringValue : getString(R.string.value_unknown));
return true;
}
});
activityTypePreference.setSummary(defaultActivity != null && !defaultActivity.equals(PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT) ? defaultActivity : getString(R.string.value_unknown));
activityTypePreference.setRecordingSettingsActivity(this);
}
private void configListPreference(int key, int valueArray, boolean metricUnits) {
ListPreference preference = (ListPreference) findPreference(getString(key));
@Deprecated
@Deprecated //TODO Can we make values an int array?
String[] values = getResources().getStringArray(valueArray);
String[] options = new String[values.length];
switch (key) {
@@ -59,10 +59,7 @@ public class StatsSettingsActivity extends PreferenceActivity {
listPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference pref, Object newValue) {
if (listener != null) {
listener.onPreferenceChange(pref, newValue);
}
return true;
return listener.onPreferenceChange(pref, newValue);
}
});
}
@@ -19,9 +19,9 @@ package de.dennisguse.opentracks.stats;
import androidx.annotation.NonNull;
/**
* This class maintains a buffer of doubles. This buffer is a convenient class
* for storing a series of doubles and calculating information about them. This
* is a FIFO buffer.
* This class maintains a buffer of doubles.
* This buffer is a convenient class for storing a series of doubles and calculating information about them.
* This is a FIFO buffer.
*
* @author Sandor Dornbush
*/
@@ -29,8 +29,10 @@ public class DoubleBuffer {
// The sliding buffer of doubles.
private final double[] buffer;
// The location that the next write will occur at.
private int index;
// True if the buffer is full
private boolean isFull;
@@ -39,7 +41,7 @@ public class DoubleBuffer {
*
* @param size the size
*/
public DoubleBuffer(int size) {
DoubleBuffer(int size) {
if (size < 1) {
throw new IllegalArgumentException("The buffer size must be greater than 1.");
}
@@ -58,7 +60,7 @@ public class DoubleBuffer {
/**
* Returns true if the buffer is full.
*/
public boolean isFull() {
boolean isFull() {
return isFull;
}
@@ -78,8 +80,8 @@ public class DoubleBuffer {
}
/**
* Adds a double to the buffer. If the buffer is full the oldest element is
* overwritten.
* Adds a double to the buffer.
* If the buffer is full the oldest element is overwritten.
*
* @param value the double to add
*/
@@ -35,6 +35,7 @@ public class TripStatistics implements Parcelable {
* Creator of {@link TripStatistics} from parcels.
*/
public static final Creator CREATOR = new Creator();
// The min and max latitude seen in this trip.
private final ExtremityMonitor latitudeExtremities = new ExtremityMonitor();
// The min and max longitude seen in this trip.
@@ -43,6 +44,7 @@ public class TripStatistics implements Parcelable {
private final ExtremityMonitor elevationExtremities = new ExtremityMonitor();
// The min and max grade seen on this trip.
private final ExtremityMonitor gradeExtremities = new ExtremityMonitor();
// The trip start time. This is the system time, might not match the GPs time.
private long startTime = -1L;
// The trip stop time. This is the system time, might not match the GPS time.
@@ -173,10 +175,9 @@ public class TripStatistics implements Parcelable {
}
/**
* Gets the total time in milliseconds that this track has been active. This
* statistic is only updated when a new point is added to the statistics, so
* it may be off. If you need to calculate the proper total time, use
* {@link #getStartTime} with the current time.
* Gets the total time in milliseconds that this track has been active.
* This statistic is only updated when a new point is added to the statistics, so it may be off.
* If you need to calculate the proper total time, use {@link #getStartTime} with the current time.
*/
public long getTotalTime() {
return totalTime;
@@ -217,16 +218,14 @@ public class TripStatistics implements Parcelable {
}
/**
* Gets the topmost position (highest latitude) of the track, in signed
* degrees.
* Gets the topmost position (highest latitude) of the track, in signed degrees.
*/
public double getTopDegrees() {
return latitudeExtremities.getMax();
}
/**
* Gets the topmost position (highest latitude) of the track, in signed
* millions of degrees.
* Gets the topmost position (highest latitude) of the track, in signed millions of degrees.
*/
public int getTop() {
return (int) (latitudeExtremities.getMax() * 1E6);
@@ -241,40 +240,35 @@ public class TripStatistics implements Parcelable {
}
/**
* Gets the bottommost position (lowest latitude) of the track, in signed
* millions of degrees.
* Gets the bottommost position (lowest latitude) of the track, in signed millions of degrees.
*/
public int getBottom() {
return (int) (latitudeExtremities.getMin() * 1E6);
}
/**
* Gets the leftmost position (lowest longitude) of the track, in signed
* degrees.
* Gets the leftmost position (lowest longitude) of the track, in signed degrees.
*/
public double getLeftDegrees() {
return longitudeExtremities.getMin();
}
/**
* Gets the leftmost position (lowest longitude) of the track, in signed
* millions of degrees.
* Gets the leftmost position (lowest longitude) of the track, in signed millions of degrees.
*/
public int getLeft() {
return (int) (longitudeExtremities.getMin() * 1E6);
}
/**
* Gets the rightmost position (highest longitude) of the track, in signed
* degrees.
* Gets the rightmost position (highest longitude) of the track, in signed degrees.
*/
public double getRightDegrees() {
return longitudeExtremities.getMax();
}
/**
* Gets the rightmost position (highest longitude) of the track, in signed
* millions of degrees.
* Gets the rightmost position (highest longitude) of the track, in signed millions of degrees.
*/
public int getRight() {
return (int) (longitudeExtremities.getMax() * 1E6);
@@ -327,9 +321,8 @@ public class TripStatistics implements Parcelable {
}
/**
* Gets the average speed in meters/second. This calculation only takes into
* account the displacement until the last point that was accounted for in
* statistics.
* Gets the average speed in meters/second.
* This calculation only takes into account the displacement until the last point that was accounted for in statistics.
*/
public double getAverageSpeed() {
if (totalTime == 0L) {
@@ -365,8 +358,8 @@ public class TripStatistics implements Parcelable {
}
/**
* Gets the minimum elevation. This is calculated from the smoothed elevation
* so this can actually be more than the current elevation.
* Gets the minimum elevation.
* This is calculated from the smoothed elevation, so this can actually be more than the current elevation.
*/
public double getMinElevation() {
return elevationExtremities.getMin();
@@ -382,8 +375,8 @@ public class TripStatistics implements Parcelable {
}
/**
* Gets the maximum elevation. This is calculated from the smoothed elevation
* so this can actually be less than the current elevation.
* Gets the maximum elevation.
* This is calculated from the smoothed elevation, so this can actually be less than the current elevation.
*/
public double getMaxElevation() {
return elevationExtremities.getMax();
@@ -408,8 +401,7 @@ public class TripStatistics implements Parcelable {
}
/**
* Gets the total elevation gain in meters. This is calculated as the sum of
* all positive differences in the smoothed elevation.
* Gets the total elevation gain in meters. This is calculated as the sum of all positive differences in the smoothed elevation.
*/
public double getTotalElevationGain() {
return totalElevationGain;
@@ -27,9 +27,9 @@ import static de.dennisguse.opentracks.services.TrackRecordingService.MAX_NO_MOV
import static de.dennisguse.opentracks.services.TrackRecordingService.PAUSE_LATITUDE;
/**
* Updater for {@link TripStatistics}. For updating track trip statistics as new
* locations are added. Note that some of the locations represent pause/resume
* separator.
* Updater for {@link TripStatistics}.
* For updating track trip statistics as new locations are added.
* NOTE:Some of the locations represent pause/resume separator.
*
* @author Sandor Dornbush
* @author Rodrigo Damazio
@@ -45,28 +45,30 @@ public class TripStatisticsUpdater {
* signal.
*/
@VisibleForTesting
static final int ELEVATION_SMOOTHING_FACTOR = 25;
private static final int ELEVATION_SMOOTHING_FACTOR = 25;
/**
* The number of run readings to smooth for calculating grade.
*/
@VisibleForTesting
static final int RUN_SMOOTHING_FACTOR = 25;
private static final int RUN_SMOOTHING_FACTOR = 25;
/**
* The number of speed reading to smooth to get a somewhat accurate signal.
*/
@VisibleForTesting
static final int SPEED_SMOOTHING_FACTOR = 25;
private static final int SPEED_SMOOTHING_FACTOR = 25;
private static final String TAG = TripStatisticsUpdater.class.getSimpleName();
/**
* Ignore any acceleration faster than this. Will ignore any speeds that imply
* acceleration greater than 2g's 2g = 19.6 m/s^2 = 0.0002 m/ms^2 = 0.02
* m/(m*ms)
* Ignore any acceleration faster than this.
* Will ignore any speeds that imply acceleration greater than 2g's
* 2g = 19.6 m/s^2 = 0.0002 m/ms^2 = 0.02 m/(m*ms)
*/
private static final double MAX_ACCELERATION = 0.02;
// The track's trip statistics
private final TripStatistics tripStatistics;
// A buffer of the recent elevation readings (m)
private final DoubleBuffer elevationBuffer = new DoubleBuffer(ELEVATION_SMOOTHING_FACTOR);
// A buffer of the recent run readings (m) for calculating grade
@@ -75,6 +77,7 @@ public class TripStatisticsUpdater {
private final DoubleBuffer gradeBuffer = new DoubleBuffer(GRADE_SMOOTHING_FACTOR);
// A buffer of the recent speed readings (m/s) for calculating max speed
private final DoubleBuffer speedBuffer = new DoubleBuffer(SPEED_SMOOTHING_FACTOR);
// The current segment's trip statistics
private TripStatistics currentSegment;
// Current segment's last location.
@@ -108,7 +111,8 @@ public class TripStatisticsUpdater {
}
/**
* Adds a location. TODO: This assume location has a valid time.
* Adds a location.
* TODO: This assume location has a valid time.
*
* @param location the location
* @param minRecordingDistance the min recording distance
@@ -119,8 +123,7 @@ public class TripStatisticsUpdater {
if (!LocationUtils.isValidLocation(location)) {
// Either pause or resume marker
if (location.getLatitude() == PAUSE_LATITUDE) {
if (lastLocation != null && lastMovingLocation != null
&& lastLocation != lastMovingLocation) {
if (lastLocation != null && lastMovingLocation != null && lastLocation != lastMovingLocation) {
currentSegment.addTotalDistance(lastMovingLocation.distanceTo(lastLocation));
}
tripStatistics.merge(currentSegment);
@@ -134,11 +137,12 @@ public class TripStatisticsUpdater {
speedBuffer.reset();
return;
}
currentSegment.updateLatitudeExtremities(location.getLatitude());
currentSegment.updateLongitudeExtremities(location.getLongitude());
double elevationDifference = location.hasAltitude() ? updateElevation(location.getAltitude())
: 0.0;
//TODO Use Barometer to compute elevation gain.
double elevationDifference = location.hasAltitude() ? updateElevation(location.getAltitude()) : 0.0;
if (lastLocation == null || lastMovingLocation == null) {
lastLocation = location;
@@ -147,8 +151,7 @@ public class TripStatisticsUpdater {
}
double movingDistance = lastMovingLocation.distanceTo(location);
if (movingDistance < minRecordingDistance
&& (!location.hasSpeed() || location.getSpeed() < MAX_NO_MOVEMENT_SPEED)) {
if (movingDistance < minRecordingDistance && (!location.hasSpeed() || location.getSpeed() < MAX_NO_MOVEMENT_SPEED)) {
speedBuffer.reset();
lastLocation = location;
return;
@@ -171,8 +174,7 @@ public class TripStatisticsUpdater {
// Update max speed
if (location.hasSpeed() && lastLocation.hasSpeed()) {
updateSpeed(
location.getTime(), location.getSpeed(), lastLocation.getTime(), lastLocation.getSpeed());
updateSpeed(location.getTime(), location.getSpeed(), lastLocation.getTime(), lastLocation.getSpeed());
}
lastLocation = location;
@@ -180,9 +182,8 @@ public class TripStatisticsUpdater {
}
/**
* Gets the smoothed elevation over several readings. The elevation readings
* is noisy so the smoothed elevation is better than the raw elevation for
* many tasks.
* Gets the smoothed elevation over several readings.
* The elevation readings is noisy so the smoothed elevation is better than the raw elevation for many tasks.
*/
public double getSmoothedElevation() {
return elevationBuffer.getAverage();
@@ -193,7 +194,7 @@ public class TripStatisticsUpdater {
}
/**
* Updates a speed reading. Assumes the user is moving.
* Updates a speed reading while assuming the user is moving.
*
* @param time the time
* @param speed the speed
@@ -201,7 +202,7 @@ public class TripStatisticsUpdater {
* @param lastLocationSpeed the last location speed
*/
@VisibleForTesting
void updateSpeed(long time, double speed, long lastLocationTime, double lastLocationSpeed) {
private void updateSpeed(long time, double speed, long lastLocationTime, double lastLocationSpeed) {
if (speed < MAX_NO_MOVEMENT_SPEED) {
speedBuffer.reset();
} else if (isValidSpeed(time, speed, lastLocationTime, lastLocationSpeed)) {
@@ -220,7 +221,7 @@ public class TripStatisticsUpdater {
* @param elevation the elevation
*/
@VisibleForTesting
double updateElevation(double elevation) {
private double updateElevation(double elevation) {
// Update elevation using the smoothed average
double oldAverage = elevationBuffer.getAverage();
elevationBuffer.setNext(elevation);
@@ -241,15 +242,12 @@ public class TripStatisticsUpdater {
* @param rise the rise
*/
@VisibleForTesting
void updateGrade(double run, double rise) {
private void updateGrade(double run, double rise) {
runBuffer.setNext(run);
double smoothedRun = runBuffer.getAverage();
/*
* With the error in the altitude measurement it is dangerous to divide by
* anything less than 5.
*/
// With the error in the altitude measurement, it is dangerous to divide by * anything less than 5.
if (smoothedRun < 5.0) {
return;
}
@@ -272,46 +270,31 @@ public class TripStatisticsUpdater {
* @param lastLocationTime the last location time
* @param lastLocationSpeed the last location speed
*/
private boolean isValidSpeed(
long time, double speed, long lastLocationTime, double lastLocationSpeed) {
/*
* There are a lot of noisy speed readings. Do the cheapest checks first,
* most expensive last.
*/
private boolean isValidSpeed(long time, double speed, long lastLocationTime, double lastLocationSpeed) {
// There are a lot of noisy speed readings. Do the cheapest checks first, most expensive last.
if (speed == 0) {
return false;
}
/*
* The following code will ignore unlikely readings. 128 m/s seems to be an
* internal android error code.
*/
// The following code will ignore unlikely readings. 128 m/s seems to be an internal android error code.
if (Math.abs(speed - 128) < 1) {
return false;
}
/*
* See if the speed seems physically likely. Ignore any speeds that imply
* acceleration greater than 2g.
*/
// See if the speed seems physically likely. Ignore any speeds that imply acceleration greater than 2g.
long timeDifference = time - lastLocationTime;
double speedDifference = Math.abs(lastLocationSpeed - speed);
if (speedDifference > MAX_ACCELERATION * timeDifference) {
return false;
}
/*
* Only check if the speed buffer is full. Check that the speed is less than
* 10X the smoothed average and the speed difference doesn't imply 2g
* acceleration.
*/
// Only check if the speed buffer is full. Check that the speed is less than 10X the smoothed average and the speed difference doesn't imply 2g acceleration.
if (speedBuffer.isFull()) {
double average = speedBuffer.getAverage();
double diff = Math.abs(average - speed);
return (speed < average * 10) && (diff < MAX_ACCELERATION * timeDifference);
} else {
return true;
}
return true;
}
}
@@ -34,13 +34,13 @@ public class FileUtils {
/**
* Used to transfer picture from the camera.
*/
public static final String FILEPROVIDER = BuildConfig.APPLICATION_ID + ".fileprovider";
static final String FILEPROVIDER = BuildConfig.APPLICATION_ID + ".fileprovider";
/**
* Our external storage directory.
*/
static final String SDCARD_TOP_DIR = "OpenTracks";
static final String TRACK_PREFIX = "OpenTracks";
private static final String TRACK_PREFIX = "OpenTracks";
/**
* The maximum FAT32 path length. See the FAT32 spec at
* http://msdn.microsoft.com/en-us/windows/hardware/gg463080
@@ -127,16 +127,14 @@ public class FileUtils {
}
/**
* Builds a filename with the given base name (prefix) and the given
* extension, possibly adding a suffix to ensure the file doesn't exist.
* 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) {
public static synchronized String buildUniqueFileName(File directory, String fileBaseName, String extension) {
return buildUniqueFileName(directory, fileBaseName, extension, 0);
}
@@ -154,7 +152,8 @@ public class FileUtils {
}
/**
* Gets the extension from a file name. Returns null if there is no extension.
* Gets the extension from a file name.
* Returns null if there is no extension.
*
* @param fileName the file name
*/
@@ -173,8 +172,7 @@ public class FileUtils {
}
/**
* Builds a filename with the given base and the given extension, possibly
* adding a suffix to ensure the file doesn't exist.
* 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
@@ -182,8 +180,7 @@ public class FileUtils {
* @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) {
private static String buildUniqueFileName(File directory, String base, String extension, int suffix) {
String suffixName = "";
if (suffix > 0) {
suffixName += "(" + suffix + ")";
@@ -201,10 +198,9 @@ public class FileUtils {
}
/**
* 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.
* 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
*/
@@ -260,8 +256,7 @@ public class FileUtils {
}
/**
* Truncates the name if necessary so the filename path length (directory +
* name + suffix) meets the Fat32 path limit.
* Truncates the name if necessary so the filename path length (directory + name + suffix) meets the Fat32 path limit.
*
* @param directory directory
* @param name name
@@ -273,8 +268,8 @@ public class FileUtils {
if (name.length() + requiredLength > MAX_FAT32_PATH_LENGTH) {
int limit = MAX_FAT32_PATH_LENGTH - requiredLength;
return name.substring(0, limit);
} else {
return name;
}
return name;
}
}
@@ -48,12 +48,13 @@ public class IntentUtils {
private final static String TAG = IntentUtils.class.getSimpleName();
private static final String JPEG_EXTENSION = "jpeg";
private IntentUtils() {
}
/**
* Creates an intent with {@link Intent#FLAG_ACTIVITY_CLEAR_TOP} and
* {@link Intent#FLAG_ACTIVITY_NEW_TASK}.
* 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
@@ -62,12 +63,10 @@ public class IntentUtils {
return new Intent(context, cls).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
}
private static final String JPEG_EXTENSION = "jpeg";
/**
* Creates an intent to share a track file with an app.
*
* @param context the context
* @param context the context
* @param trackIds the track ids
*/
public static Intent newShareFileIntent(Context context, long[] trackIds) {
@@ -90,6 +89,14 @@ public class IntentUtils {
showCoordinateOnMap(context, waypoint.getLocation().getLatitude(), waypoint.getLocation().getLongitude(), waypoint.getName());
}
/**
* Send intent to show coordinates on a map (needs an another app).
*
* @param context the context
* @param latitude the latitude
* @param longitude the longitude
* @param label the label
*/
public static void showCoordinateOnMap(Context context, double latitude, double longitude, String label) {
//SEE https://developer.android.com/guide/components/intents-common.html#Maps
String uri = "geo:0,0?q=" + latitude + "," + longitude;
@@ -61,11 +61,7 @@ public class ListItemUtils {
* @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) {
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);
@@ -134,17 +130,11 @@ public class ListItemUtils {
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);
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) {
// 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);
@@ -158,8 +148,7 @@ public class ListItemUtils {
// 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;
params.gravity = timeDistanceTextView.getVisibility() == View.GONE && markerCountIcon.getVisibility() == View.GONE ? Gravity.TOP : Gravity.CENTER_VERTICAL;
}
/**
@@ -196,22 +185,17 @@ public class ListItemUtils {
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};
}
if (isToday && useRelativeTime) {
return new String[]{DateUtils.getRelativeTimeSpanString(time, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString(), null};
} else if (isToday) {
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)};
return new String[]{DateUtils.formatDateTime(context, time, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL), DateUtils.formatDateTime(context, time, timeFlags)};
}
/**
@@ -225,14 +209,15 @@ public class ListItemUtils {
public static void setTextView(Context context, TextView textView, String value, boolean addShadow) {
if (value == null || value.length() == 0) {
textView.setVisibility(View.GONE);
return;
}
textView.setVisibility(View.VISIBLE);
textView.setText(value);
if (addShadow) {
textView.setShadowLayer(5, 0, 2, context.getResources().getColor(android.R.color.black));
} 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);
}
textView.setShadowLayer(0, 0, 0, 0);
}
}
@@ -18,30 +18,30 @@ 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.List;
import java.util.Stack;
import de.dennisguse.opentracks.content.Track;
/**
* Utility class for decimating tracks at a given level of precision.
*
* @author Leif Hendrik Wilden
*/
//TODO Cleanup: rename variables
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 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.
* 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
@@ -64,31 +64,36 @@ public class LocationUtils {
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.
* 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) {
//TODO What was it used for? Sharing data with other apps?
private static void decimate(double tolerance, List<Location> locations, List<Location> decimated) {
final int n = locations.size();
if (n < 1) {
return;
@@ -153,16 +158,16 @@ public class LocationUtils {
}
/**
* 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.
* Checks if a given location is a valid (i.e. physically possible) locationon 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
return location != null
&& Math.abs(location.getLatitude()) <= 90
&& Math.abs(location.getLongitude()) <= 180;
}
@@ -172,7 +177,6 @@ public class LocationUtils {
* @param location the location
*/
public static boolean isLocationOld(Location location) {
return !LocationUtils.isValidLocation(location)
|| (System.currentTimeMillis() - location.getTime() > MAX_LOCATION_AGE_MS);
return !LocationUtils.isValidLocation(location) || (System.currentTimeMillis() - location.getTime() > MAX_LOCATION_AGE_MS);
}
}
@@ -31,8 +31,8 @@ import de.dennisguse.opentracks.R;
public class PreferencesUtils {
/*
* Preferences values. The defaults need to match the defaults in the xml
* files.
* Preferences values.
* The defaults need to match the defaults in the xml files.
*/
public static final int AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT = 0;
@@ -48,7 +48,10 @@ public class PreferencesUtils {
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";
@Deprecated
//NOTE: is at the moment still used to determine if a track is currently recorded; better ask the service directly.
//NOTE: This is also used to recover from a reboot, but this data should not be exposed to the whole application.
public static final long RECORDING_TRACK_ID_DEFAULT = -1L;
public static final String DEFAULT_ACTIVITY_DEFAULT = "";
@@ -68,20 +71,16 @@ public class PreferencesUtils {
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;
@Deprecated //NOTE: is at the moment still used to determine if a track is currently recorded; better ask the service directly.
public static final long RECORDING_TRACK_ID_DEFAULT = -1L;
static final boolean STATS_SHOW_COORDINATE_DEFAULT = false;
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_ELEVATION_DEFAULT = false;
static final boolean STATS_SHOW_ELEVATION_DEFAULT = false;
static final String TRACK_NAME_DEFAULT = "DATE_ISO_8601";
private static final String CHART_X_AXIS_DEFAULT = "DISTANCE";
public static final String STATS_UNITS_DEFAULT = "METRIC";
public static final String TRACK_NAME_DEFAULT = "DATE_ISO_8601";
// Stats
private static final String STATS_RATE_DEFAULT = "SPEED";
public static final boolean SHOW_TRACKDETAIL_WHILE_RECORDING_ON_LOCKSCREEN = false;
@@ -47,10 +47,8 @@ public class 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 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
*/
@@ -96,19 +94,17 @@ public class StatsUtils {
}
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);
// 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);
}
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);
}
}
@@ -126,13 +122,10 @@ public class StatsUtils {
* 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 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
* @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);
@@ -265,8 +258,7 @@ public class StatsUtils {
* @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));
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);
}
@@ -279,8 +271,7 @@ public class StatsUtils {
* @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);
String value = Double.isNaN(coordinate) || Double.isInfinite(coordinate) ? null : StringUtils.formatCoordinate(coordinate);
setItem(context, view, labelId, value, null);
}
@@ -17,13 +17,9 @@ 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;
@@ -32,6 +28,8 @@ import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.dennisguse.opentracks.R;
/**
* Various string manipulation methods.
*
@@ -41,18 +39,17 @@ import java.util.regex.Pattern;
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}))?$");
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() {
}
@@ -63,9 +60,8 @@ public class StringUtils {
* @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);
return DateUtils.formatDateTime(context, time, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE)
+ " " + DateUtils.formatDateTime(context, time, DateUtils.FORMAT_SHOW_TIME);
}
/**
@@ -80,19 +76,20 @@ public class StringUtils {
/**
* Formats the elapsed timed in the form "MM:SS" or "H:MM:SS".
* TODO Remove as min API level is now 21; replace with DateUtils.formatElapsedTime(time * MS_TO_S).
*
* @param time the time in milliseconds
*/
public static String formatElapsedTime(long time) {
DateUtils.formatElapsedTime(1);
/*
* 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.
* 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);
@@ -135,6 +132,7 @@ public class StringUtils {
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;
@@ -153,11 +151,11 @@ public class StringUtils {
}
}
public static String formatDecimal(double value) {
static String formatDecimal(double value) {
return StringUtils.formatDecimal(value, 2);
}
public static String formatDecimal(double value, int decimalPlaces) {
static String formatDecimal(double value, int decimalPlaces) {
String result = String.format(Locale.getDefault(), "%1$,." + decimalPlaces + "f", value);
return result.replaceAll("[0]*$", "").replaceAll("\\.$", "");
}
@@ -172,14 +170,16 @@ public class StringUtils {
}
/**
* 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.
* 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
*/
//TODO Return Pair<Distance, Unit>
public static String[] getDistanceParts(Context context, double distance, boolean metricUnits) {
String[] result = new String[2];
if (Double.isNaN(distance) || Double.isInfinite(distance)) {
@@ -211,17 +211,18 @@ public class StringUtils {
}
/**
* 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.
* 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) {
//TODO Return Pair<Distance, Unit>
public static String[] getSpeedParts(Context context, double speed, boolean metricUnits, boolean reportSpeed) {
String[] result = new String[2];
int unitId;
if (metricUnits) {
@@ -268,7 +269,7 @@ public class StringUtils {
* @param category the category
* @param description the description
*/
public static String getCategoryDescription(String category, String description) {
static String getCategoryDescription(String category, String description) {
if (category == null || category.length() == 0) {
return description;
}
@@ -282,9 +283,9 @@ public class StringUtils {
}
/**
* 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.
* Formats the given text as a XML CDATA element.
* This includes adding the starting and ending CDATA tags.
* NOTE: This may result in multiple consecutive CDATA tags.
*
* @param text the given text
*/
@@ -293,25 +294,23 @@ public class StringUtils {
}
/**
* Gets the time, in milliseconds, from an XML date time string as defined at
* http://www.w3.org/TR/xmlschema-2/#dateTime
* 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
*/
//TODO Can this be replaced using java.time?
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() + ")");
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.
// 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);
}
@@ -354,9 +353,8 @@ public class StringUtils {
}
/**
* 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.
* 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.
@@ -379,22 +377,6 @@ public class StringUtils {
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.
*
@@ -409,9 +391,7 @@ public class StringUtils {
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));
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);
}
@@ -33,12 +33,12 @@ 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;
import de.dennisguse.opentracks.R;
/**
* Utilities for track icon.
*
@@ -55,31 +55,16 @@ public class TrackIconUtils {
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 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<>();
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));
@@ -173,10 +158,8 @@ public class TrackIconUtils {
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)}) {
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) {
@@ -211,8 +194,7 @@ public class TrackIconUtils {
* @param menu the menu
*/
public static void setMenuIconColor(Menu menu) {
int size = menu.size();
for (int i = 0; i < size; i++) {
for (int i = 0; i < menu.size(); i++) {
MenuItem menuitem = menu.getItem(i);
setMenuIconColor(menuitem);
}
@@ -32,6 +32,7 @@ import de.dennisguse.opentracks.R;
*/
public class TrackNameUtils {
//TODO Could be available in java.time?
@VisibleForTesting
static final String ISO_8601_FORMAT = "yyyy-MM-dd HH:mm";
@@ -24,14 +24,14 @@ import android.content.Intent;
import android.util.Log;
import android.widget.Toast;
import java.util.List;
import de.dennisguse.opentracks.R;
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}.
@@ -52,6 +52,11 @@ public class TrackRecordingServiceConnectionUtils {
*/
public static boolean isRecordingServiceRunning(Context context) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
if (activityManager == null) {
return false;
}
//TODO This approach is deprecated as of API level 26 and should be replaced.
List<RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE);
for (RunningServiceInfo serviceInfo : services) {
@@ -99,13 +104,12 @@ public class TrackRecordingServiceConnectionUtils {
*/
public static void stopRecording(Context context, TrackRecordingServiceConnection trackRecordingServiceConnection, boolean showEditor) {
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
if (trackRecordingService != null) {
if (trackRecordingService == null) {
resetRecordingState(context);
} else {
try {
if (showEditor) {
/*
* Need to remember the recordingTrackId before calling
* endCurrentTrack. endCurrentTrack sets the value to -1L.
*/
// Need to remember the recordingTrackId before calling endCurrentTrack() as 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) {
@@ -118,10 +122,9 @@ public class TrackRecordingServiceConnectionUtils {
trackRecordingService.endCurrentTrack();
}
} catch (Exception e) {
//TODO What exception are we catching here? Should be removed...
Log.e(TAG, "Unable to stop recording.", e);
}
} else {
resetRecordingState(context);
}
trackRecordingServiceConnection.unbindAndStop();
}
@@ -130,8 +133,7 @@ public class TrackRecordingServiceConnectionUtils {
* Resumes the track recording service connection.
*
* @param context the context
* @param trackRecordingServiceConnection the track recording service
* connection
* @param trackRecordingServiceConnection the track recording service connection
*/
public static void startConnection(Context context, TrackRecordingServiceConnection trackRecordingServiceConnection) {
trackRecordingServiceConnection.bindIfStarted();
@@ -143,25 +145,19 @@ public class TrackRecordingServiceConnectionUtils {
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);
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);
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);
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();
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 {
@@ -175,6 +171,7 @@ public class TrackRecordingServiceConnectionUtils {
Log.e(TAG, "Unable to add marker.", e);
}
}
Toast.makeText(context, R.string.marker_add_error, Toast.LENGTH_LONG).show();
return -1L;
}
@@ -24,6 +24,7 @@ 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;
@@ -24,13 +24,13 @@ import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.R;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* Activity to configure the track widget.
*
@@ -67,34 +67,22 @@ public class TrackWidgetConfigActivity extends Activity {
addItem(list, R.string.stats_total_time);
addItem(list, reportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
addItem(list, R.string.stats_moving_time);
addItem(list,
reportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
addItem(list, reportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
configSpinner(item1, list, PreferencesUtils.getInt(
this, R.string.track_widget_item1, PreferencesUtils.TRACK_WIDGET_ITEM1_DEFAULT));
configSpinner(item2, list, PreferencesUtils.getInt(
this, R.string.track_widget_item2, PreferencesUtils.TRACK_WIDGET_ITEM2_DEFAULT));
configSpinner(item3, list, PreferencesUtils.getInt(
this, R.string.track_widget_item3, PreferencesUtils.TRACK_WIDGET_ITEM3_DEFAULT));
configSpinner(item4, list, PreferencesUtils.getInt(
this, R.string.track_widget_item4, PreferencesUtils.TRACK_WIDGET_ITEM4_DEFAULT));
configSpinner(item1, list, PreferencesUtils.getInt(this, R.string.track_widget_item1, PreferencesUtils.TRACK_WIDGET_ITEM1_DEFAULT));
configSpinner(item2, list, PreferencesUtils.getInt(this, R.string.track_widget_item2, PreferencesUtils.TRACK_WIDGET_ITEM2_DEFAULT));
configSpinner(item3, list, PreferencesUtils.getInt(this, R.string.track_widget_item3, PreferencesUtils.TRACK_WIDGET_ITEM3_DEFAULT));
configSpinner(item4, list, PreferencesUtils.getInt(this, R.string.track_widget_item4, PreferencesUtils.TRACK_WIDGET_ITEM4_DEFAULT));
findViewById(R.id.track_widget_config_add).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Push widget update to surface with newly set prefix
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(
TrackWidgetConfigActivity.this);
TrackWidgetProvider.updateAppWidget(
TrackWidgetConfigActivity.this, appWidgetManager, appWidgetId, -1L);
PreferencesUtils.setInt(TrackWidgetConfigActivity.this, R.string.track_widget_item1,
item1.getSelectedItemPosition());
PreferencesUtils.setInt(TrackWidgetConfigActivity.this, R.string.track_widget_item2,
item2.getSelectedItemPosition());
PreferencesUtils.setInt(TrackWidgetConfigActivity.this, R.string.track_widget_item3,
item3.getSelectedItemPosition());
PreferencesUtils.setInt(TrackWidgetConfigActivity.this, R.string.track_widget_item4,
item4.getSelectedItemPosition());
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(TrackWidgetConfigActivity.this);
TrackWidgetProvider.updateAppWidget(TrackWidgetConfigActivity.this, appWidgetManager, appWidgetId, -1L);
PreferencesUtils.setInt(TrackWidgetConfigActivity.this, R.string.track_widget_item1, item1.getSelectedItemPosition());
PreferencesUtils.setInt(TrackWidgetConfigActivity.this, R.string.track_widget_item2, item2.getSelectedItemPosition());
PreferencesUtils.setInt(TrackWidgetConfigActivity.this, R.string.track_widget_item3, item3.getSelectedItemPosition());
PreferencesUtils.setInt(TrackWidgetConfigActivity.this, R.string.track_widget_item4, item4.getSelectedItemPosition());
// Make sure we pass back the original appWidgetId
Intent resultValue = new Intent();
@@ -113,9 +101,7 @@ public class TrackWidgetConfigActivity extends Activity {
// Find the app widget id from the intent.
Intent intent = getIntent();
Bundle extras = intent.getExtras();
appWidgetId = extras != null ? extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
: AppWidgetManager.INVALID_APPWIDGET_ID;
appWidgetId = extras != null ? extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID) : AppWidgetManager.INVALID_APPWIDGET_ID;
// If they gave us an intent without the widget id, just bail.
if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
@@ -128,8 +114,7 @@ public class TrackWidgetConfigActivity extends Activity {
}
private void configSpinner(Spinner spinner, List<CharSequence> list, int position) {
ArrayAdapter<CharSequence> adapter = new ArrayAdapter<>(
this, android.R.layout.simple_spinner_item, list);
ArrayAdapter<CharSequence> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, list);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
spinner.setSelection(position);
@@ -43,9 +43,7 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
/**
* A track widget to start/stop/pause/resume recording, launch the main activity, and
* display track statistics (total distance, total time, average speed, and
* moving time) for the recording track, the selected track or the last track.
* A track widget to start/stop/pause/resume recording, launch the main activity, and display track statistics (total distance, total time, average speed, and moving time) for the recording track, the selected track or the last track.
*
* @author Sandor Dornbush
* @author Paul R. Saxman
@@ -60,18 +58,10 @@ public class TrackWidgetProvider extends AppWidgetProvider {
private static final int THREE_CELLS = 180;
private static final int FOUR_CELLS = 250;
private static final int[] ITEM1_IDS = {R.id.track_widget_item1_label,
R.id.track_widget_item1_value, R.id.track_widget_item1_unit,
R.id.track_widget_item1_chronometer};
private static final int[] ITEM2_IDS = {R.id.track_widget_item2_label,
R.id.track_widget_item2_value, R.id.track_widget_item2_unit,
R.id.track_widget_item2_chronometer};
private static final int[] ITEM3_IDS = {R.id.track_widget_item3_label,
R.id.track_widget_item3_value, R.id.track_widget_item3_unit,
R.id.track_widget_item3_chronometer};
private static final int[] ITEM4_IDS = {R.id.track_widget_item4_label,
R.id.track_widget_item4_value, R.id.track_widget_item4_unit,
R.id.track_widget_item4_chronometer};
private static final int[] ITEM1_IDS = {R.id.track_widget_item1_label, R.id.track_widget_item1_value, R.id.track_widget_item1_unit, R.id.track_widget_item1_chronometer};
private static final int[] ITEM2_IDS = {R.id.track_widget_item2_label, R.id.track_widget_item2_value, R.id.track_widget_item2_unit, R.id.track_widget_item2_chronometer};
private static final int[] ITEM3_IDS = {R.id.track_widget_item3_label, R.id.track_widget_item3_value, R.id.track_widget_item3_unit, R.id.track_widget_item3_chronometer};
private static final int[] ITEM4_IDS = {R.id.track_widget_item4_label, R.id.track_widget_item4_value, R.id.track_widget_item4_unit, R.id.track_widget_item4_chronometer};
/**
* Updates an app widget.
@@ -81,8 +71,7 @@ public class TrackWidgetProvider extends AppWidgetProvider {
* @param appWidgetId the app widget id
* @param trackId the track id. -1L to not specify one
*/
public static void updateAppWidget(
Context context, AppWidgetManager appWidgetManager, int appWidgetId, long trackId) {
public static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId, long trackId) {
int size = getAppWidgetSize(appWidgetManager, appWidgetId);
RemoteViews remoteViews = getRemoteViews(context, trackId, size);
appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
@@ -96,8 +85,7 @@ public class TrackWidgetProvider extends AppWidgetProvider {
*/
private static void updateAllAppWidgets(Context context, long trackId) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(
new ComponentName(context, TrackWidgetProvider.class));
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, TrackWidgetProvider.class));
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId, trackId);
}
@@ -134,41 +122,31 @@ public class TrackWidgetProvider extends AppWidgetProvider {
// Get the preferences
long recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key);
boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
boolean isPaused = PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key,
PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
boolean isPaused = PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
boolean reportSpeed = PreferencesUtils.isReportSpeed(context);
int item1 = PreferencesUtils.getInt(
context, R.string.track_widget_item1, PreferencesUtils.TRACK_WIDGET_ITEM1_DEFAULT);
int item2 = PreferencesUtils.getInt(
context, R.string.track_widget_item2, PreferencesUtils.TRACK_WIDGET_ITEM2_DEFAULT);
int item1 = PreferencesUtils.getInt(context, R.string.track_widget_item1, PreferencesUtils.TRACK_WIDGET_ITEM1_DEFAULT);
int item2 = PreferencesUtils.getInt(context, R.string.track_widget_item2, PreferencesUtils.TRACK_WIDGET_ITEM2_DEFAULT);
// Get track and trip statistics
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
if (trackId == -1L) {
trackId = recordingTrackId;
}
Track track = trackId != -1L ? contentProviderUtils.getTrack(trackId)
: contentProviderUtils.getLastTrack();
Track track = trackId != -1L ? contentProviderUtils.getTrack(trackId) : contentProviderUtils.getLastTrack();
TripStatistics tripStatistics = track == null ? null : track.getTripStatistics();
updateStatisticsContainer(context, remoteViews, track);
setItem(context, remoteViews, ITEM1_IDS, item1, tripStatistics, isRecording, isPaused,
metricUnits, reportSpeed);
setItem(context, remoteViews, ITEM2_IDS, item2, tripStatistics, isRecording, isPaused,
metricUnits, reportSpeed);
setItem(context, remoteViews, ITEM1_IDS, item1, tripStatistics, isRecording, isPaused, metricUnits, reportSpeed);
setItem(context, remoteViews, ITEM2_IDS, item2, tripStatistics, isRecording, isPaused, metricUnits, reportSpeed);
updateRecordButton(context, remoteViews, isRecording, isPaused);
updateStopButton(context, remoteViews, isRecording);
if (heightSize > 1) {
int item3 = PreferencesUtils.getInt(
context, R.string.track_widget_item3, PreferencesUtils.TRACK_WIDGET_ITEM3_DEFAULT);
int item4 = PreferencesUtils.getInt(
context, R.string.track_widget_item4, PreferencesUtils.TRACK_WIDGET_ITEM4_DEFAULT);
setItem(context, remoteViews, ITEM3_IDS, item3, tripStatistics, isRecording, isPaused,
metricUnits, reportSpeed);
setItem(context, remoteViews, ITEM4_IDS, item4, tripStatistics, isRecording, isPaused,
metricUnits, reportSpeed);
int item3 = PreferencesUtils.getInt(context, R.string.track_widget_item3, PreferencesUtils.TRACK_WIDGET_ITEM3_DEFAULT);
int item4 = PreferencesUtils.getInt(context, R.string.track_widget_item4, PreferencesUtils.TRACK_WIDGET_ITEM4_DEFAULT);
setItem(context, remoteViews, ITEM3_IDS, item3, tripStatistics, isRecording, isPaused, metricUnits, reportSpeed);
setItem(context, remoteViews, ITEM4_IDS, item4, tripStatistics, isRecording, isPaused, metricUnits, reportSpeed);
updateRecordStatus(context, remoteViews, isRecording, isPaused);
}
return remoteViews;
@@ -196,9 +174,7 @@ public class TrackWidgetProvider extends AppWidgetProvider {
* @param metricUnits true to use metric units
* @param reportSpeed try to report speed
*/
private static void setItem(Context context, RemoteViews remoteViews, int[] ids, int value,
TripStatistics tripStatistics, boolean isRecording, boolean isPaused, boolean metricUnits,
boolean reportSpeed) {
private static void setItem(Context context, RemoteViews remoteViews, int[] ids, int value, TripStatistics tripStatistics, boolean isRecording, boolean isPaused, boolean metricUnits, boolean reportSpeed) {
switch (value) {
case 0:
updateDistance(context, remoteViews, ids, tripStatistics, metricUnits);
@@ -213,8 +189,7 @@ public class TrackWidgetProvider extends AppWidgetProvider {
updateMovingTime(context, remoteViews, ids, tripStatistics);
break;
case 4:
updateAverageMovingSpeed(
context, remoteViews, ids, tripStatistics, metricUnits, reportSpeed);
updateAverageMovingSpeed(context, remoteViews, ids, tripStatistics, metricUnits, reportSpeed);
break;
default:
updateDistance(context, remoteViews, ids, tripStatistics, metricUnits);
@@ -236,8 +211,7 @@ public class TrackWidgetProvider extends AppWidgetProvider {
* @param remoteViews the remote views
* @param track the track
*/
private static void updateStatisticsContainer(
Context context, RemoteViews remoteViews, Track track) {
private static void updateStatisticsContainer(Context context, RemoteViews remoteViews, Track track) {
PendingIntent pendingIntent;
if (track != null) {
Intent intent = IntentUtils.newIntent(context, TrackDetailActivity.class)
@@ -260,8 +234,7 @@ public class TrackWidgetProvider extends AppWidgetProvider {
* @param tripStatistics the trip statistics
* @param metricUnits true to use metric units
*/
private static void updateDistance(Context context, RemoteViews remoteViews, int[] ids,
TripStatistics tripStatistics, boolean metricUnits) {
private static void updateDistance(Context context, RemoteViews remoteViews, int[] ids, TripStatistics tripStatistics, boolean metricUnits) {
double totalDistance = tripStatistics == null ? Double.NaN : tripStatistics.getTotalDistance();
String[] totalDistanceParts = StringUtils.getDistanceParts(context, totalDistance, metricUnits);
if (totalDistanceParts[0] == null) {
@@ -280,11 +253,9 @@ public class TrackWidgetProvider extends AppWidgetProvider {
* @param ids the item's ids
* @param tripStatistics the trip statistics
*/
private static void updateTotalTime(Context context, RemoteViews remoteViews, int[] ids,
TripStatistics tripStatistics, boolean isRecording, boolean isPaused) {
private static void updateTotalTime(Context context, RemoteViews remoteViews, int[] ids, TripStatistics tripStatistics, boolean isRecording, boolean isPaused) {
if (isRecording && !isPaused && tripStatistics != null) {
long time = tripStatistics.getTotalTime() + System.currentTimeMillis()
- tripStatistics.getStopTime();
long time = tripStatistics.getTotalTime() + System.currentTimeMillis() - tripStatistics.getStopTime();
remoteViews.setChronometer(ids[3], SystemClock.elapsedRealtime() - time, null, true);
remoteViews.setViewVisibility(ids[1], View.GONE);
remoteViews.setViewVisibility(ids[2], View.GONE);
@@ -295,8 +266,7 @@ public class TrackWidgetProvider extends AppWidgetProvider {
remoteViews.setViewVisibility(ids[2], View.GONE);
remoteViews.setViewVisibility(ids[3], View.GONE);
String totalTime = tripStatistics == null ? context.getString(R.string.value_unknown)
: StringUtils.formatElapsedTime(tripStatistics.getTotalTime());
String totalTime = tripStatistics == null ? context.getString(R.string.value_unknown) : StringUtils.formatElapsedTime(tripStatistics.getTotalTime());
remoteViews.setTextViewText(ids[0], context.getString(R.string.stats_total_time));
remoteViews.setTextViewText(ids[1], totalTime);
}
@@ -312,10 +282,8 @@ public class TrackWidgetProvider extends AppWidgetProvider {
* @param metricUnits true to use metric units
* @param reportSpeed true to report speed
*/
private static void updateAverageSpeed(Context context, RemoteViews remoteViews, int[] ids,
TripStatistics tripStatistics, boolean metricUnits, boolean reportSpeed) {
String averageSpeedLabel = context.getString(
reportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
private static void updateAverageSpeed(Context context, RemoteViews remoteViews, int[] ids, TripStatistics tripStatistics, boolean metricUnits, boolean reportSpeed) {
String averageSpeedLabel = context.getString(reportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
remoteViews.setTextViewText(ids[0], averageSpeedLabel);
double speed = tripStatistics == null ? Double.NaN : tripStatistics.getAverageSpeed();
@@ -337,10 +305,8 @@ public class TrackWidgetProvider extends AppWidgetProvider {
* @param ids the item's ids
* @param tripStatistics the trip statistics
*/
private static void updateMovingTime(
Context context, RemoteViews remoteViews, int[] ids, TripStatistics tripStatistics) {
String movingTime = tripStatistics == null ? context.getString(R.string.value_unknown)
: StringUtils.formatElapsedTime(tripStatistics.getMovingTime());
private static void updateMovingTime(Context context, RemoteViews remoteViews, int[] ids, TripStatistics tripStatistics) {
String movingTime = tripStatistics == null ? context.getString(R.string.value_unknown) : StringUtils.formatElapsedTime(tripStatistics.getMovingTime());
remoteViews.setTextViewText(ids[0], context.getString(R.string.stats_moving_time));
remoteViews.setTextViewText(ids[1], movingTime);
remoteViews.setViewVisibility(ids[2], View.GONE);
@@ -356,10 +322,8 @@ public class TrackWidgetProvider extends AppWidgetProvider {
* @param metricUnits true to use metric units
* @param reportSpeed true to report speed
*/
private static void updateAverageMovingSpeed(Context context, RemoteViews remoteViews, int[] ids,
TripStatistics tripStatistics, boolean metricUnits, boolean reportSpeed) {
String averageMovingSpeedLabel = context.getString(
reportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
private static void updateAverageMovingSpeed(Context context, RemoteViews remoteViews, int[] ids, TripStatistics tripStatistics, boolean metricUnits, boolean reportSpeed) {
String averageMovingSpeedLabel = context.getString(reportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
remoteViews.setTextViewText(ids[0], averageMovingSpeedLabel);
double speed = tripStatistics == null ? Double.NaN : tripStatistics.getAverageMovingSpeed();
@@ -381,21 +345,16 @@ public class TrackWidgetProvider extends AppWidgetProvider {
* @param isRecording true if recording
* @param recordingTrackPaused true if recording track is paused
*/
private static void updateRecordButton(
Context context, RemoteViews remoteViews, boolean isRecording, boolean recordingTrackPaused) {
remoteViews.setImageViewResource(R.id.track_widget_record_button,
isRecording && !recordingTrackPaused ? R.drawable.button_pause : R.drawable.button_record);
private static void updateRecordButton(Context context, RemoteViews remoteViews, boolean isRecording, boolean recordingTrackPaused) {
remoteViews.setImageViewResource(R.id.track_widget_record_button, isRecording && !recordingTrackPaused ? R.drawable.button_pause : R.drawable.button_record);
int recordActionId;
if (isRecording) {
recordActionId = recordingTrackPaused ? R.string.track_action_resume
: R.string.track_action_pause;
recordActionId = recordingTrackPaused ? R.string.track_action_resume : R.string.track_action_pause;
} else {
recordActionId = R.string.track_action_start;
}
Intent intent = new Intent(context, ControlRecordingService.class).setAction(
context.getString(recordActionId));
PendingIntent pendingIntent = PendingIntent.getService(
context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Intent intent = new Intent(context, ControlRecordingService.class).setAction(context.getString(recordActionId));
PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.track_widget_record_button, pendingIntent);
}
@@ -406,16 +365,12 @@ public class TrackWidgetProvider extends AppWidgetProvider {
* @param remoteViews the remote views
* @param isRecording true if recording
*/
private static void updateStopButton(
Context context, RemoteViews remoteViews, boolean isRecording) {
remoteViews.setImageViewResource(R.id.track_widget_stop_button,
isRecording ? R.drawable.button_stop : R.drawable.ic_button_stop_disabled);
private static void updateStopButton(Context context, RemoteViews remoteViews, boolean isRecording) {
remoteViews.setImageViewResource(R.id.track_widget_stop_button, isRecording ? R.drawable.button_stop : R.drawable.ic_button_stop_disabled);
remoteViews.setBoolean(R.id.track_widget_stop_button, "setEnabled", isRecording);
if (isRecording) {
Intent intent = new Intent(context, ControlRecordingService.class).setAction(
context.getString(R.string.track_action_end));
PendingIntent pendingIntent = PendingIntent.getService(
context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Intent intent = new Intent(context, ControlRecordingService.class).setAction(context.getString(R.string.track_action_end));
PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.track_widget_stop_button, pendingIntent);
}
}
@@ -428,20 +383,17 @@ public class TrackWidgetProvider extends AppWidgetProvider {
* @param isRecording true if recording
* @param recordingTrackPaused true if recording track is paused
*/
private static void updateRecordStatus(
Context context, RemoteViews remoteViews, boolean isRecording, boolean recordingTrackPaused) {
private static void updateRecordStatus(Context context, RemoteViews remoteViews, boolean isRecording, boolean recordingTrackPaused) {
String status;
int colorId;
if (isRecording) {
status = context.getString(
recordingTrackPaused ? R.string.generic_paused : R.string.generic_recording);
status = context.getString(recordingTrackPaused ? R.string.generic_paused : R.string.generic_recording);
colorId = recordingTrackPaused ? android.R.color.white : R.color.recording_text;
} else {
status = "";
colorId = android.R.color.white;
}
remoteViews.setTextColor(
R.id.track_widget_record_status, context.getResources().getColor(colorId));
remoteViews.setTextColor(R.id.track_widget_record_status, context.getResources().getColor(colorId));
remoteViews.setTextViewText(R.id.track_widget_record_status, status);
}
@@ -475,13 +427,11 @@ public class TrackWidgetProvider extends AppWidgetProvider {
@TargetApi(16)
@Override
public void onAppWidgetOptionsChanged(
Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
if (newOptions != null) {
int newSize;
if (newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, -1)
== AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) {
if (newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, -1) == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) {
newSize = 1;
} else {
int height = newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT);