forked from upstream-mirrors/OpenTracks
Merge heads
This commit is contained in:
@@ -16,7 +16,7 @@ limitations under the License.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="40"
|
||||
android:versionCode="41"
|
||||
android:versionName="2.0"
|
||||
package="com.google.android.maps.mytracks" >
|
||||
|
||||
|
||||
@@ -34,12 +34,12 @@ limitations under the License.
|
||||
<TextView
|
||||
android:id="@+id/sensor_state_last_sensor_time"
|
||||
style="@style/SensorStateValue" />
|
||||
<!-- Power -->
|
||||
<!-- Heart rate -->
|
||||
<TextView
|
||||
android:text="@string/sensor_state_power"
|
||||
android:text="@string/sensor_state_heart_rate"
|
||||
style="@style/TextMedium" />
|
||||
<TextView
|
||||
android:id="@+id/sensor_state_power"
|
||||
android:id="@+id/sensor_state_heart_rate"
|
||||
style="@style/SensorStateValue" />
|
||||
<!-- Cadence -->
|
||||
<TextView
|
||||
@@ -48,12 +48,12 @@ limitations under the License.
|
||||
<TextView
|
||||
android:id="@+id/sensor_state_cadence"
|
||||
style="@style/SensorStateValue" />
|
||||
<!-- Heart rate -->
|
||||
<!-- Power -->
|
||||
<TextView
|
||||
android:text="@string/sensor_state_heart_rate"
|
||||
android:text="@string/sensor_state_power"
|
||||
style="@style/TextMedium" />
|
||||
<TextView
|
||||
android:id="@+id/sensor_state_heart_rate"
|
||||
android:id="@+id/sensor_state_power"
|
||||
style="@style/SensorStateValue" />
|
||||
<!-- Battery level -->
|
||||
<TextView
|
||||
|
||||
@@ -33,6 +33,10 @@ limitations under the License.
|
||||
<color name="speed_border">#FF0066FF</color>
|
||||
<color name="speed_fill">#40C8E0FF</color>
|
||||
|
||||
<!-- Blue and a transparent blue -->
|
||||
<color name="pace_border">#FF0066FF</color>
|
||||
<color name="pace_fill">#40C8E0FF</color>
|
||||
|
||||
<!-- Red and a transparent red -->
|
||||
<color name="heartrate_border">#FFCC3300</color>
|
||||
<color name="heartrate_fill">#40E88A70</color>
|
||||
|
||||
@@ -451,6 +451,10 @@ limitations under the License.
|
||||
%2$.1f speed in miles per hour">
|
||||
Average speed: %1$.2f km/h (%2$.1f mi/h)
|
||||
</string>
|
||||
<string name="description_elevation_imperial"
|
||||
translation_description="Elevation in imperial units, feet.">
|
||||
Elevation (ft)
|
||||
</string>
|
||||
<string name="description_elevation_gain"
|
||||
translation_description="Elevation gain description.
|
||||
|
||||
@@ -458,6 +462,10 @@ limitations under the License.
|
||||
%2$d elevation in feet">
|
||||
Elevation gain: %1$d m (%2$d ft)
|
||||
</string>
|
||||
<string name="description_elevation_metric"
|
||||
translation_description="Elevation in metric units, meters.">
|
||||
Elevation (m)
|
||||
</string>
|
||||
<string name="description_fastest_pace"
|
||||
translation_description="Fastest pace description.
|
||||
|
||||
@@ -489,10 +497,6 @@ limitations under the License.
|
||||
translation_description="Label for location speed in meters per second.">
|
||||
Speed (m/s)
|
||||
</string>
|
||||
<string name="description_location_time"
|
||||
translation_description="Label for location time.">
|
||||
Time
|
||||
</string>
|
||||
<string name="description_max_elevation"
|
||||
translation_description="Max elevation description.
|
||||
|
||||
@@ -532,6 +536,14 @@ limitations under the License.
|
||||
%1$s time. E.g., '1:08:59'">
|
||||
Moving time: %1$s
|
||||
</string>
|
||||
<string name="description_pace_imperial"
|
||||
translation_description="Pace in imperial units, hours per mile.">
|
||||
Pace (hr/mi)
|
||||
</string>
|
||||
<string name="description_pace_metric"
|
||||
translation_description="Pace in metric units, hours per kilometer.">
|
||||
Pace (hr/km)
|
||||
</string>
|
||||
<string name="description_recorded_time"
|
||||
translation_description="Recorded time description.
|
||||
|
||||
@@ -554,6 +566,18 @@ limitations under the License.
|
||||
translation_description="Label for power with its unit watt (W).">
|
||||
Power (W)
|
||||
</string>
|
||||
<string name="description_speed_imperial"
|
||||
translation_description="Speed in imperial units, miles per hour.">
|
||||
Speed (mi/hr)
|
||||
</string>
|
||||
<string name="description_speed_metric"
|
||||
translation_description="Speed in metric units, kilometers per hour.">
|
||||
Speed (km/hr)
|
||||
</string>
|
||||
<string name="description_time"
|
||||
translation_description="Label for time.">
|
||||
Time
|
||||
</string>
|
||||
<string name="description_track_point"
|
||||
translation_description="Label for a track point">
|
||||
Point
|
||||
@@ -1721,6 +1745,12 @@ limitations under the License.
|
||||
%1$d: sensor id number. E.g., '1'.">
|
||||
Paired with: %1$d.
|
||||
</string>
|
||||
<string name="settings_sensor_bluetooth_connected"
|
||||
translation_description="The message when connected to a bluetooth sensor.
|
||||
|
||||
%1$s: sensor name. E.g., 'polar bluetooth'.">
|
||||
Connected to %1$s
|
||||
</string>
|
||||
<string name="settings_sensor_bluetooth_options"
|
||||
translation_description="In the 'Sensor' settings, the section header to configure the Bluetooth
|
||||
options.
|
||||
@@ -1898,26 +1928,6 @@ limitations under the License.
|
||||
of inclination to the horizontal, also known as slope or incline.">
|
||||
Grade
|
||||
</string>
|
||||
<string name="settings_stats_rate_pace_imperial"
|
||||
translation_description="In the 'Stats' settings, the option to display pace, with example in
|
||||
imperial units.">
|
||||
Pace (hr/mi)
|
||||
</string>
|
||||
<string name="settings_stats_rate_pace_metric"
|
||||
translation_description="In the 'Stats' settings, the option to display pace, with example in
|
||||
metric units.">
|
||||
Pace (hr/km)
|
||||
</string>
|
||||
<string name="settings_stats_rate_speed_imperial"
|
||||
translation_description="In the 'Stats' settings, the option to display speed, with example in
|
||||
imperial units.">
|
||||
Speed (mi/hr)
|
||||
</string>
|
||||
<string name="settings_stats_rate_speed_metric"
|
||||
translation_description="In the 'Stats' settings, the option to display speed, with example in
|
||||
metric units.">
|
||||
Speed (km/hr)
|
||||
</string>
|
||||
<string name="settings_stats_rate_title"
|
||||
translation_description="In the 'Stats' settings, the option to change the preferred rate,
|
||||
either speed (mi/hr) or pace (hr/mi).">
|
||||
|
||||
@@ -13,293 +13,252 @@
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import com.google.android.apps.mytracks.stats.ExtremityMonitor;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Paint.Align;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.graphics.Path;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
|
||||
/**
|
||||
* This class encapsulates meta data about one series of values for a chart.
|
||||
*
|
||||
* This class encapsulates the meta data for one series of the chart values.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class ChartValueSeries {
|
||||
|
||||
private final ExtremityMonitor monitor = new ExtremityMonitor();
|
||||
private final NumberFormat format;
|
||||
private final Path path = new Path();
|
||||
private static final float STROKE_WIDTH = 2f;
|
||||
|
||||
private final int absoluteMin;
|
||||
private final int absoluteMax;
|
||||
private final int[] intervalValues;
|
||||
private final int metricTitleId;
|
||||
private final int imperialTitleId;
|
||||
private final Paint fillPaint;
|
||||
private final Paint strokePaint;
|
||||
private final Paint labelPaint;
|
||||
private final ZoomSettings zoomSettings;
|
||||
|
||||
private String title;
|
||||
private double min;
|
||||
private double max = 1.0;
|
||||
private int effectiveMax;
|
||||
private int effectiveMin;
|
||||
private double spread;
|
||||
private int interval;
|
||||
private final Paint titlePaint;
|
||||
private final Paint markerPaint;
|
||||
private final ExtremityMonitor extremityMonitor;
|
||||
private final NumberFormat numberFormat;
|
||||
private final Path path;
|
||||
|
||||
private int interval = 1;
|
||||
private int minMarkerValue = 0;
|
||||
private int maxMarkerValue = interval * ChartView.Y_AXIS_INTERVALS;
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* This class controls how effective min/max values of a {@link ChartValueSeries} are calculated.
|
||||
* Constructor.
|
||||
*
|
||||
* @param context the context
|
||||
* @param absoluteMin the absolute min value
|
||||
* @param absoluteMax the absolute max value
|
||||
* @param intervalValues the list of interval values
|
||||
* @param metricTitleId the metric title id
|
||||
* @param imperialTitleId the imperial title id
|
||||
* @param fillColor the fill color
|
||||
* @param strokeColor the stroke color
|
||||
*/
|
||||
public static class ZoomSettings {
|
||||
private int intervals;
|
||||
private final int absoluteMin;
|
||||
private final int absoluteMax;
|
||||
private final int[] zoomLevels;
|
||||
public 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;
|
||||
this.metricTitleId = metricTitleId;
|
||||
this.imperialTitleId = imperialTitleId;
|
||||
|
||||
public ZoomSettings(int intervals, int[] zoomLevels) {
|
||||
this.intervals = intervals;
|
||||
this.absoluteMin = Integer.MAX_VALUE;
|
||||
this.absoluteMax = Integer.MIN_VALUE;
|
||||
this.zoomLevels = zoomLevels;
|
||||
checkArgs();
|
||||
}
|
||||
|
||||
public ZoomSettings(int intervals, int absoluteMin, int absoluteMax, int[] zoomLevels) {
|
||||
this.intervals = intervals;
|
||||
this.absoluteMin = absoluteMin;
|
||||
this.absoluteMax = absoluteMax;
|
||||
this.zoomLevels = zoomLevels;
|
||||
checkArgs();
|
||||
}
|
||||
|
||||
private void checkArgs() {
|
||||
if (intervals <= 0 || zoomLevels == null || zoomLevels.length == 0) {
|
||||
throw new IllegalArgumentException("Expecing positive intervals and non-empty zoom levels");
|
||||
}
|
||||
for (int i = 1; i < zoomLevels.length; ++i) {
|
||||
if (zoomLevels[i] <= zoomLevels[i - 1]) {
|
||||
throw new IllegalArgumentException("Expecting zoom levels in ascending order");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getIntervals() {
|
||||
return intervals;
|
||||
}
|
||||
|
||||
public int getAbsoluteMin() {
|
||||
return absoluteMin;
|
||||
}
|
||||
|
||||
public int getAbsoluteMax() {
|
||||
return absoluteMax;
|
||||
}
|
||||
|
||||
public int[] getZoomLevels() {
|
||||
return zoomLevels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the interval between markings given the min and max values.
|
||||
* This function attempts to find the smallest zoom level that fits [min,max] after rounding
|
||||
* it to the current zoom level.
|
||||
*
|
||||
* @param min the minimum value in the series
|
||||
* @param max the maximum value in the series
|
||||
* @return the calculated interval for the given range
|
||||
*/
|
||||
public int calculateInterval(double min, double max) {
|
||||
min = Math.min(min, absoluteMin);
|
||||
max = Math.max(max, absoluteMax);
|
||||
for (int i = 0; i < zoomLevels.length; ++i) {
|
||||
int zoomLevel = zoomLevels[i];
|
||||
int roundedMin = (int)(min / zoomLevel) * zoomLevel;
|
||||
if (roundedMin > min) {
|
||||
roundedMin -= zoomLevel;
|
||||
}
|
||||
double interval = (max - roundedMin) / intervals;
|
||||
if (zoomLevel >= interval) {
|
||||
return zoomLevel;
|
||||
}
|
||||
}
|
||||
return zoomLevels[zoomLevels.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new chart value series.
|
||||
*
|
||||
* @param context The context for the chart
|
||||
* @param fillColor The paint for filling the chart
|
||||
* @param strokeColor The paint for stroking the outside the chart, optional
|
||||
* @param zoomSettings The settings related to zooming
|
||||
* @param titleId The title ID
|
||||
*
|
||||
* TODO: Get rid of Context and inject appropriate values instead.
|
||||
*/
|
||||
public ChartValueSeries(
|
||||
Context context, int fillColor, int strokeColor, ZoomSettings zoomSettings, int titleId) {
|
||||
this.format = NumberFormat.getIntegerInstance();
|
||||
fillPaint = new Paint();
|
||||
fillPaint.setStyle(Style.FILL);
|
||||
fillPaint.setColor(context.getResources().getColor(fillColor));
|
||||
fillPaint.setAntiAlias(true);
|
||||
if (strokeColor != -1) {
|
||||
strokePaint = new Paint();
|
||||
strokePaint.setStyle(Style.STROKE);
|
||||
strokePaint.setColor(context.getResources().getColor(strokeColor));
|
||||
strokePaint.setAntiAlias(true);
|
||||
// Make a copy of the stroke paint with the default thickness.
|
||||
labelPaint = new Paint(strokePaint);
|
||||
strokePaint.setStrokeWidth(2f);
|
||||
} else {
|
||||
strokePaint = null;
|
||||
labelPaint = fillPaint;
|
||||
}
|
||||
this.zoomSettings = zoomSettings;
|
||||
this.title = context.getString(titleId);
|
||||
|
||||
strokePaint = new Paint();
|
||||
strokePaint.setStyle(Style.STROKE);
|
||||
strokePaint.setColor(context.getResources().getColor(strokeColor));
|
||||
strokePaint.setAntiAlias(true);
|
||||
|
||||
float scale = context.getResources().getDisplayMetrics().density;
|
||||
|
||||
// Make copies of the stroke paint with the default thickness
|
||||
titlePaint = new Paint(strokePaint);
|
||||
titlePaint.setTextSize(ChartView.MEDIUM_TEXT_SIZE * scale);
|
||||
titlePaint.setTextAlign(Align.CENTER);
|
||||
|
||||
markerPaint = new Paint(strokePaint);
|
||||
markerPaint.setTextSize(ChartView.SMALL_TEXT_SIZE * scale);
|
||||
markerPaint.setTextAlign(Align.RIGHT);
|
||||
|
||||
// Set stroke paint thickness
|
||||
strokePaint.setStrokeWidth(STROKE_WIDTH);
|
||||
|
||||
extremityMonitor = new ExtremityMonitor();
|
||||
numberFormat = NumberFormat.getIntegerInstance();
|
||||
path = new Path();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the path of the chart
|
||||
* Sets the series enabled value.
|
||||
*
|
||||
* @param enabled true to enable
|
||||
*/
|
||||
public void drawPath(Canvas c) {
|
||||
c.drawPath(path, fillPaint);
|
||||
if (strokePaint != null) {
|
||||
c.drawPath(path, strokePaint);
|
||||
}
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this series
|
||||
*/
|
||||
public void reset() {
|
||||
monitor.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates this series with a new value
|
||||
*/
|
||||
public void update(double d) {
|
||||
monitor.update(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The interval between markers
|
||||
*/
|
||||
public int getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines what the min and max of the chart will be.
|
||||
* This will round down and up the min and max respectively.
|
||||
*/
|
||||
public void updateDimension() {
|
||||
if (monitor.getMax() == Double.NEGATIVE_INFINITY) {
|
||||
min = 0;
|
||||
max = 1;
|
||||
} else {
|
||||
min = monitor.getMin();
|
||||
max = monitor.getMax();
|
||||
}
|
||||
min = Math.min(min, zoomSettings.getAbsoluteMin());
|
||||
max = Math.max(max, zoomSettings.getAbsoluteMax());
|
||||
|
||||
this.interval = zoomSettings.calculateInterval(min, max);
|
||||
// Round it up.
|
||||
effectiveMax = ((int) (max / interval)) * interval + interval;
|
||||
// Round it down.
|
||||
effectiveMin = ((int) (min / interval)) * interval;
|
||||
if (min < 0) {
|
||||
effectiveMin -= interval;
|
||||
}
|
||||
spread = effectiveMax - effectiveMin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The length of the longest string from the series
|
||||
*/
|
||||
public int getMaxLabelLength() {
|
||||
String minS = format.format(effectiveMin);
|
||||
String maxS = format.format(getMax());
|
||||
return Math.max(minS.length(), maxS.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The rounded down minimum value
|
||||
*/
|
||||
public int getMin() {
|
||||
return effectiveMin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The rounded up maximum value
|
||||
*/
|
||||
public int getMax() {
|
||||
return effectiveMax;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The difference between the min and max values in the series
|
||||
*/
|
||||
public double getSpread() {
|
||||
return spread;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The number format for this series
|
||||
*/
|
||||
NumberFormat getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The path for this series
|
||||
*/
|
||||
Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The paint for this series
|
||||
*/
|
||||
Paint getPaint() {
|
||||
return strokePaint == null ? fillPaint : strokePaint;
|
||||
}
|
||||
|
||||
public Paint getLabelPaint() {
|
||||
return labelPaint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The title of the series
|
||||
*/
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return is this series enabled
|
||||
* Returns true if the series is enabled.
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the series enabled flag.
|
||||
* Returns true if the series has data.
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
public boolean hasData() {
|
||||
return extremityMonitor.hasData();
|
||||
}
|
||||
|
||||
public boolean hasData() {
|
||||
return monitor.hasData();
|
||||
/**
|
||||
* Updates the series with a new value.
|
||||
*
|
||||
* @param value the new value
|
||||
*/
|
||||
public void update(double value) {
|
||||
extremityMonitor.update(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path.
|
||||
*/
|
||||
public Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the path on canvas.
|
||||
*
|
||||
* @param canvas the canvas
|
||||
*/
|
||||
public void drawPath(Canvas canvas) {
|
||||
canvas.drawPath(path, fillPaint);
|
||||
canvas.drawPath(path, strokePaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the y axis dimension.
|
||||
*/
|
||||
public void updateDimension() {
|
||||
double min = hasData() ? extremityMonitor.getMin() : 0.0;
|
||||
double max = hasData() ? extremityMonitor.getMax() : 1.0;
|
||||
min = Math.max(min, absoluteMin);
|
||||
max = Math.min(max, absoluteMax);
|
||||
interval = getInterval(min, max);
|
||||
minMarkerValue = getMinMarkerValue(min, interval);
|
||||
maxMarkerValue = minMarkerValue + interval * ChartView.Y_AXIS_INTERVALS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the interval value.
|
||||
*
|
||||
* @param min the min value
|
||||
* @param max the max value
|
||||
*/
|
||||
private int getInterval(double min, double max) {
|
||||
for (int i = 0; i < intervalValues.length; i++) {
|
||||
int intervalValue = intervalValues[i];
|
||||
int minValue = getMinMarkerValue(min, intervalValue);
|
||||
double targetInterval = (max - minValue) / ChartView.Y_AXIS_INTERVALS;
|
||||
if (intervalValue >= targetInterval) {
|
||||
return intervalValue;
|
||||
}
|
||||
}
|
||||
// Return the largest interval
|
||||
return intervalValues[intervalValues.length - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the min marker value.
|
||||
*
|
||||
* @param min the min series value
|
||||
* @param intervalValue the interval value
|
||||
*/
|
||||
private int getMinMarkerValue(double min, int intervalValue) {
|
||||
// Round down to the nearest intervalValue
|
||||
int value = ((int) (min / intervalValue)) * intervalValue;
|
||||
// value > min if min is negative
|
||||
if (value > min) {
|
||||
return value - intervalValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the interval value.
|
||||
*/
|
||||
public int getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the min marker value.
|
||||
*/
|
||||
public int getMinMarkerValue() {
|
||||
return minMarkerValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the max marker value.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
int getMaxMarkerValue() {
|
||||
return maxMarkerValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the title id.
|
||||
*/
|
||||
public int getTitleId(boolean metricUnits) {
|
||||
return metricUnits ? metricTitleId : imperialTitleId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the title paint.
|
||||
*/
|
||||
public Paint getTitlePaint() {
|
||||
return titlePaint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the marker paint.
|
||||
*/
|
||||
public Paint getMarkerPaint() {
|
||||
return markerPaint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the largest marker.
|
||||
*/
|
||||
public String getLargestMarker() {
|
||||
String minMarker = numberFormat.format(getMinMarkerValue());
|
||||
String maxMarker = numberFormat.format(getMaxMarkerValue());
|
||||
return minMarker.length() >= maxMarker.length() ? minMarker : maxMarker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a marker value.
|
||||
*
|
||||
* @param value the value
|
||||
*/
|
||||
public String formatMarker(int value) {
|
||||
return numberFormat.format(value);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -172,8 +172,7 @@ public class ImportActivity extends Activity {
|
||||
if (success) {
|
||||
showDialog(DIALOG_RESULT_ID);
|
||||
} else {
|
||||
Toast.makeText(this, getString(R.string.sd_card_import_error), Toast.LENGTH_LONG)
|
||||
.show();
|
||||
Toast.makeText(this, R.string.sd_card_import_error, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ public class MarkerDetailActivity extends AbstractMyTracksActivity {
|
||||
Intent intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
||||
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, waypoint.getTrackId());
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -126,7 +126,7 @@ public class SensorStateActivity extends AbstractMyTracksActivity {
|
||||
*/
|
||||
private void stopTempSensorManager() {
|
||||
if (tempSensorManager != null) {
|
||||
SensorManagerFactory.getInstance().releaseSensorManager(tempSensorManager);
|
||||
SensorManagerFactory.releaseTempSensorManager();
|
||||
tempSensorManager = null;
|
||||
}
|
||||
}
|
||||
@@ -139,7 +139,7 @@ public class SensorStateActivity extends AbstractMyTracksActivity {
|
||||
Sensor.SensorDataSet sensorDataSet = null;
|
||||
|
||||
if (tempSensorManager == null) {
|
||||
tempSensorManager = SensorManagerFactory.getInstance().getSensorManager(this);
|
||||
tempSensorManager = SensorManagerFactory.getTempSensorManager(this);
|
||||
}
|
||||
|
||||
if (tempSensorManager != null) {
|
||||
@@ -199,19 +199,19 @@ public class SensorStateActivity extends AbstractMyTracksActivity {
|
||||
|
||||
String lastSensorTime = sensorDataSet == null ? getString(R.string.value_unknown)
|
||||
: getLastSensorTime(sensorDataSet);
|
||||
String power = sensorDataSet == null ? getString(R.string.value_unknown)
|
||||
: getPower(sensorDataSet);
|
||||
String cadence = sensorDataSet == null ? getString(R.string.value_unknown)
|
||||
: getCadence(sensorDataSet);
|
||||
String heartRate = sensorDataSet == null ? getString(R.string.value_unknown)
|
||||
: getHeartRate(sensorDataSet);
|
||||
String cadence = sensorDataSet == null ? getString(R.string.value_unknown)
|
||||
: getCadence(sensorDataSet);
|
||||
String power = sensorDataSet == null ? getString(R.string.value_unknown)
|
||||
: getPower(sensorDataSet);
|
||||
String battery = sensorDataSet == null ? getString(R.string.value_unknown)
|
||||
: getBattery(sensorDataSet);
|
||||
|
||||
((TextView) findViewById(R.id.sensor_state_last_sensor_time)).setText(lastSensorTime);
|
||||
((TextView) findViewById(R.id.sensor_state_power)).setText(power);
|
||||
((TextView) findViewById(R.id.sensor_state_cadence)).setText(cadence);
|
||||
((TextView) findViewById(R.id.sensor_state_heart_rate)).setText(heartRate);
|
||||
((TextView) findViewById(R.id.sensor_state_cadence)).setText(cadence);
|
||||
((TextView) findViewById(R.id.sensor_state_power)).setText(power);
|
||||
((TextView) findViewById(R.id.sensor_state_battery)).setText(battery);
|
||||
}
|
||||
|
||||
@@ -225,19 +225,20 @@ public class SensorStateActivity extends AbstractMyTracksActivity {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the power.
|
||||
* Gets the heart rate.
|
||||
*
|
||||
* @param sensorDataSet sensor data set
|
||||
*/
|
||||
private String getPower(Sensor.SensorDataSet sensorDataSet) {
|
||||
private String getHeartRate(Sensor.SensorDataSet sensorDataSet) {
|
||||
String value;
|
||||
if (sensorDataSet.hasPower() && sensorDataSet.getPower().hasValue()
|
||||
&& sensorDataSet.getPower().getState() == Sensor.SensorState.SENDING) {
|
||||
value = getString(R.string.sensor_state_power_value, sensorDataSet.getPower().getValue());
|
||||
if (sensorDataSet.hasHeartRate() && sensorDataSet.getHeartRate().hasValue()
|
||||
&& sensorDataSet.getHeartRate().getState() == Sensor.SensorState.SENDING) {
|
||||
value = getString(
|
||||
R.string.sensor_state_heart_rate_value, sensorDataSet.getHeartRate().getValue());
|
||||
} else {
|
||||
value = SensorUtils.getStateAsString(
|
||||
sensorDataSet.hasPower() ? sensorDataSet.getPower().getState() : Sensor.SensorState.NONE,
|
||||
this);
|
||||
sensorDataSet.hasHeartRate() ? sensorDataSet.getHeartRate().getState()
|
||||
: Sensor.SensorState.NONE, this);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -261,20 +262,19 @@ public class SensorStateActivity extends AbstractMyTracksActivity {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the heart rate.
|
||||
* Gets the power.
|
||||
*
|
||||
* @param sensorDataSet sensor data set
|
||||
*/
|
||||
private String getHeartRate(Sensor.SensorDataSet sensorDataSet) {
|
||||
private String getPower(Sensor.SensorDataSet sensorDataSet) {
|
||||
String value;
|
||||
if (sensorDataSet.hasHeartRate() && sensorDataSet.getHeartRate().hasValue()
|
||||
&& sensorDataSet.getHeartRate().getState() == Sensor.SensorState.SENDING) {
|
||||
value = getString(
|
||||
R.string.sensor_state_heart_rate_value, sensorDataSet.getHeartRate().getValue());
|
||||
if (sensorDataSet.hasPower() && sensorDataSet.getPower().hasValue()
|
||||
&& sensorDataSet.getPower().getState() == Sensor.SensorState.SENDING) {
|
||||
value = getString(R.string.sensor_state_power_value, sensorDataSet.getPower().getValue());
|
||||
} else {
|
||||
value = SensorUtils.getStateAsString(
|
||||
sensorDataSet.hasHeartRate() ? sensorDataSet.getHeartRate().getState()
|
||||
: Sensor.SensorState.NONE, this);
|
||||
sensorDataSet.hasPower() ? sensorDataSet.getPower().getState() : Sensor.SensorState.NONE,
|
||||
this);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -230,6 +230,7 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del
|
||||
protected void onHomeSelected() {
|
||||
Intent intent = IntentUtils.newIntent(this, TrackListActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -154,7 +154,7 @@ class DataSourcesWrapperImpl implements DataSourcesWrapper {
|
||||
}
|
||||
|
||||
// Let the user know we have only an approximate location:
|
||||
Toast.makeText(context, context.getString(toastResId), Toast.LENGTH_LONG).show();
|
||||
Toast.makeText(context, toastResId, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
return loc;
|
||||
}
|
||||
|
||||
@@ -77,15 +77,15 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
private boolean reportSpeed = true;
|
||||
|
||||
private boolean chartByDistance = true;
|
||||
private boolean[] chartShow = new boolean[] {true, true, true, true, true};
|
||||
private boolean[] chartShow = new boolean[] {true, true, true, true, true, true};
|
||||
|
||||
// UI elements
|
||||
private ChartView chartView;
|
||||
private ZoomControls zoomControls;
|
||||
|
||||
/**
|
||||
* A runnable that will remove the spinner (if any), 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
|
||||
@@ -215,7 +215,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
@Override
|
||||
public void onNewTrackPoint(Location location) {
|
||||
if (LocationUtils.isValidLocation(location)) {
|
||||
double[] data = new double[6];
|
||||
double[] data = new double[ChartView.NUM_SERIES + 1];
|
||||
fillDataPoint(location, data);
|
||||
pendingPoints.add(data);
|
||||
}
|
||||
@@ -280,7 +280,11 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
return false;
|
||||
}
|
||||
reportSpeed = speed;
|
||||
chartView.setReportSpeed(speed, getActivity());
|
||||
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);
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -306,8 +310,13 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
R.string.chart_show_elevation_key, PreferencesUtils.CHART_SHOW_ELEVATION_DEFAULT))) {
|
||||
needUpdate = true;
|
||||
}
|
||||
if (setSeriesEnabled(ChartView.SPEED_SERIES, PreferencesUtils.getBoolean(
|
||||
getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT))) {
|
||||
|
||||
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(
|
||||
@@ -405,13 +414,14 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a location, fill in a data point, an array of double[6]. <br>
|
||||
* 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] = power <br>
|
||||
* data[4] = cadence <br>
|
||||
* data[5] = heart rate <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
|
||||
@@ -421,9 +431,10 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
double timeOrDistance = Double.NaN;
|
||||
double elevation = Double.NaN;
|
||||
double speed = Double.NaN;
|
||||
double power = Double.NaN;
|
||||
double pace = Double.NaN;
|
||||
double heartRate = Double.NaN;
|
||||
double cadence = Double.NaN;
|
||||
double heartRate = Double.NaN;
|
||||
double power = Double.NaN;
|
||||
|
||||
// TODO: Use TripStatisticsBuilder
|
||||
if (chartByDistance) {
|
||||
@@ -460,36 +471,36 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
if (!metricUnits) {
|
||||
speed *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
if (!reportSpeed) {
|
||||
speed = speed == 0 ? 0.0 : 60.0 / speed;
|
||||
}
|
||||
|
||||
pace = speed == 0 ? 0.0 : 60.0 / speed;
|
||||
|
||||
if (location instanceof MyTracksLocation
|
||||
&& ((MyTracksLocation) location).getSensorDataSet() != null) {
|
||||
SensorDataSet sensorDataSet = ((MyTracksLocation) location).getSensorDataSet();
|
||||
if (sensorDataSet.hasPower() && sensorDataSet.getPower().getState() == Sensor.SensorState.SENDING
|
||||
&& sensorDataSet.getPower().hasValue()) {
|
||||
power = sensorDataSet.getPower().getValue();
|
||||
if (sensorDataSet.hasHeartRate()
|
||||
&& sensorDataSet.getHeartRate().getState() == Sensor.SensorState.SENDING
|
||||
&& sensorDataSet.getHeartRate().hasValue()) {
|
||||
heartRate = sensorDataSet.getHeartRate().getValue();
|
||||
}
|
||||
if (sensorDataSet.hasCadence()
|
||||
&& sensorDataSet.getCadence().getState() == Sensor.SensorState.SENDING
|
||||
&& sensorDataSet.getCadence().hasValue()) {
|
||||
cadence = sensorDataSet.getCadence().getValue();
|
||||
}
|
||||
if (sensorDataSet.hasHeartRate()
|
||||
&& sensorDataSet.getHeartRate().getState() == Sensor.SensorState.SENDING
|
||||
&& sensorDataSet.getHeartRate().hasValue()) {
|
||||
heartRate = sensorDataSet.getHeartRate().getValue();
|
||||
if (sensorDataSet.hasPower()
|
||||
&& sensorDataSet.getPower().getState() == Sensor.SensorState.SENDING
|
||||
&& sensorDataSet.getPower().hasValue()) {
|
||||
power = sensorDataSet.getPower().getValue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (data != null) {
|
||||
data[0] = timeOrDistance;
|
||||
data[1] = elevation;
|
||||
data[2] = speed;
|
||||
data[3] = power;
|
||||
data[4] = cadence;
|
||||
data[5] = heartRate;
|
||||
data[3] = pace;
|
||||
data[4] = heartRate;
|
||||
data[5] = cadence;
|
||||
data[6] = power;
|
||||
}
|
||||
lastLocation = location;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ public class CsvTrackWriter implements TrackFormatWriter {
|
||||
context.getString(R.string.description_location_bearing),
|
||||
context.getString(R.string.description_location_accuracy),
|
||||
context.getString(R.string.description_location_speed),
|
||||
context.getString(R.string.description_location_time));
|
||||
context.getString(R.string.description_time));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -143,7 +143,7 @@ public class CsvTrackWriter implements TrackFormatWriter {
|
||||
context.getString(R.string.description_location_bearing),
|
||||
context.getString(R.string.description_location_accuracy),
|
||||
context.getString(R.string.description_location_speed),
|
||||
context.getString(R.string.description_location_time),
|
||||
context.getString(R.string.description_time),
|
||||
context.getString(R.string.description_sensor_power),
|
||||
context.getString(R.string.description_sensor_cadence),
|
||||
context.getString(R.string.description_sensor_heart_rate),
|
||||
|
||||
+6
-4
@@ -70,8 +70,9 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
|
||||
// See http://support.google.com/fusiontables/bin/answer.py?hl=en&answer=185991
|
||||
private static final String MARKER_TYPE_START = "large_green";
|
||||
private static final String MARKER_TYPE_END = "large_red";
|
||||
private static final String MARKER_TYPE_WAYPOINT = "large_yellow";
|
||||
|
||||
private static final String MARKER_TYPE_WAYPOINT = "large_blue";
|
||||
private static final String MARKER_TYPE_STATISTICS = "large_yellow";
|
||||
|
||||
private static final String TAG = SendFusionTablesAsyncTask.class.getSimpleName();
|
||||
|
||||
private final Context context;
|
||||
@@ -336,8 +337,9 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
|
||||
// track).
|
||||
while (cursor.moveToNext()) {
|
||||
Waypoint wpt = myTracksProviderUtils.createWaypoint(cursor);
|
||||
if (!createNewPoint(
|
||||
wpt.getName(), wpt.getDescription(), wpt.getLocation(), MARKER_TYPE_WAYPOINT)) {
|
||||
String type = wpt.getType() == Waypoint.TYPE_STATISTICS ? MARKER_TYPE_STATISTICS
|
||||
: MARKER_TYPE_WAYPOINT;
|
||||
if (!createNewPoint(wpt.getName(), wpt.getDescription(), wpt.getLocation(), type)) {
|
||||
Log.d(TAG, "Upload waypoints failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
+2
-1
@@ -44,7 +44,8 @@ import android.widget.TextView;
|
||||
public class UploadResultActivity extends FragmentActivity {
|
||||
|
||||
private static final String TAG = UploadResultActivity.class.getSimpleName();
|
||||
private static final int DIALOG_RESULT_ID = 0;
|
||||
@VisibleForTesting
|
||||
static final int DIALOG_RESULT_ID = 0;
|
||||
|
||||
private SendRequest sendRequest;
|
||||
private String shareUrl;
|
||||
|
||||
@@ -359,7 +359,7 @@ public class TrackRecordingService extends Service {
|
||||
unregisterLocationListener();
|
||||
shutdownTaskExecutors();
|
||||
if (sensorManager != null) {
|
||||
SensorManagerFactory.getInstance().releaseSensorManager(sensorManager);
|
||||
SensorManagerFactory.releaseSystemSensorManager();
|
||||
sensorManager = null;
|
||||
}
|
||||
|
||||
@@ -595,7 +595,7 @@ public class TrackRecordingService extends Service {
|
||||
length = 0;
|
||||
showNotification();
|
||||
registerLocationListener();
|
||||
sensorManager = SensorManagerFactory.getInstance().getSensorManager(this);
|
||||
sensorManager = SensorManagerFactory.getSystemSensorManager(this);
|
||||
|
||||
// Reset the number of auto-resume retries.
|
||||
setAutoResumeTrackRetries(0);
|
||||
@@ -724,7 +724,7 @@ public class TrackRecordingService extends Service {
|
||||
boolean hasSensorData = sensorManager != null
|
||||
&& sensorManager.isEnabled()
|
||||
&& sensorManager.getSensorDataSet() != null
|
||||
&& sensorManager.isDataValid();
|
||||
&& sensorManager.isSensorDataSetValid();
|
||||
|
||||
// If the user has been stationary for two recording just record the first
|
||||
// two and ignore the rest. This code will only have an effect if the
|
||||
@@ -823,7 +823,7 @@ public class TrackRecordingService extends Service {
|
||||
Location locationToInsert = location;
|
||||
if (sensorManager != null && sensorManager.isEnabled()) {
|
||||
SensorDataSet sd = sensorManager.getSensorDataSet();
|
||||
if (sd != null && sensorManager.isDataValid()) {
|
||||
if (sd != null && sensorManager.isSensorDataSetValid()) {
|
||||
locationToInsert = new MyTracksLocation(location, sd);
|
||||
}
|
||||
}
|
||||
@@ -1042,7 +1042,7 @@ public class TrackRecordingService extends Service {
|
||||
PreferencesUtils.setLong(this, R.string.recording_track_id_key, recordingTrackId);
|
||||
|
||||
if (sensorManager != null) {
|
||||
SensorManagerFactory.getInstance().releaseSensorManager(sensorManager);
|
||||
SensorManagerFactory.releaseSystemSensorManager();
|
||||
sensorManager = null;
|
||||
}
|
||||
|
||||
|
||||
+129
-247
@@ -16,8 +16,8 @@
|
||||
|
||||
package com.google.android.apps.mytracks.services.sensors;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.content.Sensor;
|
||||
import com.google.android.apps.mytracks.content.Sensor.SensorState;
|
||||
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
@@ -30,382 +30,264 @@ import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* This class does all the work for setting up and managing Bluetooth
|
||||
* connections with other devices. It has a thread that listens for incoming
|
||||
* connections, a thread for connecting with a device, and a thread for
|
||||
* performing data transmissions when connected.
|
||||
*
|
||||
* Manages bluetooth connection. It has a thread for connecting with a bluetooth
|
||||
* device and a thread for performing data transmission when connected.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class BluetoothConnectionManager {
|
||||
|
||||
// Unique Bluetooth UUID for My Tracks
|
||||
public static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
|
||||
// My Tracks UUID
|
||||
public static final UUID MY_TRACKS_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
|
||||
|
||||
private MessageParser parser;
|
||||
// Message types sent to hander
|
||||
public static final int MESSAGE_DEVICE_NAME = 1;
|
||||
public static final int MESSAGE_READ = 2;
|
||||
|
||||
// Member fields
|
||||
private final BluetoothAdapter adapter;
|
||||
// Key for storing the device name
|
||||
public static final String KEY_DEVICE_NAME = "device_name";
|
||||
|
||||
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
|
||||
|
||||
private final BluetoothAdapter bluetoothAdapter;
|
||||
private final Handler handler;
|
||||
private final MessageParser messageParser;
|
||||
private SensorState sensorState;
|
||||
|
||||
private ConnectThread connectThread;
|
||||
private ConnectedThread connectedThread;
|
||||
|
||||
private Sensor.SensorState state;
|
||||
|
||||
// Message types sent from the BluetoothSenorService Handler
|
||||
public static final int MESSAGE_STATE_CHANGE = 1;
|
||||
public static final int MESSAGE_READ = 2;
|
||||
public static final int MESSAGE_WRITE = 3;
|
||||
public static final int MESSAGE_DEVICE_NAME = 4;
|
||||
|
||||
// Key names received from the BluetoothSenorService Handler
|
||||
public static final String DEVICE_NAME = "device_name";
|
||||
|
||||
/**
|
||||
* Constructor. Prepares a new BluetoothSensor session.
|
||||
*
|
||||
* @param handler A Handler to send messages back to the UI Activity
|
||||
* @param parser A message parser
|
||||
* Constructor.
|
||||
*
|
||||
* @param bluetoothAdapter the bluetooth adapter
|
||||
* @param handler a hander for sending messages back to the UI activity
|
||||
* @param messageParser a message parser
|
||||
*/
|
||||
public BluetoothConnectionManager(Handler handler, MessageParser parser) {
|
||||
this.adapter = BluetoothAdapter.getDefaultAdapter();
|
||||
this.state = Sensor.SensorState.NONE;
|
||||
public BluetoothConnectionManager(
|
||||
BluetoothAdapter bluetoothAdapter, Handler handler, MessageParser messageParser) {
|
||||
this.bluetoothAdapter = bluetoothAdapter;
|
||||
this.handler = handler;
|
||||
this.parser = parser;
|
||||
this.messageParser = messageParser;
|
||||
this.sensorState = SensorState.NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current state of the sensor connection
|
||||
*
|
||||
* @param state An integer defining the current connection state
|
||||
* Gets the sensor state.
|
||||
*/
|
||||
private synchronized void setState(Sensor.SensorState state) {
|
||||
// TODO pretty print this.
|
||||
Log.d(Constants.TAG, "setState(" + state + ")");
|
||||
this.state = state;
|
||||
|
||||
// Give the new state to the Handler so the UI Activity can update
|
||||
handler.obtainMessage(MESSAGE_STATE_CHANGE, state.getNumber(), -1).sendToTarget();
|
||||
public synchronized SensorState getSensorState() {
|
||||
return sensorState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current connection state.
|
||||
* Sets the sensor state.
|
||||
*
|
||||
* @param sensorState the sensor state
|
||||
*/
|
||||
public synchronized Sensor.SensorState getState() {
|
||||
return state;
|
||||
private synchronized void setState(Sensor.SensorState sensorState) {
|
||||
this.sensorState = sensorState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the sensor service. Specifically start AcceptThread to begin a session
|
||||
* in listening (server) mode. Called by the Activity onResume()
|
||||
* Resets the bluetooth connection manager.
|
||||
*/
|
||||
public synchronized void start() {
|
||||
Log.d(Constants.TAG, "BluetoothConnectionManager.start()");
|
||||
|
||||
// Cancel any thread attempting to make a connection
|
||||
if (connectThread != null) {
|
||||
connectThread.cancel();
|
||||
connectThread = null;
|
||||
}
|
||||
|
||||
// Cancel any thread currently running a connection
|
||||
if (connectedThread != null) {
|
||||
connectedThread.cancel();
|
||||
connectedThread = null;
|
||||
}
|
||||
|
||||
public synchronized void reset() {
|
||||
cancelThreads();
|
||||
setState(Sensor.SensorState.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the ConnectThread to initiate a connection to a remote device.
|
||||
*
|
||||
* @param device The BluetoothDevice to connect
|
||||
* Cancels all the threads.
|
||||
*/
|
||||
public synchronized void connect(BluetoothDevice device) {
|
||||
Log.d(Constants.TAG, "connect to: " + device);
|
||||
|
||||
// Cancel any thread attempting to make a connection
|
||||
if (state == Sensor.SensorState.CONNECTING) {
|
||||
if (connectThread != null) {
|
||||
connectThread.cancel();
|
||||
connectThread = null;
|
||||
}
|
||||
private void cancelThreads() {
|
||||
if (connectThread != null) {
|
||||
connectThread.cancel();
|
||||
connectThread = null;
|
||||
}
|
||||
|
||||
// Cancel any thread currently running a connection
|
||||
if (connectedThread != null) {
|
||||
connectedThread.cancel();
|
||||
connectedThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Start the thread to connect with the given device
|
||||
connectThread = new ConnectThread(device);
|
||||
/**
|
||||
* Connects to a bluetooth device.
|
||||
*
|
||||
* @param bluetoothDevice the bluetooth device
|
||||
*/
|
||||
public synchronized void connect(BluetoothDevice bluetoothDevice) {
|
||||
Log.d(TAG, "connect to: " + bluetoothDevice);
|
||||
cancelThreads();
|
||||
|
||||
connectThread = new ConnectThread(bluetoothDevice);
|
||||
connectThread.start();
|
||||
setState(Sensor.SensorState.CONNECTING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the ConnectedThread to begin managing a Bluetooth connection
|
||||
*
|
||||
* @param socket The BluetoothSocket on which the connection was made
|
||||
* @param device The BluetoothDevice that has been connected
|
||||
* Starts the ConnectedThread to read data.
|
||||
*
|
||||
* @param bluetoothSocket the bluetooth socket
|
||||
* @param bluetoothDevice the bluetooth device
|
||||
*/
|
||||
public synchronized void connected(BluetoothSocket socket,
|
||||
BluetoothDevice device) {
|
||||
Log.d(Constants.TAG, "connected");
|
||||
private synchronized void connected(
|
||||
BluetoothSocket bluetoothSocket, BluetoothDevice bluetoothDevice) {
|
||||
cancelThreads();
|
||||
|
||||
// Cancel the thread that completed the connection
|
||||
if (connectThread != null) {
|
||||
connectThread.cancel();
|
||||
connectThread = null;
|
||||
}
|
||||
|
||||
// Cancel any thread currently running a connection
|
||||
if (connectedThread != null) {
|
||||
connectedThread.cancel();
|
||||
connectedThread = null;
|
||||
}
|
||||
|
||||
// Start the thread to manage the connection and perform transmissions
|
||||
connectedThread = new ConnectedThread(socket);
|
||||
connectedThread = new ConnectedThread(bluetoothSocket);
|
||||
connectedThread.start();
|
||||
|
||||
// Send the name of the connected device back to the UI Activity
|
||||
Message msg = handler.obtainMessage(MESSAGE_DEVICE_NAME);
|
||||
// Send the device name to the handler
|
||||
Message message = handler.obtainMessage(MESSAGE_DEVICE_NAME);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(DEVICE_NAME, device.getName());
|
||||
msg.setData(bundle);
|
||||
handler.sendMessage(msg);
|
||||
bundle.putString(KEY_DEVICE_NAME, bluetoothDevice.getName());
|
||||
message.setData(bundle);
|
||||
handler.sendMessage(message);
|
||||
|
||||
setState(Sensor.SensorState.CONNECTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop all threads
|
||||
*/
|
||||
public synchronized void stop() {
|
||||
Log.d(Constants.TAG, "stop()");
|
||||
if (connectThread != null) {
|
||||
connectThread.cancel();
|
||||
connectThread = null;
|
||||
}
|
||||
if (connectedThread != null) {
|
||||
connectedThread.cancel();
|
||||
connectedThread = null;
|
||||
}
|
||||
setState(Sensor.SensorState.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to the ConnectedThread in an unsynchronized manner
|
||||
*
|
||||
* @param out The bytes to write
|
||||
* @see ConnectedThread#write(byte[])
|
||||
*/
|
||||
public void write(byte[] out) {
|
||||
// Create temporary object
|
||||
ConnectedThread r;
|
||||
// Synchronize a copy of the ConnectedThread
|
||||
synchronized (this) {
|
||||
if (state != Sensor.SensorState.CONNECTED) {
|
||||
return;
|
||||
}
|
||||
r = connectedThread;
|
||||
}
|
||||
// Perform the write unsynchronized
|
||||
r.write(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the connection attempt failed and notify the UI Activity.
|
||||
*/
|
||||
private void connectionFailed() {
|
||||
setState(Sensor.SensorState.DISCONNECTED);
|
||||
Log.i(Constants.TAG, "Bluetooth connection failed.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the connection was lost and notify the UI Activity.
|
||||
*/
|
||||
private void connectionLost() {
|
||||
setState(Sensor.SensorState.DISCONNECTED);
|
||||
Log.i(Constants.TAG, "Bluetooth connection lost.");
|
||||
}
|
||||
|
||||
/**
|
||||
* This thread runs while attempting to make an outgoing connection with a
|
||||
* device. It runs straight through; the connection either succeeds or fails.
|
||||
* A thread to connect to a bluetooth device.
|
||||
*/
|
||||
private class ConnectThread extends Thread {
|
||||
private final BluetoothSocket socket;
|
||||
private final BluetoothDevice device;
|
||||
private final BluetoothSocket bluetoothSocket;
|
||||
private final BluetoothDevice bluetoothDevice;
|
||||
|
||||
public ConnectThread(BluetoothDevice device) {
|
||||
setName("ConnectThread-" + device.getName());
|
||||
this.device = device;
|
||||
this.bluetoothDevice = device;
|
||||
BluetoothSocket tmp = null;
|
||||
|
||||
// Get a BluetoothSocket for a connection with the
|
||||
// given BluetoothDevice
|
||||
try {
|
||||
tmp = ApiAdapterFactory.getApiAdapter().getBluetoothSocket(device);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "create() failed", e);
|
||||
Log.e(TAG, "Unable to get blueooth socket.", e);
|
||||
}
|
||||
socket = tmp;
|
||||
bluetoothSocket = tmp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d(Constants.TAG, "BEGIN mConnectThread");
|
||||
if (bluetoothAdapter == null) {
|
||||
BluetoothConnectionManager.this.reset();
|
||||
return;
|
||||
}
|
||||
// Cancel discovery to prevent slow down
|
||||
bluetoothAdapter.cancelDiscovery();
|
||||
|
||||
// Always cancel discovery because it will slow down a connection
|
||||
adapter.cancelDiscovery();
|
||||
|
||||
// Make a connection to the BluetoothSocket
|
||||
try {
|
||||
// This is a blocking call and will only return on a
|
||||
// successful connection or an exception
|
||||
socket.connect();
|
||||
} catch (IOException e) {
|
||||
connectionFailed();
|
||||
// Close the socket
|
||||
bluetoothSocket.connect();
|
||||
} catch (IOException connectException) {
|
||||
Log.i(TAG, "Unable to connect.", connectException);
|
||||
setState(Sensor.SensorState.DISCONNECTED);
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e2) {
|
||||
Log.e(Constants.TAG,
|
||||
"unable to close() socket during connection failure", e2);
|
||||
bluetoothSocket.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to close blueooth socket.", e);
|
||||
}
|
||||
// Start the service over to restart listening mode
|
||||
BluetoothConnectionManager.this.start();
|
||||
// Reset the bluetooth connection manager
|
||||
BluetoothConnectionManager.this.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset the ConnectThread because we're done
|
||||
// Reset the ConnectThread since we are done
|
||||
synchronized (BluetoothConnectionManager.this) {
|
||||
connectThread = null;
|
||||
}
|
||||
|
||||
// Start the connected thread
|
||||
connected(socket, device);
|
||||
connected(bluetoothSocket, bluetoothDevice);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels this thread.
|
||||
*/
|
||||
public void cancel() {
|
||||
try {
|
||||
socket.close();
|
||||
bluetoothSocket.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "close() of connect socket failed", e);
|
||||
Log.e(TAG, "Unable to close bluetooth socket.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This thread runs during a connection with a remote device. It handles all
|
||||
* incoming and outgoing transmissions.
|
||||
* This thread handles data transmission when connected.
|
||||
*/
|
||||
private class ConnectedThread extends Thread {
|
||||
private final BluetoothSocket btSocket;
|
||||
private final InputStream mmInStream;
|
||||
private final OutputStream mmOutStream;
|
||||
private final BluetoothSocket bluetoothSSocket;
|
||||
private final InputStream inputStream;
|
||||
|
||||
public ConnectedThread(BluetoothSocket socket) {
|
||||
Log.d(Constants.TAG, "create ConnectedThread");
|
||||
btSocket = socket;
|
||||
InputStream tmpIn = null;
|
||||
OutputStream tmpOut = null;
|
||||
public ConnectedThread(BluetoothSocket bluetoothSocket) {
|
||||
this.bluetoothSSocket = bluetoothSocket;
|
||||
InputStream tmp = null;
|
||||
|
||||
// Get the BluetoothSocket input and output streams
|
||||
try {
|
||||
tmpIn = socket.getInputStream();
|
||||
tmpOut = socket.getOutputStream();
|
||||
tmp = bluetoothSocket.getInputStream();
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "temp sockets not created", e);
|
||||
Log.e(TAG, "Unable to get input stream.", e);
|
||||
}
|
||||
|
||||
mmInStream = tmpIn;
|
||||
mmOutStream = tmpOut;
|
||||
inputStream = tmp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Log.i(Constants.TAG, "BEGIN mConnectedThread");
|
||||
byte[] buffer = new byte[parser.getFrameSize()];
|
||||
int bytes;
|
||||
byte[] buffer = new byte[messageParser.getFrameSize()];
|
||||
int bytes; // bytes read
|
||||
int offset = 0;
|
||||
|
||||
// Keep listening to the InputStream while connected
|
||||
// Keep listening to the inputStream while connected
|
||||
while (true) {
|
||||
try {
|
||||
// Read from the InputStream
|
||||
bytes = mmInStream.read(buffer, offset, parser.getFrameSize() - offset);
|
||||
// Read from the inputStream
|
||||
bytes = inputStream.read(buffer, offset, messageParser.getFrameSize() - offset);
|
||||
|
||||
if (bytes < 0) {
|
||||
throw new IOException("EOF reached");
|
||||
}
|
||||
if (bytes == -1) { throw new IOException("EOF reached."); }
|
||||
|
||||
offset += bytes;
|
||||
|
||||
if (offset != parser.getFrameSize()) {
|
||||
// partial frame received, call read() again to receive the rest
|
||||
if (offset != messageParser.getFrameSize()) {
|
||||
// Partial frame received. Call read again to receive the rest.
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if its a valid frame
|
||||
if (!parser.isValid(buffer)) {
|
||||
int index = parser.findNextAlignment(buffer);
|
||||
if (index > 0) {
|
||||
// re-align
|
||||
offset = parser.getFrameSize() - index;
|
||||
System.arraycopy(buffer, index, buffer, 0, offset);
|
||||
Log.w(Constants.TAG, "Misaligned data, found new message at " +
|
||||
index + " recovering...");
|
||||
if (!messageParser.isValid(buffer)) {
|
||||
int index = messageParser.findNextAlignment(buffer);
|
||||
if (index == -1) {
|
||||
Log.w(TAG, "Could not find any valid data. Drop data.");
|
||||
offset = 0;
|
||||
continue;
|
||||
}
|
||||
Log.w(Constants.TAG, "Could not find valid data, dropping data");
|
||||
offset = 0;
|
||||
Log.w(TAG, "Misaligned data. Found new message at " + index + ". Recovering...");
|
||||
offset = messageParser.getFrameSize() - index;
|
||||
System.arraycopy(buffer, index, buffer, 0, offset);
|
||||
continue;
|
||||
}
|
||||
|
||||
offset = 0;
|
||||
|
||||
// Send copy of the obtained bytes to the UI Activity.
|
||||
// Avoids memory inconsistency issues.
|
||||
handler.obtainMessage(MESSAGE_READ, bytes, -1, buffer.clone())
|
||||
.sendToTarget();
|
||||
// Send a copy of the obtained bytes to the handler to avoid memory
|
||||
// inconsistency issues
|
||||
handler.obtainMessage(MESSAGE_READ, bytes, -1, buffer.clone()).sendToTarget();
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "disconnected", e);
|
||||
connectionLost();
|
||||
Log.i(TAG, "Bluetooth connection lost.", e);
|
||||
setState(Sensor.SensorState.DISCONNECTED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to the connected OutStream.
|
||||
*
|
||||
* @param buffer The bytes to write
|
||||
* Cancels this thread.
|
||||
*/
|
||||
public void write(byte[] buffer) {
|
||||
try {
|
||||
mmOutStream.write(buffer);
|
||||
|
||||
// Share the sent message back to the UI Activity
|
||||
handler.obtainMessage(MESSAGE_WRITE, -1, -1, buffer).sendToTarget();
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Exception during write", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
try {
|
||||
btSocket.close();
|
||||
bluetoothSSocket.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "close() of connect socket failed", e);
|
||||
Log.e(TAG, "Unable to close bluetooth socket.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+86
-124
@@ -13,12 +13,11 @@
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package com.google.android.apps.mytracks.services.sensors;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.content.Sensor;
|
||||
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
|
||||
import com.google.android.apps.mytracks.content.Sensor.SensorState;
|
||||
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
@@ -34,66 +33,31 @@ import android.widget.Toast;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Manage the connection to a bluetooth sensor.
|
||||
*
|
||||
* Bluetooth sensor manager.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class BluetoothSensorManager extends SensorManager {
|
||||
|
||||
// Local Bluetooth adapter
|
||||
private static final BluetoothAdapter bluetoothAdapter = getDefaultBluetoothAdapter();
|
||||
|
||||
// Member object for the sensor threads and connections.
|
||||
private BluetoothConnectionManager connectionManager = null;
|
||||
|
||||
// Name of the connected device
|
||||
private String connectedDeviceName = null;
|
||||
|
||||
private Context context = null;
|
||||
|
||||
private Sensor.SensorDataSet sensorDataSet = null;
|
||||
|
||||
private MessageParser parser;
|
||||
|
||||
public BluetoothSensorManager(
|
||||
Context context, MessageParser parser) {
|
||||
this.context = context;
|
||||
this.parser = parser;
|
||||
// If BT is not available or not enabled quit.
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setupSensor();
|
||||
}
|
||||
|
||||
private void setupSensor() {
|
||||
Log.d(Constants.TAG, "setupSensor()");
|
||||
|
||||
// Initialize the BluetoothSensorAdapter to perform bluetooth connections.
|
||||
connectionManager = new BluetoothConnectionManager(messageHandler, parser);
|
||||
}
|
||||
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* Code for assigning the local bluetooth adapter
|
||||
*
|
||||
* @return The default bluetooth adapter, if one is available, NULL if it isn't.
|
||||
* Gets the default bluetooth adapter.
|
||||
*/
|
||||
private static BluetoothAdapter getDefaultBluetoothAdapter() {
|
||||
// Check if the calling thread is the main application thread,
|
||||
// if it is, do it directly.
|
||||
// If from the main application thread, return directly
|
||||
if (Thread.currentThread().equals(Looper.getMainLooper().getThread())) {
|
||||
return BluetoothAdapter.getDefaultAdapter();
|
||||
}
|
||||
|
||||
// If the calling thread, isn't the main application thread,
|
||||
// then get the main application thread to return the default adapter.
|
||||
|
||||
// Get the default adapter from the main application thread
|
||||
final ArrayList<BluetoothAdapter> adapters = new ArrayList<BluetoothAdapter>(1);
|
||||
final Object mutex = new Object();
|
||||
|
||||
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
@Override
|
||||
public void run() {
|
||||
adapters.add(BluetoothAdapter.getDefaultAdapter());
|
||||
synchronized (mutex) {
|
||||
@@ -101,7 +65,7 @@ public class BluetoothSensorManager extends SensorManager {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
while (adapters.isEmpty()) {
|
||||
synchronized (mutex) {
|
||||
try {
|
||||
@@ -111,106 +75,104 @@ public class BluetoothSensorManager extends SensorManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (adapters.get(0) == null) {
|
||||
Log.w(TAG, "No bluetooth adapter found!");
|
||||
Log.w(TAG, "No bluetooth adapter found.");
|
||||
return null;
|
||||
}
|
||||
return adapters.get(0);
|
||||
}
|
||||
|
||||
|
||||
private final Context context;
|
||||
private final MessageParser messageParser;
|
||||
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
|
||||
public void handleMessage(Message message) {
|
||||
switch (message.what) {
|
||||
case BluetoothConnectionManager.MESSAGE_DEVICE_NAME:
|
||||
String deviceName = message.getData()
|
||||
.getString(BluetoothConnectionManager.KEY_DEVICE_NAME);
|
||||
Toast.makeText(context,
|
||||
context.getString(R.string.settings_sensor_bluetooth_connected, deviceName),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
break;
|
||||
case BluetoothConnectionManager.MESSAGE_READ:
|
||||
try {
|
||||
byte[] readBuf = (byte[]) message.obj;
|
||||
sensorDataSet = messageParser.parseBuffer(readBuf);
|
||||
Log.d(TAG, "MESSAGE_READ: " + sensorDataSet);
|
||||
} catch (IllegalArgumentException e) {
|
||||
sensorDataSet = null;
|
||||
Log.i(TAG, "Unexpected exception on read", e);
|
||||
} catch (RuntimeException e) {
|
||||
sensorDataSet = null;
|
||||
Log.i(TAG, "Unexpected exception on read.", e);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param context the context
|
||||
* @param messageParser the message parser
|
||||
*/
|
||||
public BluetoothSensorManager(Context context, MessageParser messageParser) {
|
||||
this.context = context;
|
||||
this.messageParser = messageParser;
|
||||
bluetoothConnectionManager = new BluetoothConnectionManager(
|
||||
bluetoothAdapter, messageHandler, messageParser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
|
||||
}
|
||||
|
||||
public void setupChannel() {
|
||||
if (!isEnabled() || connectionManager == null) {
|
||||
Log.w(Constants.TAG, "Disabled manager onStartTrack");
|
||||
@Override
|
||||
protected void setUpChannel() {
|
||||
if (!isEnabled()) {
|
||||
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 blueooth address.");
|
||||
return;
|
||||
}
|
||||
Log.w(Constants.TAG, "Connecting to bluetooth sensor: " + address);
|
||||
// Get the BluetoothDevice object
|
||||
Log.w(TAG, "Connecting to bluetooth address: " + address);
|
||||
|
||||
BluetoothDevice device;
|
||||
try {
|
||||
device = bluetoothAdapter.getRemoteDevice(address);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.d(Constants.TAG, "Invalid address " + address, e);
|
||||
Log.d(TAG, "Unable to get remote device for: " + address, e);
|
||||
return;
|
||||
}
|
||||
// Attempt to connect to the device
|
||||
connectionManager.connect(device);
|
||||
|
||||
// Performing this check in onResume() covers the case in which BT was
|
||||
// not enabled during onStart(), so we were paused to enable it...
|
||||
// onResume() will be called when ACTION_REQUEST_ENABLE activity returns.
|
||||
if (connectionManager != null) {
|
||||
// Only if the state is STATE_NONE, do we know that we haven't started
|
||||
// already
|
||||
if (connectionManager.getState() == Sensor.SensorState.NONE) {
|
||||
// Start the Bluetooth sensor services
|
||||
Log.w(Constants.TAG, "Disabled manager onStartTrack");
|
||||
connectionManager.start();
|
||||
}
|
||||
}
|
||||
bluetoothConnectionManager.connect(device);
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
// Stop the Bluetooth sensor services
|
||||
if (connectionManager != null) {
|
||||
connectionManager.stop();
|
||||
}
|
||||
@Override
|
||||
protected void tearDownChannel() {
|
||||
bluetoothConnectionManager.reset();
|
||||
}
|
||||
|
||||
public Sensor.SensorDataSet getSensorDataSet() {
|
||||
@Override
|
||||
public SensorState getSensorState() {
|
||||
return bluetoothConnectionManager.getSensorState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SensorDataSet getSensorDataSet() {
|
||||
return sensorDataSet;
|
||||
}
|
||||
|
||||
public Sensor.SensorState getSensorState() {
|
||||
return (connectionManager == null)
|
||||
? Sensor.SensorState.NONE
|
||||
: connectionManager.getState();
|
||||
}
|
||||
|
||||
// The Handler that gets information back from the BluetoothSensorService
|
||||
private final Handler messageHandler = new Handler(Looper.getMainLooper()) {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case BluetoothConnectionManager.MESSAGE_STATE_CHANGE:
|
||||
// TODO should we update the SensorManager state var?
|
||||
Log.i(Constants.TAG, "MESSAGE_STATE_CHANGE: " + msg.arg1);
|
||||
break;
|
||||
case BluetoothConnectionManager.MESSAGE_WRITE:
|
||||
break;
|
||||
case BluetoothConnectionManager.MESSAGE_READ:
|
||||
byte[] readBuf = null;
|
||||
try {
|
||||
readBuf = (byte[]) msg.obj;
|
||||
sensorDataSet = parser.parseBuffer(readBuf);
|
||||
Log.d(Constants.TAG, "MESSAGE_READ: " + sensorDataSet.toString());
|
||||
} catch (IllegalArgumentException iae) {
|
||||
sensorDataSet = null;
|
||||
Log.i(Constants.TAG,
|
||||
"Got bad sensor data: " + new String(readBuf, 0, readBuf.length),
|
||||
iae);
|
||||
} catch (RuntimeException re) {
|
||||
sensorDataSet = null;
|
||||
Log.i(Constants.TAG, "Unexpected exception on read.", re);
|
||||
}
|
||||
break;
|
||||
case BluetoothConnectionManager.MESSAGE_DEVICE_NAME:
|
||||
// save the connected device's name
|
||||
connectedDeviceName =
|
||||
msg.getData().getString(BluetoothConnectionManager.DEVICE_NAME);
|
||||
Toast.makeText(context.getApplicationContext(),
|
||||
"Connected to " + connectedDeviceName, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+4
-2
@@ -13,14 +13,16 @@
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package com.google.android.apps.mytracks.services.sensors;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* PolarSensorManager - A sensor manager for Polar heart rate monitors.
|
||||
* A Polar heart rate sensor manager.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
|
||||
public class PolarSensorManager extends BluetoothSensorManager {
|
||||
public PolarSensorManager(Context context) {
|
||||
super(context, new PolarMessageParser());
|
||||
|
||||
@@ -13,126 +13,118 @@
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package com.google.android.apps.mytracks.services.sensors;
|
||||
|
||||
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
|
||||
import com.google.android.apps.mytracks.content.Sensor.SensorState;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.content.Sensor;
|
||||
|
||||
/**
|
||||
* Manage the connection to a sensor.
|
||||
*
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public abstract class SensorManager {
|
||||
|
||||
/**
|
||||
* The maximum age where the data is considered valid.
|
||||
*/
|
||||
public static final long MAX_AGE = 5000;
|
||||
private static final String TAG = SensorManager.class.getSimpleName();
|
||||
private static final long MAX_SENSOR_DATE_SET_AGE = 5000;
|
||||
private static final long MAX_SENSOR_STATE_AGE = 30000;
|
||||
private static final int RETRY_PERIOD = 30000;
|
||||
|
||||
private SensorState sensorState = SensorState.NONE;
|
||||
private long sensorStateTimestamp = System.currentTimeMillis();
|
||||
|
||||
/**
|
||||
* Time to wait after a time out to retry.
|
||||
* A time task to check sensor connection.
|
||||
*/
|
||||
public static final int RETRY_PERIOD = 30000;
|
||||
|
||||
private Sensor.SensorState sensorState = Sensor.SensorState.NONE;
|
||||
|
||||
private long sensorStateTimestamp = 0;
|
||||
|
||||
/**
|
||||
* A task to run periodically to check to see if connection was lost.
|
||||
*/
|
||||
private TimerTask checkSensorManager = new TimerTask() {
|
||||
@Override
|
||||
private TimerTask checkSensorConnectionTimeTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.i(Constants.TAG,
|
||||
"SensorManager state: " + getSensorState());
|
||||
switch (getSensorState()) {
|
||||
case CONNECTING:
|
||||
long age = System.currentTimeMillis() - getSensorStateTimestamp();
|
||||
if (age > 2 * RETRY_PERIOD) {
|
||||
Log.i(Constants.TAG, "Retrying connecting SensorManager.");
|
||||
setupChannel();
|
||||
if (System.currentTimeMillis() - sensorStateTimestamp > MAX_SENSOR_STATE_AGE) {
|
||||
Log.i(TAG, "Retry setUpChannel");
|
||||
setUpChannel();
|
||||
}
|
||||
break;
|
||||
case NONE:
|
||||
case DISCONNECTED:
|
||||
Log.i(Constants.TAG,
|
||||
"Re-registering disconnected SensoManager.");
|
||||
setupChannel();
|
||||
setUpChannel();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This timer invokes periodically the checkLocationListener timer task.
|
||||
*/
|
||||
private final Timer timer = new Timer();
|
||||
|
||||
/**
|
||||
* Is the sensor that this manages enabled.
|
||||
* @return true if the sensor is enabled
|
||||
* Returns true if the sensor is enabled.
|
||||
*/
|
||||
public abstract boolean isEnabled();
|
||||
|
||||
/**
|
||||
* This is called when my tracks starts recording a new track.
|
||||
* This is the place to open connections to the sensor.
|
||||
* Sets up the sensor channel.
|
||||
*/
|
||||
public void onStartTrack() {
|
||||
setupChannel();
|
||||
timer.schedule(checkSensorManager, RETRY_PERIOD, RETRY_PERIOD);
|
||||
protected abstract void setUpChannel();
|
||||
|
||||
/**
|
||||
* Tears down the sensor channel.
|
||||
*/
|
||||
protected abstract void tearDownChannel();
|
||||
|
||||
/**
|
||||
* Gets the sensor data set.
|
||||
*/
|
||||
public abstract SensorDataSet getSensorDataSet();
|
||||
|
||||
/**
|
||||
* Starts the sensor.
|
||||
*/
|
||||
public void startSensor() {
|
||||
setUpChannel();
|
||||
timer.schedule(checkSensorConnectionTimeTask, RETRY_PERIOD, RETRY_PERIOD);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to set up any necessary connections to underlying
|
||||
* sensor hardware.
|
||||
* Stops the sensor.
|
||||
*/
|
||||
protected abstract void setupChannel();
|
||||
|
||||
public void shutdown() {
|
||||
public void stopSensor() {
|
||||
timer.cancel();
|
||||
onDestroy();
|
||||
tearDownChannel();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called when my tracks stops recording.
|
||||
* This is the place to shutdown any open connections.
|
||||
* Sets the sensor state.
|
||||
*
|
||||
* @param sensorState the sensor state
|
||||
*/
|
||||
public abstract void onDestroy();
|
||||
|
||||
/**
|
||||
* Return the last sensor reading.
|
||||
* @return The last reading from the sensor.
|
||||
*/
|
||||
public abstract Sensor.SensorDataSet getSensorDataSet();
|
||||
|
||||
public void setSensorState(Sensor.SensorState sensorState) {
|
||||
public void setSensorState(SensorState sensorState) {
|
||||
sensorStateTimestamp = System.currentTimeMillis();
|
||||
this.sensorState = sensorState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current sensor state.
|
||||
* @return The current sensor state.
|
||||
* Gets the sensor state.
|
||||
*/
|
||||
public Sensor.SensorState getSensorState() {
|
||||
public SensorState getSensorState() {
|
||||
return sensorState;
|
||||
}
|
||||
|
||||
public long getSensorStateTimestamp() {
|
||||
return sensorStateTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if the data is recent enough to be considered valid.
|
||||
* Returns true if the sensor data set is valid.
|
||||
*/
|
||||
public boolean isDataValid() {
|
||||
return (System.currentTimeMillis() - getSensorDataSet().getCreationTime()) < MAX_AGE;
|
||||
public boolean isSensorDataSetValid() {
|
||||
SensorDataSet sensorDataSet = getSensorDataSet();
|
||||
if (sensorDataSet == null) {
|
||||
return false;
|
||||
}
|
||||
return (System.currentTimeMillis() - sensorDataSet.getCreationTime()) < MAX_SENSOR_DATE_SET_AGE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+60
-62
@@ -13,101 +13,99 @@
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package com.google.android.apps.mytracks.services.sensors;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.services.sensors.ant.AntDirectSensorManager;
|
||||
import com.google.android.apps.mytracks.services.sensors.ant.AntSrmBridgeSensorManager;
|
||||
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* A factory of SensorManagers.
|
||||
* A factory of {@link SensorManager}.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class SensorManagerFactory {
|
||||
|
||||
private String activeSensorType;
|
||||
private SensorManager activeSensorManager;
|
||||
private int refCount;
|
||||
private static SensorManager systemSensorManager = null;
|
||||
private static SensorManager tempSensorManager = null;
|
||||
|
||||
private static SensorManagerFactory instance = new SensorManagerFactory();
|
||||
private SensorManagerFactory() {}
|
||||
|
||||
private SensorManagerFactory() {
|
||||
/**
|
||||
* Gets the system sensor manager.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public static SensorManager getSystemSensorManager(Context context) {
|
||||
releaseTempSensorManager();
|
||||
releaseSystemSensorManager();
|
||||
systemSensorManager = getSensorManager(context);
|
||||
if (systemSensorManager != null) {
|
||||
systemSensorManager.startSensor();
|
||||
}
|
||||
return systemSensorManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the factory instance.
|
||||
* Releases the system sensor manager.
|
||||
*/
|
||||
public static SensorManagerFactory getInstance() {
|
||||
return instance;
|
||||
public static void releaseSystemSensorManager() {
|
||||
if (systemSensorManager != null) {
|
||||
systemSensorManager.stopSensor();
|
||||
}
|
||||
systemSensorManager = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and start a new sensor manager.
|
||||
* @param context Context to fetch system preferences.
|
||||
* @return The sensor manager that corresponds to the sensor type setting.
|
||||
* Gets the temp sensor manager.
|
||||
*
|
||||
* @param context
|
||||
*/
|
||||
public SensorManager getSensorManager(Context context) {
|
||||
context = context.getApplicationContext();
|
||||
public static SensorManager getTempSensorManager(Context context) {
|
||||
releaseTempSensorManager();
|
||||
if (systemSensorManager != null) {
|
||||
return null;
|
||||
}
|
||||
tempSensorManager = getSensorManager(context);
|
||||
if (tempSensorManager != null) {
|
||||
tempSensorManager.startSensor();
|
||||
}
|
||||
return tempSensorManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the temp sensor manager.
|
||||
*/
|
||||
public static void releaseTempSensorManager() {
|
||||
if (tempSensorManager != null) {
|
||||
tempSensorManager.stopSensor();
|
||||
}
|
||||
tempSensorManager = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the sensor manager.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
private static SensorManager getSensorManager(Context context) {
|
||||
String sensorTypeValueNone = context.getString(R.string.sensor_type_value_none);
|
||||
String sensorType = PreferencesUtils.getString(
|
||||
context, R.string.sensor_type_key, sensorTypeValueNone);
|
||||
Log.i(Constants.TAG, "Creating sensor of type: " + sensorType);
|
||||
|
||||
if (sensorTypeValueNone.equals(sensorType)) {
|
||||
reset();
|
||||
return null;
|
||||
}
|
||||
if (sensorType.equals(activeSensorType)) {
|
||||
Log.i(Constants.TAG, "Returning existing sensor manager.");
|
||||
refCount++;
|
||||
return activeSensorManager;
|
||||
}
|
||||
reset();
|
||||
|
||||
if (sensorType.equals(context.getString(R.string.sensor_type_value_ant))) {
|
||||
activeSensorManager = new AntDirectSensorManager(context);
|
||||
return new AntDirectSensorManager(context);
|
||||
} else if (sensorType.equals(context.getString(R.string.sensor_type_value_srm_ant_bridge))) {
|
||||
activeSensorManager = new AntSrmBridgeSensorManager(context);
|
||||
return new AntSrmBridgeSensorManager(context);
|
||||
} else if (sensorType.equals(context.getString(R.string.sensor_type_value_zephyr))) {
|
||||
activeSensorManager = new ZephyrSensorManager(context);
|
||||
return new ZephyrSensorManager(context);
|
||||
} else if (sensorType.equals(context.getString(R.string.sensor_type_value_polar))) {
|
||||
activeSensorManager = new PolarSensorManager(context);
|
||||
} else {
|
||||
Log.w(Constants.TAG, "Unable to find sensor type: " + sensorType);
|
||||
return null;
|
||||
return new PolarSensorManager(context);
|
||||
}
|
||||
activeSensorType = sensorType;
|
||||
refCount = 1;
|
||||
activeSensorManager.onStartTrack();
|
||||
return activeSensorManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish using a sensor manager.
|
||||
*/
|
||||
public void releaseSensorManager(SensorManager sensorManager) {
|
||||
Log.i(Constants.TAG, "releaseSensorManager: " + activeSensorType + " " + refCount);
|
||||
if (sensorManager != activeSensorManager) {
|
||||
Log.e(Constants.TAG, "invalid parameter to releaseSensorManager");
|
||||
}
|
||||
if (--refCount > 0) {
|
||||
return;
|
||||
}
|
||||
reset();
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
activeSensorType = null;
|
||||
if (activeSensorManager != null) {
|
||||
activeSensorManager.shutdown();
|
||||
}
|
||||
activeSensorManager = null;
|
||||
refCount = 0;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -13,14 +13,19 @@
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package com.google.android.apps.mytracks.services.sensors;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* A Zephyr heart rate sensor manager.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class ZephyrSensorManager extends BluetoothSensorManager {
|
||||
|
||||
public ZephyrSensorManager(Context context) {
|
||||
super(context, new ZephyrMessageParser());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -133,7 +133,7 @@ public abstract class AntSensorManager extends SensorManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
protected void tearDownChannel() {
|
||||
Log.i(TAG, "destroying AntSensorManager");
|
||||
|
||||
cleanAntInterface();
|
||||
@@ -161,7 +161,7 @@ public abstract class AntSensorManager extends SensorManager {
|
||||
* {@link #setupAntSensorChannels}.
|
||||
*/
|
||||
@Override
|
||||
protected final void setupChannel() {
|
||||
protected void setUpChannel() {
|
||||
setup();
|
||||
}
|
||||
|
||||
|
||||
@@ -71,11 +71,9 @@ public class StatsSettingsActivity extends AbstractSettingsActivity {
|
||||
getString(R.string.report_speed_key));
|
||||
boolean metric = PreferencesUtils.getBoolean(
|
||||
this, R.string.metric_units_key, PreferencesUtils.METRIC_UNITS_DEFAULT);
|
||||
reportSpeedCheckBoxPreference.setSummaryOn(
|
||||
metric ? getString(R.string.settings_stats_rate_speed_metric)
|
||||
: getString(R.string.settings_stats_rate_speed_imperial));
|
||||
reportSpeedCheckBoxPreference.setSummaryOff(
|
||||
metric ? getString(R.string.settings_stats_rate_pace_metric)
|
||||
: getString(R.string.settings_stats_rate_pace_imperial));
|
||||
reportSpeedCheckBoxPreference.setSummaryOn(metric ? getString(R.string.description_speed_metric)
|
||||
: getString(R.string.description_speed_imperial));
|
||||
reportSpeedCheckBoxPreference.setSummaryOff(metric ? getString(R.string.description_pace_metric)
|
||||
: getString(R.string.description_pace_imperial));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,10 +37,10 @@ public class Api10Adapter extends Api9Adapter {
|
||||
public BluetoothSocket getBluetoothSocket(BluetoothDevice bluetoothDevice) throws IOException {
|
||||
try {
|
||||
return bluetoothDevice.createInsecureRfcommSocketToServiceRecord(
|
||||
BluetoothConnectionManager.SPP_UUID);
|
||||
BluetoothConnectionManager.MY_TRACKS_UUID);
|
||||
} catch (IOException e) {
|
||||
Log.d(Constants.TAG, "Unable to create insecure connection", e);
|
||||
}
|
||||
return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.SPP_UUID);
|
||||
return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.MY_TRACKS_UUID);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ public class Api7Adapter implements ApiAdapter {
|
||||
} catch (InvocationTargetException e) {
|
||||
Log.d(Constants.TAG, "Unable to create insecure connection", e);
|
||||
}
|
||||
return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.SPP_UUID);
|
||||
return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.MY_TRACKS_UUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,36 +15,34 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import com.google.android.apps.mytracks.ChartValueSeries.ZoomSettings;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.graphics.Paint.Style;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
/**
|
||||
* Tests {@link ChartValueSeries}.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class ChartValueSeriesTest extends AndroidTestCase {
|
||||
private ChartValueSeries series;
|
||||
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
series = new ChartValueSeries(getContext(),
|
||||
Integer.MIN_VALUE,
|
||||
Integer.MAX_VALUE,
|
||||
new int[] {100, 1000 },
|
||||
R.string.description_elevation_metric,
|
||||
R.string.description_elevation_imperial,
|
||||
R.color.elevation_fill,
|
||||
R.color.elevation_border,
|
||||
new ZoomSettings(5, new int[] {100}),
|
||||
R.string.stats_elevation);
|
||||
R.color.elevation_border);
|
||||
}
|
||||
|
||||
public void testInitialConditions() {
|
||||
assertEquals(0, series.getInterval());
|
||||
assertEquals(1, series.getMaxLabelLength());
|
||||
assertEquals(0, series.getMin());
|
||||
assertEquals(0, series.getMax());
|
||||
assertEquals(0.0, series.getSpread());
|
||||
assertEquals(Style.STROKE, series.getPaint().getStyle());
|
||||
assertEquals(getContext().getString(R.string.stats_elevation),
|
||||
series.getTitle());
|
||||
assertEquals(1, series.getInterval());
|
||||
assertEquals(0, series.getMinMarkerValue());
|
||||
assertEquals(5, series.getMaxMarkerValue());
|
||||
assertTrue(series.isEnabled());
|
||||
}
|
||||
|
||||
@@ -52,87 +50,31 @@ public class ChartValueSeriesTest extends AndroidTestCase {
|
||||
series.setEnabled(false);
|
||||
assertFalse(series.isEnabled());
|
||||
}
|
||||
|
||||
|
||||
public void testSmallUpdates() {
|
||||
series.update(0);
|
||||
series.update(10);
|
||||
series.updateDimension();
|
||||
assertEquals(100, series.getInterval());
|
||||
assertEquals(3, series.getMaxLabelLength());
|
||||
assertEquals(0, series.getMin());
|
||||
assertEquals(100, series.getMax());
|
||||
assertEquals(100.0, series.getSpread());
|
||||
assertEquals(0, series.getMinMarkerValue());
|
||||
assertEquals(500, series.getMaxMarkerValue());
|
||||
}
|
||||
|
||||
public void testBigUpdates() {
|
||||
series.update(0);
|
||||
series.update(901);
|
||||
series.updateDimension();
|
||||
assertEquals(100, series.getInterval());
|
||||
assertEquals(5, series.getMaxLabelLength());
|
||||
assertEquals(0, series.getMin());
|
||||
assertEquals(1000, series.getMax());
|
||||
assertEquals(1000.0, series.getSpread());
|
||||
assertEquals(1000, series.getInterval());
|
||||
assertEquals(0, series.getMinMarkerValue());
|
||||
assertEquals(5000, series.getMaxMarkerValue());
|
||||
}
|
||||
|
||||
public void testNotZeroBasedUpdates() {
|
||||
series.update(500);
|
||||
series.update(1401);
|
||||
series.update(220);
|
||||
series.update(250);
|
||||
series.updateDimension();
|
||||
assertEquals(100, series.getInterval());
|
||||
assertEquals(5, series.getMaxLabelLength());
|
||||
assertEquals(500, series.getMin());
|
||||
assertEquals(1500, series.getMax());
|
||||
assertEquals(1000.0, series.getSpread());
|
||||
}
|
||||
|
||||
public void testZoomSettings_invalidArgs() {
|
||||
try {
|
||||
new ZoomSettings(0, new int[] {10, 50, 100});
|
||||
fail("Expected IllegalArgumentException");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// OK.
|
||||
}
|
||||
try {
|
||||
new ZoomSettings(1, null);
|
||||
fail("Expected IllegalArgumentException");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// OK.
|
||||
}
|
||||
try {
|
||||
new ZoomSettings(1, new int[] {});
|
||||
fail("Expected IllegalArgumentException");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// OK.
|
||||
}
|
||||
try {
|
||||
new ZoomSettings(1, new int[] {1, 3, 2});
|
||||
fail("Expected IllegalArgumentException");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// OK.
|
||||
}
|
||||
}
|
||||
|
||||
public void testZoomSettings_minAligned() {
|
||||
ZoomSettings settings = new ZoomSettings(5, new int[] {10, 50, 100});
|
||||
assertEquals(10, settings.calculateInterval(0, 15));
|
||||
assertEquals(10, settings.calculateInterval(0, 50));
|
||||
assertEquals(50, settings.calculateInterval(0, 111));
|
||||
assertEquals(50, settings.calculateInterval(0, 250));
|
||||
assertEquals(100, settings.calculateInterval(0, 251));
|
||||
assertEquals(100, settings.calculateInterval(0, 10000));
|
||||
}
|
||||
|
||||
public void testZoomSettings_minNotAligned() {
|
||||
ZoomSettings settings = new ZoomSettings(5, new int[] {10, 50, 100});
|
||||
assertEquals(50, settings.calculateInterval(5, 55));
|
||||
assertEquals(10, settings.calculateInterval(10, 60));
|
||||
assertEquals(50, settings.calculateInterval(7, 250));
|
||||
assertEquals(100, settings.calculateInterval(7, 257));
|
||||
assertEquals(100, settings.calculateInterval(11, 10000));
|
||||
|
||||
// A regression test.
|
||||
settings = new ZoomSettings(5, new int[] {5, 10, 20});
|
||||
assertEquals(10, settings.calculateInterval(-37.14, -11.89));
|
||||
assertEquals(200, series.getMinMarkerValue());
|
||||
assertEquals(700, series.getMaxMarkerValue());
|
||||
}
|
||||
}
|
||||
|
||||
+38
-36
@@ -52,31 +52,32 @@ public class ChartFragmentTest extends AndroidTestCase {
|
||||
|
||||
// No input.
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation);
|
||||
assertEquals(Double.NaN, point[3]);
|
||||
assertEquals(Double.NaN, point[4]);
|
||||
assertEquals(Double.NaN, point[5]);
|
||||
assertEquals(Double.NaN, point[ChartView.HEART_RATE_SERIES + 1]);
|
||||
assertEquals(Double.NaN, point[ChartView.CADENCE_SERIES + 1]);
|
||||
assertEquals(Double.NaN, point[ChartView.POWER_SERIES + 1]);
|
||||
|
||||
// Input incorrect state.
|
||||
// Creates SensorData.
|
||||
Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder()
|
||||
.setValue(20).setState(Sensor.SensorState.NONE);
|
||||
Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder()
|
||||
.setValue(20).setState(Sensor.SensorState.NONE);
|
||||
Sensor.SensorData.Builder heartRateData = Sensor.SensorData.newBuilder()
|
||||
.setValue(20).setState(Sensor.SensorState.NONE);
|
||||
.setValue(100).setState(Sensor.SensorState.NONE);
|
||||
Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder()
|
||||
.setValue(101).setState(Sensor.SensorState.NONE);
|
||||
Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder()
|
||||
.setValue(102).setState(Sensor.SensorState.NONE);
|
||||
|
||||
// Creates SensorDataSet.
|
||||
SensorDataSet sensorDataSet = myTracksLocation.getSensorDataSet();
|
||||
sensorDataSet = sensorDataSet.toBuilder()
|
||||
.setPower(powerData)
|
||||
.setCadence(cadenceData)
|
||||
.setHeartRate(heartRateData)
|
||||
.setCadence(cadenceData)
|
||||
.setPower(powerData)
|
||||
.build();
|
||||
myTracksLocation.setSensorData(sensorDataSet);
|
||||
// Test.
|
||||
point = fillDataPointTestHelper(myTracksLocation);
|
||||
assertEquals(Double.NaN, point[3]);
|
||||
assertEquals(Double.NaN, point[4]);
|
||||
assertEquals(Double.NaN, point[5]);
|
||||
assertEquals(Double.NaN, point[ChartView.HEART_RATE_SERIES + 1]);
|
||||
assertEquals(Double.NaN, point[ChartView.CADENCE_SERIES + 1]);
|
||||
assertEquals(Double.NaN, point[ChartView.POWER_SERIES + 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,31 +88,31 @@ public class ChartFragmentTest extends AndroidTestCase {
|
||||
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
|
||||
// No input.
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation);
|
||||
assertEquals(Double.NaN, point[3]);
|
||||
assertEquals(Double.NaN, point[4]);
|
||||
assertEquals(Double.NaN, point[5]);
|
||||
assertEquals(Double.NaN, point[ChartView.HEART_RATE_SERIES + 1]);
|
||||
assertEquals(Double.NaN, point[ChartView.CADENCE_SERIES + 1]);
|
||||
assertEquals(Double.NaN, point[ChartView.POWER_SERIES + 1]);
|
||||
|
||||
// Creates SensorData.
|
||||
Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder()
|
||||
.setValue(20).setState(Sensor.SensorState.SENDING);
|
||||
Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder()
|
||||
.setValue(20).setState(Sensor.SensorState.SENDING);
|
||||
Sensor.SensorData.Builder heartRateData = Sensor.SensorData.newBuilder()
|
||||
.setValue(20).setState(Sensor.SensorState.SENDING);
|
||||
.setValue(100).setState(Sensor.SensorState.SENDING);
|
||||
Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder()
|
||||
.setValue(101).setState(Sensor.SensorState.SENDING);
|
||||
Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder()
|
||||
.setValue(102).setState(Sensor.SensorState.SENDING);
|
||||
|
||||
// Creates SensorDataSet.
|
||||
SensorDataSet sensorDataSet = myTracksLocation.getSensorDataSet();
|
||||
sensorDataSet = sensorDataSet.toBuilder()
|
||||
.setPower(powerData)
|
||||
.setCadence(cadenceData)
|
||||
.setHeartRate(heartRateData)
|
||||
.setCadence(cadenceData)
|
||||
.setPower(powerData)
|
||||
.build();
|
||||
myTracksLocation.setSensorData(sensorDataSet);
|
||||
// Test.
|
||||
point = fillDataPointTestHelper(myTracksLocation);
|
||||
assertEquals(20.0, point[3]);
|
||||
assertEquals(20.0, point[4]);
|
||||
assertEquals(20.0, point[5]);
|
||||
assertEquals(100.0, point[ChartView.HEART_RATE_SERIES + 1]);
|
||||
assertEquals(101.0, point[ChartView.CADENCE_SERIES + 1]);
|
||||
assertEquals(102.0, point[ChartView.POWER_SERIES + 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -230,7 +231,7 @@ public class ChartFragmentTest extends AndroidTestCase {
|
||||
* parameter. Then only one value INITIALLONGTITUDE in buffer.
|
||||
*/
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation1);
|
||||
assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point[1]);
|
||||
assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point[ChartView.ELEVATION_SERIES + 1]);
|
||||
|
||||
/*
|
||||
* Send another value to buffer, now there are two values, INITIALALTITUDE
|
||||
@@ -239,8 +240,8 @@ public class ChartFragmentTest extends AndroidTestCase {
|
||||
MyTracksLocation myTracksLocation2 = TrackStubUtils.createMyTracksLocation();
|
||||
myTracksLocation2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2);
|
||||
point = fillDataPointTestHelper(myTracksLocation2);
|
||||
assertEquals(
|
||||
(TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0, point[1]);
|
||||
assertEquals((TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0,
|
||||
point[ChartView.ELEVATION_SERIES + 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,7 +261,7 @@ public class ChartFragmentTest extends AndroidTestCase {
|
||||
MyTracksLocation myTracksLocation1 = TrackStubUtils.createMyTracksLocation();
|
||||
myTracksLocation1.setSpeed(129);
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation1);
|
||||
assertEquals(0.0, point[2]);
|
||||
assertEquals(0.0, point[ChartView.SPEED_SERIES + 1]);
|
||||
|
||||
/*
|
||||
* Tests the logic when both metricUnits and reportSpeed are true.This
|
||||
@@ -275,7 +276,7 @@ public class ChartFragmentTest extends AndroidTestCase {
|
||||
myTracksLocation2.setTime(myTracksLocation1.getTime() + 222);
|
||||
myTracksLocation2.setSpeed(130);
|
||||
point = fillDataPointTestHelper(myTracksLocation2);
|
||||
assertEquals(130.0 * UnitConversions.MS_TO_KMH, point[2]);
|
||||
assertEquals(130.0 * UnitConversions.MS_TO_KMH, point[ChartView.SPEED_SERIES + 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -287,7 +288,8 @@ public class ChartFragmentTest extends AndroidTestCase {
|
||||
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
|
||||
myTracksLocation.setSpeed(132);
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation);
|
||||
assertEquals(132.0 * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI, point[2]);
|
||||
assertEquals(132.0 * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI,
|
||||
point[ChartView.SPEED_SERIES + 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -299,8 +301,8 @@ public class ChartFragmentTest extends AndroidTestCase {
|
||||
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
|
||||
myTracksLocation.setSpeed(134);
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation);
|
||||
assertEquals(HOURS_PER_UNIT / (134.0 * UnitConversions.MS_TO_KMH), point[2]);
|
||||
|
||||
assertEquals(
|
||||
HOURS_PER_UNIT / (134.0 * UnitConversions.MS_TO_KMH), point[ChartView.PACE_SERIES + 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -313,7 +315,7 @@ public class ChartFragmentTest extends AndroidTestCase {
|
||||
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
|
||||
myTracksLocation.setSpeed(0);
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation);
|
||||
assertEquals(0.0, point[2]);
|
||||
assertEquals(0.0, point[ChartView.PACE_SERIES + 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -323,7 +325,7 @@ public class ChartFragmentTest extends AndroidTestCase {
|
||||
* @return data of this location
|
||||
*/
|
||||
private double[] fillDataPointTestHelper(Location location) {
|
||||
double[] point = new double[6];
|
||||
double[] point = new double[ChartView.NUM_SERIES + 1];
|
||||
chartFragment.fillDataPoint(location, point);
|
||||
return point;
|
||||
}
|
||||
|
||||
+3
@@ -46,6 +46,7 @@ public class UploadResultActivityTest
|
||||
*/
|
||||
public void testAllSuccess() {
|
||||
initialActivity(true, true, true, true, true, true);
|
||||
uploadResultActivity.showDialog(UploadResultActivity.DIALOG_RESULT_ID);
|
||||
Dialog dialog = uploadResultActivity.getDialog();
|
||||
TextView textView = (TextView) dialog.findViewById(R.id.upload_result_success_footer);
|
||||
assertTrue(textView.isShown());
|
||||
@@ -57,6 +58,7 @@ public class UploadResultActivityTest
|
||||
public void testAllFailed() {
|
||||
// Send all kinds but all failed.
|
||||
initialActivity(true, true, true, false, false, false);
|
||||
uploadResultActivity.showDialog(UploadResultActivity.DIALOG_RESULT_ID);
|
||||
Dialog dialog = uploadResultActivity.getDialog();
|
||||
TextView textView = (TextView) dialog.findViewById(R.id.upload_result_error_footer);
|
||||
assertTrue(textView.isShown());
|
||||
@@ -72,6 +74,7 @@ public class UploadResultActivityTest
|
||||
*/
|
||||
public void testPartialSuccess() {
|
||||
initialActivity(true, false, true, true, false, false);
|
||||
uploadResultActivity.showDialog(UploadResultActivity.DIALOG_RESULT_ID);
|
||||
Dialog dialog = uploadResultActivity.getDialog();
|
||||
TextView textView = (TextView) dialog.findViewById(R.id.upload_result_error_footer);
|
||||
assertTrue(textView.isShown());
|
||||
|
||||
+3
-3
@@ -23,7 +23,7 @@ public class SensorManagerFactoryTest extends AndroidTestCase {
|
||||
|
||||
@SmallTest
|
||||
public void testDefaultSettings() throws Exception {
|
||||
assertNull(SensorManagerFactory.getInstance().getSensorManager(getContext()));
|
||||
assertNull(SensorManagerFactory.getSystemSensorManager(getContext()));
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
@@ -38,9 +38,9 @@ public class SensorManagerFactoryTest extends AndroidTestCase {
|
||||
|
||||
private void assertClassForName(Class<?> c, int i) {
|
||||
PreferencesUtils.setString(getContext(), R.string.sensor_type_key, getContext().getString(i));
|
||||
SensorManager sm = SensorManagerFactory.getInstance().getSensorManager(getContext());
|
||||
SensorManager sm = SensorManagerFactory.getSystemSensorManager(getContext());
|
||||
assertNotNull(sm);
|
||||
assertTrue(c.isInstance(sm));
|
||||
SensorManagerFactory.getInstance().releaseSensorManager(sm);
|
||||
SensorManagerFactory.releaseSystemSensorManager();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user