Rebranding to OpenTracks (code).

This commit is contained in:
Dennis Guse
2019-07-24 06:28:17 +02:00
parent cd52dc29c9
commit 233522a5e6
150 changed files with 690 additions and 693 deletions
@@ -0,0 +1,48 @@
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.fragments;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import de.dennisguse.opentracks.util.DialogUtils;
/**
* @author Jimmy Shih
*/
public abstract class AbstractDialogFragment extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Dialog dialog = createDialog();
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialogInterface) {
DialogUtils.setDialogTitleDivider(getActivity(), dialog);
}
});
return dialog;
}
protected abstract Dialog createDialog();
}
@@ -0,0 +1,521 @@
/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.fragments;
import android.location.Location;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.ZoomControls;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import de.dennisguse.opentracks.ChartView;
import de.dennisguse.opentracks.TrackDetailActivity;
import de.dennisguse.opentracks.content.SensorDataSetLocation;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.TrackDataHub;
import de.dennisguse.opentracks.content.TrackDataListener;
import de.dennisguse.opentracks.content.TrackDataType;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.stats.TripStatistics;
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.
*
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public class ChartFragment extends Fragment implements TrackDataListener {
public static final String CHART_FRAGMENT_TAG = "chartFragment";
private final ArrayList<double[]> pendingPoints = new ArrayList<>();
private TrackDataHub trackDataHub;
// Stats gathered from the received data
private TripStatisticsUpdater tripStatisticsUpdater;
private long startTime;
private boolean metricUnits = true;
private boolean reportSpeed = true;
private int recordingDistanceInterval = PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT;
// Modes of operation
private boolean chartByDistance = true;
private boolean[] chartShow = new boolean[]{true, true, true, true, true, true};
// UI elements
private ChartView chartView;
private ZoomControls zoomControls;
/**
* A runnable that will enable/disable zoom controls and orange pointer as
* appropriate and redraw.
*/
private final Runnable updateChart = new Runnable() {
@Override
public void run() {
if (!isResumed() || trackDataHub == null) {
return;
}
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
chartView.setShowPointer(isSelectedTrackRecording());
chartView.invalidate();
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/*
* 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) {
View view = inflater.inflate(R.layout.chart, container, false);
zoomControls = view.findViewById(R.id.chart_zoom_controls);
zoomControls.setOnZoomInClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
zoomIn();
}
});
zoomControls.setOnZoomOutClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
zoomOut();
}
});
return view;
}
@Override
public void onStart() {
super.onStart();
ViewGroup layout = getActivity().findViewById(R.id.chart_view_layout);
LayoutParams layoutParams = new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
layout.addView(chartView, layoutParams);
}
@Override
public void onResume() {
super.onResume();
resumeTrackDataHub();
checkChartSettings();
getActivity().runOnUiThread(updateChart);
}
@Override
public void onPause() {
super.onPause();
pauseTrackDataHub();
}
@Override
public void onStop() {
super.onStop();
ViewGroup layout = getActivity().findViewById(R.id.chart_view_layout);
layout.removeView(chartView);
}
@Override
public void onTrackUpdated(Track track) {
if (isResumed()) {
if (track == null || track.getTripStatistics() == null) {
startTime = -1L;
return;
}
startTime = track.getTripStatistics().getStartTime();
}
}
@Override
public void clearTrackPoints() {
if (isResumed()) {
tripStatisticsUpdater = startTime != -1L ? new TripStatisticsUpdater(startTime) : null;
pendingPoints.clear();
chartView.reset();
runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
chartView.resetScroll();
}
}
});
}
}
@Override
public void onSampledInTrackPoint(Location location) {
if (isResumed()) {
double[] data = new double[ChartView.NUM_SERIES + 1];
fillDataPoint(location, data);
pendingPoints.add(data);
}
}
@Override
public void onSampledOutTrackPoint(Location location) {
if (isResumed()) {
fillDataPoint(location, null);
}
}
@Override
public void onSegmentSplit(Location location) {
if (isResumed()) {
fillDataPoint(location, null);
}
}
@Override
public void onNewTrackPointsDone() {
if (isResumed()) {
chartView.addDataPoints(pendingPoints);
pendingPoints.clear();
runOnUiThread(updateChart);
}
}
@Override
public void clearWaypoints() {
if (isResumed()) {
chartView.clearWaypoints();
}
}
@Override
public void onNewWaypoint(Waypoint waypoint) {
if (isResumed() && waypoint != null && LocationUtils.isValidLocation(waypoint.getLocation())) {
chartView.addWaypoint(waypoint);
}
}
@Override
public void onNewWaypointsDone() {
if (isResumed()) {
runOnUiThread(updateChart);
}
}
@Override
public boolean onMetricUnitsChanged(boolean metric) {
if (isResumed()) {
if (metricUnits == metric) {
return false;
}
metricUnits = metric;
chartView.setMetricUnits(metricUnits);
runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
chartView.requestLayout();
}
}
});
return true;
}
return false;
}
@Override
public boolean onReportSpeedChanged(boolean speed) {
if (isResumed()) {
if (reportSpeed == speed) {
return false;
}
reportSpeed = speed;
chartView.setReportSpeed(reportSpeed);
boolean chartShowSpeed = PreferencesUtils.getBoolean(
getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT);
setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed);
setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed);
runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
chartView.requestLayout();
}
}
});
return true;
}
return false;
}
@Override
public boolean onRecordingGpsAccuracy(int minRequiredAccuracy) {
// We don't care.
return false;
}
@Override
public boolean onRecordingDistanceIntervalChanged(int value) {
if (isResumed()) {
if (recordingDistanceInterval == value) {
return false;
}
recordingDistanceInterval = value;
return true;
}
return false;
}
/**
* Checks the chart settings.
*/
private void checkChartSettings() {
boolean needUpdate = false;
if (chartByDistance != PreferencesUtils.isChartByDistance(getActivity())) {
chartByDistance = !chartByDistance;
chartView.setChartByDistance(chartByDistance);
reloadTrackDataHub();
needUpdate = true;
}
if (setSeriesEnabled(ChartView.ELEVATION_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_elevation_key, PreferencesUtils.CHART_SHOW_ELEVATION_DEFAULT))) {
needUpdate = true;
}
boolean chartShowSpeed = PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT);
if (setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed)) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed)) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.POWER_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_power_key, PreferencesUtils.CHART_SHOW_POWER_DEFAULT))) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.CADENCE_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_cadence_key, PreferencesUtils.CHART_SHOW_CADENCE_DEFAULT))) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.HEART_RATE_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_heart_rate_key, PreferencesUtils.CHART_SHOW_HEART_RATE_DEFAULT))) {
needUpdate = true;
}
if (needUpdate) {
chartView.postInvalidate();
}
}
/**
* Sets the series enabled value.
*
* @param index the series index
* @param value the value
* @return true if changed
*/
private boolean setSeriesEnabled(int index, boolean value) {
if (chartShow[index] != value) {
chartShow[index] = value;
chartView.setChartValueSeriesEnabled(index, value);
return true;
} else {
return false;
}
}
/**
* Resumes the trackDataHub. Needs to be synchronized because trackDataHub can
* be accessed by multiple threads.
*/
private synchronized void resumeTrackDataHub() {
trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, EnumSet.of(TrackDataType.TRACKS_TABLE,
TrackDataType.WAYPOINTS_TABLE, TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE,
TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE, TrackDataType.PREFERENCE));
}
/**
* Pauses the trackDataHub. Needs to be synchronized because trackDataHub can
* be accessed by multiple threads.
*/
private synchronized void pauseTrackDataHub() {
trackDataHub.unregisterTrackDataListener(this);
trackDataHub = null;
}
/**
* Returns true if the selected track is recording. Needs to be synchronized
* because trackDataHub can be accessed by multiple threads.
*/
private synchronized boolean isSelectedTrackRecording() {
return trackDataHub != null && trackDataHub.isSelectedTrackRecording();
}
/**
* Reloads the trackDataHub. Needs to be synchronized because trackDataHub can
* be accessed by multiple threads.
*/
private synchronized void reloadTrackDataHub() {
if (trackDataHub != null) {
trackDataHub.reloadDataForListener(this);
}
}
/**
* To zoom in.
*/
private void zoomIn() {
chartView.zoomIn();
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
}
/**
* To zoom out.
*/
private void zoomOut() {
chartView.zoomOut();
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
}
/**
* Runs a runnable on the UI thread if possible.
*
* @param runnable the runnable
*/
private void runOnUiThread(Runnable runnable) {
FragmentActivity fragmentActivity = getActivity();
if (fragmentActivity != null) {
fragmentActivity.runOnUiThread(runnable);
}
}
/**
* Given a location, fill in a data point, an array of double[]. <br>
* data[0] = time/distance <br>
* data[1] = elevation <br>
* data[2] = speed <br>
* data[3] = pace <br>
* data[4] = heart rate <br>
* data[5] = cadence <br>
* data[6] = power <br>
*
* @param location the location
* @param data the data point to fill in, can be null
*/
@VisibleForTesting
void fillDataPoint(Location location, double[] data) {
double timeOrDistance = Double.NaN;
double elevation = Double.NaN;
double speed = Double.NaN;
double pace = Double.NaN;
double heartRate = Double.NaN;
double cadence = Double.NaN;
double power = Double.NaN;
if (tripStatisticsUpdater != null) {
tripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
TripStatistics tripStatistics = tripStatisticsUpdater.getTripStatistics();
if (chartByDistance) {
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
if (!metricUnits) {
distance *= UnitConversions.KM_TO_MI;
}
timeOrDistance = distance;
} else {
timeOrDistance = tripStatistics.getTotalTime();
}
elevation = tripStatisticsUpdater.getSmoothedElevation();
if (!metricUnits) {
elevation *= UnitConversions.M_TO_FT;
}
speed = tripStatisticsUpdater.getSmoothedSpeed() * UnitConversions.MS_TO_KMH;
if (!metricUnits) {
speed *= UnitConversions.KM_TO_MI;
}
pace = speed == 0 ? 0.0 : 60.0 / speed;
}
if (location instanceof SensorDataSetLocation && ((SensorDataSetLocation) location).getSensorDataSet() != null) {
SensorDataSet sensorDataSet = ((SensorDataSetLocation) location).getSensorDataSet();
if (sensorDataSet.hasHeartRate()) {
heartRate = sensorDataSet.getHeartRate();
}
if (sensorDataSet.hasCadence()) {
cadence = sensorDataSet.getCadence();
}
if (sensorDataSet.hasPower()) {
power = sensorDataSet.getPower();
}
}
if (data != null) {
data[0] = timeOrDistance;
data[1] = elevation;
data[2] = speed;
data[3] = pace;
data[4] = heartRate;
data[5] = cadence;
data[6] = power;
}
}
@VisibleForTesting
void setTripStatisticsUpdater(long time) {
tripStatisticsUpdater = new TripStatisticsUpdater(time);
}
@VisibleForTesting
void setChartView(ChartView view) {
chartView = view;
}
@VisibleForTesting
void setMetricUnits(boolean value) {
metricUnits = value;
}
@VisibleForTesting
void setReportSpeed(boolean value) {
reportSpeed = value;
}
@VisibleForTesting
void setChartByDistance(boolean value) {
chartByDistance = value;
}
}
@@ -0,0 +1,155 @@
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.fragments;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.GridView;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import de.dennisguse.opentracks.util.DialogUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.R;
import java.util.ArrayList;
import java.util.List;
/**
* A DialogFragment to choose an activity type.
*
* @author apoorvn
*/
public class ChooseActivityTypeDialogFragment extends DialogFragment {
public static final String CHOOSE_ACTIVITY_TYPE_DIALOG_TAG = "chooseActivityType";
private static final String KEY_CATEGORY = "category";
private ChooseActivityTypeCaller caller;
public static ChooseActivityTypeDialogFragment newInstance(String category) {
Bundle bundle = new Bundle();
bundle.putString(KEY_CATEGORY, category);
ChooseActivityTypeDialogFragment fragment = new ChooseActivityTypeDialogFragment();
fragment.setArguments(bundle);
return fragment;
}
public static Dialog getDialog(final Activity activity, final String category, final ChooseActivityTypeCaller caller) {
View view = activity.getLayoutInflater().inflate(R.layout.choose_activity_type, null);
GridView gridView = view.findViewById(R.id.choose_activity_type_grid_view);
List<Integer> imageIds = new ArrayList<>();
for (String iconValue : TrackIconUtils.getAllIconValues()) {
imageIds.add(TrackIconUtils.getIconDrawable(iconValue));
}
Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(activity.getResources(), R.drawable.ic_track_airplane, options);
int padding = 32;
int width = options.outWidth + 2 * padding;
int height = options.outHeight + 2 * padding;
gridView.setColumnWidth(width);
final ChooseActivityTypeImageAdapter imageAdapter = new ChooseActivityTypeImageAdapter(activity, imageIds, width, height, padding);
gridView.setAdapter(imageAdapter);
final AlertDialog alertDialog = new AlertDialog.Builder(activity).setTitle(R.string.track_edit_activity_type_hint).setView(view).create();
alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
int position = getPosition(activity, category);
if (position != -1) {
imageAdapter.setSelected(position);
imageAdapter.notifyDataSetChanged();
}
DialogUtils.setDialogTitleDivider(activity, alertDialog);
}
});
gridView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
caller.onChooseActivityTypeDone(TrackIconUtils.getAllIconValues().get(position));
alertDialog.dismiss();
}
});
return alertDialog;
}
private static int getPosition(Activity activity, String category) {
if (category == null) {
return -1;
}
String iconValue = TrackIconUtils.getIconValue(activity, category);
if (iconValue.equals("")) {
return -1;
}
List<String> iconValues = TrackIconUtils.getAllIconValues();
for (int i = 0; i < iconValues.size(); i++) {
if (iconValues.get(i).equals(iconValue)) {
return i;
}
}
return -1;
}
private static void updateWeightContainer(View weightContainer, int position) {
boolean showWeight = position == 0 || position == 1 || position == 2;
weightContainer.setVisibility(showWeight ? View.VISIBLE : View.GONE);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
caller = (ChooseActivityTypeCaller) context;
} catch (ClassCastException e) {
throw new ClassCastException(context + " must implement " + ChooseActivityTypeCaller.class.getSimpleName());
}
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return getDialog(getActivity(), getArguments().getString(KEY_CATEGORY), caller);
}
/**
* Interface for caller of this dialog fragment.
*
* @author apoorvn
*/
public interface ChooseActivityTypeCaller {
/**
* Called when choose activity type is done.
*/
void onChooseActivityTypeDone(String iconValue);
}
}
@@ -0,0 +1,95 @@
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.fragments;
import android.content.Context;
import android.graphics.Color;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import de.dennisguse.opentracks.R;
import java.util.List;
/**
* Image adapter for choosing an activity type.
*
* @author apoorvn
*/
public class ChooseActivityTypeImageAdapter extends BaseAdapter {
private final Context context;
private final List<Integer> imageIds;
private final int width;
private final int height;
private final int padding;
private int selected = -1;
public ChooseActivityTypeImageAdapter(
Context context, List<Integer> imageIds, int width, int height, int padding) {
this.context = context;
this.imageIds = imageIds;
this.width = width;
this.height = height;
this.padding = padding;
}
@Override
public int getCount() {
return imageIds.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
public int getSelected() {
return selected;
}
public void setSelected(int position) {
selected = position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
imageView = new ImageView(context);
} else {
imageView = (ImageView) convertView;
}
if (position == selected) {
imageView.setBackgroundResource(R.drawable.list_selector_background_transition_holo_dark);
} else {
imageView.setBackgroundColor(Color.TRANSPARENT);
}
imageView.setImageResource(imageIds.get(position));
imageView.setMinimumHeight(height);
imageView.setMinimumWidth(width);
imageView.setPadding(padding, padding, padding, padding);
return imageView;
}
}
@@ -0,0 +1,96 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.fragments;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import de.dennisguse.opentracks.util.DialogUtils;
import de.dennisguse.opentracks.R;
/**
* A DialogFragment to confirm delete.
*
* @author Jimmy Shih
*/
public class ConfirmDeleteDialogFragment extends AbstractDialogFragment {
public static final String CONFIRM_DELETE_DIALOG_TAG = "confirmDeleteDialog";
private static final String KEY_TRACK_IDS = "trackIds";
private ConfirmDeleteCaller caller;
/**
* Create a new instance.
*
* @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();
bundle.putLongArray(KEY_TRACK_IDS, trackIds);
ConfirmDeleteDialogFragment deleteTrackDialogFragment = new ConfirmDeleteDialogFragment();
deleteTrackDialogFragment.setArguments(bundle);
return deleteTrackDialogFragment;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
caller = (ConfirmDeleteCaller) context;
} catch (ClassCastException e) {
throw new ClassCastException(context + " must implement " + ConfirmDeleteCaller.class.getSimpleName());
}
}
@Override
protected Dialog createDialog() {
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;
return DialogUtils.createConfirmationDialog(
getActivity(), titleId, getString(messageId), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
caller.onConfirmDeleteDone(trackIds);
}
});
}
/**
* Interface for caller of this dialog fragment.
*
* @author Jimmy Shih
*/
public interface ConfirmDeleteCaller {
/**
* Called when confirm delete is done.
*
* @param trackIds list of track ids to delete. To delete all, set to size 1
* with trackIds[0] == -1L
*/
void onConfirmDeleteDone(long[] trackIds);
}
}
@@ -0,0 +1,108 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.fragments;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import androidx.fragment.app.FragmentActivity;
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.
*
* @author Jimmy Shih
*/
public class DeleteMarkerDialogFragment extends AbstractDialogFragment {
public static final String DELETE_MARKER_DIALOG_TAG = "deleteMarkerDialog";
private static final String KEY_MARKER_IDS = "markerIds";
private DeleteMarkerCaller caller;
public static DeleteMarkerDialogFragment newInstance(long[] markerIds) {
Bundle bundle = new Bundle();
bundle.putLongArray(KEY_MARKER_IDS, markerIds);
DeleteMarkerDialogFragment deleteMarkerDialogFragment = new DeleteMarkerDialogFragment();
deleteMarkerDialogFragment.setArguments(bundle);
return deleteMarkerDialogFragment;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
caller = (DeleteMarkerCaller) context;
} catch (ClassCastException e) {
throw new ClassCastException(context + " must implement " + DeleteMarkerCaller.class.getSimpleName());
}
}
@Override
protected Dialog createDialog() {
final FragmentActivity fragmentActivity = getActivity();
final long[] markerIds = getArguments().getLongArray(KEY_MARKER_IDS);
int titleId;
int messageId;
if (markerIds.length == 1 && markerIds[0] == -1L) {
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;
}
return DialogUtils.createConfirmationDialog(
fragmentActivity, titleId, getString(messageId), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new Thread(new Runnable() {
@Override
public void run() {
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(
fragmentActivity);
for (long markerId : markerIds) {
contentProviderUtils.deleteWaypoint(fragmentActivity,
markerId, new DescriptionGeneratorImpl(fragmentActivity));
}
caller.onDeleteMarkerDone();
}
}).start();
}
});
}
/**
* Interface for caller of this dialog fragment.
*
* @author Jimmy Shih
*/
public interface DeleteMarkerCaller {
/**
* Called when delete marker is done.
*/
void onDeleteMarkerDone();
}
}
@@ -0,0 +1,98 @@
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.fragments;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.R;
/**
* A DialogFragment to select a file type, gpx, kml, etc.
*
* @author Jimmy Shih
*/
public class FileTypeDialogFragment extends AbstractDialogFragment {
public static final String FILE_TYPE_DIALOG_TAG = "fileType";
private static final String KEY_MENU_ID = "menuId";
private static final String KEY_TITLE_ID = "titleId";
private static final String KEY_OPTION_ID = "optionId";
private FileTypeCaller caller;
public static FileTypeDialogFragment newInstance(int menuId, int titleId, int optionId) {
Bundle bundle = new Bundle();
bundle.putInt(KEY_MENU_ID, menuId);
bundle.putInt(KEY_TITLE_ID, titleId);
bundle.putInt(KEY_OPTION_ID, optionId);
FileTypeDialogFragment fileTypeDialogFragment = new FileTypeDialogFragment();
fileTypeDialogFragment.setArguments(bundle);
return fileTypeDialogFragment;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
caller = (FileTypeCaller) context;
} catch (ClassCastException e) {
throw new ClassCastException(context + " must implement " + FileTypeCaller.class.getSimpleName());
}
}
@Override
protected Dialog createDialog() {
int optionId = getArguments().getInt(KEY_OPTION_ID);
final int titleId = getArguments().getInt(KEY_TITLE_ID);
final int menuId = getArguments().getInt(KEY_MENU_ID);
TrackFileFormat[] trackFileFormats = TrackFileFormat.values();
String[] choices = new String[trackFileFormats.length];
for (int i = 0; i < choices.length; i++) {
TrackFileFormat trackFileFormat = trackFileFormats[i];
choices[i] = getString(optionId, trackFileFormat.name(),
FileUtils.getPathDisplayName(trackFileFormat.getExtension()));
}
return new AlertDialog.Builder(getActivity()).setNegativeButton(R.string.generic_cancel, null)
.setPositiveButton(R.string.generic_ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int position = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
caller.onFileTypeDone(menuId, TrackFileFormat.values()[position]);
}
}).setSingleChoiceItems(choices, 0, null).setTitle(titleId).create();
}
/**
* Interface for caller of this dialog fragment.
*
* @author Jimmy Shih
*/
public interface FileTypeCaller {
/**
* Called when file type selection is done.
*/
void onFileTypeDone(int menuId, TrackFileFormat trackFileFormat);
}
}
@@ -0,0 +1,107 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.fragments;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import androidx.fragment.app.FragmentActivity;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.R;
/**
* A DialogFragment to configure frequency.
*
* @author Jimmy Shih
*/
public class FrequencyDialogFragment extends AbstractDialogFragment {
public static final String FREQUENCY_DIALOG_TAG = "frequencyDialog";
private static final String KEY_PREFERENCE_ID = "preferenceId";
private static final String KEY_DEFAULT_VALUE = "defaultValue";
private static final String KEY_TITLE_ID = "titleId";
public static FrequencyDialogFragment newInstance(
int preferenceId, int defaultValue, int titleId) {
Bundle bundle = new Bundle();
bundle.putInt(KEY_PREFERENCE_ID, preferenceId);
bundle.putInt(KEY_DEFAULT_VALUE, defaultValue);
bundle.putInt(KEY_TITLE_ID, titleId);
FrequencyDialogFragment frequencyDialogFragment = new FrequencyDialogFragment();
frequencyDialogFragment.setArguments(bundle);
return frequencyDialogFragment;
}
@Override
protected Dialog createDialog() {
FragmentActivity fragmentActivity = getActivity();
final int preferenceId = getArguments().getInt(KEY_PREFERENCE_ID);
int defaultValue = getArguments().getInt(KEY_DEFAULT_VALUE);
int titleId = getArguments().getInt(KEY_TITLE_ID);
int frequencyValue = PreferencesUtils.getInt(fragmentActivity, preferenceId, defaultValue);
return new AlertDialog.Builder(fragmentActivity).setPositiveButton(
R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int listIndex = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
PreferencesUtils.setInt(getActivity(), preferenceId, getFrequencyValue(listIndex));
}
}).setSingleChoiceItems(
getFrequencyDisplayOptions(fragmentActivity), getListIndex(frequencyValue), null)
.setTitle(titleId).create();
}
/**
* Gets the frequency display options.
*/
private String[] getFrequencyDisplayOptions(FragmentActivity fragmentActivity) {
boolean metricUnits = PreferencesUtils.isMetricUnits(fragmentActivity);
return StringUtils.getFrequencyOptions(fragmentActivity, metricUnits);
}
/**
* 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);
for (int i = 0; i < values.length; i++) {
if (frequencyValue == Integer.parseInt(values[i])) {
return i;
}
}
return 0;
}
/**
* Gets the frequency value from a list index.
*
* @param listIndex the list index
*/
private int getFrequencyValue(int listIndex) {
String[] values = getResources().getStringArray(R.array.frequency_values);
return Integer.parseInt(values[listIndex]);
}
}
@@ -0,0 +1,287 @@
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.fragments;
import android.content.Intent;
import android.location.Location;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import de.dennisguse.opentracks.MarkerEditActivity;
import de.dennisguse.opentracks.content.ContentProviderUtils;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.ListItemUtils;
import de.dennisguse.opentracks.util.StatsUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.R;
/**
* A fragment to show marker details.
*
* @author Jimmy Shih
*/
public class MarkerDetailFragment extends Fragment {
private static final String TAG = MarkerDetailFragment.class.getSimpleName();
private static final String KEY_MARKER_ID = "markerId";
private static final String KEY_TITLE = "title";
private static final long HIDE_TEXT_DELAY = 4000L; // 4 seconds
private ContentProviderUtils contentProviderUtils;
private Handler handler;
private ImageView photo;
private ImageView textGradient;
private LinearLayout waypointInfo;
private Waypoint waypoint;
private Runnable hideText = new Runnable() {
@Override
public void run() {
Animation animation = AnimationUtils.loadAnimation(getActivity(), R.anim.fadeout);
animation.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation anim) {
}
@Override
public void onAnimationRepeat(Animation anim) {
}
@Override
public void onAnimationEnd(Animation anim) {
textGradient.setVisibility(View.GONE);
waypointInfo.setVisibility(View.GONE);
}
});
textGradient.startAnimation(animation);
waypointInfo.startAnimation(animation);
}
};
public static MarkerDetailFragment newInstance(long markerId, String title) {
Bundle bundle = new Bundle();
bundle.putLong(KEY_MARKER_ID, markerId);
bundle.putString(KEY_TITLE, title);
MarkerDetailFragment fragment = new MarkerDetailFragment();
fragment.setArguments(bundle);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
long markerId = getArguments().getLong(KEY_MARKER_ID);
if (markerId == -1L) {
Log.d(TAG, "invalid marker id");
getFragmentManager().popBackStack();
return;
}
contentProviderUtils = ContentProviderUtils.Factory.get(getActivity());
handler = new Handler();
setHasOptionsMenu(true);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.marker_detail_fragment, container, false);
getActivity().setTitle(getArguments().getString(KEY_TITLE));
photo = view.findViewById(R.id.marker_detail_waypoint_photo);
textGradient = view.findViewById(R.id.marker_detail_waypoint_text_gradient);
waypointInfo = view.findViewById(R.id.marker_detail_waypoint_info);
photo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
handler.removeCallbacks(hideText);
int visibility = waypointInfo.getVisibility() == View.GONE ? View.VISIBLE : View.GONE;
textGradient.setVisibility(visibility);
waypointInfo.setVisibility(visibility);
if (visibility == View.VISIBLE) {
handler.postDelayed(hideText, HIDE_TEXT_DELAY);
}
}
});
return view;
}
@Override
public void onResume() {
super.onResume();
// Need to update the waypoint in case returning after an edit
updateWaypoint(true);
updateUi();
}
@Override
public void onPause() {
super.onPause();
handler.removeCallbacks(hideText);
}
@Override
public void setMenuVisibility(boolean menuVisible) {
super.setMenuVisibility(menuVisible);
/*
* View pager caches the neighboring fragments in the resumed state. If
* becoming visible from the resumed state, update the UI to display the
* text above the image.
*/
if (isResumed()) {
if (menuVisible) {
updateUi();
} else {
handler.removeCallbacks(hideText);
}
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.marker_detail, menu);
updateWaypoint(false);
TrackIconUtils.setMenuIconColor(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
long markerId = getArguments().getLong(KEY_MARKER_ID);
FragmentActivity fragmentActivity = getActivity();
Intent intent;
switch (item.getItemId()) {
case R.id.marker_detail_show_on_map:
intent = IntentUtils.newShowOnMapIntent(waypoint);
startActivity(intent);
return true;
case R.id.marker_detail_edit:
intent = IntentUtils.newIntent(fragmentActivity, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerId);
startActivity(intent);
return true;
case R.id.marker_detail_delete:
DeleteMarkerDialogFragment.newInstance(new long[]{markerId})
.show(getChildFragmentManager(), DeleteMarkerDialogFragment.DELETE_MARKER_DIALOG_TAG);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
/**
* Updates the waypoint.
*
* @param refresh true to always update
*/
private void updateWaypoint(boolean refresh) {
if (refresh || waypoint == null) {
waypoint = contentProviderUtils.getWaypoint(getArguments().getLong(KEY_MARKER_ID));
if (waypoint == null) {
Log.d(TAG, "waypoint is null");
getFragmentManager().popBackStack();
}
}
}
/**
* Updates the UI.
*/
private void updateUi() {
View waypointView = getView().findViewById(R.id.marker_detail_waypoint);
View statisticsView = getView().findViewById(R.id.marker_detail_statistics);
boolean isWaypoint = waypoint.getType() == WaypointType.WAYPOINT;
waypointView.setVisibility(isWaypoint ? View.VISIBLE : View.GONE);
statisticsView.setVisibility(isWaypoint ? View.GONE : View.VISIBLE);
if (isWaypoint) {
boolean hasPhoto = waypoint.hasPhoto();
photo.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
textGradient.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
waypointInfo.setVisibility(View.VISIBLE);
if (hasPhoto) {
handler.removeCallbacks(hideText);
photo.setImageURI(waypoint.getPhotoURI());
handler.postDelayed(hideText, HIDE_TEXT_DELAY);
}
setName(R.id.marker_detail_waypoint_name, hasPhoto);
TextView category = getView().findViewById(R.id.marker_detail_waypoint_category);
ListItemUtils.setTextView(getActivity(), category, StringUtils.getCategory(waypoint.getCategory()), hasPhoto);
TextView description = getView().findViewById(R.id.marker_detail_waypoint_description);
ListItemUtils.setTextView(getActivity(), description, waypoint.getDescription(), hasPhoto);
setLocation(R.id.marker_detail_waypoint_location, hasPhoto);
} else {
setName(R.id.marker_detail_statistics_name, false);
setLocation(R.id.marker_detail_statistics_location, false);
Track track = contentProviderUtils.getTrack(waypoint.getTrackId());
StatsUtils.setTripStatisticsValues(getActivity(), null, getView(), waypoint.getTripStatistics(), null);
StatsUtils.setLocationValues(getActivity(), null, getView(), waypoint.getLocation(), false);
}
}
private void setName(int resId, boolean addShadow) {
TextView textView = getView().findViewById(resId);
ListItemUtils.setTextView(getActivity(), textView, waypoint.getName(), addShadow);
}
private void setLocation(int resId, boolean addShadow) {
TextView textView = getView().findViewById(resId);
Location location = waypoint.getLocation();
String value;
if (location == null) {
value = null;
} else {
value = "[" + getString(R.string.stats_latitude) + " "
+ StringUtils.formatCoordinate(location.getLatitude()) + ", "
+ getString(R.string.stats_longitude) + " "
+ StringUtils.formatCoordinate(location.getLongitude()) + "]";
}
ListItemUtils.setTextView(getActivity(), textView, value, addShadow);
}
}
@@ -0,0 +1,324 @@
/*
* 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.fragments;
import android.location.Location;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Spinner;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import de.dennisguse.opentracks.TrackDetailActivity;
import de.dennisguse.opentracks.content.Track;
import de.dennisguse.opentracks.content.TrackDataHub;
import de.dennisguse.opentracks.content.TrackDataListener;
import de.dennisguse.opentracks.content.TrackDataType;
import de.dennisguse.opentracks.content.Waypoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.ITrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StatsUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.TrackRecordingServiceConnectionUtils;
import de.dennisguse.opentracks.util.UnitConversions;
import de.dennisguse.opentracks.R;
import java.util.EnumSet;
/**
* A fragment to display track statistics to the user.
*
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public class StatsFragment extends Fragment implements TrackDataListener {
public static final String STATS_FRAGMENT_TAG = "statsFragment";
private TrackDataHub trackDataHub;
private Handler handlerUpdateUI;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private Location lastLocation = null;
private TripStatistics lastTripStatistics = null;
private SensorDataSet sensorDataSet = null;
private final Runnable updateUIeachSecond = new Runnable() {
public void run() {
if (isResumed() && isSelectedTrackRecording()) {
if (!isSelectedTrackPaused() && lastTripStatistics != null) {
StatsUtils.setTotalTimeValue(getActivity(), System.currentTimeMillis() - lastTripStatistics.getStopTime() + lastTripStatistics.getTotalTime());
updateSensorDataUI();
}
handlerUpdateUI.postDelayed(this, UnitConversions.ONE_SECOND);
}
}
};
private String category = "";
private int recordingGpsAccuracy = PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.stats, container, false);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
handlerUpdateUI = new Handler();
trackRecordingServiceConnection = new TrackRecordingServiceConnection(getContext(), null);
TrackRecordingServiceConnectionUtils.startConnection(getContext(), trackRecordingServiceConnection);
Spinner activityTypeIcon = getView().findViewById(R.id.stats_activity_type_icon);
activityTypeIcon.setAdapter(TrackIconUtils.getIconSpinnerAdapter(getActivity(), ""));
activityTypeIcon.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
((TrackDetailActivity) getActivity()).chooseActivityType(category);
}
return true;
}
});
activityTypeIcon.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
((TrackDetailActivity) getActivity()).chooseActivityType(category);
}
return true;
}
});
}
@Override
public void onResume() {
super.onResume();
resumeTrackDataHub();
updateUi(getActivity());
if (isSelectedTrackRecording()) {
handlerUpdateUI.post(updateUIeachSecond);
}
}
@Override
public void onPause() {
super.onPause();
pauseTrackDataHub();
handlerUpdateUI.removeCallbacks(updateUIeachSecond);
}
@Override
public void onStop() {
super.onStop();
trackRecordingServiceConnection.unbind();
}
@Override
public void onTrackUpdated(final Track track) {
if (isResumed()) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
lastTripStatistics = track != null ? track.getTripStatistics() : null;
category = track != null ? track.getCategory() : "";
updateUi(getActivity());
}
}
});
}
}
@Override
public void clearTrackPoints() {
lastLocation = null;
}
@Override
public void onSampledInTrackPoint(Location location) {
lastLocation = location;
}
@Override
public void onSampledOutTrackPoint(Location location) {
lastLocation = location;
}
@Override
public void onSegmentSplit(Location location) {
// We don't care.
}
@Override
public void onNewTrackPointsDone() {
if (isResumed()) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
if (!isSelectedTrackRecording() || isSelectedTrackPaused()) {
lastLocation = null;
}
if (lastLocation != null) {
boolean hasFix = !LocationUtils.isLocationOld(lastLocation);
boolean hasGoodFix = lastLocation.hasAccuracy() && lastLocation.getAccuracy() < recordingGpsAccuracy;
if (!hasFix || !hasGoodFix) {
lastLocation = null;
}
}
StatsUtils.setLocationValues(getActivity(), getActivity(), null, lastLocation, isSelectedTrackRecording());
}
}
});
}
}
@Override
public void clearWaypoints() {
// We don't care.
}
@Override
public void onNewWaypoint(Waypoint wpt) {
// We don't care.
}
@Override
public void onNewWaypointsDone() {
// We don't care.
}
@Override
public boolean onMetricUnitsChanged(final boolean metric) {
if (isResumed()) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
updateUi(getActivity());
}
}
});
}
return true;
}
@Override
public boolean onReportSpeedChanged(final boolean speed) {
if (isResumed()) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
updateUi(getActivity());
}
}
});
}
return true;
}
@Override
public boolean onRecordingGpsAccuracy(int newValue) {
recordingGpsAccuracy = newValue;
return false;
}
@Override
public boolean onRecordingDistanceIntervalChanged(int minRecordingDistance) {
// We don't care.
return false;
}
/**
* Resumes the trackDataHub. Needs to be synchronized because trackDataHub can
* be accessed by multiple threads.
*/
private synchronized void resumeTrackDataHub() {
trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, EnumSet.of(TrackDataType.TRACKS_TABLE,
TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE, TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE,
TrackDataType.PREFERENCE));
}
/**
* Pauses the trackDataHub. Needs to be synchronized because trackDataHub can
* be accessed by multiple threads.
*/
private synchronized void pauseTrackDataHub() {
trackDataHub.unregisterTrackDataListener(this);
trackDataHub = null;
}
/**
* Returns true if the selected track is recording. Needs to be synchronized
* because trackDataHub can be accessed by multiple threads.
*/
private synchronized boolean isSelectedTrackRecording() {
return trackDataHub != null && trackDataHub.isSelectedTrackRecording();
}
/**
* Returns true if the selected track is paused. Needs to be synchronized
* because trackDataHub can be accessed by multiple threads.
*/
private synchronized boolean isSelectedTrackPaused() {
return trackDataHub != null && trackDataHub.isSelectedTrackPaused();
}
/**
* Tries to fetch most recent {@link SensorDataSet} {@link de.dennisguse.opentracks.services.sensors.SensorManager}.
*/
private void updateSensorDataUI() {
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
if (trackRecordingService == null) {
Log.d(STATS_FRAGMENT_TAG, "Cannot get the track recording service.");
sensorDataSet = null;
} else {
//TODO sensorState = trackRecordingService.getSensorState();
sensorDataSet = trackRecordingService.getSensorData();
}
StatsUtils.setSensorData(getActivity(), getActivity(), sensorDataSet, isSelectedTrackRecording());
}
/**
* Updates the UI.
*/
private void updateUi(FragmentActivity activity) {
String trackIconValue = TrackIconUtils.getIconValue(activity, category);
StatsUtils.setTripStatisticsValues(activity, activity, null, lastTripStatistics, trackIconValue);
StatsUtils.setLocationValues(activity, activity, null, lastLocation, isSelectedTrackRecording());
updateSensorDataUI();
}
}